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, адреса). - Пиры и спикеры настраиваются в разделе «Сеть». - -