Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m59s
199 lines
7.5 KiB
JavaScript
199 lines
7.5 KiB
JavaScript
/**
|
|
* Система оповещений: агрегирует проблемы из 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,
|
|
};
|