Feature: Sistema de tracking de sesiones RADIUS en tiempo real
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 30s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 30s
- Nueva tabla `sesiones` para historial persistente de conexiones - Job de detección de stale cada 2 min (10 min idle = desconectado) - Inicialización resiliente desde BD al arrancar el servidor - Nuevos endpoints: /api/sessions, /api/sessions/history, /api/sessions.csv - Nueva vista "Sesiones" en el dashboard con estadísticas - Historial integrado en UserCard y DispositivoCard - Estadísticas de bytes in/out y duración por sesión - Retención configurable de historial (90 días por defecto)
This commit is contained in:
@@ -1,17 +1,113 @@
|
||||
import { createApp } from './src/app.js';
|
||||
import { ensureSchema, disableGuestsFromYesterday } from './src/services/db.js';
|
||||
import {
|
||||
ensureSchema,
|
||||
disableGuestsFromYesterday,
|
||||
getActiveSessions,
|
||||
markStaleSessions,
|
||||
syncConnectedDevicesFromSessions,
|
||||
cleanOldSessions
|
||||
} from './src/services/db.js';
|
||||
import { activeSessions } from './src/services/radius.js';
|
||||
import { broadcastStatus } from './src/sse.js';
|
||||
|
||||
const app = createApp();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
// Configuration from environment
|
||||
const STALE_CHECK_INTERVAL = parseInt(process.env.STALE_CHECK_INTERVAL || '120000', 10); // 2 min
|
||||
const MAX_IDLE_MINUTES = parseInt(process.env.MAX_IDLE_MINUTES || '10', 10);
|
||||
const SESSION_HISTORY_RETENTION_DAYS = parseInt(process.env.SESSION_HISTORY_RETENTION_DAYS || '90', 10);
|
||||
|
||||
try {
|
||||
await ensureSchema();
|
||||
} catch (e) {
|
||||
console.error('Database schema ensure failed:', e?.message || e);
|
||||
}
|
||||
|
||||
// Initialize sessions from database on startup
|
||||
async function initializeSessionsFromDb() {
|
||||
try {
|
||||
// Sync dispositivos_conectados from active sessions
|
||||
await syncConnectedDevicesFromSessions();
|
||||
|
||||
// Load active sessions into memory Map for CoA
|
||||
const active = await getActiveSessions();
|
||||
for (const sess of active) {
|
||||
activeSessions.set(sess.session_id, {
|
||||
username: sess.username,
|
||||
sessionId: sess.session_id,
|
||||
nasIp: sess.nas_ip,
|
||||
nasId: sess.nas_id,
|
||||
callingStationId: sess.calling_station_id,
|
||||
calledStationId: sess.called_station_id,
|
||||
updatedAt: new Date(sess.last_update).getTime(),
|
||||
});
|
||||
}
|
||||
console.log(`[init] Loaded ${active.length} active sessions from database`);
|
||||
} catch (e) {
|
||||
console.error('[init] Failed to load sessions:', e?.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
// Job to detect and mark stale sessions
|
||||
function scheduleStaleSessionsJob() {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const result = await markStaleSessions(MAX_IDLE_MINUTES);
|
||||
if (result.count > 0) {
|
||||
console.log(`[stale-sessions] Marked ${result.count} sessions as stale`);
|
||||
// Remove stale sessions from memory Map
|
||||
for (const sess of result.sessions) {
|
||||
activeSessions.delete(sess.session_id);
|
||||
}
|
||||
// Notify SSE clients
|
||||
broadcastStatus({ type: 'sessions-updated', staleCount: result.count });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[stale-sessions] Error:', e?.message || e);
|
||||
}
|
||||
}, STALE_CHECK_INTERVAL);
|
||||
}
|
||||
|
||||
// Job to clean old sessions (runs daily at 3:00 AM local)
|
||||
function scheduleSessionCleanupJob() {
|
||||
function schedule() {
|
||||
const now = new Date();
|
||||
const next = new Date(now);
|
||||
next.setUTCHours(9, 0, 0, 0); // 9:00 UTC = 3:00 AM Honduras (UTC-6)
|
||||
if (next <= now) next.setUTCDate(next.getUTCDate() + 1);
|
||||
const delay = next - now;
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const deleted = await cleanOldSessions(SESSION_HISTORY_RETENTION_DAYS);
|
||||
if (deleted > 0) {
|
||||
console.log(`[session-cleanup] Deleted ${deleted} old sessions (>${SESSION_HISTORY_RETENTION_DAYS} days)`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[session-cleanup] Error:', e?.message || e);
|
||||
} finally {
|
||||
schedule(); // Re-schedule for tomorrow
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
schedule();
|
||||
}
|
||||
|
||||
// Initialize sessions from database
|
||||
try {
|
||||
await initializeSessionsFromDb();
|
||||
} catch (e) {
|
||||
console.error('Session initialization failed:', e?.message || e);
|
||||
}
|
||||
|
||||
// Start maintenance jobs
|
||||
scheduleStaleSessionsJob();
|
||||
scheduleSessionCleanupJob();
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Node RADIUS REST API listening on :${port}`);
|
||||
console.log(`[config] Stale check: every ${STALE_CHECK_INTERVAL/1000}s, idle timeout: ${MAX_IDLE_MINUTES} min, retention: ${SESSION_HISTORY_RETENTION_DAYS} days`);
|
||||
});
|
||||
|
||||
// Schedule daily guest disable at 4:00 AM America/Tegucigalpa (UTC-6 -> 10:00 UTC)
|
||||
|
||||
Reference in New Issue
Block a user