From ac727ad1e3d08709025b24cbc979dbce7b5e6e8a Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 9 Jul 2026 16:54:18 +0700 Subject: [PATCH] refactor: update dialog components and enhance UI consistency Refactored ConfirmDialog and FormDrawer components to improve layout and user experience. Integrated new DrawerActionsFooter for better action handling in dialogs. Updated styles for consistency across components, including adjustments to the skeletons and analytics card layouts. Removed deprecated alert components from various routes to streamline the codebase and enhance clarity in the UI. --- .../analytics/analytics-card-shell.tsx | 4 +- apps/web/src/components/confirm-dialog.tsx | 16 ++- apps/web/src/components/drawer-layout.tsx | 31 +++++ apps/web/src/components/form-drawer.tsx | 16 ++- apps/web/src/components/skeletons.tsx | 4 +- apps/web/src/routes/_auth/access.tsx | 19 +-- apps/web/src/routes/_auth/dashboard.tsx | 90 ++------------ apps/web/src/routes/_auth/directories.tsx | 12 +- apps/web/src/routes/_auth/firewall.tsx | 13 +- .../src/routes/_auth/modules/$moduleId.tsx | 24 +--- apps/web/src/routes/_auth/monitoring.tsx | 117 ++++++------------ apps/web/src/routes/_auth/network.tsx | 11 +- apps/web/src/routes/_auth/operations.tsx | 12 +- apps/web/src/routes/_auth/schedule.tsx | 13 +- apps/web/src/routes/_auth/settings.tsx | 13 +- apps/web/src/routes/_auth/tenant-settings.tsx | 20 +-- apps/web/tsconfig.tsbuildinfo | 2 +- 17 files changed, 113 insertions(+), 304 deletions(-) create mode 100644 apps/web/src/components/drawer-layout.tsx diff --git a/apps/web/src/components/analytics/analytics-card-shell.tsx b/apps/web/src/components/analytics/analytics-card-shell.tsx index 030b6a6..6b61b58 100644 --- a/apps/web/src/components/analytics/analytics-card-shell.tsx +++ b/apps/web/src/components/analytics/analytics-card-shell.tsx @@ -34,7 +34,7 @@ export function AnalyticsCardShell({ children: ReactNode }) { return ( - +
@@ -57,7 +57,7 @@ export function AnalyticsCardShell({
{actions ?
{actions}
: null}
- {children} + {children} {footer ? {footer} : null}
) diff --git a/apps/web/src/components/confirm-dialog.tsx b/apps/web/src/components/confirm-dialog.tsx index 87acb56..2e33cc8 100644 --- a/apps/web/src/components/confirm-dialog.tsx +++ b/apps/web/src/components/confirm-dialog.tsx @@ -4,13 +4,17 @@ import { DrawerClose, DrawerContent, DrawerDescription, - DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, } from '@evobgp/ui/components/drawer' import type { ReactElement, ReactNode } from 'react' +import { + confirmDrawerContentClassName, + DrawerActionsFooter, +} from '@/components/drawer-layout' + type ConfirmDialogBaseProps = { title: string description?: ReactNode @@ -58,11 +62,11 @@ function ConfirmDrawerBody({ return ( <> - + {title} {description ? {description} : null} - + }> {cancelLabel} @@ -87,7 +91,7 @@ function ConfirmDrawerBody({ {confirmText} )} - + ) } @@ -109,7 +113,7 @@ export function ConfirmDialog(props: ConfirmDialogProps) { return ( - + @@ -118,7 +122,7 @@ export function ConfirmDialog(props: ConfirmDialogProps) { return ( - + diff --git a/apps/web/src/components/drawer-layout.tsx b/apps/web/src/components/drawer-layout.tsx new file mode 100644 index 0000000..5f67399 --- /dev/null +++ b/apps/web/src/components/drawer-layout.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from 'react' + +import { cn } from '@evobgp/ui/lib/utils' +import { DrawerFooter } from '@evobgp/ui/components/drawer' + +/** Shared footer layout for right-side form and confirm drawers. */ +export function DrawerActionsFooter({ + children, + className, +}: { + children: ReactNode + className?: string +}) { + return ( + +
+ {children} +
+
+ ) +} + +export const formDrawerContentClassName = + 'flex h-full max-h-dvh flex-col sm:max-w-lg' + +export const confirmDrawerContentClassName = 'flex h-auto max-h-dvh flex-col sm:max-w-sm' diff --git a/apps/web/src/components/form-drawer.tsx b/apps/web/src/components/form-drawer.tsx index 9fbd67c..cdfeaa1 100644 --- a/apps/web/src/components/form-drawer.tsx +++ b/apps/web/src/components/form-drawer.tsx @@ -5,12 +5,16 @@ import { Drawer, DrawerContent, DrawerDescription, - DrawerFooter, DrawerHeader, DrawerTitle, } from '@evobgp/ui/components/drawer' import { ScrollArea } from '@evobgp/ui/components/scroll-area' +import { + DrawerActionsFooter, + formDrawerContentClassName, +} from '@/components/drawer-layout' + interface FormDrawerProps { open: boolean onOpenChange: (open: boolean) => void @@ -32,15 +36,15 @@ export function FormDrawer({ }: FormDrawerProps) { return ( - - + + {title} {description ? {description} : null} - -
{children}
+ +
{children}
- {footer} + {footer}
) diff --git a/apps/web/src/components/skeletons.tsx b/apps/web/src/components/skeletons.tsx index 543f3d0..d23cb21 100644 --- a/apps/web/src/components/skeletons.tsx +++ b/apps/web/src/components/skeletons.tsx @@ -17,8 +17,8 @@ export function SectionCardsSkeleton({ count = 4 }: { count?: number }) { export function AnalyticsDashboardSkeleton() { return ( -
- +
+
diff --git a/apps/web/src/routes/_auth/access.tsx b/apps/web/src/routes/_auth/access.tsx index a3b5361..95f14ec 100644 --- a/apps/web/src/routes/_auth/access.tsx +++ b/apps/web/src/routes/_auth/access.tsx @@ -1,9 +1,8 @@ import { createFileRoute, Link } from '@tanstack/react-router' import { useQuery } from '@tanstack/react-query' -import { Info, KeyRound, RefreshCw, ShieldCheck, ShieldOff } from 'lucide-react' +import { KeyRound, RefreshCw, ShieldCheck, ShieldOff } from 'lucide-react' import { useMemo } from 'react' -import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert' import { Button } from '@evobgp/ui/components/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card' @@ -79,22 +78,6 @@ function AccessComponent() { } /> - - - О API-ключах - - Роли: viewer (чтение),{' '} - editor (CRUD), operator{' '} - (apply и настройки), node (API ноды). Полный токен - показывается один раз при создании и ротации. Токен браузера — в{' '} - - настройках - - ; для локальной разработки с demo-seed подойдёт dev{' '} - (роль operator). - - - {session ? ( diff --git a/apps/web/src/routes/_auth/dashboard.tsx b/apps/web/src/routes/_auth/dashboard.tsx index 77d75e8..b84fd84 100644 --- a/apps/web/src/routes/_auth/dashboard.tsx +++ b/apps/web/src/routes/_auth/dashboard.tsx @@ -1,9 +1,8 @@ import { createFileRoute } from '@tanstack/react-router' import { useQueries } from '@tanstack/react-query' -import { CheckCircle, Info, RefreshCw, XCircle } from 'lucide-react' +import { RefreshCw } from 'lucide-react' import { useState } from 'react' -import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert' import { Button } from '@evobgp/ui/components/button' import { Card, @@ -27,7 +26,6 @@ import { AnalyticsDashboardSkeleton } from '@/components/skeletons' import { moduleNameById, - overviewHealthQueryOptions, overviewJobsQueryOptions, overviewModulesQueryOptions, overviewPeersQueryOptions, @@ -44,7 +42,6 @@ function DashboardComponent() { const results = useQueries({ queries: [ - overviewHealthQueryOptions(), overviewModulesQueryOptions(), overviewPeersQueryOptions(), overviewSpeakersQueryOptions(), @@ -53,7 +50,7 @@ function DashboardComponent() { ], }) - const [healthQ, modulesQ, peersQ, speakersQ, revisionsQ, jobsQ] = results + const [modulesQ, peersQ, speakersQ, revisionsQ, jobsQ] = results const initialLoading = modulesQ.isLoading || peersQ.isLoading || speakersQ.isLoading || revisionsQ.isLoading || jobsQ.isLoading const refreshing = results.some((r) => r.isFetching && !r.isLoading) @@ -94,34 +91,17 @@ function DashboardComponent() { } /> - - - Панель управления EvoBGP - - Сводка по модулям, сети и фоновым задачам. BGP и ноды — «Сеть», префиксы — «Модули», - деплой — «Операции», здоровье API — «Мониторинг». - - - - - {initialLoading ? ( ) : ( -
-
- -
+
+
@@ -163,53 +143,3 @@ function DashboardComponent() {
) } - -function HealthAlert({ - loading, - ok, - loadError, -}: { - loading: boolean - ok: boolean | undefined - loadError: string | null -}) { - if (loading) { - return ( - - - Проверка API… - - Запрос к /v1/health - - - ) - } - if (ok && !loadError) { - return ( - - - API работает - Сервер отвечает на запросы health-check. - - ) - } - if (ok && loadError) { - return ( - - - API доступен, данные не загружены - {loadError}. Проверьте Bearer-токен в «Настройках». - - ) - } - return ( - - - API недоступен - - Не удалось получить ответ от сервера. Проверьте, что API запущен (порт 8080) и в dev работает - прокси Vite. - - - ) -} diff --git a/apps/web/src/routes/_auth/directories.tsx b/apps/web/src/routes/_auth/directories.tsx index 70a9521..15a9715 100644 --- a/apps/web/src/routes/_auth/directories.tsx +++ b/apps/web/src/routes/_auth/directories.tsx @@ -1,8 +1,7 @@ import { createFileRoute } from '@tanstack/react-router' import { useQuery } from '@tanstack/react-query' -import { BookText, Globe, Info, RefreshCw, Tags } from 'lucide-react' +import { BookText, Globe, RefreshCw, Tags } from 'lucide-react' -import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert' import { Button } from '@evobgp/ui/components/button' import { BadgeTabs, TabsContent } from '@/components/badge-tabs' import { DataGridCard } from '@/components/data-grid-shell' @@ -68,15 +67,6 @@ function DirectoriesComponent() { } /> - - - О справочниках - - Сообщества BGP используются в AS- и CDN-модулях для тегирования префиксов. DoH-профили — в - доменных модулях для DNS-over-HTTPS резолвинга. - - - {loading ? : } - - - Политика - - Правила сопоставляются с BGP community префиксов опубликованной revision.{' '} - block добавляет префиксы community в kernel; accept — не блокирует. - Community «Все» — правило для любого community. Default без совпадений — accept. - - - diff --git a/apps/web/src/routes/_auth/modules/$moduleId.tsx b/apps/web/src/routes/_auth/modules/$moduleId.tsx index b777e4f..8750d4f 100644 --- a/apps/web/src/routes/_auth/modules/$moduleId.tsx +++ b/apps/web/src/routes/_auth/modules/$moduleId.tsx @@ -1,8 +1,7 @@ import { createFileRoute, Link } from '@tanstack/react-router' import { useQuery, useQueryClient } from '@tanstack/react-query' -import { ArrowLeft, Info, RefreshCw } from 'lucide-react' +import { ArrowLeft, RefreshCw } from 'lucide-react' -import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert' import { Button } from '@evobgp/ui/components/button' import { PageHeader } from '@/components/page-header' @@ -17,25 +16,12 @@ import { directoriesDohQueryOptions, } from '@/queries/directories' import { moduleDetailQueryOptions, moduleEntriesQueryOptions, modulesKeys } from '@/queries/modules' -import type { AsEntry, ModuleRow } from '@/types/api' +import type { AsEntry } from '@/types/api' export const Route = createFileRoute('/_auth/modules/$moduleId')({ component: ModuleDetailComponent, }) -function moduleTypeAlert(type: ModuleRow['type']): string { - switch (type) { - case 'AS_PREFIXES': - return 'Модуль AS получает префиксы через RIPEstat по указанным ASN. После refresh счётчики префиксов обновляются в таблице записей.' - case 'CDN_CIDRS': - return 'Модуль CDN скачивает списки CIDR по URL (plaintext или JSON). Используйте предпросмотр при добавлении источника.' - case 'DOMAINS': - return 'Модуль доменов резолвит FQDN через DoH-профили и конвертирует IP в префиксы. Политика и профили настраиваются в редактировании модуля.' - case 'IP_RANGES': - return 'Модуль IP-диапазонов использует статические CIDR без внешнего refresh (сервер может вернуть 204). Записи участвуют в агрегации напрямую.' - } -} - function ModuleDetailComponent() { const { moduleId } = Route.useParams() const queryClient = useQueryClient() @@ -114,12 +100,6 @@ function ModuleDetailComponent() { )}
- - - О модуле - {moduleTypeAlert(m.type)} - - @@ -118,17 +122,6 @@ function MonitoringComponent() {
)} - - - - Версия API: {versionText} - {versionQ.data?.git_sha ? ` · ${versionQ.data.git_sha.slice(0, 8)}` : ''} - - - {overallHint({ health: healthQ.data, jobsFailed: failed })} - - -
Короткая шпаргалка для triage - - - - API недоступен - - Если /v1/health возвращает ошибку — проверьте процесс - API и его логи. - - - - - Readiness не «Готов» - - Сначала postgres, затем{' '} - store и jobs в checks. - - - - - Низкий ratio BGP - - Проверьте /v1/bird/status, затем состояние пиров в Сети. - - - - - Ошибки задач - - Откройте Операции и проверьте последние неуспешные jobs. - - + +
    +
  • + API недоступен. Если{' '} + /v1/health возвращает ошибку — проверьте процесс API и + его логи. +
  • +
  • + Readiness не «Готов». Сначала{' '} + postgres, затем store{' '} + и jobs в checks. +
  • +
  • + Низкий ratio BGP. Проверьте{' '} + /v1/bird/status, затем состояние пиров в Сети. +
  • +
  • + Ошибки задач. Откройте Операции и + проверьте последние неуспешные jobs. +
  • +
@@ -265,18 +248,11 @@ function MonitoringComponent() { PostgreSQL - Статус соединения и пул + + Статус соединения и пул. PostgreSQL отображается в readiness-проверке на вкладке «Система» + (check postgres). + - - - - Статус готовности - - PostgreSQL-соединение отображается в readiness-проверке на вкладке «Система» (check{' '} - postgres). - - - @@ -284,18 +260,11 @@ function MonitoringComponent() { Файловые логи - Логи API и pipeline + + Логи API и pipeline настраиваются переменной EVOBGP_LOG_* и + управляются в tenant-settings. + - - - - Логи на сервере - - Файловые логи настраиваются переменной EVOBGP_LOG_* и - управляются tenant-settings на странице «Настройки BIRD». - - - @@ -317,18 +286,6 @@ function formatVersion(version?: VersionInfo | null): string { return version.version ?? version.app ?? '—' } -interface OverallInput { - health?: { ok?: boolean } | null - ready?: ReadyStatus | null - jobsFailed: number -} - -function overallHint(input: OverallInput): string { - if (!input.health?.ok) return 'API недоступен или возвращает ошибку' - if (input.jobsFailed > 0) return `Есть провальные задачи (${input.jobsFailed})` - return 'Все системы работают в штатном режиме' -} - function BirdSummary({ bird }: { bird: import('@/types/api').BirdStatus }) { if (!bird.birdc_configured) { return ( diff --git a/apps/web/src/routes/_auth/network.tsx b/apps/web/src/routes/_auth/network.tsx index 9f287c5..1f16598 100644 --- a/apps/web/src/routes/_auth/network.tsx +++ b/apps/web/src/routes/_auth/network.tsx @@ -1,9 +1,8 @@ import { createFileRoute, useSearch } from '@tanstack/react-router' import { useQuery } from '@tanstack/react-query' -import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert' import { Button } from '@evobgp/ui/components/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card' -import { Info, RefreshCw } from 'lucide-react' +import { RefreshCw } from 'lucide-react' import { DashboardNetworkCapacityCard, @@ -60,14 +59,6 @@ function NetworkComponent() { } /> - - - О сетевой конфигурации - - Вкладка «Обзор» — live-статус agent и BGP на CP и репликах. Apply и ревизии — на странице «Операции». - - - diff --git a/apps/web/src/routes/_auth/operations.tsx b/apps/web/src/routes/_auth/operations.tsx index d4fa098..8175b95 100644 --- a/apps/web/src/routes/_auth/operations.tsx +++ b/apps/web/src/routes/_auth/operations.tsx @@ -1,10 +1,9 @@ import { createFileRoute, useSearch } from '@tanstack/react-router' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { Info, RefreshCw } from 'lucide-react' +import { RefreshCw } from 'lucide-react' import { toast } from 'sonner' import { useState, useMemo } from 'react' -import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert' import { Button } from '@evobgp/ui/components/button' import { Card, CardContent, CardHeader, CardTitle } from '@evobgp/ui/components/card' import { BadgeTabs, TabsContent } from '@/components/badge-tabs' @@ -94,15 +93,6 @@ function OperationsComponent() { } /> - - - Три раздела на одной странице - - Ревизии — история конфигов и откат; Сравнение — diff - префиксов; Задачи — ingest, apply, rollback. - - -
- - - Как работает расписание - - Планировщик использует refresh_interval_sec и опционально{' '} - cron_expr. Ручной запуск —{' '} - POST /v1/modules/{id}/refresh. - - - {loading ? : } - - - Локальная разработка - - При включённом demo-seed API принимает токен dev (роль{' '} - operator). Вводите только значение токена, без префикса{' '} - Bearer — он добавляется автоматически. - - - Подключение к API diff --git a/apps/web/src/routes/_auth/tenant-settings.tsx b/apps/web/src/routes/_auth/tenant-settings.tsx index a799714..c944887 100644 --- a/apps/web/src/routes/_auth/tenant-settings.tsx +++ b/apps/web/src/routes/_auth/tenant-settings.tsx @@ -1,10 +1,9 @@ import { createFileRoute, useSearch } from '@tanstack/react-router' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { Info, Save } from 'lucide-react' +import { Save } from 'lucide-react' import { useEffect, useState } from 'react' import { toast } from 'sonner' -import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card' import { Input } from '@evobgp/ui/components/input' import { Label } from '@evobgp/ui/components/label' @@ -104,15 +103,6 @@ function TenantSettingsComponent() { description="Параметры control plane для текущего tenant (API /v1/settings)" /> - - - Operator-only - - Изменение значений через PATCH /v1/settings требует роли - operator. При отсутствии прав API вернёт 403. - - - @@ -137,14 +127,6 @@ function TenantSettingsComponent() { - - - Подстановка в конфиг - - Значения используются при генерации BIRD-конфигурации (router id, local AS, адреса). - Пиры и спикеры настраиваются в разделе «Сеть». - -