/** * Планировщик проверок Uptime Monitor. * Периодически вызывает POST /api/uptime/check для каждого jumphost/home и обновляет кеш в S3. * После перезапуска контейнера кеш обновляется по расписанию без необходимости открывать страницу. */ const { readS3TextObject } = require('./s3Service'); const UI_SETTINGS_KEY = 'bgp_data/rt_ui_settings.json'; const DEFAULT_INTERVAL_MINUTES = 2; const PARALLEL_CHECKS = 2; let schedulerTimer = null; let lastScheduledRunAt = null; function getSettingsFromUiSettings(uiSettings) { return { enabled: uiSettings.uptimeMonitorSchedulerEnabled !== false, intervalMinutes: Math.max(1, Math.min(1440, parseInt(uiSettings.uptimeMonitorSchedulerIntervalMinutes, 10) || DEFAULT_INTERVAL_MINUTES)), }; } function getNextRunAt(settings) { if (!settings || !settings.enabled) return null; const intervalMs = (settings.intervalMinutes || DEFAULT_INTERVAL_MINUTES) * 60 * 1000; const from = lastScheduledRunAt || Date.now(); return from + intervalMs; } /** * Запустить планировщик Uptime Monitor. * @param {object} logger - pino logger * @param {number} port - порт приложения для вызова API */ function initUptimeMonitorScheduler(logger, port) { const log = logger || console; const baseUrl = `http://127.0.0.1:${port}`; async function runChecks(serverIds) { const runOne = async (serverId) => { try { const res = await fetch(`${baseUrl}/api/uptime/check`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ serverId }), }); if (!res.ok) { log.warn({ component: 'uptime-monitor-scheduler', serverId, status: res.status }, 'Uptime check failed'); } } catch (err) { log.warn({ component: 'uptime-monitor-scheduler', serverId, err: err?.message }, 'Uptime check error'); } }; for (let i = 0; i < serverIds.length; i += PARALLEL_CHECKS) { const chunk = serverIds.slice(i, i + PARALLEL_CHECKS); await Promise.all(chunk.map(runOne)); } } async function tick() { lastScheduledRunAt = Date.now(); let uiSettings; try { const raw = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' })); uiSettings = JSON.parse(raw?.body || '{}') || {}; } catch (_) { return; } const settings = getSettingsFromUiSettings(uiSettings); if (!settings.enabled) return; let servers = []; try { const res = await fetch(`${baseUrl}/api/servers`); const data = res.ok ? await res.json() : []; servers = Array.isArray(data) ? data : []; } catch (err) { log.error({ component: 'uptime-monitor-scheduler', err: err?.message }, 'Failed to fetch servers'); return; } const jumphosts = servers.filter( (s) => s && ['jumphost', 'home'].includes(String(s.type || '').toLowerCase()) ); const serverIds = jumphosts .map((s) => s.id || s.dns || s.ip) .filter(Boolean); if (serverIds.length === 0) { log.info({ component: 'uptime-monitor-scheduler' }, 'No jumphost/home servers to check'); return; } log.info( { component: 'uptime-monitor-scheduler', count: serverIds.length }, 'Running uptime monitor checks for %d server(s)', serverIds.length ); await runChecks(serverIds); } async function schedule() { let uiSettings; try { const raw = await readS3TextObject(UI_SETTINGS_KEY).catch(() => ({ body: '{}' })); uiSettings = JSON.parse(raw?.body || '{}') || {}; } catch (_) { return; } const settings = getSettingsFromUiSettings(uiSettings); if (!settings.enabled) { log.info({ component: 'uptime-monitor-scheduler' }, 'Uptime monitor scheduler disabled in settings'); return; } const intervalMs = settings.intervalMinutes * 60 * 1000; log.info( { component: 'uptime-monitor-scheduler', intervalMinutes: settings.intervalMinutes }, 'Uptime monitor scheduler started (first run in 20 s, then every %d min)', settings.intervalMinutes ); schedulerTimer = setInterval(tick, intervalMs); setTimeout(() => tick(), 20 * 1000); } schedule(); } function stopUptimeMonitorScheduler() { if (schedulerTimer) { clearInterval(schedulerTimer); schedulerTimer = null; } } module.exports = { initUptimeMonitorScheduler, stopUptimeMonitorScheduler, getSettingsFromUiSettings, getNextRunAt, };