- lib/format.ts: ru-RU форматтеры дат/чисел в одном месте — убраны дубли из 8 компонентов (agent-card, fleet-grid, blocked-ips/ports, facts, lifecycle, host-firewall, lists-columns, system-monitor) - lib/nav.ts: единый конфиг маршрутов — sidebar, ⌘K-поиск и breadcrumbs рендерятся из одного источника (было 3 расходящихся копии) - lib/permissions.ts + useCan: клиентский RBAC — кнопки создания/подтверждения скрываются без fw:*:write/admin (сервер остаётся авторитетным) - удалён мёртвый код ~2500 строк: stepper, data-grid dnd/virtual/visibility варианты, settings-shell
96 lines
2.3 KiB
TypeScript
96 lines
2.3 KiB
TypeScript
import {
|
|
BarChart3Icon,
|
|
LayoutDashboardIcon,
|
|
ListIcon,
|
|
ServerIcon,
|
|
SettingsIcon,
|
|
ShieldIcon,
|
|
} from 'lucide-react'
|
|
|
|
/**
|
|
* Single source of the app's route structure: sidebar sections, ⌘K search and
|
|
* header breadcrumbs all render from this config.
|
|
*/
|
|
|
|
export type NavSectionId = 'overview' | 'ops' | 'system'
|
|
|
|
export type NavItem = {
|
|
to: string
|
|
label: string
|
|
/** Exact active-state match (root only); prefix match otherwise. */
|
|
exact?: boolean
|
|
keywords: string[]
|
|
icon: typeof ServerIcon
|
|
section: NavSectionId
|
|
}
|
|
|
|
export const NAV_ITEMS: readonly NavItem[] = [
|
|
{
|
|
to: '/',
|
|
label: 'Панель управления',
|
|
exact: true,
|
|
keywords: ['dashboard', 'панель', 'обзор'],
|
|
icon: LayoutDashboardIcon,
|
|
section: 'overview',
|
|
},
|
|
{
|
|
to: '/agents',
|
|
label: 'Агенты',
|
|
keywords: ['agents', 'агенты', 'nodes'],
|
|
icon: ServerIcon,
|
|
section: 'ops',
|
|
},
|
|
{
|
|
to: '/lists',
|
|
label: 'Списки',
|
|
keywords: ['lists', 'списки', 'blocklist'],
|
|
icon: ListIcon,
|
|
section: 'ops',
|
|
},
|
|
{
|
|
to: '/rules',
|
|
label: 'Наборы правил',
|
|
keywords: ['rules', 'правила', 'policy', 'наборы', 'sets'],
|
|
icon: ShieldIcon,
|
|
section: 'ops',
|
|
},
|
|
{
|
|
to: '/stats',
|
|
label: 'Статистика',
|
|
keywords: ['stats', 'статистика', 'packets'],
|
|
icon: BarChart3Icon,
|
|
section: 'ops',
|
|
},
|
|
{
|
|
to: '/settings',
|
|
label: 'Настройки',
|
|
keywords: ['settings', 'настройки'],
|
|
icon: SettingsIcon,
|
|
section: 'system',
|
|
},
|
|
]
|
|
|
|
export const NAV_SECTIONS = [
|
|
{ id: 'overview', label: 'Обзор' },
|
|
{ id: 'ops', label: 'Операции' },
|
|
{ id: 'system', label: 'Система' },
|
|
] as const satisfies readonly { id: NavSectionId; label: string }[]
|
|
|
|
export function navItemsForSection(section: NavSectionId): readonly NavItem[] {
|
|
return NAV_ITEMS.filter((item) => item.section === section)
|
|
}
|
|
|
|
export function navLabel(to: string): string | undefined {
|
|
return NAV_ITEMS.find((item) => item.to === to)?.label
|
|
}
|
|
|
|
/** Detail routes (…/:id) that nest under a nav route. */
|
|
const DETAIL_PARENT_RE = /^\/(agents|lists|rules)\/[^/]+$/
|
|
|
|
export function navParentForDetail(
|
|
pathname: string,
|
|
): string | undefined {
|
|
const m = pathname.match(DETAIL_PARENT_RE)
|
|
return m ? `/${m[1]}` : undefined
|
|
}
|