feat(network-map): add network speed thresholds for alerts and enhance settings UI for configuration
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m32s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m32s
This commit is contained in:
@@ -18,6 +18,12 @@ const DEFAULT_ALERT_SETTINGS = {
|
|||||||
highCpu: { enabled: true, thresholdPercent: 85, durationMinutes: 10 },
|
highCpu: { enabled: true, thresholdPercent: 85, durationMinutes: 10 },
|
||||||
highRam: { enabled: true, thresholdPercent: 85, durationMinutes: 10 },
|
highRam: { enabled: true, thresholdPercent: 85, durationMinutes: 10 },
|
||||||
highHdd: { enabled: true, thresholdPercent: 90, durationMinutes: 10 },
|
highHdd: { enabled: true, thresholdPercent: 90, durationMinutes: 10 },
|
||||||
|
/** Глобальные пороги карты сети для UI-аналитики */
|
||||||
|
networkMapNodeThresholds: {
|
||||||
|
lowSpeedMbps: 40,
|
||||||
|
belowNormSpeedMbps: 80,
|
||||||
|
normalSpeedMbps: 120,
|
||||||
|
},
|
||||||
/** Индивидуальные пороги по туннелям (карта сети) */
|
/** Индивидуальные пороги по туннелям (карта сети) */
|
||||||
tunnelThresholds: [],
|
tunnelThresholds: [],
|
||||||
};
|
};
|
||||||
@@ -30,6 +36,12 @@ function getAlertSettings(uiSettings) {
|
|||||||
highCpu: { ...DEFAULT_ALERT_SETTINGS.highCpu, ...raw.highCpu },
|
highCpu: { ...DEFAULT_ALERT_SETTINGS.highCpu, ...raw.highCpu },
|
||||||
highRam: { ...DEFAULT_ALERT_SETTINGS.highRam, ...raw.highRam },
|
highRam: { ...DEFAULT_ALERT_SETTINGS.highRam, ...raw.highRam },
|
||||||
highHdd: { ...DEFAULT_ALERT_SETTINGS.highHdd, ...raw.highHdd },
|
highHdd: { ...DEFAULT_ALERT_SETTINGS.highHdd, ...raw.highHdd },
|
||||||
|
networkMapNodeThresholds: {
|
||||||
|
...DEFAULT_ALERT_SETTINGS.networkMapNodeThresholds,
|
||||||
|
...(raw.networkMapNodeThresholds && typeof raw.networkMapNodeThresholds === 'object'
|
||||||
|
? raw.networkMapNodeThresholds
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
tunnelThresholds: Array.isArray(raw.tunnelThresholds) ? raw.tunnelThresholds : [],
|
tunnelThresholds: Array.isArray(raw.tunnelThresholds) ? raw.tunnelThresholds : [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export default function NetworkMapDashboard() {
|
|||||||
const lastSuccessfulPingRef = useRef({});
|
const lastSuccessfulPingRef = useRef({});
|
||||||
const lastSuccessfulSpeedRef = useRef({});
|
const lastSuccessfulSpeedRef = useRef({});
|
||||||
const [pingCacheSeconds, setPingCacheSeconds] = useState(0);
|
const [pingCacheSeconds, setPingCacheSeconds] = useState(0);
|
||||||
|
const [alertSettings, setAlertSettings] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [pingLoading, setPingLoading] = useState(false);
|
const [pingLoading, setPingLoading] = useState(false);
|
||||||
const [speedLoading, setSpeedLoading] = useState(false);
|
const [speedLoading, setSpeedLoading] = useState(false);
|
||||||
@@ -102,12 +103,17 @@ export default function NetworkMapDashboard() {
|
|||||||
const config = configRes?.data || {};
|
const config = configRes?.data || {};
|
||||||
const tunnelInterfaces = Array.isArray(config.tunnelInterfaces) ? config.tunnelInterfaces : [];
|
const tunnelInterfaces = Array.isArray(config.tunnelInterfaces) ? config.tunnelInterfaces : [];
|
||||||
const uiSettings = uiSettingsRes?.data || {};
|
const uiSettings = uiSettingsRes?.data || {};
|
||||||
|
const uiAlertSettings =
|
||||||
|
uiSettings.alertSettings && typeof uiSettings.alertSettings === 'object'
|
||||||
|
? uiSettings.alertSettings
|
||||||
|
: {};
|
||||||
const cache = cacheRes?.data || {};
|
const cache = cacheRes?.data || {};
|
||||||
const ttlSeconds = Math.max(
|
const ttlSeconds = Math.max(
|
||||||
0,
|
0,
|
||||||
parseInt(uiSettings.networkMapPingCacheSeconds, 10) || 0
|
parseInt(uiSettings.networkMapPingCacheSeconds, 10) || 0
|
||||||
);
|
);
|
||||||
setPingCacheSeconds(ttlSeconds);
|
setPingCacheSeconds(ttlSeconds);
|
||||||
|
setAlertSettings(uiAlertSettings);
|
||||||
|
|
||||||
const getServer = (serverId) =>
|
const getServer = (serverId) =>
|
||||||
serversList.find((s) => s.id === serverId || s.ip === serverId || s.dns === serverId);
|
serversList.find((s) => s.id === serverId || s.ip === serverId || s.dns === serverId);
|
||||||
@@ -462,6 +468,7 @@ export default function NetworkMapDashboard() {
|
|||||||
onRefreshSpeedForConnection={handleRefreshSpeedForConnection}
|
onRefreshSpeedForConnection={handleRefreshSpeedForConnection}
|
||||||
pingLoading={pingLoading}
|
pingLoading={pingLoading}
|
||||||
speedLoading={speedLoading}
|
speedLoading={speedLoading}
|
||||||
|
alertSettings={alertSettings}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ export default function NetworkMapUnifi({
|
|||||||
onRefreshSpeedForConnection,
|
onRefreshSpeedForConnection,
|
||||||
pingLoading = false,
|
pingLoading = false,
|
||||||
speedLoading = false,
|
speedLoading = false,
|
||||||
|
alertSettings = {},
|
||||||
}) {
|
}) {
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const svgRef = useRef(null);
|
const svgRef = useRef(null);
|
||||||
@@ -154,6 +155,18 @@ export default function NetworkMapUnifi({
|
|||||||
const [hoveredEdgeKey, setHoveredEdgeKey] = useState(null);
|
const [hoveredEdgeKey, setHoveredEdgeKey] = useState(null);
|
||||||
const [selectedEdge, setSelectedEdge] = useState(null);
|
const [selectedEdge, setSelectedEdge] = useState(null);
|
||||||
|
|
||||||
|
const mapSpeedThresholds = useMemo(() => {
|
||||||
|
const raw =
|
||||||
|
alertSettings?.networkMapNodeThresholds &&
|
||||||
|
typeof alertSettings.networkMapNodeThresholds === 'object'
|
||||||
|
? alertSettings.networkMapNodeThresholds
|
||||||
|
: {};
|
||||||
|
const low = Math.max(1, Number(raw.lowSpeedMbps) || 40);
|
||||||
|
const belowNorm = Math.max(low, Number(raw.belowNormSpeedMbps) || 80);
|
||||||
|
const normal = Math.max(belowNorm, Number(raw.normalSpeedMbps) || 120);
|
||||||
|
return { low, belowNorm, normal };
|
||||||
|
}, [alertSettings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = containerRef.current;
|
const el = containerRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -532,12 +545,15 @@ export default function NetworkMapUnifi({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (n.minSpeedMbps != null) {
|
if (n.minSpeedMbps != null) {
|
||||||
if (n.minSpeedMbps < 40) {
|
if (n.minSpeedMbps < mapSpeedThresholds.low) {
|
||||||
issues.push('Низкая скорость');
|
issues.push('Низкая скорость');
|
||||||
riskScore += 3;
|
riskScore += 3;
|
||||||
} else if (n.minSpeedMbps < 80) {
|
} else if (n.minSpeedMbps < mapSpeedThresholds.belowNorm) {
|
||||||
issues.push('Скорость ниже нормы');
|
issues.push('Скорость ниже нормы');
|
||||||
riskScore += 2;
|
riskScore += 2;
|
||||||
|
} else if (n.minSpeedMbps < mapSpeedThresholds.normal) {
|
||||||
|
issues.push('Норма скорости не достигнута');
|
||||||
|
riskScore += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +580,7 @@ export default function NetworkMapUnifi({
|
|||||||
(b.maxPingMs ?? -1) - (a.maxPingMs ?? -1) ||
|
(b.maxPingMs ?? -1) - (a.maxPingMs ?? -1) ||
|
||||||
(a.minSpeedMbps ?? Infinity) - (b.minSpeedMbps ?? Infinity)
|
(a.minSpeedMbps ?? Infinity) - (b.minSpeedMbps ?? Infinity)
|
||||||
);
|
);
|
||||||
}, [servers, connections, routedEdges]);
|
}, [servers, connections, routedEdges, mapSpeedThresholds]);
|
||||||
|
|
||||||
const criticalNodes = useMemo(
|
const criticalNodes = useMemo(
|
||||||
() => problematicNodes.filter((n) => n.riskScore >= 4).slice(0, 8),
|
() => problematicNodes.filter((n) => n.riskScore >= 4).slice(0, 8),
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ export default function SettingsPage() {
|
|||||||
const [alertHighHddEnabled, setAlertHighHddEnabled] = useState(true);
|
const [alertHighHddEnabled, setAlertHighHddEnabled] = useState(true);
|
||||||
const [alertHighHddThreshold, setAlertHighHddThreshold] = useState('90');
|
const [alertHighHddThreshold, setAlertHighHddThreshold] = useState('90');
|
||||||
const [alertHighHddDurationMinutes, setAlertHighHddDurationMinutes] = useState('10');
|
const [alertHighHddDurationMinutes, setAlertHighHddDurationMinutes] = useState('10');
|
||||||
|
const [alertMapLowSpeedMbps, setAlertMapLowSpeedMbps] = useState('40');
|
||||||
|
const [alertMapBelowNormSpeedMbps, setAlertMapBelowNormSpeedMbps] = useState('80');
|
||||||
|
const [alertMapNormalSpeedMbps, setAlertMapNormalSpeedMbps] = useState('120');
|
||||||
const [serversList, setServersList] = useState([]);
|
const [serversList, setServersList] = useState([]);
|
||||||
const [tunnelConnections, setTunnelConnections] = useState([]);
|
const [tunnelConnections, setTunnelConnections] = useState([]);
|
||||||
const [tunnelThresholds, setTunnelThresholds] = useState([]);
|
const [tunnelThresholds, setTunnelThresholds] = useState([]);
|
||||||
@@ -385,6 +388,21 @@ export default function SettingsPage() {
|
|||||||
setAlertHighHddDurationMinutes(
|
setAlertHighHddDurationMinutes(
|
||||||
a.highHdd?.durationMinutes != null ? String(a.highHdd.durationMinutes) : '10'
|
a.highHdd?.durationMinutes != null ? String(a.highHdd.durationMinutes) : '10'
|
||||||
);
|
);
|
||||||
|
setAlertMapLowSpeedMbps(
|
||||||
|
a.networkMapNodeThresholds?.lowSpeedMbps != null
|
||||||
|
? String(a.networkMapNodeThresholds.lowSpeedMbps)
|
||||||
|
: '40'
|
||||||
|
);
|
||||||
|
setAlertMapBelowNormSpeedMbps(
|
||||||
|
a.networkMapNodeThresholds?.belowNormSpeedMbps != null
|
||||||
|
? String(a.networkMapNodeThresholds.belowNormSpeedMbps)
|
||||||
|
: '80'
|
||||||
|
);
|
||||||
|
setAlertMapNormalSpeedMbps(
|
||||||
|
a.networkMapNodeThresholds?.normalSpeedMbps != null
|
||||||
|
? String(a.networkMapNodeThresholds.normalSpeedMbps)
|
||||||
|
: '120'
|
||||||
|
);
|
||||||
|
|
||||||
const rawTunnelThresholds = Array.isArray(a.tunnelThresholds) ? a.tunnelThresholds : [];
|
const rawTunnelThresholds = Array.isArray(a.tunnelThresholds) ? a.tunnelThresholds : [];
|
||||||
setTunnelThresholds(
|
setTunnelThresholds(
|
||||||
@@ -510,6 +528,9 @@ export default function SettingsPage() {
|
|||||||
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
|
const parsedLow = Math.max(1, parseInt(alertMapLowSpeedMbps, 10) || 40);
|
||||||
|
const parsedBelowNorm = Math.max(parsedLow, parseInt(alertMapBelowNormSpeedMbps, 10) || 80);
|
||||||
|
const parsedNormal = Math.max(parsedBelowNorm, parseInt(alertMapNormalSpeedMbps, 10) || 120);
|
||||||
const mergedSettings = {
|
const mergedSettings = {
|
||||||
...rawSettings,
|
...rawSettings,
|
||||||
dohServer: String(dohServer || '').trim(),
|
dohServer: String(dohServer || '').trim(),
|
||||||
@@ -617,6 +638,11 @@ export default function SettingsPage() {
|
|||||||
Math.min(1440, parseInt(alertHighHddDurationMinutes, 10) || 10)
|
Math.min(1440, parseInt(alertHighHddDurationMinutes, 10) || 10)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
networkMapNodeThresholds: {
|
||||||
|
lowSpeedMbps: Math.min(100000, parsedLow),
|
||||||
|
belowNormSpeedMbps: Math.min(100000, parsedBelowNorm),
|
||||||
|
normalSpeedMbps: Math.min(100000, parsedNormal),
|
||||||
|
},
|
||||||
tunnelThresholds: Array.isArray(tunnelThresholds)
|
tunnelThresholds: Array.isArray(tunnelThresholds)
|
||||||
? tunnelThresholds
|
? tunnelThresholds
|
||||||
.filter((t) => {
|
.filter((t) => {
|
||||||
@@ -1489,6 +1515,58 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Карта сети: глобальные пороги скорости */}
|
||||||
|
<div className="list-group-item d-flex flex-column flex-md-row align-items-stretch align-items-md-center gap-3 py-3">
|
||||||
|
<div className="flex-grow-1 min-w-0">
|
||||||
|
<div className="fw-semibold d-flex align-items-center gap-2">
|
||||||
|
<IconNetwork size={18} className="text-azure" />
|
||||||
|
Карта сети: пороги скорости
|
||||||
|
</div>
|
||||||
|
<div className="text-muted small">
|
||||||
|
Единые пороги для аналитики узлов на карте и классификации: «Низкая скорость», «Скорость ниже нормы», «Норма».
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex flex-wrap align-items-center gap-2">
|
||||||
|
<div className="input-group input-group-sm" style={{ width: 132 }}>
|
||||||
|
<span className="input-group-text">Низкая</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
min={1}
|
||||||
|
max={100000}
|
||||||
|
value={alertMapLowSpeedMbps}
|
||||||
|
onChange={(e) => setAlertMapLowSpeedMbps(e.target.value)}
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="input-group input-group-sm" style={{ width: 162 }}>
|
||||||
|
<span className="input-group-text">Ниже нормы</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
min={1}
|
||||||
|
max={100000}
|
||||||
|
value={alertMapBelowNormSpeedMbps}
|
||||||
|
onChange={(e) => setAlertMapBelowNormSpeedMbps(e.target.value)}
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="input-group input-group-sm" style={{ width: 120 }}>
|
||||||
|
<span className="input-group-text">Норма</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="form-control"
|
||||||
|
min={1}
|
||||||
|
max={100000}
|
||||||
|
value={alertMapNormalSpeedMbps}
|
||||||
|
onChange={(e) => setAlertMapNormalSpeedMbps(e.target.value)}
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
<span className="input-group-text">Мбит/с</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Туннели (карта сети): индивидуальные пороги */}
|
{/* Туннели (карта сети): индивидуальные пороги */}
|
||||||
<div className="list-group-item py-3">
|
<div className="list-group-item py-3">
|
||||||
<div className="fw-semibold mb-1">Туннели (карта сети)</div>
|
<div className="fw-semibold mb-1">Туннели (карта сети)</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user