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

This commit is contained in:
Denozordec committed 2026-09-28 11:27:20 +07:00
1 parent 65241c94e9
commit c6d146d38f
7 files changed
+56 -27

No files matched your search

+10 -5
View File
@@ -782,7 +782,7 @@ export default function TrafficPage() {
const [search, setSearch] = useState("")
const [trafficRetry, setTrafficRetry] = useState(0)
const trafficKey = JSON.stringify([isLive, backendUrl, range, groupMode, trafficRetry])
const [trafficState, setTrafficState] = useState<{ key: string; status: "ready" | "error"; error?: string } | null>(null)
const [trafficState, setTrafficState] = useState<{ key: string; status: "ready" | "error"; receivedAt?: number; error?: string } | null>(null)
const trafficReady = trafficState?.key === trafficKey && trafficState.status === "ready"
const trafficLoading = trafficState?.key !== trafficKey
const trafficError = trafficState?.key === trafficKey ? trafficState.error : undefined
@@ -791,7 +791,7 @@ export default function TrafficPage() {
const [selectedIface, setSelectedIface] = useState("__all__")
const [hideDisabledIfaces, setHideDisabledIfaces] = useState(true)
const [detailRetry, setDetailRetry] = useState(0)
const [detailResult, setDetailResult] = useState<{ key: string; server: ServerTraffic | null; error: string | null } | null>(null)
const [detailResult, setDetailResult] = useState<{ key: string; server: ServerTraffic | null; receivedAt?: number; error: string | null } | null>(null)
const [liveUsers, setLiveUsers] = useState<UserTraffic[]>([])
const [liveBoundIfaces, setLiveBoundIfaces] = useState<BoundIfaceTraffic[]>([])
const [flowScope, setFlowScope] = useState<FlowScope>("servers")
@@ -819,7 +819,7 @@ export default function TrafficPage() {
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({
const { sample: liveSample, error: liveStreamError, receivedAt: liveReceivedAt } = useTrafficLive({
enabled: isLive && effectiveMode === "servers" && liveServers.some((s) => s.id === selectedId),
backendUrl,
serverId: selectedId,
@@ -860,7 +860,7 @@ export default function TrafficPage() {
setLiveServers(mapped)
setLiveUsers(mappedUsers)
setLiveBoundIfaces(liveIfaces)
setTrafficState({ key, status: "ready" })
setTrafficState({ key, status: "ready", receivedAt: Date.now() })
setSelectedId((prev) => {
if (groupMode === "users") return mappedUsers.some((x) => x.id === prev) ? prev : (mappedUsers[0]?.id ?? "")
if (groupMode === "ifaces") return liveIfaces.some((x) => x.id === prev) ? prev : (liveIfaces[0]?.id ?? "")
@@ -952,7 +952,7 @@ export default function TrafficPage() {
apiFetch<{ server: LiveTrafficServer }>(`/api/traffic/servers/${encodeURIComponent(selectedId)}?range=${encodeURIComponent(range)}&iface=${encodeURIComponent(selectedIface)}`)
.then((res) => {
if (cancelled) return
setDetailResult({ key: detailKey, error: null, server: {
setDetailResult({ key: detailKey, receivedAt: Date.now(), error: null, server: {
...toLiveServer(res.server),
boundIfaces: liveBoundIfaces.filter((i) => i.serverId === selectedId),
} })
@@ -1183,6 +1183,7 @@ export default function TrafficPage() {
items={effectiveMode === "flows" ? flowKpiItems : counterKpiItems}
/>}
{isLive && effectiveMode !== "flows" && trafficReady && <TrafficDataTime label="Получение списка трафика" receivedAt={trafficState.receivedAt} source="API" />}
{effectiveMode === "flows" ? (
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between gap-2 flex-wrap">
@@ -1420,6 +1421,10 @@ export default function TrafficPage() {
{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>}
</>}
{isLive && effectiveMode === "servers" && detailServer && <>
<TrafficDataTime label="Получение истории сервера" receivedAt={currentDetail?.receivedAt} source="API" />
<TrafficDataTime label="Последнее измерение сервера" receivedAt={liveReceivedAt} source="live-поток" serverAt={liveSample?.at} />
</>}
{effectiveMode === "servers" && detailServer && (
<ServerDetail
sel={detailServer}