From 2e02d550f621daf8cc73c2ad791383f665a68b47 Mon Sep 17 00:00:00 2001 From: shats Date: Tue, 24 Feb 2026 01:04:29 +0700 Subject: [PATCH] feat(alerts): enhance alert settings with customizable durations for server offline and resource usage notifications --- backend/routes/alertsRoutes.js | 16 +- frontend/src/SettingsPage.jsx | 371 ++++++++++++++++++++------------- 2 files changed, 232 insertions(+), 155 deletions(-) diff --git a/backend/routes/alertsRoutes.js b/backend/routes/alertsRoutes.js index c93b7a5..49bb3df 100644 --- a/backend/routes/alertsRoutes.js +++ b/backend/routes/alertsRoutes.js @@ -11,11 +11,11 @@ const { getUptimeCacheData } = require('./mikrotikConfigRoutes'); /** Значения по умолчанию для настроек оповещений. */ const DEFAULT_ALERT_SETTINGS = { - serverOffline: { enabled: true }, - mikrotikUnreachable: { enabled: true }, - highCpu: { enabled: true, thresholdPercent: 85 }, - highRam: { enabled: true, thresholdPercent: 85 }, - highHdd: { enabled: true, thresholdPercent: 90 }, + 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) { @@ -64,9 +64,11 @@ async function getAlerts(req, res) { if (isRouter) { const entry = uptimeResults[serverId]; - offline = entry ? entry.ok === false : false; + 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} недоступен (по проверке Uptime Monitor).` + ? `${name} недоступен более ${offlineMinutes} мин (по проверке Uptime Monitor).` : ''; } else { const idx = nonRouterServers.findIndex( diff --git a/frontend/src/SettingsPage.jsx b/frontend/src/SettingsPage.jsx index 1a4dc23..3668e55 100644 --- a/frontend/src/SettingsPage.jsx +++ b/frontend/src/SettingsPage.jsx @@ -102,13 +102,18 @@ export default function SettingsPage() { const [uptimeMonitorSchedulerEnabled, setUptimeMonitorSchedulerEnabled] = useState(true); const [uptimeMonitorSchedulerIntervalMinutes, setUptimeMonitorSchedulerIntervalMinutes] = useState('2'); const [alertServerOffline, setAlertServerOffline] = useState(true); + const [alertServerOfflineMinutes, setAlertServerOfflineMinutes] = useState('5'); const [alertMikrotikUnreachable, setAlertMikrotikUnreachable] = useState(true); + const [alertMikrotikUnreachableMinutes, setAlertMikrotikUnreachableMinutes] = useState('5'); const [alertHighCpuEnabled, setAlertHighCpuEnabled] = useState(true); const [alertHighCpuThreshold, setAlertHighCpuThreshold] = useState('85'); + const [alertHighCpuDurationMinutes, setAlertHighCpuDurationMinutes] = useState('10'); const [alertHighRamEnabled, setAlertHighRamEnabled] = useState(true); const [alertHighRamThreshold, setAlertHighRamThreshold] = useState('85'); + const [alertHighRamDurationMinutes, setAlertHighRamDurationMinutes] = useState('10'); const [alertHighHddEnabled, setAlertHighHddEnabled] = useState(true); const [alertHighHddThreshold, setAlertHighHddThreshold] = useState('90'); + const [alertHighHddDurationMinutes, setAlertHighHddDurationMinutes] = useState('10'); const [serversList, setServersList] = useState([]); const [sidebarSearch, setSidebarSearch] = useState(''); const [activeSection, setActiveSection] = useState(() => { @@ -316,13 +321,18 @@ export default function SettingsPage() { ); const a = data?.alertSettings || {}; setAlertServerOffline(a.serverOffline?.enabled !== false); + setAlertServerOfflineMinutes(a.serverOffline?.offlineMinutes != null ? String(a.serverOffline.offlineMinutes) : '5'); setAlertMikrotikUnreachable(a.mikrotikUnreachable?.enabled !== false); + setAlertMikrotikUnreachableMinutes(a.mikrotikUnreachable?.durationMinutes != null ? String(a.mikrotikUnreachable.durationMinutes) : '5'); setAlertHighCpuEnabled(a.highCpu?.enabled !== false); setAlertHighCpuThreshold(a.highCpu?.thresholdPercent != null ? String(a.highCpu.thresholdPercent) : '85'); + setAlertHighCpuDurationMinutes(a.highCpu?.durationMinutes != null ? String(a.highCpu.durationMinutes) : '10'); setAlertHighRamEnabled(a.highRam?.enabled !== false); setAlertHighRamThreshold(a.highRam?.thresholdPercent != null ? String(a.highRam.thresholdPercent) : '85'); + setAlertHighRamDurationMinutes(a.highRam?.durationMinutes != null ? String(a.highRam.durationMinutes) : '10'); setAlertHighHddEnabled(a.highHdd?.enabled !== false); setAlertHighHddThreshold(a.highHdd?.thresholdPercent != null ? String(a.highHdd.thresholdPercent) : '90'); + setAlertHighHddDurationMinutes(a.highHdd?.durationMinutes != null ? String(a.highHdd.durationMinutes) : '10'); const e = settingsRes?.headers?.etag || settingsRes?.headers?.ETag || ''; setEtag(e ? String(e) : ''); @@ -438,19 +448,28 @@ export default function SettingsPage() { })) : [], alertSettings: { - serverOffline: { enabled: alertServerOffline }, - mikrotikUnreachable: { enabled: alertMikrotikUnreachable }, + serverOffline: { + enabled: alertServerOffline, + offlineMinutes: Math.max(1, Math.min(1440, parseInt(alertServerOfflineMinutes, 10) || 5)), + }, + mikrotikUnreachable: { + enabled: alertMikrotikUnreachable, + durationMinutes: Math.max(1, Math.min(1440, parseInt(alertMikrotikUnreachableMinutes, 10) || 5)), + }, highCpu: { enabled: alertHighCpuEnabled, thresholdPercent: Math.max(1, Math.min(100, parseInt(alertHighCpuThreshold, 10) || 85)), + durationMinutes: Math.max(1, Math.min(1440, parseInt(alertHighCpuDurationMinutes, 10) || 10)), }, highRam: { enabled: alertHighRamEnabled, thresholdPercent: Math.max(1, Math.min(100, parseInt(alertHighRamThreshold, 10) || 85)), + durationMinutes: Math.max(1, Math.min(1440, parseInt(alertHighRamDurationMinutes, 10) || 10)), }, highHdd: { enabled: alertHighHddEnabled, thresholdPercent: Math.max(1, Math.min(100, parseInt(alertHighHddThreshold, 10) || 90)), + durationMinutes: Math.max(1, Math.min(1440, parseInt(alertHighHddDurationMinutes, 10) || 10)), }, }, }; @@ -1088,160 +1107,216 @@ export default function SettingsPage() { <>

- Включите или отключите типы оповещений и задайте пороги. Оповещения отображаются на панели и в колокольчике в шапке. + Настройте пороги и длительность для каждого типа оповещений. Оповещения отображаются на панели и в колокольчике в шапке.

-
-

Статус и доступность

-

- Для jumphost/home используются те же правила, что и в Uptime Monitor: тип проверки (HTTP, internal-ping, external-ping) и кеш. Настройки — в разделе { e.preventDefault(); goToSection('uptime-monitor'); }}>Uptime Monitor. Для остальных серверов — проверка TCP (80/443). -

-
- setAlertServerOffline(e.target.checked)} - disabled={saving} - /> - +
+ {/* Статус: сервер недоступен */} +
+
+
Статус
+
Система не в сети более указанного времени. Для jumphost/home — правила { e.preventDefault(); goToSection('uptime-monitor'); }}>Uptime Monitor.
+
+
+
+ setAlertServerOfflineMinutes(e.target.value)} + disabled={saving || !alertServerOffline} + /> + мин +
+
+ setAlertServerOffline(e.target.checked)} + disabled={saving} + /> + +
+
-
- setAlertMikrotikUnreachable(e.target.checked)} - disabled={saving} - /> - -
-
-
-

Ресурсы роутеров (MikroTik)

-
-
-
-
-
- - - - Использование CPU -
-
- setAlertHighCpuEnabled(e.target.checked)} - disabled={saving} - /> - -
-
- Порог - setAlertHighCpuThreshold(e.target.value)} - disabled={saving || !alertHighCpuEnabled} - /> - % -
-
Среднее превышает указанный %.
-
+ {/* MikroTik недоступен */} +
+
+
MikroTik недоступен
+
Ошибка доступа к RouterOS (таймаут, неверный пароль) в течение указанного времени.
+
+
+
+ setAlertMikrotikUnreachableMinutes(e.target.value)} + disabled={saving || !alertMikrotikUnreachable} + /> + мин +
+
+ setAlertMikrotikUnreachable(e.target.checked)} + disabled={saving} + /> +
-
-
-
-
- - - - Использование памяти -
-
- setAlertHighRamEnabled(e.target.checked)} - disabled={saving} - /> - -
-
- Порог - setAlertHighRamThreshold(e.target.value)} - disabled={saving || !alertHighRamEnabled} - /> - % -
-
Среднее превышает указанный %.
-
+
+ + {/* Использование CPU */} +
+
+
+ + Использование CPU +
+
Среднее превышает порог в течение указанного времени.
+
+
+
+ setAlertHighCpuThreshold(e.target.value)} + disabled={saving || !alertHighCpuEnabled} + /> + % +
+
+ setAlertHighCpuDurationMinutes(e.target.value)} + disabled={saving || !alertHighCpuEnabled} + /> + мин +
+
+ setAlertHighCpuEnabled(e.target.checked)} + disabled={saving} + /> +
-
-
-
-
- - - - Использование диска -
-
- setAlertHighHddEnabled(e.target.checked)} - disabled={saving} - /> - -
-
- Порог - setAlertHighHddThreshold(e.target.value)} - disabled={saving || !alertHighHddEnabled} - /> - % -
-
Среднее превышает указанный %.
-
+
+ + {/* Использование памяти */} +
+
+
+ + Использование памяти +
+
Среднее превышает порог в течение указанного времени.
+
+
+
+ setAlertHighRamThreshold(e.target.value)} + disabled={saving || !alertHighRamEnabled} + /> + % +
+
+ setAlertHighRamDurationMinutes(e.target.value)} + disabled={saving || !alertHighRamEnabled} + /> + мин +
+
+ setAlertHighRamEnabled(e.target.checked)} + disabled={saving} + /> + +
+
+
+ + {/* Использование диска */} +
+
+
+ + Использование диска +
+
Среднее превышает порог в течение указанного времени.
+
+
+
+ setAlertHighHddThreshold(e.target.value)} + disabled={saving || !alertHighHddEnabled} + /> + % +
+
+ setAlertHighHddDurationMinutes(e.target.value)} + disabled={saving || !alertHighHddEnabled} + /> + мин +
+
+ setAlertHighHddEnabled(e.target.checked)} + disabled={saving} + /> +