servidor funcionando dashboard 80%
This commit is contained in:
@@ -2,6 +2,8 @@ import express from 'express';
|
||||
import morgan from 'morgan';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import dgram from 'dgram';
|
||||
import radius from 'radius';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
@@ -16,6 +18,9 @@ const VLAN_ID = process.env.VLAN_ID || '2';
|
||||
const MAX_UP = process.env.MAX_UP || '10000000'; // bits per second
|
||||
const MAX_DOWN = process.env.MAX_DOWN || '10000000'; // bits per second
|
||||
const MAX_REQUESTS = parseInt(process.env.MAX_REQUESTS || '200', 10);
|
||||
const RADIUS_HOST = process.env.RADIUS_HOST || 'freeradius';
|
||||
const RADIUS_AUTH_PORT = parseInt(process.env.RADIUS_AUTH_PORT || '1812', 10);
|
||||
const RADIUS_SECRET = process.env.RADIUS_SECRET || process.env.RADIUS_SHARED_SECRET || 'tamosbien';
|
||||
|
||||
// In-memory request store + SSE clients
|
||||
const requests = [];
|
||||
@@ -99,6 +104,49 @@ app.get('/api/requests', (req, res) => {
|
||||
res.json({ items: requests.slice(-MAX_REQUESTS) });
|
||||
});
|
||||
|
||||
// Clear recent requests
|
||||
app.delete('/api/requests', (req, res) => {
|
||||
requests.length = 0;
|
||||
// Notify live clients to refresh if they want
|
||||
const payload = `event: clear\n` + `data: {"ok":true}\n\n`;
|
||||
for (const resSse of sseClients) {
|
||||
try { resSse.write(payload); } catch {}
|
||||
}
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// Export CSV of recent requests
|
||||
app.get('/api/requests.csv', (req, res) => {
|
||||
const cols = [
|
||||
'ts','type','user','nas','calling','called','decision','vlan','bw_down','bw_up'
|
||||
];
|
||||
const lines = [cols.join(',')];
|
||||
for (const ev of requests) {
|
||||
const attrs = ev.attrs || {};
|
||||
const row = [
|
||||
ev.ts || '',
|
||||
ev.type || '',
|
||||
attrs['User-Name'] || attrs['User-Name*0'] || '',
|
||||
attrs['NAS-IP-Address'] || attrs['NAS-Identifier'] || '',
|
||||
attrs['Calling-Station-Id'] || '',
|
||||
attrs['Called-Station-Id'] || '',
|
||||
ev.decision || '',
|
||||
ev.vlan || '',
|
||||
(ev.bandwidth && ev.bandwidth.down) || '',
|
||||
(ev.bandwidth && ev.bandwidth.up) || ''
|
||||
];
|
||||
const esc = (v) => String(v).includes(',') || String(v).includes('"') || String(v).includes('\n')
|
||||
? '"' + String(v).replace(/"/g, '""') + '"'
|
||||
: String(v);
|
||||
lines.push(row.map(esc).join(','));
|
||||
}
|
||||
const csv = lines.join('\n');
|
||||
const ts = new Date().toISOString().replace(/[:T]/g, '-').split('.')[0];
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="radius-events-${ts}.csv"`);
|
||||
res.send(csv);
|
||||
});
|
||||
|
||||
// SSE stream for live updates
|
||||
app.get('/events', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
@@ -117,6 +165,71 @@ app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
// Self-test: send a RADIUS Access-Request to FreeRADIUS
|
||||
async function sendRadiusSelfTest() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const packet = radius.encode({
|
||||
code: 'Access-Request',
|
||||
secret: RADIUS_SECRET,
|
||||
attributes: {
|
||||
'User-Name': 'selftest-node',
|
||||
'NAS-Identifier': 'node-dashboard',
|
||||
'Calling-Station-Id': '001122334455',
|
||||
},
|
||||
});
|
||||
const client = dgram.createSocket('udp4');
|
||||
const started = Date.now();
|
||||
const timeout = setTimeout(() => {
|
||||
client.close();
|
||||
reject(new Error('timeout'));
|
||||
}, 4000);
|
||||
client.on('message', (msg) => {
|
||||
clearTimeout(timeout);
|
||||
client.close();
|
||||
const res = radius.decode({ packet: msg, secret: RADIUS_SECRET });
|
||||
resolve({
|
||||
code: res.code,
|
||||
rtt_ms: Date.now() - started,
|
||||
});
|
||||
});
|
||||
client.send(packet, 0, packet.length, RADIUS_AUTH_PORT, RADIUS_HOST, (err) => {
|
||||
if (err) {
|
||||
clearTimeout(timeout);
|
||||
client.close();
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.post('/test/radius', async (req, res) => {
|
||||
try {
|
||||
const result = await sendRadiusSelfTest();
|
||||
pushRequest({
|
||||
id: Date.now() + ':' + Math.random().toString(16).slice(2),
|
||||
ts: new Date().toISOString(),
|
||||
type: 'selftest',
|
||||
attrs: { 'User-Name': 'selftest-node' },
|
||||
decision: result.code,
|
||||
});
|
||||
res.json({ ok: true, result });
|
||||
} catch (err) {
|
||||
pushRequest({
|
||||
id: Date.now() + ':' + Math.random().toString(16).slice(2),
|
||||
ts: new Date().toISOString(),
|
||||
type: 'selftest',
|
||||
attrs: { 'User-Name': 'selftest-node' },
|
||||
decision: 'error',
|
||||
error: String(err && err.message || err),
|
||||
});
|
||||
res.status(500).json({ ok: false, error: String(err && err.message || err) });
|
||||
}
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
app.listen(port, () => {
|
||||
console.log(`Node RADIUS REST API listening on :${port}`);
|
||||
|
||||
Reference in New Issue
Block a user