fix(ui): показывать частичные сбои опроса VXLAN
This commit is contained in:
1 parent
4dc2ee2129
commit
e163a9ed9d
9 files changed
+120
-41
No files matched your search
@@ -38,6 +38,7 @@ interface BackendServer {
|
||||
}
|
||||
|
||||
interface VxlanApiResponse {
|
||||
failures?: Array<{ serverId: string; serverName?: string; error: string }>
|
||||
tunnels: VxlanTunnel[]
|
||||
}
|
||||
|
||||
@@ -139,6 +140,7 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
|
||||
const [liveTunnels, setLiveTunnels] = useState<VxlanTunnel[]>([])
|
||||
const [liveServers, setLiveServers] = useState<Server[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [failures, setFailures] = useState<NonNullable<VxlanApiResponse["failures"]>>([])
|
||||
const [hasLoaded, setHasLoaded] = useState(false)
|
||||
const loadEpoch = useRef(0)
|
||||
useEffect(() => () => { loadEpoch.current += 1 }, [backendUrl, isLive])
|
||||
@@ -156,7 +158,12 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
|
||||
])
|
||||
if (epoch !== loadEpoch.current) return
|
||||
if (!Array.isArray(tunnelsRes?.tunnels) || !Array.isArray(serversRes)) throw new Error("Некорректный ответ загрузки VXLAN")
|
||||
setLiveTunnels(tunnelsRes.tunnels)
|
||||
const nextFailures = tunnelsRes.failures ?? []
|
||||
if (!Array.isArray(nextFailures) || nextFailures.some(f => !f || typeof f.serverId !== "string" || typeof f.error !== "string")) throw new Error("Некорректный ответ загрузки VXLAN")
|
||||
const failedIds = new Set(nextFailures.map(f => f.serverId))
|
||||
const enabledIds = new Set(serversRes.filter(s => s.enabled).map(s => String(s.id)))
|
||||
setLiveTunnels(previous => [...tunnelsRes.tunnels.filter(t => !failedIds.has(t.serverId)), ...previous.filter(t => failedIds.has(t.serverId) && enabledIds.has(t.serverId))])
|
||||
setFailures(nextFailures)
|
||||
setHasLoaded(true)
|
||||
setLiveServers(serversRes.filter((s) => s.enabled).map(mapBackendServer))
|
||||
} catch (e) {
|
||||
@@ -224,6 +231,8 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
|
||||
)
|
||||
}, [search, scopedTunnels, serverById])
|
||||
|
||||
const failedServerIds = useMemo(() => isLive ? failures.map(f => f.serverId) : [], [isLive, failures])
|
||||
const scopedFailures = failures.filter(f => effectiveServerId === ALL_SERVERS_ID || f.serverId === effectiveServerId)
|
||||
const upCount = scopedTunnels.filter((t) => t.status === "up").length
|
||||
const vnis = new Set(scopedTunnels.map((t) => t.vni)).size
|
||||
|
||||
@@ -261,6 +270,13 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-5">
|
||||
{isLive && scopedFailures.length > 0 && <ContextAlert variant="warning" role="alert">
|
||||
<ContextTitle>Не все устройства ответили</ContextTitle>
|
||||
<ContextDescription>
|
||||
<p>Не удалось опросить: {scopedFailures.map(f => f.serverName ?? f.serverId).join(", ")}. Для этих устройств сохранены последние данные; текущее состояние туннелей неизвестно.</p>
|
||||
<Button variant="outline" size="sm" disabled={loading} onClick={() => void loadLive()}>Повторить опрос</Button>
|
||||
</ContextDescription>
|
||||
</ContextAlert>}
|
||||
{selectedTunnelId !== null && (
|
||||
<ContextAlert variant={liveError || ((!isLive || !loading) && !selectedTunnel) ? "warning" : "info"} role="status" aria-label="VXLAN по ссылке">
|
||||
<ContextTitle className="line-clamp-none wrap-anywhere">{selectedTunnel ? "Выбран VXLAN: " + selectedTunnel.name : "VXLAN по ссылке"}</ContextTitle>
|
||||
@@ -283,7 +299,7 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
|
||||
)}
|
||||
{isLive && loading && <p role="status" className="text-sm text-muted-foreground">{hasLoaded ? "Обновление VXLAN…" : "Загрузка VXLAN…"}</p>}
|
||||
{isLive && liveError && hasLoaded && <p className="text-sm text-muted-foreground">Показаны последние полученные данные; они могут быть устаревшими.</p>}
|
||||
{selectedTunnelId === null && isLive && !loading && displayTunnels.length === 0 && !liveError && (
|
||||
{selectedTunnelId === null && isLive && !loading && displayTunnels.length === 0 && !liveError && scopedFailures.length === 0 && (
|
||||
<div className="rounded-md border border-border bg-muted/30 px-4 py-6 text-center text-sm text-muted-foreground">
|
||||
На опрошенных серверах нет VXLAN-интерфейсов
|
||||
</div>
|
||||
@@ -300,28 +316,28 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
|
||||
{
|
||||
id: "tunnels",
|
||||
label: "Туннелей",
|
||||
value: isLive && !hasLoaded ? "—" : scopedTunnels.length,
|
||||
value: isLive && (!hasLoaded || scopedFailures.length > 0) ? "—" : scopedTunnels.length,
|
||||
icon: <NetworkIcon className="size-4" />,
|
||||
iconClassName: "text-muted-foreground",
|
||||
},
|
||||
{
|
||||
id: "up",
|
||||
label: "Активных",
|
||||
value: isLive && !hasLoaded ? "—" : upCount,
|
||||
value: isLive && (!hasLoaded || scopedFailures.length > 0) ? "—" : upCount,
|
||||
icon: <LayersIcon className="size-4" />,
|
||||
iconClassName: "text-success",
|
||||
},
|
||||
{
|
||||
id: "vni",
|
||||
label: "Уникальных VNI",
|
||||
value: isLive && !hasLoaded ? "—" : vnis,
|
||||
value: isLive && (!hasLoaded || scopedFailures.length > 0) ? "—" : vnis,
|
||||
icon: <LayersIcon className="size-4" />,
|
||||
iconClassName: "text-info",
|
||||
},
|
||||
{
|
||||
id: "servers",
|
||||
label: "Серверов",
|
||||
value: isLive && !hasLoaded ? "—" : new Set(scopedTunnels.map((t) => t.serverId)).size,
|
||||
value: isLive && (!hasLoaded || scopedFailures.length > 0) ? "—" : new Set(scopedTunnels.map((t) => t.serverId)).size,
|
||||
icon: <NetworkIcon className="size-4" />,
|
||||
iconClassName: "text-primary",
|
||||
},
|
||||
@@ -348,6 +364,7 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
|
||||
/>
|
||||
<VxlanDataGrid
|
||||
tunnels={filtered}
|
||||
failedServerIds={failedServerIds}
|
||||
servers={displayServers}
|
||||
onExport={setExportTunnel}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user