/** * Система оповещений: агрегирует проблемы из availability и resources/stats. * Критерии и пороги задаются в ui-settings.alertSettings. */ const { sendError } = require('../middleware/errorHandler'); const { readServersFromS3 } = require('./serversRoutes'); const { checkOneServerFast, loadUiSettingsSync } = require('./miscRoutes'); const { getResourceStatsData } = require('./resourceStatsRoutes'); const { getUptimeCacheData } = require('./mikrotikConfigRoutes'); /** Значения по умолчанию для настроек оповещений. */ const DEFAULT_ALERT_SETTINGS = { serverOffline: { enabled: true, offlineMinutes: 5 }, mikrotikUnreachable: { enabled: true, durationMinutes: 5 }, highCpu: { enabled: true, thresholdPercent: 85, durationMinutes: 10 }, highRam: { enabled: true, thresholdPercent: 85, durationMinutes: 10 }, highHdd: { enabled: true, thresholdPercent: 90, durationMinutes: 10 }, }; function getAlertSettings(uiSettings) { const raw = uiSettings?.alertSettings && typeof uiSettings.alertSettings === 'object' ? uiSettings.alertSettings : {}; return { serverOffline: { ...DEFAULT_ALERT_SETTINGS.serverOffline, ...raw.serverOffline }, mikrotikUnreachable: { ...DEFAULT_ALERT_SETTINGS.mikrotikUnreachable, ...raw.mikrotikUnreachable }, highCpu: { ...DEFAULT_ALERT_SETTINGS.highCpu, ...raw.highCpu }, highRam: { ...DEFAULT_ALERT_SETTINGS.highRam, ...raw.highRam }, highHdd: { ...DEFAULT_ALERT_SETTINGS.highHdd, ...raw.highHdd }, }; } /** * GET /api/alerts * Возвращает список активных оповещений (учёт настроек из ui-settings.alertSettings). */ async function getAlerts(req, res) { try { const uiSettings = await loadUiSettingsSync(); const settings = getAlertSettings(uiSettings); const servers = await readServersFromS3(); const alerts = []; const now = new Date().toISOString(); // 1) Доступность серверов: jumphost/home — по правилам Uptime Monitor (кеш), остальные — TCP 80/443 if (settings.serverOffline.enabled) { const routerTypes = ['jumphost', 'home']; const { results: uptimeResults } = await getUptimeCacheData(); const nonRouterServers = servers.filter( (s) => !routerTypes.includes(String(s.type || '').toLowerCase()) ); const availabilityChecks = await Promise.allSettled( nonRouterServers.map((s) => checkOneServerFast(s)) ); servers.forEach((server) => { const serverId = server.id || server.dns || server.ip; const name = server.name || server.dns || server.ip || server.id || 'Сервер'; const isRouter = routerTypes.includes(String(server.type || '').toLowerCase()); let offline = false; let description = ''; if (isRouter) { const entry = uptimeResults[serverId]; const offlineMinutes = Math.max(1, Math.min(1440, Number(settings.serverOffline.offlineMinutes) || 5)); const offlineSinceMs = entry && entry.lastCheckTs ? (Date.now() - entry.lastCheckTs) : 0; offline = entry && entry.ok === false && offlineSinceMs >= offlineMinutes * 60 * 1000; description = offline ? `${name} недоступен более ${offlineMinutes} мин (по проверке Uptime Monitor).` : ''; } else { const idx = nonRouterServers.findIndex( (s) => (s.id || s.dns || s.ip) === serverId ); const online = idx >= 0 && availabilityChecks[idx].status === 'fulfilled' && availabilityChecks[idx].value; offline = !online; description = offline ? `${name} не отвечает на TCP (80/443).` : ''; } if (offline && description) { alerts.push({ id: `server-offline-${serverId}`, type: 'server_offline', severity: 'critical', title: 'Сервер недоступен', description, entity: name, entityId: serverId, link: isRouter ? '/uptime-monitor' : '/servers', at: now, }); } }); } // 2) Ресурсы роутеров (MikroTik): ошибка доступа, высокое CPU/RAM/HDD let resourceData; try { resourceData = await getResourceStatsData(); } catch (err) { console.warn('alerts: getResourceStatsData failed', err?.message); resourceData = { routers: [] }; } const thCpu = Math.max(1, Math.min(100, Number(settings.highCpu.thresholdPercent) || 85)); const thRam = Math.max(1, Math.min(100, Number(settings.highRam.thresholdPercent) || 85)); const thHdd = Math.max(1, Math.min(100, Number(settings.highHdd.thresholdPercent) || 90)); const routers = resourceData.routers || []; routers.forEach((router) => { const { serverId, name, error, resource } = router; const entityName = name || serverId; if (error && settings.mikrotikUnreachable.enabled) { alerts.push({ id: `mikrotik-error-${serverId}`, type: 'mikrotik_unreachable', severity: 'critical', title: 'MikroTik недоступен', description: `${entityName}: ${error}`, entity: entityName, entityId: serverId, link: '/resource-stats', at: now, }); return; } if (!resource) return; if (settings.highCpu.enabled && resource.cpuLoad != null && resource.cpuLoad >= thCpu) { alerts.push({ id: `cpu-high-${serverId}`, type: 'high_cpu', severity: 'warning', title: 'Высокое использование CPU', description: `${entityName}: ${resource.cpuLoad}% (порог ${thCpu}%).`, entity: entityName, entityId: serverId, value: resource.cpuLoad, threshold: thCpu, link: '/resource-stats', at: now, }); } if (settings.highRam.enabled && resource.memoryUsagePercent != null && resource.memoryUsagePercent >= thRam) { alerts.push({ id: `ram-high-${serverId}`, type: 'high_ram', severity: 'warning', title: 'Высокое использование RAM', description: `${entityName}: ${resource.memoryUsagePercent}% (порог ${thRam}%).`, entity: entityName, entityId: serverId, value: resource.memoryUsagePercent, threshold: thRam, link: '/resource-stats', at: now, }); } if (settings.highHdd.enabled && resource.hddUsagePercent != null && resource.hddUsagePercent >= thHdd) { alerts.push({ id: `hdd-high-${serverId}`, type: 'high_hdd', severity: 'warning', title: 'Высокое использование диска', description: `${entityName}: ${resource.hddUsagePercent}% (порог ${thHdd}%).`, entity: entityName, entityId: serverId, value: resource.hddUsagePercent, threshold: thHdd, link: '/resource-stats', at: now, }); } }); return res.json({ alerts, total: alerts.length, at: now, }); } catch (error) { console.error('getAlerts:', error); return sendError( res, 500, error.message || 'Ошибка загрузки оповещений', 'E_ALERTS' ); } } module.exports = { getAlerts, };