feat(alerts): add tunnel high ping and low speed alert settings with corresponding UI controls
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m55s

This commit is contained in:
2026-02-24 22:07:31 +07:00
parent 228a1b9525
commit 53be4cd4ad
2 changed files with 314 additions and 17 deletions
+129
View File
@@ -8,6 +8,8 @@ const { readServersFromS3 } = require('./serversRoutes');
const { checkOneServerFast, loadUiSettingsSync } = require('./miscRoutes');
const { getResourceStatsData } = require('./resourceStatsRoutes');
const { getUptimeCacheData } = require('./mikrotikConfigRoutes');
const { buildConnections } = require('../services/networkMapScheduler');
const { readS3TextObject } = require('../services/s3Service');
/** Значения по умолчанию для настроек оповещений. */
const DEFAULT_ALERT_SETTINGS = {
@@ -16,6 +18,10 @@ const DEFAULT_ALERT_SETTINGS = {
highCpu: { enabled: true, thresholdPercent: 85, durationMinutes: 10 },
highRam: { enabled: true, thresholdPercent: 85, durationMinutes: 10 },
highHdd: { enabled: true, thresholdPercent: 90, durationMinutes: 10 },
/** Порог по пингу туннелей (карта сети) */
tunnelHighPing: { enabled: false, thresholdMs: 0 },
/** Пороги по скорости туннелей (карта сети) */
tunnelLowSpeed: { enabled: false, minDownloadMbps: 0, minUploadMbps: 0 },
};
function getAlertSettings(uiSettings) {
@@ -26,9 +32,19 @@ function getAlertSettings(uiSettings) {
highCpu: { ...DEFAULT_ALERT_SETTINGS.highCpu, ...raw.highCpu },
highRam: { ...DEFAULT_ALERT_SETTINGS.highRam, ...raw.highRam },
highHdd: { ...DEFAULT_ALERT_SETTINGS.highHdd, ...raw.highHdd },
tunnelHighPing: { ...DEFAULT_ALERT_SETTINGS.tunnelHighPing, ...raw.tunnelHighPing },
tunnelLowSpeed: { ...DEFAULT_ALERT_SETTINGS.tunnelLowSpeed, ...raw.tunnelLowSpeed },
};
}
function edgePingKey(a, b) {
return [String(a), String(b)].sort().join(':');
}
function speedKey(key1, key2) {
return [String(key1), String(key2)].sort().join(':');
}
/**
* GET /api/alerts
* Возвращает список активных оповещений (учёт настроек из ui-settings.alertSettings).
@@ -177,6 +193,119 @@ async function getAlerts(req, res) {
}
});
// 3) Туннели (карта сети): высокий пинг и/или низкая скорость
const tunnelPingSettings = settings.tunnelHighPing || {};
const tunnelSpeedSettings = settings.tunnelLowSpeed || {};
const tunnelPingEnabled = Boolean(tunnelPingSettings.enabled);
const tunnelSpeedEnabled = Boolean(tunnelSpeedSettings.enabled);
if (tunnelPingEnabled || tunnelSpeedEnabled) {
let cache = null;
let connectionsData = null;
try {
const [rawCache, built] = await Promise.all([
readS3TextObject('network-map-cache/latest.json').catch(() => null),
buildConnections().catch(() => null),
]);
if (rawCache?.body) {
const parsed = JSON.parse(rawCache.body);
if (parsed && typeof parsed === 'object') cache = parsed;
}
if (built && typeof built === 'object') connectionsData = built;
} catch (e) {
// Ошибки при чтении кеша/конфигурации не должны ломать остальные оповещения
console.warn('alerts: network-map-cache read failed', e?.message || e);
}
if (cache && connectionsData && Array.isArray(connectionsData.connections)) {
const pingMap = cache.pingMap && typeof cache.pingMap === 'object' ? cache.pingMap : {};
const speedMap = cache.speedMap && typeof cache.speedMap === 'object' ? cache.speedMap : {};
const thPingMs = Math.max(1, Number(tunnelPingSettings.thresholdMs) || 0);
const minDownMbps = Math.max(0, Number(tunnelSpeedSettings.minDownloadMbps) || 0);
const minUpMbps = Math.max(0, Number(tunnelSpeedSettings.minUploadMbps) || 0);
if (thPingMs > 0 || minDownMbps > 0 || minUpMbps > 0) {
const serversByIp = new Map();
servers.forEach((s) => {
if (s && s.ip) {
serversByIp.set(String(s.ip), s);
}
});
connectionsData.connections.forEach((c) => {
const fromIp = String(c.from);
const toIp = String(c.to);
if (!fromIp || !toIp || fromIp === toIp) return;
const fromServer = serversByIp.get(fromIp) || null;
const toServer = serversByIp.get(toIp) || null;
const fromName = fromServer?.name || fromServer?.dns || fromIp;
const toName = toServer?.name || toServer?.dns || toIp;
const pingMapKey = edgePingKey(fromIp, toIp);
const pingMs = typeof pingMap[pingMapKey] === 'number' ? pingMap[pingMapKey] : null;
if (tunnelPingEnabled && thPingMs > 0 && pingMs != null && pingMs >= thPingMs) {
alerts.push({
id: `tunnel-ping-${pingMapKey}-${thPingMs}`,
type: 'tunnel_high_ping',
severity: 'warning',
title: 'Высокий пинг по туннелю',
description: `Пинг между ${fromName} и ${toName}: ${pingMs} ms (порог ${thPingMs} ms).`,
entity: `${fromName}${toName}`,
entityId: `${c.fromKey || fromIp}__${c.toKey || toIp}__${c.interfaceName || ''}`,
link: '/network-map',
at: now,
});
}
if (tunnelSpeedEnabled && (minDownMbps > 0 || minUpMbps > 0) && c.fromKey && c.toKey) {
const sk = speedKey(c.fromKey, c.toKey);
const speed = speedMap[sk];
if (speed && typeof speed === 'object') {
const downMbps =
typeof speed.tcpDownloadBps === 'number' ? speed.tcpDownloadBps / 1e6 : null;
const upMbps =
typeof speed.tcpUploadBps === 'number' ? speed.tcpUploadBps / 1e6 : null;
const downTooLow = minDownMbps > 0 && (downMbps == null || downMbps < minDownMbps);
const upTooLow = minUpMbps > 0 && (upMbps == null || upMbps < minUpMbps);
if (downTooLow || upTooLow) {
const parts = [];
if (downTooLow) {
parts.push(
`${downMbps != null ? downMbps.toFixed(1) : '—'} Мбит/с (порог ${minDownMbps} Мбит/с)`
);
}
if (upTooLow) {
parts.push(
`${upMbps != null ? upMbps.toFixed(1) : '—'} Мбит/с (порог ${minUpMbps} Мбит/с)`
);
}
const ifaceLabel = c.interfaceName ? `, интерфейс ${c.interfaceName}` : '';
alerts.push({
id: `tunnel-speed-${sk}-${minDownMbps}-${minUpMbps}`,
type: 'tunnel_low_speed',
severity: 'warning',
title: 'Низкая скорость по туннелю',
description: `Туннель между ${fromName} и ${toName}${ifaceLabel}: ${parts.join(
'; '
)}.`,
entity: `${fromName}${toName}`,
entityId: `${c.fromKey || fromIp}__${c.toKey || toIp}__${c.interfaceName || ''}`,
link: '/network-map',
at: now,
});
}
}
}
});
}
}
}
return res.json({
alerts,
total: alerts.length,