fix(ui): подтверждать GRE перед тестом пропускной способности
This commit is contained in:
1 parent
b856215c03
commit
155dfc5716
4 files changed
+75
-18
No files matched your search
+33
-17
@@ -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" />}
|
||||
|
||||
Reference in new issue
Block a user