"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 (
)
}