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

This commit is contained in:
Denozordec committed 2026-09-20 17:00:30 +07:00
1 parent 668c7c6823
commit 6c8e66693b
5 files changed
+147 -8

No files matched your search

+44 -6
View File
@@ -1,6 +1,9 @@
"use client"
import { useState, useMemo, useEffect, useRef, useCallback } from "react"
import { Suspense, useState, useMemo, useEffect, useRef, useCallback } from "react"
import Link from "next/link"
import { useSearchParams } from "next/navigation"
import { Alert as ContextAlert, AlertTitle as ContextTitle, AlertDescription as ContextDescription } from "@/components/reui/alert"
import { PageHeader } from "@/components/page-header"
import { FormField, FormToggle, SegmentedControl } from "@/components/form-kit"
import { Frame, FramePanel } from "@/components/reui/frame"
@@ -1047,12 +1050,13 @@ function ResourcesTab({ resources, serversList, liveApi }: { resources: ServerRe
// ── page ───────────────────────────────────────────────────────────────────────
export default function UptimePage() {
function UptimePageContent({ selectedProbeId, allProbesHref }: { selectedProbeId: string | null; allProbesHref: string }) {
const { mode, backendUrl, backendStatus, checkBackend } = useDataSource()
/** При mode=live всегда ходим на backend. Нельзя требовать backendStatus===true: до ответа /health там undefined — иначе обзор/«Обновить» молчат. */
const liveApi = mode === "live"
const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl])
const [overviewState, setOverviewState] = useState<"loading" | "ready" | "error">("loading")
const [allServers, setAllServers] = useState<Server[]>([])
const [tab, setTab] = useState<"probes" | "resources" | "speed">("probes")
const [probes, setProbes] = useState<PingProbe[]>([])
@@ -1095,6 +1099,7 @@ export default function UptimePage() {
setAllServers(mockServers)
setProbes(mockProbesWithSavedStars(INIT_PROBES))
setResources(INIT_RESOURCES)
setOverviewState("ready")
}, [liveApi])
const isPausedRef = useRef(isPaused)
@@ -1116,9 +1121,11 @@ export default function UptimePage() {
setAllServers(mapBackendServersToServers(serverRows))
setProbes(data.probes)
setResources(data.resources)
setOverviewState("ready")
} catch (e) {
if (myReq !== overviewReqRef.current) return
setOpError(e instanceof Error ? e.message : "Не удалось загрузить uptime")
setOverviewState("error")
}
}, [apiFetch, liveApi])
@@ -1395,7 +1402,11 @@ export default function UptimePage() {
[resources, allServers],
)
const selectedProbe = probes.find(probe => probe.id === selectedProbeId)
const selectedProbeServer = allServers.find(server => server.id === selectedProbe?.srcServerId)
const filtered = useMemo(() => probes.filter(p => {
if (selectedProbeId !== null && p.id !== selectedProbeId) return false
if (serverFilter.length > 0 && !serverFilter.includes(p.srcServerId)) return false
if (statusFilter !== "all" && p.status !== statusFilter) return false
if (search) {
@@ -1407,7 +1418,7 @@ export default function UptimePage() {
)
}
return true
}), [probes, serverFilter, statusFilter, search])
}), [probes, serverFilter, statusFilter, search, selectedProbeId])
const grouped = useMemo(() => {
const byServer = new Map<string, Map<string, { name: string; target: string; probes: PingProbe[] }>>()
@@ -1946,13 +1957,13 @@ export default function UptimePage() {
<Button variant="outline" size="sm" disabled={liveApi && uptimeRefreshBusy} onClick={() => {
if (liveApi) {
void refreshUptimeLive({ showSpinner: true, pollDevices: tab === "resources" || tab === "probes" })
void refreshUptimeLive({ showSpinner: true, pollDevices: selectedProbeId === null && (tab === "resources" || tab === "probes") })
return
}
setProbes(mockProbesWithSavedStars(INIT_PROBES))
setResources(INIT_RESOURCES)
}}>
<RefreshCwIcon className={cn("size-4", uptimeRefreshBusy && "animate-spin")} />{liveApi ? "Обновить" : "Сбросить"}
<RefreshCwIcon className={cn("size-4", uptimeRefreshBusy && "animate-spin")} />{liveApi ? (selectedProbeId !== null ? "Обновить данные" : "Обновить") : "Сбросить"}
</Button>
{tab === "probes" && (
@@ -2324,6 +2335,21 @@ export default function UptimePage() {
</div>
)}
{selectedProbeId !== null && (
<div className="px-4 py-3 md:px-6">
<ContextAlert variant={overviewState === "error" || (overviewState === "ready" && !selectedProbe) ? "warning" : "info"} role="status" aria-label="Проверка по ссылке">
<ContextTitle className="line-clamp-none wrap-anywhere">{selectedProbe ? "Выбрана проверка: " + selectedProbe.name : "Проверка по ссылке"}</ContextTitle>
<ContextDescription>
<p className="wrap-anywhere">{overviewState === "loading" ? "Загрузка проверки…" : overviewState === "error" ? "Не удалось обновить данные проверки. Повторите обновление; сохранённые данные могут быть устаревшими." : selectedProbe ? (selectedProbeServer?.name ?? selectedProbe.srcServerId) + " → " + selectedProbe.target + ". Список ограничен выбранной проверкой; сводка относится ко всем проверкам." : "Проверка не найдена или недоступна. Возможно, она удалена или у вас нет доступа."}</p>
<div className="flex flex-wrap gap-4">
<Link href={allProbesHref} className="underline underline-offset-2">Все проверки</Link>
<Link href="/dashboard" className="underline underline-offset-2">К дашборду</Link>
</div>
</ContextDescription>
</ContextAlert>
</div>
)}
{/* ── summary + server filter ── */}
<div className="border-b bg-muted/20 px-6 py-3 flex items-center gap-3 flex-wrap">
<StatChip label="Всего" value={stats.total} />
@@ -2488,7 +2514,7 @@ export default function UptimePage() {
{group.probes.map((p) => {
const linkedSp = findLinkedSpeedProbe(p, speedProbes, allServers, speedIfaces)
return (
<div key={p.id} className="border-b border-border/40 last:border-b-0">
<div key={p.id} data-probe-id={p.id} className="border-b border-border/40 last:border-b-0">
<div
className={cn(
"grid items-center gap-3 px-4 py-2.5 hover:bg-muted/20 transition-colors",
@@ -2897,3 +2923,15 @@ export default function UptimePage() {
</div>
)
}
function UptimeRoute() {
const params = useSearchParams()
const id = params.get("probe")
const rest = new URLSearchParams(params.toString())
rest.delete("probe")
return <UptimePageContent key={id === null ? "list" : "probe:" + id} selectedProbeId={id} allProbesHref={"/uptime" + (rest.size ? "?" + rest : "")} />
}
export default function UptimePage() {
return <Suspense fallback={<p role="status" className="p-6">Загрузка мониторинга…</p>}><UptimeRoute /></Suspense>
}