"use client" import Link from "next/link" import { ActivityIcon, BellIcon, CableIcon, FilterIcon, GitMergeIcon, HardDriveIcon, HeartPulseIcon, MapIcon, ServerIcon, ShieldIcon, } from "lucide-react" import { PageHeader } from "@/components/page-header" import { OpsPanel } from "@/components/ops-panel" import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid" import { QuickActionGrid } from "@/components/reui-kit/quick-action-grid" import { AttentionQueue } from "@/components/reui-kit/attention-queue" import { TrafficRxTxChart } from "@/components/reui-kit/traffic-rx-tx-chart" import { LatencyChart } from "@/components/dashboard/latency-chart" import { DashboardEventsTimeline } from "@/components/dashboard/dashboard-events-timeline" import { InternetPathPanel } from "@/components/dashboard/internet-path-panel" import { DashboardActiveProbesDataGrid } from "@/components/data-grids/dashboard-active-probes-data-grid" import { EmptyState } from "@/components/empty-state" import { DashboardDataStatus } from "@/components/dashboard/dashboard-data-status" import { Badge } from "@/components/reui/badge" import { buttonVariants } from "@/components/ui/button" import { cn } from "@/lib/utils" import { fmtRate } from "@/lib/fmt-rate" import { useDashboardLive, type AttentionRow } from "@/hooks/use-dashboard-live" function fmtIntRu(n: number): string { return n.toLocaleString("ru-RU") } function AttentionRows({ rows }: { rows: AttentionRow[] }) { function renderRow(row: AttentionRow) { return ( {row.title} {row.hint} ) } return (
{rows.slice(0, 5).map(renderRow)} {rows.length > 5 && (
Ещё {rows.length - 5} {rows.slice(5).map(renderRow)}
)}
) } export default function DashboardPage() { const dash = useDashboardLive() const offline = Math.max(0, dash.totalServers - dash.onlineCount) const health = dash.probeDown + dash.probeWarn return (
{dash.isLive && } 0 ? `${offline} не в сети` : dash.totalServers > 0 ? "все в сети" : "нет серверов", href: "/servers", icon: , iconClassName: "text-muted-foreground", variant: offline > 0 ? "warning" : "default", }, { id: "overlay", label: "Туннели работают", value: dash.availability.overlay ? String(dash.overlay.up) : "—", hint: !dash.availability.overlay ? "не все источники доступны" : dash.overlay.total > 0 ? `Не работают: ${dash.overlay.down}` : "нет туннелей", href: "/gre", icon: , iconClassName: "text-info", variant: dash.overlay.down > 0 ? "warning" : "default", }, { id: "bgp", label: "Префиксы BGP", value: dash.bgp?.prefixSum != null ? fmtIntRu(dash.bgp.prefixSum) : "—", hint: dash.bgp ? `Сессий установлено: ${dash.bgp.establishedCount ?? "неизвестно"}${dash.bgp.prefixSum === null ? " · префиксы неизвестны" : ""}` : "нет данных", href: "/bgp", icon: , iconClassName: "text-primary", }, { id: "health", label: "Проверки с проблемами", value: dash.availability.probes ? String(health) : "—", hint: dash.availability.probes ? `${dash.probeDown} ошибок · ${dash.probeWarn} предупреждений` : "нет данных", href: "/uptime", icon: , iconClassName: health > 0 ? "text-destructive" : "text-muted-foreground", variant: health > 0 ? "destructive" : "default", }, ]} />

Требует внимания

0 ? "Серверы без проблем" : "Нет серверов") : "Состояние не подтверждено", emptyDescription: dash.reliable.servers ? "Нет включённых серверов с ошибками доступности." : "Проверьте состояние источников выше.", children: , }, { id: "overlay", title: "Туннели", icon: CableIcon, iconClassName: "text-info", count: dash.attentionOverlay.length, countVariant: "destructive-light", emptyTitle: dash.reliable.overlay ? (dash.overlay.total > 0 ? "Туннели работают" : "Нет туннелей") : "Состояние не подтверждено", emptyDescription: dash.reliable.overlay ? "В GRE, WireGuard и VXLAN проблем не обнаружено." : "Проверьте состояние источников выше.", children: , }, { id: "probes", title: "Проверки", icon: HeartPulseIcon, iconClassName: "text-destructive", count: dash.attentionProbes.length, countVariant: "destructive-light", emptyTitle: dash.reliable.probes ? (dash.probeCount > 0 ? "Проверки без ошибок" : "Нет активных проверок") : "Состояние не подтверждено", emptyDescription: dash.reliable.probes ? "Нет ошибок и предупреждений активных проверок." : "Проверьте состояние источников выше.", children: , }, ]} />
{dash.latency.kind === "loading" && (
)} {dash.latency.kind === "empty" && (

{dash.latency.message}

)} {(dash.latency.kind === "mock" || dash.latency.kind === "live") && ( )} Все → } >
{dash.traffic.demo ? демо : null} RX {fmtRate(dash.traffic.rxNow)} TX {fmtRate(dash.traffic.txNow)} Трафик →
) : null } contentClassName="px-3 pb-3" > {dash.loading && !dash.traffic ? (
) : !dash.traffic ? (

Нет данных трафика за последний час.{" "} Открыть трафик

) : ( )}
{dash.probesSubtitle} } headerRight={ Мониторинг → } contentClassName="px-0 pb-0" > {dash.activeProbes.length === 0 && !dash.probesLoading ? ( Открыть мониторинг } /> ) : ( )} , iconClassName: "text-info", }, { id: "map", title: "Карта сети", description: "Топология GRE и WAN", href: "/network-map", icon: , iconClassName: "text-primary", }, { id: "filters", title: "Фильтры", description: "Правила обхода и списки", href: "/filters", icon: , iconClassName: "text-info", }, { id: "firewall", title: "Firewall", description: "Цепочки filter / nat", href: "/firewall", icon: , iconClassName: "text-warning", }, { id: "backups", title: "Бэкапы", description: "Снапшоты и расписание", href: "/backups", icon: , iconClassName: "text-muted-foreground", }, { id: "bgp", title: "BGP", description: "Сессии и префиксы", href: "/bgp", icon: , iconClassName: "text-primary", }, ]} />
) }