fix(ui): сохранять устройство диагностики в URL
This commit is contained in:
1 parent
1b91ba4e1d
commit
d0c715e93c
7 files changed
+89
-28
No files matched your search
+22
-11
@@ -1,6 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef, useState, useMemo, useCallback } from "react"
|
||||
import { Suspense, useEffect, useRef, useState, useMemo, useCallback } from "react"
|
||||
import { useSearchParams } from "next/navigation"
|
||||
import { readProbeUrl, patchProbeUrl } from "@/lib/probe-url"
|
||||
import { PageHeader } from "@/components/page-header"
|
||||
import { FormToggle } from "@/components/form-kit"
|
||||
import { Frame, FramePanel } from "@/components/reui/frame"
|
||||
@@ -567,12 +569,23 @@ function ScheduleTab({
|
||||
// ─── page ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function ProbesPage() {
|
||||
return <Suspense fallback={<p role="status">Загрузка диагностики…</p>}><ProbesRoute /></Suspense>
|
||||
}
|
||||
|
||||
function ProbesRoute() {
|
||||
const params = useSearchParams()
|
||||
const url = readProbeUrl(new URLSearchParams(params.toString()))
|
||||
const { mode, backendUrl, prefsHydrated } = useDataSource()
|
||||
if (url.error) return <div role="alert" aria-label="Ссылка диагностики" className="p-4 space-y-2"><p>{url.error}</p><Button type="button" variant="outline" onClick={() => window.history.replaceState(null, "", patchProbeUrl(new URLSearchParams(params.toString()), null) + window.location.hash)}>Сбросить контекст диагностики</Button></div>
|
||||
if (!prefsHydrated) return <p role="status">Загрузка настроек диагностики…</p>
|
||||
return <ProbesContent key={JSON.stringify([mode, backendUrl])} />
|
||||
}
|
||||
|
||||
function ProbesContent() {
|
||||
const params = useSearchParams()
|
||||
const { server } = readProbeUrl(new URLSearchParams(params.toString()))
|
||||
const srcId = server ?? ""
|
||||
const selectServer = (id: string) => window.history.pushState(null, "", patchProbeUrl(new URLSearchParams(params.toString()), id || null) + window.location.hash)
|
||||
const { mode, backendUrl } = useDataSource()
|
||||
const canRead = can("mm:servers:read")
|
||||
const canRun = can("mm:servers:write")
|
||||
@@ -590,7 +603,6 @@ function ProbesContent() {
|
||||
const [greByServer, setGreByServer] = useState<Record<string, GreTunnel[]>>({})
|
||||
|
||||
const [tool, setTool] = useState<DiagTool>("ping")
|
||||
const [srcId, setSrcId] = useState(servers[0]?.id ?? "srv1")
|
||||
const [target, setTarget] = useState("8.8.8.8")
|
||||
const [pingCount, setPingCount] = useState(5)
|
||||
const [pingSize, setPingSize] = useState(64)
|
||||
@@ -653,8 +665,8 @@ function ProbesContent() {
|
||||
), [allServers])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLive) {
|
||||
setRosSrcV4(undefined)
|
||||
if (!isLive || !canRead || liveLoad !== "idle" || !allServers.some(s => s.id === srcId && s.enabled)) {
|
||||
queueMicrotask(() => setRosSrcV4(undefined))
|
||||
return
|
||||
}
|
||||
const n = Number.parseInt(srcId, 10)
|
||||
@@ -672,7 +684,7 @@ function ProbesContent() {
|
||||
if (!cancelled) setRosSrcV4(null)
|
||||
})
|
||||
return () => { cancelled = true }
|
||||
}, [isLive, srcId, apiFetch])
|
||||
}, [isLive, canRead, liveLoad, allServers, srcId, apiFetch])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLive || !canReadGre || liveLoad !== "idle" || !allServers.some(s => s.id === srcId && s.enabled)) return
|
||||
@@ -699,11 +711,6 @@ function ProbesContent() {
|
||||
return greTunnels.filter(t => t.serverId === srcId)
|
||||
}, [isLive, greByServer, srcId])
|
||||
|
||||
useEffect(() => {
|
||||
const first = allServers[0]?.id
|
||||
if (!first) return
|
||||
if (!allServers.some(s => s.id === srcId)) setSrcId(first)
|
||||
}, [allServers, srcId])
|
||||
|
||||
useEffect(() => {
|
||||
const list = isLive ? (greByServer[srcId] ?? []) : greTunnels.filter(t => t.serverId === srcId)
|
||||
@@ -939,7 +946,8 @@ function ProbesContent() {
|
||||
<ServerRailLayout
|
||||
items={probeRailItems}
|
||||
selectedId={srcId}
|
||||
onSelect={setSrcId}
|
||||
onSelect={selectServer}
|
||||
preserveSelection
|
||||
showAll={false}
|
||||
loading={isLive && liveLoad === "loading" && allServers.length === 0}
|
||||
header={
|
||||
@@ -963,6 +971,9 @@ function ProbesContent() {
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
{!server && <p role="status">Выберите устройство для диагностики. Выбор сохраняется в URL.</p>}
|
||||
{server && (!isLive || catalogReadAt !== null) && !srcServer && <div role="alert" aria-label="Устройство диагностики"><p>Указанное устройство отсутствует в текущем каталоге. Другое устройство автоматически не выбирается.</p><Button type="button" variant="outline" onClick={() => selectServer("")}>Сбросить выбор устройства</Button></div>}
|
||||
{srcServer && !srcServer.enabled && <p role="status">Выбранное устройство выключено. Диагностика недоступна.</p>}
|
||||
{isLive && liveLoad === "error" && (
|
||||
<div role="alert" aria-label="Каталог диагностики" className="rounded-lg border border-destructive/30 bg-destructive/10 px-4 py-2.5 text-sm text-destructive">
|
||||
<p>{catalogReadAt === null ? "Устройства пока неизвестны." : "Не удалось обновить устройства. Последний список сохранён; запуск приостановлен."}</p>
|
||||
|
||||
Reference in new issue
Block a user