From 2fef952403014d9ed9342f4fb840befb2c272873 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 1 Jul 2026 00:42:50 +0700 Subject: [PATCH] feat(ui): refactor components and enhance UX Updated various UI components to improve user experience and maintainability. Key changes include: - Refactored `PageHeader` to remove icon handling, simplifying its structure. - Replaced `KpiMetricsGrid` with `SectionCards` for better layout consistency. - Enhanced `AppShell` and navigation components to utilize a grouped navigation structure, improving accessibility and organization. - Introduced new properties in `crud-list-page` for handling empty states and loading indicators. - Updated styles and classes for better visual consistency across components. These changes streamline the UI and enhance the overall user experience across the application. --- .cursor/rules/frontend-ui-patterns.mdc | 14 +- AGENTS.md | 9 +- .../src/lib/components/crud-list-page.svelte | 46 ++- .../web/src/lib/components/empty-state.svelte | 31 -- .../lib/components/layout/app-shell.svelte | 103 +++---- .../modules/ModuleDetailHeader.svelte | 8 +- .../network/NetworkOverviewTab.svelte | 110 ++----- .../overview/OverviewRecentJobsCard.svelte | 74 ++--- .../OverviewRecentRevisionsCard.svelte | 77 ++--- .../patterns/confirm/confirm-dialog.svelte | 2 +- .../patterns/kpi/kpi-metrics-grid.svelte | 112 ------- .../web/src/lib/components/query-state.svelte | 2 +- .../src/lib/components/section-cards.svelte | 2 +- .../src/lib/components/status-badge.svelte | 35 ++- .../tenant-settings/TenantSettingsPage.svelte | 16 +- .../src/lib/ui/app/layout/app-brand.svelte | 36 +++ .../lib/ui/app/layout/app-mobile-nav.svelte | 29 +- .../lib/ui/app/layout/app-nav-links.svelte | 75 ++--- .../src/lib/ui/app/layout/app-topbar.svelte | 38 --- apps/web/src/lib/ui/app/layout/nav.ts | 77 ++++- .../lib/ui/app/page-header/page-header.svelte | 31 +- apps/web/src/lib/ui/app/tabs.ts | 3 + apps/web/src/routes/+page.svelte | 288 +++++++++++------- apps/web/src/routes/access/+page.svelte | 4 +- apps/web/src/routes/directories/+page.svelte | 65 ++-- apps/web/src/routes/modules/+page.svelte | 127 ++++---- apps/web/src/routes/monitoring/+page.svelte | 120 +++----- apps/web/src/routes/network/+page.svelte | 2 - apps/web/src/routes/operations/+page.svelte | 82 ++--- apps/web/src/routes/schedule/+page.svelte | 284 ++++++++--------- apps/web/src/routes/settings/+page.svelte | 9 +- packages/ui/src/styles/globals.css | 60 ++-- 32 files changed, 873 insertions(+), 1098 deletions(-) delete mode 100644 apps/web/src/lib/components/empty-state.svelte delete mode 100644 apps/web/src/lib/components/patterns/kpi/kpi-metrics-grid.svelte create mode 100644 apps/web/src/lib/ui/app/layout/app-brand.svelte delete mode 100644 apps/web/src/lib/ui/app/layout/app-topbar.svelte create mode 100644 apps/web/src/lib/ui/app/tabs.ts diff --git a/.cursor/rules/frontend-ui-patterns.mdc b/.cursor/rules/frontend-ui-patterns.mdc index c439b47..f336da7 100644 --- a/.cursor/rules/frontend-ui-patterns.mdc +++ b/.cursor/rules/frontend-ui-patterns.mdc @@ -31,7 +31,7 @@ apps/web/src/lib/components/ ← shared + domain + layout | Элемент | Компонент | Primitive | |---------|-----------|-----------| | Page wrapper | `PageShell` | — | -| Page title | `PageHeader` (`ui/app/page-header`) | — | +| Page title | `PageHeader` (`ui/app/page-header`) — **без icon** | — | | Stat metrics | `SectionCards` | `Card` | | Data list | `DataTableCard` + `AppDataTable` | `Table` | | Filters | `ListFiltersBar` | `Select`, `Badge` | @@ -40,8 +40,16 @@ apps/web/src/lib/components/ ← shared + domain + layout | Status | `StatusBadge` | `Badge` | | Create/Edit form | `FormSheet` / Dialog | `Sheet`, `Field` | | Delete confirm | `ConfirmDialog` | `AlertDialog` | -| Nav | `AppShell` | `Sidebar` | -| Breadcrumbs | `AppShell` header | `Breadcrumb` | +| Nav | `AppShell` + `navGroups` | `Sidebar` | +| Breadcrumbs | `AppShell` header (parent route) | `Breadcrumb` | +| Tabs (dashboard, ops) | `LINE_TAB_TRIGGER_CLASS` | `Tabs` | + +## Запрещено + +- `icon` / `iconClass` в `PageHeader` +- `KpiMetricsGrid` — только `SectionCards` +- `mx-auto max-w-*` на `PageShell` list-страниц +- Голый `AppDataTable` без `DataTableCard` на list-экранах ## Spacing diff --git a/AGENTS.md b/AGENTS.md index 61b5654..26b8163 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,9 +39,14 @@ pnpm --filter @evobgp/web lint После правок `apps/web/**` — **обязательно** `check` и `lint` (WEB-19). -## UX-эталон +## UX-эталон (vps-tracker, без ReUI) -vps-tracker: `PageShell`, `SectionCards`, `QueryState`, `DataTableCard`, sidebar-07 layout. ReUI не используется — Data Table на shadcn-svelte. +- **Shell:** `layout/app-shell.svelte` — sidebar-07, `collapsible="icon"`, 5 nav-групп (`nav.ts`), `AppBrand`, `ThemeMenu` в top header, breadcrumb с parent +- **Страница:** `PageShell` → `PageHeader` (без icon) → `QueryState` / контент +- **Метрики:** `SectionCards` (не KpiMetricsGrid) +- **Таблицы:** `DataTableCard` + `AppDataTable` (не голый `Card` + `Table`) +- **Tabs:** line-variant через `LINE_TAB_TRIGGER_CLASS` (`$lib/ui/app/tabs.ts`) +- **Тема:** `packages/ui/src/styles/globals.css` синхронизирован с vps-tracker (semantic tokens) ## Коммиты diff --git a/apps/web/src/lib/components/crud-list-page.svelte b/apps/web/src/lib/components/crud-list-page.svelte index 79042a8..9c1e494 100644 --- a/apps/web/src/lib/components/crud-list-page.svelte +++ b/apps/web/src/lib/components/crud-list-page.svelte @@ -1,20 +1,26 @@ - @@ -39,15 +50,24 @@ {/if} {/snippet} - {#if isLoading} - {#if skeleton} - {@render skeleton()} - {/if} - {:else if isError} - - {:else} - {@render content()} + + {#if sheet} + {@render sheet()} {/if} -{#snippet emptyChild()}{/snippet} +{#snippet tableSkeleton()} + +{/snippet} diff --git a/apps/web/src/lib/components/empty-state.svelte b/apps/web/src/lib/components/empty-state.svelte deleted file mode 100644 index bf74c56..0000000 --- a/apps/web/src/lib/components/empty-state.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - -
- {#if Icon} - - {/if} -

{title}

- {#if description} -

{description}

- {/if} - {#if action} -
{@render action()}
- {/if} -
diff --git a/apps/web/src/lib/components/layout/app-shell.svelte b/apps/web/src/lib/components/layout/app-shell.svelte index b6022e4..cc8530c 100644 --- a/apps/web/src/lib/components/layout/app-shell.svelte +++ b/apps/web/src/lib/components/layout/app-shell.svelte @@ -6,7 +6,8 @@ import * as Breadcrumb from '@evobgp/ui/components/breadcrumb/index.js'; import { Separator } from '@evobgp/ui/components/separator/index.js'; import type { ThemePreference } from '$lib/theme.js'; - import { mainNav, bottomNav } from '$lib/ui/app/layout/nav.js'; + import { allNavItems, navGroups, parentRoute, routeLabels } from '$lib/ui/app/layout/nav.js'; + import AppBrand from '$lib/ui/app/layout/app-brand.svelte'; import ThemeMenu from '$lib/ui/app/layout/theme-menu.svelte'; import AppVersion from '$lib/ui/app/layout/app-version.svelte'; import AppMobileNav from '$lib/ui/app/layout/app-mobile-nav.svelte'; @@ -20,14 +21,6 @@ let mobileNavOpen = $state(false); - const routeLabels: Record = { - '/': 'Обзор', - ...Object.fromEntries(mainNav.map((i) => [i.href, i.label])), - ...Object.fromEntries(bottomNav.map((i) => [i.href, i.label])) - }; - - const breadcrumbLabel = $derived(routeLabels[page.url.pathname] ?? 'EvoBGP'); - // eslint-disable-next-line @typescript-eslint/no-explicit-any const navHref = (href: string) => resolve(href as any); @@ -36,78 +29,74 @@ if (href === '/') return pathname === '/'; return pathname === href || pathname.startsWith(href + '/'); } + + const activeItem = $derived(allNavItems.find((i) => isActive(i.href)) ?? allNavItems[0]); + + const parentHref = $derived(parentRoute[activeItem.href]); + const parentLabel = $derived(parentHref ? routeLabels[parentHref] : null); + const currentLabel = $derived(routeLabels[activeItem.href] ?? 'EvoBGP'); - - -
-
- EvoBGP -

Панель управления

-
- -
+ + + - - Операции - - - {#each mainNav as item (item.href)} - {@const Icon = item.icon} - - - {#snippet child({ props })} - - - {item.label} - - {/snippet} - - - {/each} - - - + {#each navGroups as group (group.label)} + + {group.label} + + + {#each group.items as item (item.href)} + {@const Icon = item.icon} + + + {#snippet child({ props })} + + + {item.label} + + {/snippet} + + + {/each} + + + + {/each} - - - {#each bottomNav as item (item.href)} - {@const Icon = item.icon} - - - {#snippet child({ props })} - - - {item.label} - - {/snippet} - - - {/each} - +
EvoBGP
{@render children()} diff --git a/apps/web/src/lib/components/modules/ModuleDetailHeader.svelte b/apps/web/src/lib/components/modules/ModuleDetailHeader.svelte index 721a791..5c0b56a 100644 --- a/apps/web/src/lib/components/modules/ModuleDetailHeader.svelte +++ b/apps/web/src/lib/components/modules/ModuleDetailHeader.svelte @@ -26,13 +26,7 @@ - + {#snippet actions()}
{moduleTypeRu(mod.type)} diff --git a/apps/web/src/lib/components/network/NetworkOverviewTab.svelte b/apps/web/src/lib/components/network/NetworkOverviewTab.svelte index dd19ab1..20238d6 100644 --- a/apps/web/src/lib/components/network/NetworkOverviewTab.svelte +++ b/apps/web/src/lib/components/network/NetworkOverviewTab.svelte @@ -10,7 +10,8 @@ } from '$lib/network/network-metrics.js'; import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js'; import { Button } from '@evobgp/ui/components/button/index.js'; - import KpiMetricsGrid from '$lib/components/patterns/kpi/kpi-metrics-grid.svelte'; + import SectionCards, { type SectionCardItem } from '$lib/components/section-cards.svelte'; + import SectionCardsSkeleton from '$lib/components/section-cards-skeleton.svelte'; import NetworkSpeakerStatusCard from '$lib/components/network/NetworkSpeakerStatusCard.svelte'; import CheckCircle from '@lucide/svelte/icons/check-circle'; import AlertTriangle from '@lucide/svelte/icons/alert-triangle'; @@ -41,45 +42,6 @@ onSpeakerSelect }: Props = $props(); - const statAccents = [ - { - border: 'border-l-chart-3', - bg: 'bg-chart-3/5', - iconBg: 'bg-chart-3/15', - iconText: 'text-chart-3' - }, - { - border: 'border-l-chart-2', - bg: 'bg-chart-2/5', - iconBg: 'bg-chart-2/15', - iconText: 'text-chart-2' - }, - { - border: 'border-l-chart-4', - bg: 'bg-chart-4/5', - iconBg: 'bg-chart-4/15', - iconText: 'text-chart-4' - }, - { - border: 'border-l-warning', - bg: 'bg-warning/5', - iconBg: 'bg-warning/15', - iconText: 'text-warning' - }, - { - border: 'border-l-destructive', - bg: 'bg-destructive/5', - iconBg: 'bg-destructive/15', - iconText: 'text-destructive' - }, - { - border: 'border-l-info', - bg: 'bg-info/10', - iconBg: 'bg-info/15', - iconText: 'text-info' - } - ] as const; - const metrics = $derived(aggregateNetworkMetrics(peers, speakers, bird)); const overallStatus = $derived(deriveNetworkOverallStatus(metrics)); const overallHint = $derived(networkOverallStatusHint(overallStatus, metrics)); @@ -91,76 +53,51 @@ return `${bird.bgp_established}/${bird.bgp_sessions_total}`; }); - const kpiCards = $derived.by(() => [ + const sectionCards = $derived.by((): SectionCardItem[] => [ { - id: 'peers', label: 'BGP-пиры', value: initialLoading ? '—' : String(metrics.peersTotal), - description: initialLoading + hint: initialLoading ? '' - : `${metrics.peersEstablished} Established из ${metrics.peersEnabled} вкл.`, + : `${metrics.peersEstablished} Established / ${metrics.peersEnabled} вкл.`, icon: Share2, - accent: statAccents[0], - badge: metrics.peersMismatch > 0 ? `mismatch ${metrics.peersMismatch}` : 'peers', - badgeClass: - metrics.peersMismatch > 0 ? 'border-warning/30 bg-warning/15 text-warning' : undefined + variant: metrics.peersMismatch > 0 ? 'warning' : 'default' }, { - id: 'established', label: 'Активные сессии', value: initialLoading ? '—' : String(metrics.peersEstablished), - description: 'Established среди включённых пиров', - icon: CheckCircle2, - accent: statAccents[1], - badge: metrics.peersEstablished > 0 ? 'Established' : 'нет сессий', - badgeClass: - metrics.peersEstablished > 0 ? 'border-success/30 bg-success/15 text-success' : undefined + hint: 'Established', + icon: CheckCircle2 }, { - id: 'speakers', label: 'Спикеры online', value: initialLoading ? '—' : `${metrics.speakersOnline}/${metrics.speakersTotal}`, - description: 'agent + BGP poll', + hint: 'agent + BGP poll', icon: Server, - accent: statAccents[2], - badge: metrics.speakersOnline === metrics.speakersTotal ? 'все online' : 'есть offline', - badgeClass: - metrics.speakersOnline === metrics.speakersTotal - ? 'border-success/30 bg-success/15 text-success' - : 'border-warning/30 bg-warning/15 text-warning' + variant: metrics.speakersOnline < metrics.speakersTotal ? 'warning' : 'default' }, { - id: 'drift', label: 'Drift', value: initialLoading ? '—' : String(metrics.speakersDrift), - description: 'applied ≠ published', + hint: 'applied ≠ published', icon: GitBranch, - accent: statAccents[3], - badge: metrics.speakersDrift > 0 ? 'требует apply' : 'синхронно', - badgeVariant: metrics.speakersDrift > 0 ? ('secondary' as const) : ('outline' as const) + variant: metrics.speakersDrift > 0 ? 'warning' : 'default' }, { - id: 'poll-errors', label: 'Ошибки опроса', value: initialLoading ? '—' : String(metrics.pollErrors), - description: 'agent или BGP poll', + hint: 'agent / BGP poll', icon: Activity, - accent: statAccents[4], - badge: metrics.pollErrors > 0 ? 'ошибки' : 'ok', - badgeClass: - metrics.pollErrors === 0 ? 'border-success/30 bg-success/15 text-success' : undefined + variant: metrics.pollErrors > 0 ? 'destructive' : 'default' }, { - id: 'cp-bird', label: 'BGP на CP', value: initialLoading ? '—' : birdText, - description: bird?.birdc_configured - ? 'Established / total на API-хосте' - : (bird?.message ?? 'birdc не настроен'), + hint: bird?.birdc_configured ? 'Established / total' : (bird?.message ?? 'birdc N/A'), icon: Bird, - accent: statAccents[5], - badge: !bird?.birdc_configured ? 'N/A' : bird?.healthy ? 'В норме' : 'Деградация', - href: '/monitoring' as const + onClick: () => { + window.location.href = resolve('/monitoring'); + } } ]); @@ -206,12 +143,11 @@ {/if} {/if} - + {#if initialLoading || loading} + + {:else} + + {/if}
diff --git a/apps/web/src/lib/components/overview/OverviewRecentJobsCard.svelte b/apps/web/src/lib/components/overview/OverviewRecentJobsCard.svelte index 1ab141a..ecd0e5c 100644 --- a/apps/web/src/lib/components/overview/OverviewRecentJobsCard.svelte +++ b/apps/web/src/lib/components/overview/OverviewRecentJobsCard.svelte @@ -6,13 +6,7 @@ import { jobStatusRu, jobStatusBadgeVariant } from '$lib/ui-labels.js'; import { Badge } from '@evobgp/ui/components/badge/index.js'; import { Button } from '@evobgp/ui/components/button/index.js'; - import { - Card, - CardContent, - CardHeader, - CardTitle, - CardDescription - } from '@evobgp/ui/components/card/index.js'; + import DataTableCard from '$lib/components/data-table-card.svelte'; import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte'; import ArrowRight from '@lucide/svelte/icons/arrow-right'; import ExternalLink from '@lucide/svelte/icons/external-link'; @@ -51,44 +45,36 @@ ] as const; - - -
- Последние задачи - Фоновые задачи ingest, refresh и apply -
+ + {#snippet toolbar()} -
- - j.job_id} - loading={initialLoading || loading} - {error} - emptyTitle="Нет задач" - emptyDescription="Задачи появятся после refresh или деплоя." - > - {#snippet cell({ row: j, column })} - {#if column.id === 'kind'} - {jobKindTitle(j, moduleNameById)} - {:else if column.id === 'status'} - {jobStatusRu(j.status)} - {:else if column.id === 'created'} - - {formatDateTime(j.created_at)} - - {:else if column.id === 'actions'} - - {/if} - {/snippet} - - -
+ {/snippet} + j.job_id} + loading={initialLoading || loading} + {error} + emptyTitle="Нет задач" + emptyDescription="Задачи появятся после refresh или деплоя." + > + {#snippet cell({ row: j, column })} + {#if column.id === 'kind'} + {jobKindTitle(j, moduleNameById)} + {:else if column.id === 'status'} + {jobStatusRu(j.status)} + {:else if column.id === 'created'} + + {formatDateTime(j.created_at)} + + {:else if column.id === 'actions'} + + {/if} + {/snippet} + + diff --git a/apps/web/src/lib/components/overview/OverviewRecentRevisionsCard.svelte b/apps/web/src/lib/components/overview/OverviewRecentRevisionsCard.svelte index 3e7ed19..66479aa 100644 --- a/apps/web/src/lib/components/overview/OverviewRecentRevisionsCard.svelte +++ b/apps/web/src/lib/components/overview/OverviewRecentRevisionsCard.svelte @@ -3,13 +3,7 @@ import type { RevisionRow } from '$lib/api/types.js'; import { formatDateTime } from '$lib/modules/display.js'; import { Button } from '@evobgp/ui/components/button/index.js'; - import { - Card, - CardContent, - CardHeader, - CardTitle, - CardDescription - } from '@evobgp/ui/components/card/index.js'; + import DataTableCard from '$lib/components/data-table-card.svelte'; import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte'; import ArrowRight from '@lucide/svelte/icons/arrow-right'; import ExternalLink from '@lucide/svelte/icons/external-link'; @@ -46,44 +40,39 @@ ] as const; - - -
- Последние ревизии - Снимки конфигурации BIRD после обновления модулей -
+ + {#snippet toolbar()} -
- - rev.id} - loading={initialLoading || loading} - {error} - emptyTitle="Нет ревизий" - emptyDescription="Ревизии появятся после обновления модулей." - > - {#snippet cell({ row: rev, column })} - {#if column.id === 'id'} - {rev.id.slice(0, 8)}… - {:else if column.id === 'created'} - - {formatDateTime(rev.created_at)} - - {:else if column.id === 'prefixes'} - {rev.materialized_prefix_count} - {:else if column.id === 'actions'} - - {/if} - {/snippet} - - -
+ {/snippet} + rev.id} + loading={initialLoading || loading} + {error} + emptyTitle="Нет ревизий" + emptyDescription="Ревизии появятся после обновления модулей." + > + {#snippet cell({ row: rev, column })} + {#if column.id === 'id'} + {rev.id.slice(0, 8)}… + {:else if column.id === 'created'} + + {formatDateTime(rev.created_at)} + + {:else if column.id === 'prefixes'} + {rev.materialized_prefix_count} + {:else if column.id === 'actions'} + + {/if} + {/snippet} + + diff --git a/apps/web/src/lib/components/patterns/confirm/confirm-dialog.svelte b/apps/web/src/lib/components/patterns/confirm/confirm-dialog.svelte index 57af643..871298f 100644 --- a/apps/web/src/lib/components/patterns/confirm/confirm-dialog.svelte +++ b/apps/web/src/lib/components/patterns/confirm/confirm-dialog.svelte @@ -39,7 +39,7 @@ { diff --git a/apps/web/src/lib/components/patterns/kpi/kpi-metrics-grid.svelte b/apps/web/src/lib/components/patterns/kpi/kpi-metrics-grid.svelte deleted file mode 100644 index c5f282a..0000000 --- a/apps/web/src/lib/components/patterns/kpi/kpi-metrics-grid.svelte +++ /dev/null @@ -1,112 +0,0 @@ - - -
- {#if loading} - {#each Array(skeletonCount) as _, i (i)} - - {/each} - {:else} - {#each cards as card (card.id)} - {@const Icon = card.icon} - {@const a = card.accent} - - - {#if card.href} -
- - - {card.label} - - -
- {:else} - - - {card.label} - - {/if} - {card.value} -
- - {card.badge} - {#if card.error} -

{card.error}

- {:else} -

{card.description}

- {/if} -
-
- {/each} - {/if} -
diff --git a/apps/web/src/lib/components/query-state.svelte b/apps/web/src/lib/components/query-state.svelte index f8cf37e..e6a4abf 100644 --- a/apps/web/src/lib/components/query-state.svelte +++ b/apps/web/src/lib/components/query-state.svelte @@ -4,7 +4,7 @@ import RefreshCw from '@lucide/svelte/icons/refresh-cw'; import { Button } from '@evobgp/ui/components/button/index.js'; import { Skeleton } from '@evobgp/ui/components/skeleton/index.js'; - import EmptyState from '$lib/components/empty-state.svelte'; + import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte'; type Props = { data: T | undefined; diff --git a/apps/web/src/lib/components/section-cards.svelte b/apps/web/src/lib/components/section-cards.svelte index 161d125..810553d 100644 --- a/apps/web/src/lib/components/section-cards.svelte +++ b/apps/web/src/lib/components/section-cards.svelte @@ -29,7 +29,7 @@ const valueVariantClass: Record, string> = { default: '', - warning: 'text-warning', + warning: 'text-warning-foreground', destructive: 'text-destructive' }; diff --git a/apps/web/src/lib/components/status-badge.svelte b/apps/web/src/lib/components/status-badge.svelte index 7fd4a92..2f12a31 100644 --- a/apps/web/src/lib/components/status-badge.svelte +++ b/apps/web/src/lib/components/status-badge.svelte @@ -1,19 +1,30 @@ -{text} + + {text} + diff --git a/apps/web/src/lib/components/tenant-settings/TenantSettingsPage.svelte b/apps/web/src/lib/components/tenant-settings/TenantSettingsPage.svelte index cf3adee..648fe47 100644 --- a/apps/web/src/lib/components/tenant-settings/TenantSettingsPage.svelte +++ b/apps/web/src/lib/components/tenant-settings/TenantSettingsPage.svelte @@ -10,7 +10,7 @@ import TenantRevisionSettingsCard from '$lib/components/tenant-settings/TenantRevisionSettingsCard.svelte'; import TenantAdditionalSettingsCard from '$lib/components/tenant-settings/TenantAdditionalSettingsCard.svelte'; import TenantRuntimeLogsSettingsCard from '$lib/components/tenant-settings/TenantRuntimeLogsSettingsCard.svelte'; - import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal'; + import { LINE_TAB_TRIGGER_CLASS } from '$lib/ui/app/tabs.js'; import Info from '@lucide/svelte/icons/info'; type TenantSettingsTab = 'bird' | 'revision' | 'runtime-logs' | 'additional'; @@ -49,8 +49,6 @@ @@ -63,12 +61,12 @@ -
- - BIRD - Ревизии - Файловые логи - Дополнительно +
+ + BIRD + Ревизии + Файловые логи + Дополнительно
diff --git a/apps/web/src/lib/ui/app/layout/app-brand.svelte b/apps/web/src/lib/ui/app/layout/app-brand.svelte new file mode 100644 index 0000000..6a5deb3 --- /dev/null +++ b/apps/web/src/lib/ui/app/layout/app-brand.svelte @@ -0,0 +1,36 @@ + + + + + + {#snippet child({ props })} + + +
+ EvoBGP + Панель управления BGP +
+
+ {/snippet} +
+
+
diff --git a/apps/web/src/lib/ui/app/layout/app-mobile-nav.svelte b/apps/web/src/lib/ui/app/layout/app-mobile-nav.svelte index 36276b3..6470f8c 100644 --- a/apps/web/src/lib/ui/app/layout/app-mobile-nav.svelte +++ b/apps/web/src/lib/ui/app/layout/app-mobile-nav.svelte @@ -10,8 +10,10 @@ import { Button } from '@evobgp/ui/components/button/index.js'; import type { ThemePreference } from '$lib/theme.js'; import Menu from '@lucide/svelte/icons/menu'; + import Share2 from '@lucide/svelte/icons/share-2'; import AppNavLinks from './app-nav-links.svelte'; import ThemeMenu from './theme-menu.svelte'; + import AppVersion from './app-version.svelte'; let { open = $bindable(false), @@ -29,17 +31,28 @@ - - - EvoBGP - -

Панель управления

+ +
+ +
+ + EvoBGP + +

Панель управления BGP

+
+
-
- -
(open = false)} />
+
+ + +
diff --git a/apps/web/src/lib/ui/app/layout/app-nav-links.svelte b/apps/web/src/lib/ui/app/layout/app-nav-links.svelte index f1ddb92..9a70e0f 100644 --- a/apps/web/src/lib/ui/app/layout/app-nav-links.svelte +++ b/apps/web/src/lib/ui/app/layout/app-nav-links.svelte @@ -3,9 +3,8 @@ import { page } from '$app/state'; import { cn } from '$lib/utils.js'; import { buttonVariants } from '@evobgp/ui/components/button/index.js'; - import { Separator } from '@evobgp/ui/components/separator/index.js'; import { Tooltip, TooltipContent, TooltipTrigger } from '@evobgp/ui/components/tooltip/index.js'; - import { bottomNav, mainNav } from './nav.js'; + import { navGroups } from './nav.js'; type Props = { collapsed?: boolean; @@ -40,57 +39,39 @@ } -