import { LayoutDashboard, Boxes, Network, Cog, ListChecks, Activity, Settings, BookText, KeyRound, ServerCog, Shield, } from 'lucide-react' import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, } from '@evobgp/ui/components/sidebar' import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from '@evobgp/ui/components/breadcrumb' import { Separator } from '@evobgp/ui/components/separator' import { TooltipProvider } from '@evobgp/ui/components/tooltip' import { Link, useRouterState } from '@tanstack/react-router' import type { ComponentType, CSSProperties, ReactNode } from 'react' import { CommandPalette, type CommandPaletteItem } from '@/components/layout/command-palette' import { SystemMonitorPopover } from '@/components/layout/system-monitor-popover' import { ModeToggle } from '@/components/mode-toggle' interface NavItem { to: string label: string icon: ComponentType<{ className?: string }> description?: string search?: Record } interface NavGroup { label: string items: NavItem[] } const NAV_GROUPS: NavGroup[] = [ { label: 'Обзор', items: [ { to: '/dashboard', label: 'Панель', icon: LayoutDashboard, description: 'KPI, модули и активность', }, ], }, { label: 'Маршрутизация', items: [ { to: '/modules', label: 'Модули', icon: Boxes, description: 'Списки префиксов и AS' }, { to: '/network', label: 'Сеть', icon: Network, description: 'BGP-пиры и спикеры', search: { tab: 'overview' } }, { to: '/directories', label: 'Справочники', icon: BookText, description: 'Communities и DoH' }, ], }, { label: 'Операции', items: [ { to: '/operations', label: 'Операции', icon: Cog, description: 'Ревизии и apply', search: { tab: 'revisions' } }, { to: '/firewall', label: 'Файрвол', icon: Shield, description: 'Клиенты и правила' }, { to: '/schedule', label: 'Задачи', icon: ListChecks, description: 'Расписание refresh' }, { to: '/monitoring', label: 'Мониторинг', icon: Activity, description: 'Health и BIRD', search: { tab: 'system' } }, ], }, { label: 'Система', items: [ { to: '/access', label: 'Доступ', icon: KeyRound, description: 'API-ключи' }, { to: '/tenant-settings', label: 'Настройки BIRD', icon: ServerCog, description: 'Tenant BIRD config' }, { to: '/settings', label: 'Настройки UI', icon: Settings, description: 'Токен и подключение', search: { tab: 'connection' } }, ], }, ] const ALL_NAV_ITEMS = NAV_GROUPS.flatMap((g) => g.items) const ROUTE_LABELS: Record = Object.fromEntries( ALL_NAV_ITEMS.map((i) => [i.to, i.label]), ) const PARENT_ROUTE: Record = { '/modules/new': '/modules', } const COMMAND_ITEMS: CommandPaletteItem[] = ALL_NAV_ITEMS.map((item) => ({ id: item.to, label: item.label, description: item.description, to: item.to, search: item.search, keywords: [item.to.replace(/^\//, '')], })) export function AppShell({ children }: { children: ReactNode }) { const pathname = useRouterState({ select: (s) => s.location.pathname }) const activeItem = ALL_NAV_ITEMS.find((i) => pathname === i.to || (i.to !== '/' && pathname.startsWith(`${i.to}/`))) ?? ALL_NAV_ITEMS[0] const parentTo = PARENT_ROUTE[pathname] ?? PARENT_ROUTE[activeItem.to] const parentLabel = parentTo ? ROUTE_LABELS[parentTo] : null const pageTitle = pathname.startsWith('/modules/') && pathname !== '/modules/new' ? 'Модуль' : (ROUTE_LABELS[activeItem.to] ?? activeItem.label) return (
B
EvoBGP Плоскость управления
{NAV_GROUPS.map((group) => ( {group.label} {group.items.map((item) => { const Icon = item.icon const isActive = pathname === item.to || pathname.startsWith(`${item.to}/`) return ( } isActive={isActive} tooltip={item.label} > {item.label} ) })} ))}
{parentLabel && parentTo ? ( <> }>{parentLabel} ) : null} {pageTitle}
{children}
) }