From b856215c03eb3e4bdcfddddbb62e0e56463f33b3 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 1 Oct 2026 04:14:52 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D1=81=D0=BE=D0=B3=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE=D1=81=D1=82=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D1=81=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/probes/page.tsx | 23 +++++++++++++++++------ backend/src/plugins/auth.exec.test.ts | 9 +++++---- docs/ux-ui-progress.md | 9 ++++++++- docs/ux-ui-stage-10.md | 8 ++++++++ lib/auth.ts | 1 + tests/e2e/probes-data.spec.ts | 11 +++++++++++ 6 files changed, 50 insertions(+), 11 deletions(-) diff --git a/app/(main)/probes/page.tsx b/app/(main)/probes/page.tsx index c9003651..a6ff87c8 100644 --- a/app/(main)/probes/page.tsx +++ b/app/(main)/probes/page.tsx @@ -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 { can } from "@/lib/auth" import { readProbeOutput, probeServersSchema } from "@/lib/probe-response" import { useDataSource } from "@/lib/data-source" import { @@ -572,6 +573,9 @@ export default function ProbesPage() { function ProbesContent() { const { mode, backendUrl } = useDataSource() + const canRead = can("mm:servers:read") + const canRun = can("mm:servers:write") + const canReadGre = can("mm:filters:read") const isLive = mode === "live" const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl]) @@ -600,7 +604,7 @@ function ProbesContent() { const [rosSrcV4, setRosSrcV4] = useState(undefined) useEffect(() => { - if (!isLive) return + if (!isLive || !canRead) return let cancelled = false const ctrl = new AbortController() queueMicrotask(() => { if (!cancelled) { setLiveLoad("loading"); setCatalogError(null) } }) @@ -624,7 +628,7 @@ function ProbesContent() { setLiveLoad("error") }) return () => { cancelled = true; ctrl.abort() } - }, [isLive, apiFetch, catalogRevision]) + }, [isLive, canRead, apiFetch, catalogRevision]) const allServers = useMemo(() => { if (!isLive) return servers @@ -668,7 +672,7 @@ function ProbesContent() { }, [isLive, srcId, apiFetch]) const ensureGre = useCallback(async (serverId: string) => { - if (!isLive) return + if (!isLive || !canReadGre) return try { const d = await apiFetch<{ tunnels: GreTunnel[] }>( `/api/filters/gre-tunnels?serverId=${encodeURIComponent(serverId)}`, @@ -677,7 +681,7 @@ function ProbesContent() { } catch { setGreByServer(prev => ({ ...prev, [serverId]: [] })) } - }, [isLive, apiFetch]) + }, [isLive, canReadGre, apiFetch]) useEffect(() => { if (!isLive || !srcId) return @@ -748,7 +752,7 @@ function ProbesContent() { // ── run test ── const runTest = useCallback(async () => { const srv = allServers.find(s => s.id === srcId) - if (!srv || !srv.enabled || runLock.current || (isLive && liveLoad !== "idle")) return + if (!canRun || (tool === "bandwidth" && isLive && !canReadGre) || !srv || !srv.enabled || runLock.current || (isLive && liveLoad !== "idle")) return const id = String(nextId.current++) const numericServerId = Number.parseInt(srcId, 10) @@ -875,6 +879,8 @@ function ProbesContent() { }, [ isLive, liveLoad, + canRun, + canReadGre, allServers, srcId, tool, @@ -907,6 +913,8 @@ function ProbesContent() { const running = tests.filter(t => t.status === "running") + if (!canRead) return

Для просмотра устройств требуется mm:servers:read.

+ if (isLive && liveLoad === "loading" && catalogReadAt === null) { return (
@@ -962,6 +970,9 @@ function ProbesContent() {

)} + {!canRun &&

Для запуска диагностики требуется mm:servers:write.

} + {isLive && tool === "bandwidth" && !canReadGre &&

Для выбора GRE требуется mm:filters:read.

} + {/* ── tool selector + config ── */} @@ -1078,7 +1089,7 @@ function ProbesContent() {