fix(ui): подтверждать GRE перед тестом пропускной способности

This commit is contained in:
Denozordec committed 2026-10-01 04:20:19 +07:00
1 parent b856215c03
commit 155dfc5716
4 files changed
+75 -18

No files matched your search

+33 -17
View File
@@ -20,6 +20,7 @@ import { Input } from "@/components/ui/input"
import { ServerRailLayout, ServerRailMobileButton } from "@/components/server-rail-layout"
import { type ServerTileItem } from "@/components/server-tile-rail"
import { servers, greTunnels, type GreTunnel, type Server } from "@/lib/data"
import { filtersGreResponseSchema } from "@/lib/filters-response"
import { can } from "@/lib/auth"
import { readProbeOutput, probeServersSchema } from "@/lib/probe-response"
import { useDataSource } from "@/lib/data-source"
@@ -584,6 +585,8 @@ function ProbesContent() {
const [catalogError, setCatalogError] = useState<string | null>(null)
const [catalogRevision, setCatalogRevision] = useState(0)
const [catalogReadAt, setCatalogReadAt] = useState<number | null>(null)
const [greState, setGreState] = useState<{ id: string; state: "loading" | "ready" | "error"; error?: string } | null>(null)
const [greRevision, setGreRevision] = useState(0)
const [greByServer, setGreByServer] = useState<Record<string, GreTunnel[]>>({})
const [tool, setTool] = useState<DiagTool>("ping")
@@ -671,22 +674,25 @@ function ProbesContent() {
return () => { cancelled = true }
}, [isLive, srcId, apiFetch])
const ensureGre = useCallback(async (serverId: string) => {
if (!isLive || !canReadGre) return
try {
const d = await apiFetch<{ tunnels: GreTunnel[] }>(
`/api/filters/gre-tunnels?serverId=${encodeURIComponent(serverId)}`,
)
setGreByServer(prev => ({ ...prev, [serverId]: d.tunnels }))
} catch {
setGreByServer(prev => ({ ...prev, [serverId]: [] }))
}
}, [isLive, canReadGre, apiFetch])
useEffect(() => {
if (!isLive || !srcId) return
void ensureGre(srcId)
}, [isLive, srcId, ensureGre])
if (!isLive || !canReadGre || liveLoad !== "idle" || !allServers.some(s => s.id === srcId && s.enabled)) return
let cancelled = false
const ctrl = new AbortController()
queueMicrotask(() => { if (!cancelled) setGreState({ id: srcId, state: "loading" }) })
void apiFetch<unknown>("/api/filters/gre-tunnels?serverId=" + encodeURIComponent(srcId), { signal: ctrl.signal })
.then(raw => {
const result = filtersGreResponseSchema.safeParse(raw)
if (!result.success || result.data.tunnels.some(tunnel => tunnel.serverId !== srcId)) throw new Error("Некорректный ответ API: GRE выбранного устройства не подтверждены.")
if (cancelled) return
setGreByServer(prev => ({ ...prev, [srcId]: result.data.tunnels }))
setGreState({ id: srcId, state: "ready" })
})
.catch(error => {
if (cancelled) return
setGreState({ id: srcId, state: "error", error: error instanceof Error ? error.message : "Не удалось прочитать GRE." })
})
return () => { cancelled = true; ctrl.abort() }
}, [isLive, canReadGre, liveLoad, allServers, srcId, apiFetch, greRevision])
const tunnelsForSrc = useMemo(() => {
if (isLive) return greByServer[srcId] ?? []
@@ -722,6 +728,7 @@ function ProbesContent() {
const bwTunnels = tunnelsForSrc
const srcServer = useMemo(() => allServers.find(s => s.id === srcId), [allServers, srcId])
const greReady = !isLive || (greState?.id === srcId && greState.state === "ready")
const bwTun = useMemo(() => bwTunnels.find(t => t.id === bwTunId), [bwTunnels, bwTunId])
// current command preview
@@ -752,7 +759,7 @@ function ProbesContent() {
// ── run test ──
const runTest = useCallback(async () => {
const srv = allServers.find(s => s.id === srcId)
if (!canRun || (tool === "bandwidth" && isLive && !canReadGre) || !srv || !srv.enabled || runLock.current || (isLive && liveLoad !== "idle")) return
if (!canRun || (tool === "bandwidth" && (!bwTun?.enabled || !bwTun.remoteAddress.trim() || !greReady || (isLive && !canReadGre))) || !srv || !srv.enabled || runLock.current || (isLive && liveLoad !== "idle")) return
const id = String(nextId.current++)
const numericServerId = Number.parseInt(srcId, 10)
@@ -881,6 +888,7 @@ function ProbesContent() {
liveLoad,
canRun,
canReadGre,
greReady,
allServers,
srcId,
tool,
@@ -973,6 +981,14 @@ function ProbesContent() {
{!canRun && <p role="status" className="text-sm text-muted-foreground">Для запуска диагностики требуется mm:servers:write.</p>}
{isLive && tool === "bandwidth" && !canReadGre && <p role="status" className="text-sm text-muted-foreground">Для выбора GRE требуется mm:filters:read.</p>}
{isLive && tool === "bandwidth" && canReadGre && (
<div className="text-sm">
{greState?.id === srcId && greState.state === "error" ? <div role="alert" aria-label="GRE диагностики"><p className="text-destructive">GRE недоступны. Последний список сохранён; BW-тест приостановлен. {greState.error}</p><Button type="button" size="sm" variant="outline" onClick={() => setGreRevision(n => n + 1)}>Повторить GRE</Button></div>
: !greReady ? <p role="status">GRE выбранного устройства загружаются…</p>
: !bwTunnels.length ? <p role="status">У выбранного устройства нет GRE-туннелей.</p> : !bwTun?.enabled || !bwTun.remoteAddress.trim() ? <p role="status">Выберите включённый GRE с адресом назначения.</p> : null}
</div>
)}
{/* ── tool selector + config ── */}
<OpsPanel contentClassName="pt-4 pb-4 px-4 flex flex-col gap-4">
@@ -1089,7 +1105,7 @@ function ProbesContent() {
<Button
onClick={() => void runTest()}
disabled={!canRun || (tool === "bandwidth" && isLive && !canReadGre) || runBusy || !srcServer?.enabled || (isLive && liveLoad !== "idle") || (tool === "bandwidth" && !bwTunnels.length)}
disabled={!canRun || (tool === "bandwidth" && (!bwTun?.enabled || !bwTun.remoteAddress.trim() || !greReady || (isLive && !canReadGre))) || runBusy || !srcServer?.enabled || (isLive && liveLoad !== "idle") || (tool === "bandwidth" && !bwTunnels.length)}
className="h-8 shrink-0 gap-1.5 self-end"
>
{runBusy ? <LoaderCircleIcon className="size-3.5 animate-spin" /> : <PlayIcon className="size-3.5" />}