Removed unused utility functions from the AppShell component and updated the sidebar width to a fixed value for better layout control. Revised the UI design documentation to clarify the shared App Shell chrome specifications and ensure alignment with design standards. Enhanced the sidebar configuration to reflect the new width and styling guidelines.
217 lines
7.2 KiB
TypeScript
217 lines
7.2 KiB
TypeScript
import {
|
|
LayoutDashboard,
|
|
Boxes,
|
|
Network,
|
|
Cog,
|
|
ListChecks,
|
|
Activity,
|
|
Settings,
|
|
BookText,
|
|
KeyRound,
|
|
ServerCog,
|
|
Shield,
|
|
Search,
|
|
} 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 { AppSwitcher } from '@/components/app-switcher'
|
|
import { AppsMenu } from '@/components/layout/apps-menu'
|
|
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<string, string>
|
|
}
|
|
|
|
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: '/lookup', label: 'Проверка', icon: Search, description: 'IP/домен в списках и community' },
|
|
{ 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<string, string> = Object.fromEntries(
|
|
ALL_NAV_ITEMS.map((i) => [i.to, i.label]),
|
|
)
|
|
|
|
const PARENT_ROUTE: Record<string, string> = {
|
|
'/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(/^\//, '')],
|
|
}))
|
|
|
|
/**
|
|
* Shared ops chrome etalon for CFDM / vps-tracker.
|
|
* @see https://reui.io/preview/base/app-shell-12
|
|
* @see docs/ui-design-contract.md — Shared App Shell chrome
|
|
*/
|
|
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 (
|
|
<TooltipProvider delay={0}>
|
|
<SidebarProvider
|
|
style={
|
|
{
|
|
'--sidebar-width': '240px',
|
|
} as CSSProperties
|
|
}
|
|
>
|
|
<Sidebar collapsible="icon">
|
|
<SidebarHeader>
|
|
<AppSwitcher />
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
{NAV_GROUPS.map((group) => (
|
|
<SidebarGroup key={group.label}>
|
|
<SidebarGroupLabel>{group.label}</SidebarGroupLabel>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
{group.items.map((item) => {
|
|
const Icon = item.icon
|
|
const isActive = pathname === item.to || pathname.startsWith(`${item.to}/`)
|
|
return (
|
|
<SidebarMenuItem key={item.to}>
|
|
<SidebarMenuButton
|
|
render={<Link to={item.to} search={item.search} />}
|
|
isActive={isActive}
|
|
tooltip={item.label}
|
|
>
|
|
<Icon className="size-4" />
|
|
<span>{item.label}</span>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
)
|
|
})}
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
))}
|
|
</SidebarContent>
|
|
<SidebarFooter />
|
|
</Sidebar>
|
|
<SidebarInset>
|
|
<header className="bg-background sticky top-0 z-10 flex h-12 shrink-0 items-center gap-2 border-b px-4 md:px-6">
|
|
<SidebarTrigger className="-ml-1" />
|
|
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
|
|
<Breadcrumb>
|
|
<BreadcrumbList>
|
|
{parentLabel && parentTo ? (
|
|
<>
|
|
<BreadcrumbItem className="hidden md:block">
|
|
<BreadcrumbLink render={<Link to={parentTo} />}>{parentLabel}</BreadcrumbLink>
|
|
</BreadcrumbItem>
|
|
<BreadcrumbSeparator className="hidden md:block" />
|
|
</>
|
|
) : null}
|
|
<BreadcrumbItem>
|
|
<BreadcrumbPage>{pageTitle}</BreadcrumbPage>
|
|
</BreadcrumbItem>
|
|
</BreadcrumbList>
|
|
</Breadcrumb>
|
|
<div className="ml-auto flex items-center gap-2">
|
|
<AppsMenu />
|
|
<SystemMonitorPopover />
|
|
<ModeToggle />
|
|
</div>
|
|
</header>
|
|
<main className="flex flex-1 flex-col gap-4 px-4 py-4 md:gap-6 md:px-6 md:py-5">
|
|
{children}
|
|
</main>
|
|
</SidebarInset>
|
|
<CommandPalette items={COMMAND_ITEMS} hotkeyOnly />
|
|
</SidebarProvider>
|
|
</TooltipProvider>
|
|
)
|
|
}
|