"use client" import { useEffect, useMemo, useState, type ReactNode } from "react" import { Activity, Filter, HeartPulse, Server } from "lucide-react" import { Badge } from "@/components/reui/badge" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import { cn } from "@/lib/utils" import { useDataSource } from "@/lib/data-source" import { filters, pingProbes, servers } from "@/lib/data" import type { SidebarCountsDto } from "@/lib/sidebar-badges" import { getBackendHealth } from "@/shared/api/backend-health" import {can,ensureAuthConfig} from "@/lib/auth" import {getSidebarCounts} from "@/shared/api/sidebar-counts" type MonitorMetric = { id: string label: string value: string unit: string percent: number|null icon: ReactNode tone: "success" | "warning" | "destructive" | "info" alert: boolean } function toneColor(tone: MonitorMetric["tone"]) { switch (tone) { case "success": return "var(--color-success)" case "warning": return "var(--color-warning)" case "destructive": return "var(--color-destructive)" default: return "var(--color-info)" } } function MetricBar({ percent, color }: { percent: number; color: string }) { return (
) } function MetricCell({ metric }: { metric: MonitorMetric }) { const color = toneColor(metric.tone) return (
{metric.icon}
{metric.label}
{metric.value} {metric.unit}
{metric.percent!==null?:null}
) } /** Live system monitor popover — app-shell-7. @see https://reui.io/preview/base/app-shell-7 */ export function SystemMonitorPopover() { const { mode, backendUrl, prefsHydrated } = useDataSource() const source=JSON.stringify([mode,backendUrl]) const [snapshot,setSnapshot]=useState<{source:string;healthOk:boolean|null;counts:SidebarCountsDto|null;receivedAt:string|null;countsError:string|null}|null>(null) const data=snapshot?.source===source?snapshot:null,healthOk=mode!=="live"?true:data?.healthOk??null const counts=mode!=="live"?{servers:servers.length,filterRules:filters.length,monitoringItems:pingProbes.length}:can("mm:dashboard:read")?data?.counts:null useEffect(()=>{ if(!prefsHydrated||mode!=="live")return let cancelled=false,revision=0,controller:AbortController|null=null const load=async()=>{ controller?.abort();controller=new AbortController();const request=++revision,signal=controller.signal const publish=(patch:Partial>)=>{if(cancelled||request!==revision)return;setSnapshot(previous=>({...((previous?.source===source?previous:null)??{source,healthOk:null,counts:null,receivedAt:null,countsError:null}),...patch}))} await Promise.allSettled([ (async()=>{try{await getBackendHealth(backendUrl,signal);publish({healthOk:true})}catch{publish({healthOk:false})}})(), (async()=>{await ensureAuthConfig();if(cancelled||request!==revision)return;if(!can("mm:dashboard:read")){publish({counts:null,countsError:"Нет доступа к сводке"});return}try{const result=await getSidebarCounts(backendUrl,AbortSignal.any([signal,AbortSignal.timeout(30_000)]));if(can("mm:dashboard:read"))publish({counts:result,receivedAt:new Date().toISOString(),countsError:null})}catch{publish({countsError:"Сводка не обновлена; показан последний успешный снимок, если он есть."})}})(), ]) } void load();const timer=window.setInterval(load,30_000) return()=>{cancelled=true;revision++;controller?.abort();window.clearInterval(timer)} },[mode,backendUrl,prefsHydrated,source]) const serversCount=mode!=="live"||can("mm:servers:read")?counts?.servers:undefined const filtersCount=mode!=="live"||can("mm:filters:read")?counts?.filterRules:undefined const monitoringCount=mode!=="live"||can("mm:uptime:read")?counts?.monitoringItems:undefined const apiOk = healthOk === true const metrics = useMemo( () => [ { id: "api", label: "API", value: healthOk == null ? "…" : apiOk ? "OK" : "—", unit: "", percent: healthOk===null?null:apiOk ? 100 : 0, icon: , tone: apiOk ? "success" : "destructive", alert: healthOk === false, }, { id: "servers", label: "Серверы", value: serversCount===undefined?"—":String(serversCount), unit: "шт.", percent: null, icon: , tone: (serversCount??0) > 0 ? "success" : "warning", alert: false, }, { id: "filters", label: "Фильтры", value: filtersCount===undefined?"—":String(filtersCount), unit: "шт.", percent: null, icon: , tone: "info", alert: false, }, { id: "uptime", label: "Мониторинг", value: monitoringCount===undefined?"—":String(monitoringCount), unit: "шт.", percent: null, icon: , tone: (monitoringCount??0) > 0 ? "success" : "warning", alert: false, }, ], [apiOk, filtersCount, healthOk, monitoringCount, serversCount], ) const spiking = metrics.some((m) => m.alert) return ( } > {spiking ? ( ) : null} Система {spiking?"Нет связи":apiOk?"API доступен":"Нет данных"}
Монитор MikrotikManager {mode!=="live"?"Демо":data?.receivedAt?new Date(data.receivedAt).toLocaleTimeString("ru-RU"):"Снимок не получен"}
{data?.countsError?

{data.countsError}

:null}
{metrics.map((metric, i) => (
= 2 && "border-border border-t", )} >
))}
Источник:{" "} {mode === "live" ? "API" : "мок"}
) }