fix(ui): привязать детали трафика к выбранному контексту

This commit is contained in:
Denozordec committed 2026-09-24 01:18:13 +07:00
1 parent 9eb6a3fad1
commit 480aa82f78
3 files changed
+95 -16

No files matched your search

+30 -16
View File
@@ -1,6 +1,7 @@
"use client"
import { useState, useMemo, useEffect, useCallback, type ReactNode } from "react"
import { Alert, AlertTitle, AlertDescription } from "@/components/reui/alert"
import { PageHeader } from "@/components/page-header"
import { Frame, FramePanel } from "@/components/reui/frame"
import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid"
@@ -800,8 +801,8 @@ export default function TrafficPage() {
const [serverIfaces, setServerIfaces] = useState<LiveTrafficInterface[]>([])
const [selectedIface, setSelectedIface] = useState("__all__")
const [hideDisabledIfaces, setHideDisabledIfaces] = useState(true)
const [detailBusy, setDetailBusy] = useState(false)
const [liveDetailServer, setLiveDetailServer] = useState<ServerTraffic | null>(null)
const [detailRetry, setDetailRetry] = useState(0)
const [detailResult, setDetailResult] = useState<{ key: string; server: ServerTraffic | null; error: string | null } | null>(null)
const [liveUsers, setLiveUsers] = useState<UserTraffic[]>([])
const [liveBoundIfaces, setLiveBoundIfaces] = useState<BoundIfaceTraffic[]>([])
const [flowStats, setFlowStats] = useState<FlowStatsDto | null>(null)
@@ -816,6 +817,9 @@ export default function TrafficPage() {
const [overlayOpen, setOverlayOpen] = useState(false)
const [catalogServers, setCatalogServers] = useState<ServerRead[]>([])
const effectiveMode: GroupMode = groupMode
const detailKey = JSON.stringify([isLive, backendUrl, effectiveMode, selectedId, range, selectedIface, detailRetry])
const currentDetail = detailResult?.key === detailKey ? detailResult : null
const detailBusy = isLive && effectiveMode === "servers" && Boolean(selectedId) && currentDetail === null
const { sample: liveSample, error: liveStreamError } = useTrafficLive({
enabled: isLive && effectiveMode === "servers" && liveServers.some((s) => s.id === selectedId),
backendUrl,
@@ -993,30 +997,35 @@ export default function TrafficPage() {
// eslint-disable-next-line react-hooks/set-state-in-effect
setServerIfaces([])
setSelectedIface("__all__")
setLiveDetailServer(null)
return
}
let cancelled = false
setServerIfaces([])
setSelectedIface("__all__")
apiFetch<{ interfaces: LiveTrafficInterface[] }>(`/api/traffic/servers/${encodeURIComponent(selectedId)}/interfaces`)
.then((res) => setServerIfaces(res.interfaces))
.catch(() => setServerIfaces([]))
.then((res) => { if (!cancelled) setServerIfaces(res.interfaces) })
.catch(() => { if (!cancelled) setServerIfaces([]) })
return () => { cancelled = true }
}, [isLive, selectedId, effectiveMode, apiFetch])
useEffect(() => {
if (!isLive || effectiveMode !== "servers" || !selectedId) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setLiveDetailServer(null)
return
}
setDetailBusy(true)
let cancelled = false
apiFetch<{ server: LiveTrafficServer }>(`/api/traffic/servers/${encodeURIComponent(selectedId)}?range=${encodeURIComponent(range)}&iface=${encodeURIComponent(selectedIface)}`)
.then((res) => setLiveDetailServer({
...toLiveServer(res.server),
boundIfaces: liveBoundIfaces.filter((i) => i.serverId === selectedId),
}))
.catch(() => setLiveDetailServer(null))
.finally(() => setDetailBusy(false))
}, [isLive, effectiveMode, selectedId, range, selectedIface, apiFetch, liveBoundIfaces])
.then((res) => {
if (cancelled) return
setDetailResult({ key: detailKey, error: null, server: {
...toLiveServer(res.server),
boundIfaces: liveBoundIfaces.filter((i) => i.serverId === selectedId),
} })
})
.catch((error: unknown) => {
if (!cancelled) setDetailResult({ key: detailKey, server: null, error: error instanceof Error ? error.message : "Не удалось загрузить детали трафика" })
})
return () => { cancelled = true }
}, [isLive, effectiveMode, selectedId, range, selectedIface, apiFetch, liveBoundIfaces, detailKey])
const handleModeChange = (next: GroupMode) => {
setGroupMode(next)
@@ -1106,7 +1115,7 @@ export default function TrafficPage() {
const displayedFlow = flowLiveSample ?? flowAnalytics
const selServer = useMemo(() => activeServerTraffic.find(s => s.id === selectedId) ?? activeServerTraffic[0], [selectedId, activeServerTraffic])
const detailServer = liveDetailServer?.id === selectedId ? liveDetailServer : selServer
const detailServer = isLive ? currentDetail?.server : selServer
const selUser = useMemo(() => activeUserTraffic.find(u => u.id === selectedId) ?? activeUserTraffic[0], [selectedId, activeUserTraffic])
const selIface = useMemo(() => activeBoundIfaces.find(c => c.id === selectedId) ?? activeBoundIfaces[0], [selectedId, activeBoundIfaces])
@@ -1457,6 +1466,11 @@ export default function TrafficPage() {
</div>
</div>
)}
{isLive && effectiveMode === "servers" && !detailServer && <>
<DetailHeader range={range} setRange={setRange}><h2 className="text-base font-semibold">{selServer?.name ?? "Трафик сервера"}</h2></DetailHeader>
{detailBusy && <p role="status" className="text-sm text-muted-foreground">Загрузка выбранного периода и интерфейса…</p>}
{currentDetail?.error && <Alert variant="destructive" role="alert"><AlertTitle>Детали трафика недоступны</AlertTitle><AlertDescription>{currentDetail.error}<Button type="button" size="sm" variant="outline" onClick={() => setDetailRetry(n => n + 1)}>Повторить загрузку деталей</Button></AlertDescription></Alert>}
</>}
{effectiveMode === "servers" && detailServer && (
<ServerDetail
sel={detailServer}