"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" : "мок"}
)
}