refactor(web): общие форматтеры, единая навигация, RBAC-гейтинг и чистка мёртвого кода

- 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
This commit is contained in:
Denozordec
2026-09-20 19:36:37 +07:00
parent 7a3f1fad25
commit 3dc8e6d5e2
26 changed files with 275 additions and 2178 deletions
+15 -29
View File
@@ -12,55 +12,41 @@ import { Separator } from '@evofw/ui/components/separator'
import { SystemMonitorPopover } from '@/components/layout/system-monitor-popover'
import { AppsMenu } from '@/components/layout/apps-menu'
import { SidebarTrigger } from '@evofw/ui/components/sidebar'
import { navLabel, navParentForDetail } from '@/lib/nav'
export interface RouteBreadcrumbLoaderData {
breadcrumb?: string
}
const routeTitles: Record<string, string> = {
'/': 'Панель управления',
'/agents': 'Агенты',
'/lists': 'Списки',
'/rules': 'Наборы правил',
'/stats': 'Статистика',
'/settings': 'Настройки',
}
function getBreadcrumbs(
pathname: string,
dynamicLabels: Record<string, string>,
) {
if (pathname === '/') {
return [{ label: 'Панель управления', href: '/' }]
return [{ label: navLabel('/') ?? 'Панель управления', href: '/' }]
}
if (pathname.match(/^\/agents\/[^/]+$/)) {
const parentTo = navParentForDetail(pathname)
if (parentTo) {
const parentLabel = navLabel(parentTo)
const fallback =
parentTo === '/agents'
? 'Агент'
: parentTo === '/lists'
? 'Список'
: 'Набор'
return [
{ label: 'Агенты', href: '/agents' },
{ label: dynamicLabels[pathname] ?? 'Агент', href: pathname },
{ label: parentLabel ?? parentTo, href: parentTo },
{ label: dynamicLabels[pathname] ?? fallback, href: pathname },
]
}
if (pathname.match(/^\/rules\/[^/]+$/)) {
return [
{ label: 'Наборы правил', href: '/rules' },
{ label: dynamicLabels[pathname] ?? 'Набор', href: pathname },
]
}
if (pathname.match(/^\/lists\/[^/]+$/)) {
return [
{ label: 'Списки', href: '/lists' },
{ label: dynamicLabels[pathname] ?? 'Список', href: pathname },
]
}
const title = routeTitles[pathname]
const title = navLabel(pathname)
if (title) {
return [{ label: title, href: pathname }]
}
return [{ label: 'Панель управления', href: '/' }]
return [{ label: navLabel('/') ?? 'Панель управления', href: '/' }]
}
function useDynamicBreadcrumbLabels() {