feat(network-map): enhance node metadata handling and display for improved network issue identification
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m7s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m7s
This commit is contained in:
@@ -167,6 +167,21 @@ export default function NetworkMapUnifi({
|
|||||||
return { low, belowNorm, normal };
|
return { low, belowNorm, normal };
|
||||||
}, [alertSettings]);
|
}, [alertSettings]);
|
||||||
|
|
||||||
|
const nodeMetaByIp = useMemo(() => {
|
||||||
|
const byIp = new Map();
|
||||||
|
servers.forEach((s) => {
|
||||||
|
const ip = String(s.ip || '');
|
||||||
|
if (!ip) return;
|
||||||
|
byIp.set(ip, {
|
||||||
|
ip,
|
||||||
|
dns: s.dns || '',
|
||||||
|
type: String(s.type || '').toLowerCase(),
|
||||||
|
nodeLabel: s.dns ? String(s.dns).split('.')[0] : ip,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return byIp;
|
||||||
|
}, [servers]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = containerRef.current;
|
const el = containerRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -501,7 +516,9 @@ export default function NetworkMapUnifi({
|
|||||||
linkCount: 0,
|
linkCount: 0,
|
||||||
staleCount: 0,
|
staleCount: 0,
|
||||||
maxPingMs: null,
|
maxPingMs: null,
|
||||||
|
maxPingPeerLabel: null,
|
||||||
minSpeedMbps: null,
|
minSpeedMbps: null,
|
||||||
|
minSpeedPeerLabel: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -516,15 +533,24 @@ export default function NetworkMapUnifi({
|
|||||||
[e.from, e.to].forEach((nodeIp) => {
|
[e.from, e.to].forEach((nodeIp) => {
|
||||||
const node = byIp.get(String(nodeIp));
|
const node = byIp.get(String(nodeIp));
|
||||||
if (!node) return;
|
if (!node) return;
|
||||||
|
const peerIp = String(nodeIp) === String(e.from) ? String(e.to) : String(e.from);
|
||||||
|
const peerMeta = nodeMetaByIp.get(peerIp);
|
||||||
|
const peerLabel = peerMeta?.nodeLabel || peerIp;
|
||||||
if (e.stale) node.staleCount += 1;
|
if (e.stale) node.staleCount += 1;
|
||||||
if (typeof e.pingMs === 'number') {
|
if (typeof e.pingMs === 'number') {
|
||||||
node.maxPingMs = node.maxPingMs == null ? e.pingMs : Math.max(node.maxPingMs, e.pingMs);
|
if (node.maxPingMs == null || e.pingMs > node.maxPingMs) {
|
||||||
|
node.maxPingMs = e.pingMs;
|
||||||
|
node.maxPingPeerLabel = peerLabel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const down = e.speed?.tcpDownloadBps != null ? e.speed.tcpDownloadBps / 1_000_000 : null;
|
const down = e.speed?.tcpDownloadBps != null ? e.speed.tcpDownloadBps / 1_000_000 : null;
|
||||||
const up = e.speed?.tcpUploadBps != null ? e.speed.tcpUploadBps / 1_000_000 : null;
|
const up = e.speed?.tcpUploadBps != null ? e.speed.tcpUploadBps / 1_000_000 : null;
|
||||||
const edgeMin = down != null && up != null ? Math.min(down, up) : (down ?? up);
|
const edgeMin = down != null && up != null ? Math.min(down, up) : (down ?? up);
|
||||||
if (edgeMin != null && Number.isFinite(edgeMin)) {
|
if (edgeMin != null && Number.isFinite(edgeMin)) {
|
||||||
node.minSpeedMbps = node.minSpeedMbps == null ? edgeMin : Math.min(node.minSpeedMbps, edgeMin);
|
if (node.minSpeedMbps == null || edgeMin < node.minSpeedMbps) {
|
||||||
|
node.minSpeedMbps = edgeMin;
|
||||||
|
node.minSpeedPeerLabel = peerLabel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -536,23 +562,23 @@ export default function NetworkMapUnifi({
|
|||||||
|
|
||||||
if (n.maxPingMs != null) {
|
if (n.maxPingMs != null) {
|
||||||
if (n.maxPingMs >= 120) {
|
if (n.maxPingMs >= 120) {
|
||||||
issues.push('Очень высокий ping');
|
issues.push(`Очень высокий ping${n.maxPingPeerLabel ? ` (с ${n.maxPingPeerLabel})` : ''}`);
|
||||||
riskScore += 3;
|
riskScore += 3;
|
||||||
} else if (n.maxPingMs >= 80) {
|
} else if (n.maxPingMs >= 80) {
|
||||||
issues.push('Повышенный ping');
|
issues.push(`Повышенный ping${n.maxPingPeerLabel ? ` (с ${n.maxPingPeerLabel})` : ''}`);
|
||||||
riskScore += 2;
|
riskScore += 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n.minSpeedMbps != null) {
|
if (n.minSpeedMbps != null) {
|
||||||
if (n.minSpeedMbps < mapSpeedThresholds.low) {
|
if (n.minSpeedMbps < mapSpeedThresholds.low) {
|
||||||
issues.push('Низкая скорость');
|
issues.push(`Низкая скорость${n.minSpeedPeerLabel ? ` (с ${n.minSpeedPeerLabel})` : ''}`);
|
||||||
riskScore += 3;
|
riskScore += 3;
|
||||||
} else if (n.minSpeedMbps < mapSpeedThresholds.belowNorm) {
|
} else if (n.minSpeedMbps < mapSpeedThresholds.belowNorm) {
|
||||||
issues.push('Скорость ниже нормы');
|
issues.push(`Скорость ниже нормы${n.minSpeedPeerLabel ? ` (с ${n.minSpeedPeerLabel})` : ''}`);
|
||||||
riskScore += 2;
|
riskScore += 2;
|
||||||
} else if (n.minSpeedMbps < mapSpeedThresholds.normal) {
|
} else if (n.minSpeedMbps < mapSpeedThresholds.normal) {
|
||||||
issues.push('Норма скорости не достигнута');
|
issues.push(`Норма скорости не достигнута${n.minSpeedPeerLabel ? ` (с ${n.minSpeedPeerLabel})` : ''}`);
|
||||||
riskScore += 1;
|
riskScore += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -572,6 +598,10 @@ export default function NetworkMapUnifi({
|
|||||||
issues,
|
issues,
|
||||||
riskScore,
|
riskScore,
|
||||||
nodeLabel: n.dns ? String(n.dns).split('.')[0] : n.ip,
|
nodeLabel: n.dns ? String(n.dns).split('.')[0] : n.ip,
|
||||||
|
affectedLink:
|
||||||
|
(n.maxPingPeerLabel && `${n.dns ? String(n.dns).split('.')[0] : n.ip} ↔ ${n.maxPingPeerLabel}`) ||
|
||||||
|
(n.minSpeedPeerLabel && `${n.dns ? String(n.dns).split('.')[0] : n.ip} ↔ ${n.minSpeedPeerLabel}`) ||
|
||||||
|
'—',
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((n) => n.issues.length > 0)
|
.filter((n) => n.issues.length > 0)
|
||||||
@@ -580,7 +610,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, mapSpeedThresholds]);
|
}, [servers, connections, routedEdges, mapSpeedThresholds, nodeMetaByIp]);
|
||||||
|
|
||||||
const criticalNodes = useMemo(
|
const criticalNodes = useMemo(
|
||||||
() => problematicNodes.filter((n) => n.riskScore >= 4).slice(0, 8),
|
() => problematicNodes.filter((n) => n.riskScore >= 4).slice(0, 8),
|
||||||
@@ -1078,13 +1108,14 @@ export default function NetworkMapUnifi({
|
|||||||
<th scope="col">Ping max</th>
|
<th scope="col">Ping max</th>
|
||||||
<th scope="col">Speed min</th>
|
<th scope="col">Speed min</th>
|
||||||
<th scope="col">Связи</th>
|
<th scope="col">Связи</th>
|
||||||
|
<th scope="col">Проблемная связь</th>
|
||||||
<th scope="col">Проблемы</th>
|
<th scope="col">Проблемы</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{criticalNodes.length === 0 ? (
|
{criticalNodes.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="text-muted">Критичных узлов не найдено</td>
|
<td colSpan={7} className="text-muted">Критичных узлов не найдено</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
criticalNodes.map((n) => (
|
criticalNodes.map((n) => (
|
||||||
@@ -1101,6 +1132,9 @@ export default function NetworkMapUnifi({
|
|||||||
<td>{n.maxPingMs != null ? `${n.maxPingMs} ms` : '—'}</td>
|
<td>{n.maxPingMs != null ? `${n.maxPingMs} ms` : '—'}</td>
|
||||||
<td>{n.minSpeedMbps != null ? `${n.minSpeedMbps.toFixed(1)} Мбит/с` : '—'}</td>
|
<td>{n.minSpeedMbps != null ? `${n.minSpeedMbps.toFixed(1)} Мбит/с` : '—'}</td>
|
||||||
<td>{n.linkCount}</td>
|
<td>{n.linkCount}</td>
|
||||||
|
<td>
|
||||||
|
<span className="text-muted">{n.affectedLink}</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span className="badge bg-orange-lt text-orange">
|
<span className="badge bg-orange-lt text-orange">
|
||||||
{n.issues.slice(0, 2).join(', ')}
|
{n.issues.slice(0, 2).join(', ')}
|
||||||
@@ -1130,13 +1164,14 @@ export default function NetworkMapUnifi({
|
|||||||
<th scope="col">Ping max</th>
|
<th scope="col">Ping max</th>
|
||||||
<th scope="col">Speed min</th>
|
<th scope="col">Speed min</th>
|
||||||
<th scope="col">Stale</th>
|
<th scope="col">Stale</th>
|
||||||
|
<th scope="col">Проблемная связь</th>
|
||||||
<th scope="col">Проблемы</th>
|
<th scope="col">Проблемы</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{warningNodes.length === 0 ? (
|
{warningNodes.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={5} className="text-muted">Узлов с предупреждениями не найдено</td>
|
<td colSpan={6} className="text-muted">Узлов с предупреждениями не найдено</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
warningNodes.map((n) => (
|
warningNodes.map((n) => (
|
||||||
@@ -1152,6 +1187,9 @@ export default function NetworkMapUnifi({
|
|||||||
{n.staleCount}
|
{n.staleCount}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<span className="text-muted">{n.affectedLink}</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span className="text-muted">{n.issues.slice(0, 2).join(', ')}</span>
|
<span className="text-muted">{n.issues.slice(0, 2).join(', ')}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user