feat(api): add alerts API endpoint and refactor resource stats retrieval for improved data handling

This commit is contained in:
2026-02-24 00:25:36 +07:00
parent f0db9ed013
commit 3f41bd4c7c
8 changed files with 402 additions and 13 deletions
+144
View File
@@ -0,0 +1,144 @@
/**
* Система оповещений: агрегирует проблемы из availability и resources/stats.
* Критерии проблем: сервер недоступен, MikroTik недоступен, высокое CPU/RAM/HDD.
*/
const { sendError } = require('../middleware/errorHandler');
const { readServersFromS3 } = require('./serversRoutes');
const { checkOneServerFast } = require('./miscRoutes');
const { getResourceStatsData } = require('./resourceStatsRoutes');
/** Пороги для ресурсов (проценты). */
const THRESHOLD_CPU = 85;
const THRESHOLD_RAM = 85;
const THRESHOLD_HDD = 90;
/**
* GET /api/alerts
* Возвращает список активных оповещений.
*/
async function getAlerts(req, res) {
try {
const servers = await readServersFromS3();
const alerts = [];
const now = new Date().toISOString();
// 1) Доступность серверов (TCP 80/443)
const availabilityChecks = await Promise.allSettled(
servers.map((s) => checkOneServerFast(s))
);
servers.forEach((server, i) => {
const online = availabilityChecks[i].status === 'fulfilled' && availabilityChecks[i].value;
if (!online) {
const name = server.name || server.dns || server.ip || server.id || `Сервер #${i + 1}`;
alerts.push({
id: `server-offline-${server.id || server.dns || server.ip || i}`,
type: 'server_offline',
severity: 'critical',
title: 'Сервер недоступен',
description: `${name} не отвечает на TCP (80/443).`,
entity: name,
entityId: server.id || server.dns || server.ip,
link: '/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 routers = resourceData.routers || [];
routers.forEach((router) => {
const { serverId, name, error, resource } = router;
const entityName = name || serverId;
if (error) {
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 (resource.cpuLoad != null && resource.cpuLoad >= THRESHOLD_CPU) {
alerts.push({
id: `cpu-high-${serverId}`,
type: 'high_cpu',
severity: 'warning',
title: 'Высокое использование CPU',
description: `${entityName}: ${resource.cpuLoad}% (порог ${THRESHOLD_CPU}%).`,
entity: entityName,
entityId: serverId,
value: resource.cpuLoad,
threshold: THRESHOLD_CPU,
link: '/resource-stats',
at: now,
});
}
if (resource.memoryUsagePercent != null && resource.memoryUsagePercent >= THRESHOLD_RAM) {
alerts.push({
id: `ram-high-${serverId}`,
type: 'high_ram',
severity: 'warning',
title: 'Высокое использование RAM',
description: `${entityName}: ${resource.memoryUsagePercent}% (порог ${THRESHOLD_RAM}%).`,
entity: entityName,
entityId: serverId,
value: resource.memoryUsagePercent,
threshold: THRESHOLD_RAM,
link: '/resource-stats',
at: now,
});
}
if (resource.hddUsagePercent != null && resource.hddUsagePercent >= THRESHOLD_HDD) {
alerts.push({
id: `hdd-high-${serverId}`,
type: 'high_hdd',
severity: 'warning',
title: 'Высокое использование диска',
description: `${entityName}: ${resource.hddUsagePercent}% (порог ${THRESHOLD_HDD}%).`,
entity: entityName,
entityId: serverId,
value: resource.hddUsagePercent,
threshold: THRESHOLD_HDD,
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,
};
+1
View File
@@ -776,5 +776,6 @@ module.exports = {
getPingServicesList,
getPingServices,
refreshPingServicesCache,
checkOneServerFast,
};
+16 -7
View File
@@ -74,13 +74,11 @@ function parseResource(raw) {
}
/**
* GET /api/resources/stats
* По каждому jumphost/home с MikroTik — запрос system/resource, ответ с группировкой по роутерам.
* Внутренняя функция: возвращает данные по ресурсам роутеров (без HTTP).
*/
async function getResourceStats(req, res) {
try {
const servers = await readServersFromS3();
const routers = (Array.isArray(servers) ? servers : []).filter(
async function getResourceStatsData() {
const servers = await readServersFromS3();
const routers = (Array.isArray(servers) ? servers : []).filter(
(s) =>
s &&
(String(s.type || '').toLowerCase() === 'jumphost' ||
@@ -132,7 +130,17 @@ async function getResourceStats(req, res) {
})
);
return res.json({ routers: results });
return { routers: results };
}
/**
* GET /api/resources/stats
* По каждому jumphost/home с MikroTik — запрос system/resource, ответ с группировкой по роутерам.
*/
async function getResourceStats(req, res) {
try {
const data = await getResourceStatsData();
return res.json(data);
} catch (error) {
console.error('getResourceStats:', error);
return sendError(
@@ -146,4 +154,5 @@ async function getResourceStats(req, res) {
module.exports = {
getResourceStats,
getResourceStatsData,
};
+4
View File
@@ -31,6 +31,7 @@ const mikrotikConfigRoutes = require('./routes/mikrotikConfigRoutes');
const mikrotikBackupRoutes = require('./routes/mikrotikBackupRoutes');
const trafficRoutes = require('./routes/trafficRoutes');
const resourceStatsRoutes = require('./routes/resourceStatsRoutes');
const alertsRoutes = require('./routes/alertsRoutes');
const { initMikrotikBackupScheduler } = require('./services/mikrotikBackupScheduler');
const { initNetworkMapScheduler } = require('./services/networkMapScheduler');
const { initPingServicesScheduler } = require('./services/pingServicesScheduler');
@@ -483,6 +484,9 @@ app.get('/api/traffic/interface-stats', trafficRoutes.getInterfaceStats);
// === RESOURCE STATS (RAM/CPU/HDD по роутерам) ===
app.get('/api/resources/stats', resourceStatsRoutes.getResourceStats);
// === ALERTS (агрегация проблем: серверы, ресурсы роутеров) ===
app.get('/api/alerts', alertsRoutes.getAlerts);
// === MIKROTIK BACKUPS (S3) ===
app.post('/api/mikrotik/backups', writeLimiter, mikrotikBackupRoutes.createBackup);
app.get('/api/mikrotik/backups', mikrotikBackupRoutes.listBackups);