feat(alerts): implement customizable alert settings for server availability and resource usage, enhancing user control over notifications
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m54s

This commit is contained in:
2026-02-24 00:48:34 +07:00
parent e4394ef35c
commit b35f787626
5 changed files with 331 additions and 54 deletions
+208
View File
@@ -14,6 +14,10 @@ import {
IconRefresh,
IconServer,
IconClock,
IconBell,
IconCpu,
IconDeviceDesktop,
IconDatabase,
} from '@tabler/icons-react';
import FormField from './components/FormField';
import ErrorAlert from './components/ErrorAlert';
@@ -53,6 +57,12 @@ const SIDEBAR_GROUPS = [
{ id: 'traffic-interfaces', title: 'Настройка Аналитики', icon: IconChartBar },
],
},
{
title: 'Оповещения',
items: [
{ id: 'alerts', title: 'Настройки оповещений', icon: IconBell },
],
},
];
const SIDEBAR_SECTIONS = SIDEBAR_GROUPS.flatMap((g) => g.items);
@@ -91,6 +101,14 @@ export default function SettingsPage() {
const [uptimeMonitorCacheSeconds, setUptimeMonitorCacheSeconds] = useState('120');
const [uptimeMonitorSchedulerEnabled, setUptimeMonitorSchedulerEnabled] = useState(true);
const [uptimeMonitorSchedulerIntervalMinutes, setUptimeMonitorSchedulerIntervalMinutes] = useState('2');
const [alertServerOffline, setAlertServerOffline] = useState(true);
const [alertMikrotikUnreachable, setAlertMikrotikUnreachable] = useState(true);
const [alertHighCpuEnabled, setAlertHighCpuEnabled] = useState(true);
const [alertHighCpuThreshold, setAlertHighCpuThreshold] = useState('85');
const [alertHighRamEnabled, setAlertHighRamEnabled] = useState(true);
const [alertHighRamThreshold, setAlertHighRamThreshold] = useState('85');
const [alertHighHddEnabled, setAlertHighHddEnabled] = useState(true);
const [alertHighHddThreshold, setAlertHighHddThreshold] = useState('90');
const [serversList, setServersList] = useState([]);
const [sidebarSearch, setSidebarSearch] = useState('');
const [activeSection, setActiveSection] = useState(() => {
@@ -296,6 +314,15 @@ export default function SettingsPage() {
}))
: []
);
const a = data?.alertSettings || {};
setAlertServerOffline(a.serverOffline?.enabled !== false);
setAlertMikrotikUnreachable(a.mikrotikUnreachable?.enabled !== false);
setAlertHighCpuEnabled(a.highCpu?.enabled !== false);
setAlertHighCpuThreshold(a.highCpu?.thresholdPercent != null ? String(a.highCpu.thresholdPercent) : '85');
setAlertHighRamEnabled(a.highRam?.enabled !== false);
setAlertHighRamThreshold(a.highRam?.thresholdPercent != null ? String(a.highRam.thresholdPercent) : '85');
setAlertHighHddEnabled(a.highHdd?.enabled !== false);
setAlertHighHddThreshold(a.highHdd?.thresholdPercent != null ? String(a.highHdd.thresholdPercent) : '90');
const e =
settingsRes?.headers?.etag || settingsRes?.headers?.ETag || '';
setEtag(e ? String(e) : '');
@@ -410,6 +437,22 @@ export default function SettingsPage() {
interfaceName: p.interfaceName,
}))
: [],
alertSettings: {
serverOffline: { enabled: alertServerOffline },
mikrotikUnreachable: { enabled: alertMikrotikUnreachable },
highCpu: {
enabled: alertHighCpuEnabled,
thresholdPercent: Math.max(1, Math.min(100, parseInt(alertHighCpuThreshold, 10) || 85)),
},
highRam: {
enabled: alertHighRamEnabled,
thresholdPercent: Math.max(1, Math.min(100, parseInt(alertHighRamThreshold, 10) || 85)),
},
highHdd: {
enabled: alertHighHddEnabled,
thresholdPercent: Math.max(1, Math.min(100, parseInt(alertHighHddThreshold, 10) || 90)),
},
},
};
const payload = { settings: mergedSettings, etag };
const res = await api.post('/ui-settings', payload);
@@ -1040,6 +1083,171 @@ export default function SettingsPage() {
</div>
</>
)}
{activeSection === 'alerts' && (
<>
<SectionHeading title="Настройки оповещений" icon={IconBell} />
<p className="text-muted mb-4">
Включите или отключите типы оповещений и задайте пороги. Оповещения отображаются на панели и в колокольчике в шапке.
</p>
<div className="mb-4">
<h4 className="subheader mb-3">Статус и доступность</h4>
<p className="text-muted small mb-3">
Для <strong>jumphost/home</strong> используются те же правила, что и в Uptime Monitor: тип проверки (HTTP, internal-ping, external-ping) и кеш. Настройки в разделе <a href="#uptime-monitor" onClick={(e) => { e.preventDefault(); goToSection('uptime-monitor'); }}>Uptime Monitor</a>. Для остальных серверов проверка TCP (80/443).
</p>
<div className="form-check form-switch mb-3">
<input
className="form-check-input"
type="checkbox"
id="alertServerOffline"
checked={alertServerOffline}
onChange={(e) => setAlertServerOffline(e.target.checked)}
disabled={saving}
/>
<label className="form-check-label" htmlFor="alertServerOffline">
Сервер недоступен оповещение при недоступности (по правилам Uptime Monitor для роутеров, TCP для остальных).
</label>
</div>
<div className="form-check form-switch mb-3">
<input
className="form-check-input"
type="checkbox"
id="alertMikrotikUnreachable"
checked={alertMikrotikUnreachable}
onChange={(e) => setAlertMikrotikUnreachable(e.target.checked)}
disabled={saving}
/>
<label className="form-check-label" htmlFor="alertMikrotikUnreachable">
MikroTik недоступен срабатывает при ошибке доступа к RouterOS (таймаут, неверный пароль и т.п.).
</label>
</div>
</div>
<div className="mb-4">
<h4 className="subheader mb-3">Ресурсы роутеров (MikroTik)</h4>
<div className="row g-3">
<div className="col-12 col-md-6">
<div className="card">
<div className="card-body">
<div className="d-flex align-items-center mb-2">
<span className="avatar avatar-sm me-2 bg-blue-lt text-blue">
<IconCpu size={18} />
</span>
<span className="fw-medium">Использование CPU</span>
</div>
<div className="form-check form-switch mb-2">
<input
className="form-check-input"
type="checkbox"
id="alertHighCpuEnabled"
checked={alertHighCpuEnabled}
onChange={(e) => setAlertHighCpuEnabled(e.target.checked)}
disabled={saving}
/>
<label className="form-check-label small" htmlFor="alertHighCpuEnabled">
Включить оповещение
</label>
</div>
<div className="input-group input-group-sm">
<span className="input-group-text">Порог</span>
<input
type="number"
className="form-control"
min={1}
max={100}
value={alertHighCpuThreshold}
onChange={(e) => setAlertHighCpuThreshold(e.target.value)}
disabled={saving || !alertHighCpuEnabled}
/>
<span className="input-group-text">%</span>
</div>
<div className="form-text small">Среднее превышает указанный %.</div>
</div>
</div>
</div>
<div className="col-12 col-md-6">
<div className="card">
<div className="card-body">
<div className="d-flex align-items-center mb-2">
<span className="avatar avatar-sm me-2 bg-green-lt text-green">
<IconDeviceDesktop size={18} />
</span>
<span className="fw-medium">Использование памяти</span>
</div>
<div className="form-check form-switch mb-2">
<input
className="form-check-input"
type="checkbox"
id="alertHighRamEnabled"
checked={alertHighRamEnabled}
onChange={(e) => setAlertHighRamEnabled(e.target.checked)}
disabled={saving}
/>
<label className="form-check-label small" htmlFor="alertHighRamEnabled">
Включить оповещение
</label>
</div>
<div className="input-group input-group-sm">
<span className="input-group-text">Порог</span>
<input
type="number"
className="form-control"
min={1}
max={100}
value={alertHighRamThreshold}
onChange={(e) => setAlertHighRamThreshold(e.target.value)}
disabled={saving || !alertHighRamEnabled}
/>
<span className="input-group-text">%</span>
</div>
<div className="form-text small">Среднее превышает указанный %.</div>
</div>
</div>
</div>
<div className="col-12 col-md-6">
<div className="card">
<div className="card-body">
<div className="d-flex align-items-center mb-2">
<span className="avatar avatar-sm me-2 bg-orange-lt text-orange">
<IconDatabase size={18} />
</span>
<span className="fw-medium">Использование диска</span>
</div>
<div className="form-check form-switch mb-2">
<input
className="form-check-input"
type="checkbox"
id="alertHighHddEnabled"
checked={alertHighHddEnabled}
onChange={(e) => setAlertHighHddEnabled(e.target.checked)}
disabled={saving}
/>
<label className="form-check-label small" htmlFor="alertHighHddEnabled">
Включить оповещение
</label>
</div>
<div className="input-group input-group-sm">
<span className="input-group-text">Порог</span>
<input
type="number"
className="form-control"
min={1}
max={100}
value={alertHighHddThreshold}
onChange={(e) => setAlertHighHddThreshold(e.target.value)}
disabled={saving || !alertHighHddEnabled}
/>
<span className="input-group-text">%</span>
</div>
<div className="form-text small">Среднее превышает указанный %.</div>
</div>
</div>
</div>
</div>
</div>
</>
)}
</div>
</div>
</div>
+6 -3
View File
@@ -85,9 +85,12 @@ export default function AlertsBell() {
</div>
)}
</div>
<div className="card-footer py-2">
<Link to="/dashboard" className="btn btn-sm btn-outline-primary w-100">
Перейти к панели
<div className="card-footer py-2 d-flex gap-2">
<Link to="/settings#alerts" className="btn btn-sm btn-ghost-secondary flex-grow-1">
Настройки
</Link>
<Link to="/dashboard" className="btn btn-sm btn-outline-primary flex-grow-1">
Панель
</Link>
</div>
</div>