"use client" import * as React from "react" import { NavMain, type NavGroup } from "@/components/nav-main" import { AppSwitcher } from "@/components/app-switcher" import { NavUser } from "@/components/nav-user" import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, } from "@/components/ui/sidebar" import { LayoutDashboardIcon, ActivityIcon, MapIcon, HeartPulseIcon, GlobeIcon, NetworkIcon, LayersIcon, TagIcon, ServerIcon, FilterIcon, ShieldIcon, HardDriveIcon, RouteIcon, GitForkIcon, GitMergeIcon, TerminalIcon, SettingsIcon, DatabaseIcon, CableIcon, ScanLineIcon, BellIcon, ShieldCheckIcon, BoxIcon, BadgeCheckIcon, } from "lucide-react" import { useDataSource } from "@/lib/data-source" import { useEvoBGP } from "@/lib/evobgp-context" import { formatSidebarBadgeCount, mockSidebarBadgesByUrl, type SidebarCountsDto, } from "@/lib/sidebar-badges" type NavItemBase = { title: string; url: string; icon: React.ReactNode } const navStructure: { label: string; items: NavItemBase[] }[] = [ { label: "Обзор", items: [ { title: "Дашборд", url: "/dashboard", icon: }, { title: "Трафик", url: "/traffic", icon: }, { title: "Карта сети", url: "/network-map", icon: }, { title: "Мониторинг", url: "/uptime", icon: }, ], }, { label: "Данные", items: [ { title: "Домены", url: "/domains", icon: }, { title: "IP-диапазоны", url: "/ip-ranges", icon: }, { title: "ASN", url: "/asns", icon: }, { title: "Communities", url: "/communities", icon: }, ], }, { label: "Управление", items: [ { title: "Серверы", url: "/servers", icon: }, { title: "Фильтры", url: "/filters", icon: }, { title: "Рекурсивные маршруты", url: "/recursive-routes", icon: }, { title: "Firewall", url: "/firewall", icon: }, { title: "WireGuard", url: "/wireguard", icon: }, { title: "GRE-туннели", url: "/gre", icon: }, { title: "VXLAN", url: "/vxlan", icon: }, { title: "Контейнеры", url: "/containers", icon: }, { title: "Сертификаты", url: "/certificates", icon: }, { title: "Бэкапы", url: "/backups", icon: }, ], }, { label: "Инструменты", items: [ { title: "Оптимизатор маршрутов", url: "/route-optimizer", icon: }, { title: "OSPF", url: "/ospf", icon: }, { title: "BGP", url: "/bgp", icon: }, { title: "Диагностика", url: "/probes", icon: }, { title: "Терминал", url: "/terminal", icon: }, ], }, { label: "Система", items: [ { title: "Оповещения", url: "/alerts", icon: }, { title: "Сбор данных", url: "/data-collection", icon: }, { title: "Релизы", url: "/releases", icon: }, { title: "Настройки", url: "/settings", icon: }, ], }, ] type LiveSidebarCounts = SidebarCountsDto & { greTunnels?: number; certificates?: number } export function AppSidebar({ ...props }: React.ComponentProps) { const { mode, backendUrl } = useDataSource() const evo = useEvoBGP() const [mounted, setMounted] = React.useState(false) const [liveCounts, setLiveCounts] = React.useState(null) const mockBadges = React.useMemo(() => mockSidebarBadgesByUrl(), []) React.useEffect(() => { queueMicrotask(() => { setMounted(true) }) }, []) React.useEffect(() => { if (mode !== "live") { setLiveCounts(null) return } let cancelled = false const load = async () => { try { const base = backendUrl.replace(/\/$/, "") const [cRes, gRes] = await Promise.all([ fetch(`${base}/api/sidebar-counts`), fetch(`${base}/api/filters/gre-tunnels`), ]) if (cancelled) return if (!cRes.ok) { setLiveCounts(null) return } const cJson = (await cRes.json()) as SidebarCountsDto let greN = 0 if (gRes.ok) { const gJson = (await gRes.json()) as { tunnels?: unknown[] } greN = (gJson.tunnels ?? []).length } setLiveCounts({ ...cJson, greTunnels: greN }) } catch { if (!cancelled) setLiveCounts(null) } } void load() const id = window.setInterval(load, 120_000) return () => { cancelled = true window.clearInterval(id) } }, [mode, backendUrl]) const navGroups = React.useMemo((): NavGroup[] => { function badgeFor(url: string): string | undefined { if (mode !== "live") return mockBadges[url] if (url === "/domains" || url === "/ip-ranges" || url === "/asns") { if (!evo.enabled) return undefined if (!evo.snapshot) return undefined if (url === "/domains") return formatSidebarBadgeCount(evo.snapshot.domains.length) if (url === "/ip-ranges") return formatSidebarBadgeCount(evo.snapshot.ipRanges.length) return formatSidebarBadgeCount(evo.snapshot.asns.length) } if (!liveCounts) return undefined if (url === "/servers") return formatSidebarBadgeCount(liveCounts.servers) if (url === "/filters") return formatSidebarBadgeCount(liveCounts.filterRules) if (url === "/uptime") return formatSidebarBadgeCount(liveCounts.monitoringItems) if (url === "/gre") return formatSidebarBadgeCount(liveCounts.greTunnels ?? 0) if (url === "/certificates") return formatSidebarBadgeCount(liveCounts.certificates ?? 0) if (url === "/wireguard" || url === "/containers" || url === "/bgp") { return undefined } return undefined } return navStructure.map((g) => ({ label: g.label, items: g.items.map((it) => ({ title: it.title, url: it.url, icon: it.icon, badge: mounted ? badgeFor(it.url) : undefined, })), })) }, [mounted, mode, liveCounts, mockBadges, evo.enabled, evo.snapshot]) return ( ) }