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() {