fix(ui): различать ошибку и пустые данные VXLAN

This commit is contained in:
Denozordec committed 2026-09-30 09:37:31 +07:00
1 parent 3d518ff902
commit 4dc2ee2129
5 files changed
+56 -12

No files matched your search

+17 -7
View File
@@ -1,6 +1,6 @@
"use client"
import { Suspense, useCallback, useEffect, useMemo, useState } from "react"
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { Alert as ContextAlert, AlertTitle as ContextTitle, AlertDescription as ContextDescription } from "@/components/reui/alert"
@@ -139,10 +139,14 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
const [liveTunnels, setLiveTunnels] = useState<VxlanTunnel[]>([])
const [liveServers, setLiveServers] = useState<Server[]>([])
const [loading, setLoading] = useState(true)
const [hasLoaded, setHasLoaded] = useState(false)
const loadEpoch = useRef(0)
useEffect(() => () => { loadEpoch.current += 1 }, [backendUrl, isLive])
const [liveError, setLiveError] = useState<string | null>(null)
const loadLive = useCallback(async () => {
if (!isLive) return
const epoch = ++loadEpoch.current
setLoading(true)
setLiveError(null)
try {
@@ -150,12 +154,16 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
requestJson<VxlanApiResponse>(backendUrl, "/api/vxlan"),
requestJson<BackendServer[]>(backendUrl, "/api/servers"),
])
setLiveTunnels(tunnelsRes.tunnels ?? [])
if (epoch !== loadEpoch.current) return
if (!Array.isArray(tunnelsRes?.tunnels) || !Array.isArray(serversRes)) throw new Error("Некорректный ответ загрузки VXLAN")
setLiveTunnels(tunnelsRes.tunnels)
setHasLoaded(true)
setLiveServers(serversRes.filter((s) => s.enabled).map(mapBackendServer))
} catch (e) {
if (epoch !== loadEpoch.current) return
setLiveError(e instanceof Error ? e.message : "Ошибка загрузки")
} finally {
setLoading(false)
if (epoch === loadEpoch.current) setLoading(false)
}
}, [isLive, backendUrl])
@@ -273,6 +281,8 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
<AlertDescription className="text-xs">Ошибка загрузки: {liveError}</AlertDescription>
</Alert>
)}
{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 && (
<div className="rounded-md border border-border bg-muted/30 px-4 py-6 text-center text-sm text-muted-foreground">
На опрошенных серверах нет VXLAN-интерфейсов
@@ -290,28 +300,28 @@ function VxlanPageContent({ selectedServerId, selectedTunnelId, allTunnelsHref,
{
id: "tunnels",
label: "Туннелей",
value: scopedTunnels.length,
value: isLive && !hasLoaded ? "—" : scopedTunnels.length,
icon: <NetworkIcon className="size-4" />,
iconClassName: "text-muted-foreground",
},
{
id: "up",
label: "Активных",
value: upCount,
value: isLive && !hasLoaded ? "—" : upCount,
icon: <LayersIcon className="size-4" />,
iconClassName: "text-success",
},
{
id: "vni",
label: "Уникальных VNI",
value: vnis,
value: isLive && !hasLoaded ? "—" : vnis,
icon: <LayersIcon className="size-4" />,
iconClassName: "text-info",
},
{
id: "servers",
label: "Серверов",
value: new Set(scopedTunnels.map((t) => t.serverId)).size,
value: isLive && !hasLoaded ? "—" : new Set(scopedTunnels.map((t) => t.serverId)).size,
icon: <NetworkIcon className="size-4" />,
iconClassName: "text-primary",
},