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)
133 lines
4.3 KiB
JavaScript
133 lines
4.3 KiB
JavaScript
import { createApp } from './src/app.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)
|
|
function scheduleGuestJob() {
|
|
const now = new Date();
|
|
const next = new Date(now);
|
|
next.setUTCHours(10, 0, 0, 0); // 10:00 UTC == 4:00 local (UTC-6)
|
|
if (next <= now) next.setUTCDate(next.getUTCDate() + 1);
|
|
const delay = next - now;
|
|
setTimeout(async function run() {
|
|
try {
|
|
const { count } = await disableGuestsFromYesterday();
|
|
if (count) console.log(`[guest-cron] Disabled ${count} invitado users from yesterday`);
|
|
} catch (e) {
|
|
console.error('[guest-cron] Error:', e?.message || e);
|
|
} finally {
|
|
scheduleGuestJob();
|
|
}
|
|
}, delay);
|
|
}
|
|
|
|
scheduleGuestJob();
|