refactor: update dialog components and enhance UI consistency
CI / changes (push) Successful in 13s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 1m5s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m20s
CI / changes (push) Successful in 13s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 1m5s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m20s
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.
This commit is contained in:
@@ -34,7 +34,7 @@ export function AnalyticsCardShell({
|
|||||||
children: ReactNode
|
children: ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Card className={cn('flex h-full flex-col gap-0 overflow-hidden', className)}>
|
<Card className={cn('flex flex-col gap-0 overflow-hidden', className)}>
|
||||||
<CardHeader className="flex flex-row items-start justify-between gap-3 border-b py-4">
|
<CardHeader className="flex flex-row items-start justify-between gap-3 border-b py-4">
|
||||||
<div className="min-w-0 space-y-1">
|
<div className="min-w-0 space-y-1">
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
@@ -57,7 +57,7 @@ export function AnalyticsCardShell({
|
|||||||
</div>
|
</div>
|
||||||
{actions ? <div className="shrink-0">{actions}</div> : null}
|
{actions ? <div className="shrink-0">{actions}</div> : null}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-1 flex-col gap-5 p-5">{children}</CardContent>
|
<CardContent className="flex flex-col gap-5 p-5">{children}</CardContent>
|
||||||
{footer ? <CardFooter className="gap-2 border-t p-4">{footer}</CardFooter> : null}
|
{footer ? <CardFooter className="gap-2 border-t p-4">{footer}</CardFooter> : null}
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ import {
|
|||||||
DrawerClose,
|
DrawerClose,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
DrawerDescription,
|
DrawerDescription,
|
||||||
DrawerFooter,
|
|
||||||
DrawerHeader,
|
DrawerHeader,
|
||||||
DrawerTitle,
|
DrawerTitle,
|
||||||
DrawerTrigger,
|
DrawerTrigger,
|
||||||
} from '@evobgp/ui/components/drawer'
|
} from '@evobgp/ui/components/drawer'
|
||||||
import type { ReactElement, ReactNode } from 'react'
|
import type { ReactElement, ReactNode } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
confirmDrawerContentClassName,
|
||||||
|
DrawerActionsFooter,
|
||||||
|
} from '@/components/drawer-layout'
|
||||||
|
|
||||||
type ConfirmDialogBaseProps = {
|
type ConfirmDialogBaseProps = {
|
||||||
title: string
|
title: string
|
||||||
description?: ReactNode
|
description?: ReactNode
|
||||||
@@ -58,11 +62,11 @@ function ConfirmDrawerBody({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DrawerHeader>
|
<DrawerHeader className="shrink-0 border-b border-border pb-4">
|
||||||
<DrawerTitle>{title}</DrawerTitle>
|
<DrawerTitle>{title}</DrawerTitle>
|
||||||
{description ? <DrawerDescription>{description}</DrawerDescription> : null}
|
{description ? <DrawerDescription>{description}</DrawerDescription> : null}
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<DrawerFooter className="border-t bg-muted/50 sm:flex-row sm:justify-end">
|
<DrawerActionsFooter>
|
||||||
<DrawerClose render={<Button variant="outline" disabled={confirmLoading} />}>
|
<DrawerClose render={<Button variant="outline" disabled={confirmLoading} />}>
|
||||||
{cancelLabel}
|
{cancelLabel}
|
||||||
</DrawerClose>
|
</DrawerClose>
|
||||||
@@ -87,7 +91,7 @@ function ConfirmDrawerBody({
|
|||||||
{confirmText}
|
{confirmText}
|
||||||
</DrawerClose>
|
</DrawerClose>
|
||||||
)}
|
)}
|
||||||
</DrawerFooter>
|
</DrawerActionsFooter>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -109,7 +113,7 @@ export function ConfirmDialog(props: ConfirmDialogProps) {
|
|||||||
return (
|
return (
|
||||||
<Drawer swipeDirection="right">
|
<Drawer swipeDirection="right">
|
||||||
<DrawerTrigger render={props.trigger} />
|
<DrawerTrigger render={props.trigger} />
|
||||||
<DrawerContent className="h-full max-h-none sm:max-w-sm">
|
<DrawerContent className={confirmDrawerContentClassName}>
|
||||||
<ConfirmDrawerBody {...bodyProps} />
|
<ConfirmDrawerBody {...bodyProps} />
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
@@ -118,7 +122,7 @@ export function ConfirmDialog(props: ConfirmDialogProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={props.open} onOpenChange={props.onOpenChange} swipeDirection="right">
|
<Drawer open={props.open} onOpenChange={props.onOpenChange} swipeDirection="right">
|
||||||
<DrawerContent className="h-full max-h-none sm:max-w-sm">
|
<DrawerContent className={confirmDrawerContentClassName}>
|
||||||
<ConfirmDrawerBody {...bodyProps} controlled />
|
<ConfirmDrawerBody {...bodyProps} controlled />
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<DrawerFooter
|
||||||
|
className={cn(
|
||||||
|
'mt-0 shrink-0 border-t border-border bg-muted/50 p-4',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-end">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</DrawerFooter>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
@@ -5,12 +5,16 @@ import {
|
|||||||
Drawer,
|
Drawer,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
DrawerDescription,
|
DrawerDescription,
|
||||||
DrawerFooter,
|
|
||||||
DrawerHeader,
|
DrawerHeader,
|
||||||
DrawerTitle,
|
DrawerTitle,
|
||||||
} from '@evobgp/ui/components/drawer'
|
} from '@evobgp/ui/components/drawer'
|
||||||
import { ScrollArea } from '@evobgp/ui/components/scroll-area'
|
import { ScrollArea } from '@evobgp/ui/components/scroll-area'
|
||||||
|
|
||||||
|
import {
|
||||||
|
DrawerActionsFooter,
|
||||||
|
formDrawerContentClassName,
|
||||||
|
} from '@/components/drawer-layout'
|
||||||
|
|
||||||
interface FormDrawerProps {
|
interface FormDrawerProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
onOpenChange: (open: boolean) => void
|
onOpenChange: (open: boolean) => void
|
||||||
@@ -32,15 +36,15 @@ export function FormDrawer({
|
|||||||
}: FormDrawerProps) {
|
}: FormDrawerProps) {
|
||||||
return (
|
return (
|
||||||
<Drawer open={open} onOpenChange={onOpenChange} swipeDirection="right">
|
<Drawer open={open} onOpenChange={onOpenChange} swipeDirection="right">
|
||||||
<DrawerContent className={cn('h-full max-h-none sm:max-w-lg', className)}>
|
<DrawerContent className={cn(formDrawerContentClassName, className)}>
|
||||||
<DrawerHeader>
|
<DrawerHeader className="shrink-0 border-b border-border pb-4">
|
||||||
<DrawerTitle>{title}</DrawerTitle>
|
<DrawerTitle>{title}</DrawerTitle>
|
||||||
{description ? <DrawerDescription>{description}</DrawerDescription> : null}
|
{description ? <DrawerDescription>{description}</DrawerDescription> : null}
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<ScrollArea className="min-h-0 flex-1 px-4">
|
<ScrollArea className="min-h-0 flex-1">
|
||||||
<div className="space-y-4 pb-4">{children}</div>
|
<div className="space-y-4 px-4 py-4">{children}</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<DrawerFooter className="border-t bg-muted/50 sm:flex-row sm:justify-end">{footer}</DrawerFooter>
|
<DrawerActionsFooter>{footer}</DrawerActionsFooter>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export function SectionCardsSkeleton({ count = 4 }: { count?: number }) {
|
|||||||
|
|
||||||
export function AnalyticsDashboardSkeleton() {
|
export function AnalyticsDashboardSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-4 lg:grid-cols-3 lg:grid-rows-2">
|
<div className="grid gap-4 lg:grid-cols-3 lg:items-start">
|
||||||
<Card className="gap-0 lg:row-span-2">
|
<Card className="gap-0">
|
||||||
<CardContent className="space-y-4 p-5">
|
<CardContent className="space-y-4 p-5">
|
||||||
<Skeleton className="h-4 w-40" />
|
<Skeleton className="h-4 w-40" />
|
||||||
<div className="grid gap-4 sm:grid-cols-3">
|
<div className="grid gap-4 sm:grid-cols-3">
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { createFileRoute, Link } from '@tanstack/react-router'
|
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
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 { useMemo } from 'react'
|
||||||
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
||||||
|
|
||||||
@@ -79,22 +78,6 @@ function AccessComponent() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>О API-ключах</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Роли: <code className="text-xs">viewer</code> (чтение),{' '}
|
|
||||||
<code className="text-xs">editor</code> (CRUD), <code className="text-xs">operator</code>{' '}
|
|
||||||
(apply и настройки), <code className="text-xs">node</code> (API ноды). Полный токен
|
|
||||||
показывается один раз при создании и ротации. Токен браузера — в{' '}
|
|
||||||
<Link to="/settings" className="text-primary underline-offset-4 hover:underline">
|
|
||||||
настройках
|
|
||||||
</Link>
|
|
||||||
; для локальной разработки с demo-seed подойдёт <code className="text-xs">dev</code>{' '}
|
|
||||||
(роль operator).
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
{session ? (
|
{session ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="border-b py-3">
|
<CardHeader className="border-b py-3">
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { useQueries } from '@tanstack/react-query'
|
import { useQueries } from '@tanstack/react-query'
|
||||||
import { CheckCircle, Info, RefreshCw, XCircle } from 'lucide-react'
|
import { RefreshCw } from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -27,7 +26,6 @@ import { AnalyticsDashboardSkeleton } from '@/components/skeletons'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
moduleNameById,
|
moduleNameById,
|
||||||
overviewHealthQueryOptions,
|
|
||||||
overviewJobsQueryOptions,
|
overviewJobsQueryOptions,
|
||||||
overviewModulesQueryOptions,
|
overviewModulesQueryOptions,
|
||||||
overviewPeersQueryOptions,
|
overviewPeersQueryOptions,
|
||||||
@@ -44,7 +42,6 @@ function DashboardComponent() {
|
|||||||
|
|
||||||
const results = useQueries({
|
const results = useQueries({
|
||||||
queries: [
|
queries: [
|
||||||
overviewHealthQueryOptions(),
|
|
||||||
overviewModulesQueryOptions(),
|
overviewModulesQueryOptions(),
|
||||||
overviewPeersQueryOptions(),
|
overviewPeersQueryOptions(),
|
||||||
overviewSpeakersQueryOptions(),
|
overviewSpeakersQueryOptions(),
|
||||||
@@ -53,7 +50,7 @@ function DashboardComponent() {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const [healthQ, modulesQ, peersQ, speakersQ, revisionsQ, jobsQ] = results
|
const [modulesQ, peersQ, speakersQ, revisionsQ, jobsQ] = results
|
||||||
const initialLoading =
|
const initialLoading =
|
||||||
modulesQ.isLoading || peersQ.isLoading || speakersQ.isLoading || revisionsQ.isLoading || jobsQ.isLoading
|
modulesQ.isLoading || peersQ.isLoading || speakersQ.isLoading || revisionsQ.isLoading || jobsQ.isLoading
|
||||||
const refreshing = results.some((r) => r.isFetching && !r.isLoading)
|
const refreshing = results.some((r) => r.isFetching && !r.isLoading)
|
||||||
@@ -94,34 +91,17 @@ function DashboardComponent() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>Панель управления EvoBGP</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Сводка по модулям, сети и фоновым задачам. BGP и ноды — «Сеть», префиксы — «Модули»,
|
|
||||||
деплой — «Операции», здоровье API — «Мониторинг».
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<HealthAlert
|
|
||||||
loading={healthQ.isLoading}
|
|
||||||
ok={healthQ.data === true}
|
|
||||||
loadError={modulesQ.isError || peersQ.isError ? 'Некоторые данные не загружены' : null}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{initialLoading ? (
|
{initialLoading ? (
|
||||||
<AnalyticsDashboardSkeleton />
|
<AnalyticsDashboardSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4 lg:grid-cols-3 lg:grid-rows-2">
|
<div className="grid gap-4 lg:grid-cols-3 lg:items-start">
|
||||||
<div className="lg:row-span-2">
|
<DashboardPlatformCard
|
||||||
<DashboardPlatformCard
|
modules={modules}
|
||||||
modules={modules}
|
peers={peers}
|
||||||
peers={peers}
|
speakers={speakers}
|
||||||
speakers={speakers}
|
jobs={jobs}
|
||||||
jobs={jobs}
|
revisions={revisions}
|
||||||
revisions={revisions}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<DashboardNetworkCapacityCard peers={peers} speakers={speakers} jobs={jobs} />
|
<DashboardNetworkCapacityCard peers={peers} speakers={speakers} jobs={jobs} />
|
||||||
<DashboardOperationsFlowCard jobs={jobs} modules={modules} />
|
<DashboardOperationsFlowCard jobs={jobs} modules={modules} />
|
||||||
</div>
|
</div>
|
||||||
@@ -163,53 +143,3 @@ function DashboardComponent() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function HealthAlert({
|
|
||||||
loading,
|
|
||||||
ok,
|
|
||||||
loadError,
|
|
||||||
}: {
|
|
||||||
loading: boolean
|
|
||||||
ok: boolean | undefined
|
|
||||||
loadError: string | null
|
|
||||||
}) {
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<Alert>
|
|
||||||
<Skeleton className="size-5 rounded-full" />
|
|
||||||
<AlertTitle>Проверка API…</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Запрос к <code className="text-xs">/v1/health</code>
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (ok && !loadError) {
|
|
||||||
return (
|
|
||||||
<Alert className="border-success/30 bg-success/5">
|
|
||||||
<CheckCircle className="text-success" />
|
|
||||||
<AlertTitle>API работает</AlertTitle>
|
|
||||||
<AlertDescription>Сервер отвечает на запросы health-check.</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (ok && loadError) {
|
|
||||||
return (
|
|
||||||
<Alert className="border-warning/30 bg-warning/5">
|
|
||||||
<Info className="text-warning" />
|
|
||||||
<AlertTitle>API доступен, данные не загружены</AlertTitle>
|
|
||||||
<AlertDescription>{loadError}. Проверьте Bearer-токен в «Настройках».</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Alert variant="destructive" className="border-destructive/30 bg-destructive/5">
|
|
||||||
<XCircle className="text-destructive" />
|
|
||||||
<AlertTitle>API недоступен</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Не удалось получить ответ от сервера. Проверьте, что API запущен (порт 8080) и в dev работает
|
|
||||||
прокси Vite.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
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 { Button } from '@evobgp/ui/components/button'
|
||||||
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
|
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
|
||||||
import { DataGridCard } from '@/components/data-grid-shell'
|
import { DataGridCard } from '@/components/data-grid-shell'
|
||||||
@@ -68,15 +67,6 @@ function DirectoriesComponent() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>О справочниках</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Сообщества BGP используются в AS- и CDN-модулях для тегирования префиксов. DoH-профили — в
|
|
||||||
доменных модулях для DNS-over-HTTPS резолвинга.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
{loading ? <SectionCardsSkeleton count={3} /> : <SectionCards items={items} />}
|
{loading ? <SectionCardsSkeleton count={3} /> : <SectionCards items={items} />}
|
||||||
|
|
||||||
<BadgeTabs
|
<BadgeTabs
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { Copy, Info, Plus, RefreshCw, Shield } from 'lucide-react'
|
import { Copy, Plus, RefreshCw, Shield } from 'lucide-react'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
||||||
import { Input } from '@evobgp/ui/components/input'
|
import { Input } from '@evobgp/ui/components/input'
|
||||||
@@ -126,16 +125,6 @@ function FirewallPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>Политика</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Правила сопоставляются с <strong>BGP community</strong> префиксов опубликованной revision.{' '}
|
|
||||||
<strong>block</strong> добавляет префиксы community в kernel; <strong>accept</strong> — не блокирует.
|
|
||||||
Community «Все» — правило для любого community. Default без совпадений — accept.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { createFileRoute, Link } from '@tanstack/react-router'
|
import { createFileRoute, Link } from '@tanstack/react-router'
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
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 { Button } from '@evobgp/ui/components/button'
|
||||||
|
|
||||||
import { PageHeader } from '@/components/page-header'
|
import { PageHeader } from '@/components/page-header'
|
||||||
@@ -17,25 +16,12 @@ import {
|
|||||||
directoriesDohQueryOptions,
|
directoriesDohQueryOptions,
|
||||||
} from '@/queries/directories'
|
} from '@/queries/directories'
|
||||||
import { moduleDetailQueryOptions, moduleEntriesQueryOptions, modulesKeys } from '@/queries/modules'
|
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')({
|
export const Route = createFileRoute('/_auth/modules/$moduleId')({
|
||||||
component: ModuleDetailComponent,
|
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() {
|
function ModuleDetailComponent() {
|
||||||
const { moduleId } = Route.useParams()
|
const { moduleId } = Route.useParams()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
@@ -114,12 +100,6 @@ function ModuleDetailComponent() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Alert>
|
|
||||||
<Info />
|
|
||||||
<AlertTitle>О модуле</AlertTitle>
|
|
||||||
<AlertDescription>{moduleTypeAlert(m.type)}</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<ModuleKpiCards
|
<ModuleKpiCards
|
||||||
mod={m}
|
mod={m}
|
||||||
communities={communities}
|
communities={communities}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { Activity, AlertTriangle, Bird, Database, HeartPulse, Info, ListTodo, RefreshCw } from 'lucide-react'
|
import { Activity, AlertTriangle, Bird, RefreshCw } from 'lucide-react'
|
||||||
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
||||||
import { Separator } from '@evobgp/ui/components/separator'
|
import { Separator } from '@evobgp/ui/components/separator'
|
||||||
@@ -22,7 +21,6 @@ import {
|
|||||||
monitoringHealthQueryOptions,
|
monitoringHealthQueryOptions,
|
||||||
monitoringReadyQueryOptions,
|
monitoringReadyQueryOptions,
|
||||||
monitoringVersionQueryOptions,
|
monitoringVersionQueryOptions,
|
||||||
type ReadyStatus,
|
|
||||||
type VersionInfo,
|
type VersionInfo,
|
||||||
} from '@/queries/monitoring'
|
} from '@/queries/monitoring'
|
||||||
import { networkBirdQueryOptions } from '@/queries/network'
|
import { networkBirdQueryOptions } from '@/queries/network'
|
||||||
@@ -84,7 +82,13 @@ function MonitoringComponent() {
|
|||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Мониторинг"
|
title="Мониторинг"
|
||||||
description="Состояние API, BGP и задач для диагностики инцидентов"
|
description={`Состояние API, BGP и задач для диагностики инцидентов${
|
||||||
|
versionText !== '—'
|
||||||
|
? ` · версия ${versionText}${
|
||||||
|
versionQ.data?.git_sha ? ` (${versionQ.data.git_sha.slice(0, 8)})` : ''
|
||||||
|
}`
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
actions={
|
actions={
|
||||||
<Button variant="outline" size="sm" onClick={refetchAll} disabled={refreshing}>
|
<Button variant="outline" size="sm" onClick={refetchAll} disabled={refreshing}>
|
||||||
<RefreshCw className={refreshing ? 'animate-spin' : ''} />
|
<RefreshCw className={refreshing ? 'animate-spin' : ''} />
|
||||||
@@ -118,17 +122,6 @@ function MonitoringComponent() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Alert className="border-muted bg-muted/30">
|
|
||||||
<Info className="size-4" />
|
|
||||||
<AlertTitle className="text-sm">
|
|
||||||
Версия API: {versionText}
|
|
||||||
{versionQ.data?.git_sha ? ` · ${versionQ.data.git_sha.slice(0, 8)}` : ''}
|
|
||||||
</AlertTitle>
|
|
||||||
<AlertDescription className="text-xs">
|
|
||||||
{overallHint({ health: healthQ.data, jobsFailed: failed })}
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<div className="grid gap-4 lg:grid-cols-2">
|
<div className="grid gap-4 lg:grid-cols-2">
|
||||||
<DataGridCard
|
<DataGridCard
|
||||||
title="Доступность и готовность"
|
title="Доступность и готовность"
|
||||||
@@ -225,37 +218,27 @@ function MonitoringComponent() {
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>Короткая шпаргалка для triage</CardDescription>
|
<CardDescription>Короткая шпаргалка для triage</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent>
|
||||||
<Alert>
|
<ul className="space-y-3 text-sm text-muted-foreground">
|
||||||
<HeartPulse className="size-4" />
|
<li>
|
||||||
<AlertTitle>API недоступен</AlertTitle>
|
<span className="font-medium text-foreground">API недоступен.</span> Если{' '}
|
||||||
<AlertDescription>
|
<code className="text-xs">/v1/health</code> возвращает ошибку — проверьте процесс API и
|
||||||
Если <code className="text-xs">/v1/health</code> возвращает ошибку — проверьте процесс
|
его логи.
|
||||||
API и его логи.
|
</li>
|
||||||
</AlertDescription>
|
<li>
|
||||||
</Alert>
|
<span className="font-medium text-foreground">Readiness не «Готов».</span> Сначала{' '}
|
||||||
<Alert>
|
<code className="text-xs">postgres</code>, затем <code className="text-xs">store</code>{' '}
|
||||||
<Database className="size-4" />
|
и <code className="text-xs">jobs</code> в checks.
|
||||||
<AlertTitle>Readiness не «Готов»</AlertTitle>
|
</li>
|
||||||
<AlertDescription>
|
<li>
|
||||||
Сначала <code className="text-xs">postgres</code>, затем{' '}
|
<span className="font-medium text-foreground">Низкий ratio BGP.</span> Проверьте{' '}
|
||||||
<code className="text-xs">store</code> и <code className="text-xs">jobs</code> в checks.
|
<code className="text-xs">/v1/bird/status</code>, затем состояние пиров в Сети.
|
||||||
</AlertDescription>
|
</li>
|
||||||
</Alert>
|
<li>
|
||||||
<Alert>
|
<span className="font-medium text-foreground">Ошибки задач.</span> Откройте Операции и
|
||||||
<Bird className="size-4" />
|
проверьте последние неуспешные jobs.
|
||||||
<AlertTitle>Низкий ratio BGP</AlertTitle>
|
</li>
|
||||||
<AlertDescription>
|
</ul>
|
||||||
Проверьте <code className="text-xs">/v1/bird/status</code>, затем состояние пиров в Сети.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Alert>
|
|
||||||
<ListTodo className="size-4" />
|
|
||||||
<AlertTitle>Ошибки задач</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Откройте Операции и проверьте последние неуспешные jobs.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -265,18 +248,11 @@ function MonitoringComponent() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">PostgreSQL</CardTitle>
|
<CardTitle className="text-base">PostgreSQL</CardTitle>
|
||||||
<CardDescription>Статус соединения и пул</CardDescription>
|
<CardDescription>
|
||||||
|
Статус соединения и пул. PostgreSQL отображается в readiness-проверке на вкладке «Система»
|
||||||
|
(check <code className="text-xs">postgres</code>).
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
|
||||||
<Alert>
|
|
||||||
<Database className="size-4" />
|
|
||||||
<AlertTitle>Статус готовности</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
PostgreSQL-соединение отображается в readiness-проверке на вкладке «Система» (check{' '}
|
|
||||||
<code className="text-xs">postgres</code>).
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
@@ -284,18 +260,11 @@ function MonitoringComponent() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-base">Файловые логи</CardTitle>
|
<CardTitle className="text-base">Файловые логи</CardTitle>
|
||||||
<CardDescription>Логи API и pipeline</CardDescription>
|
<CardDescription>
|
||||||
|
Логи API и pipeline настраиваются переменной <code className="text-xs">EVOBGP_LOG_*</code> и
|
||||||
|
управляются в tenant-settings.
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
|
||||||
<Alert>
|
|
||||||
<Info className="size-4" />
|
|
||||||
<AlertTitle>Логи на сервере</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Файловые логи настраиваются переменной <code className="text-xs">EVOBGP_LOG_*</code> и
|
|
||||||
управляются tenant-settings на странице «Настройки BIRD».
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</BadgeTabs>
|
</BadgeTabs>
|
||||||
@@ -317,18 +286,6 @@ function formatVersion(version?: VersionInfo | null): string {
|
|||||||
return version.version ?? version.app ?? '—'
|
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 }) {
|
function BirdSummary({ bird }: { bird: import('@/types/api').BirdStatus }) {
|
||||||
if (!bird.birdc_configured) {
|
if (!bird.birdc_configured) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
||||||
import { Info, RefreshCw } from 'lucide-react'
|
import { RefreshCw } from 'lucide-react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DashboardNetworkCapacityCard,
|
DashboardNetworkCapacityCard,
|
||||||
@@ -60,14 +59,6 @@ function NetworkComponent() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>О сетевой конфигурации</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Вкладка «Обзор» — live-статус agent и BGP на CP и репликах. Apply и ревизии — на странице «Операции».
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<BadgeTabs
|
<BadgeTabs
|
||||||
value={search.tab}
|
value={search.tab}
|
||||||
onValueChange={(tab) =>
|
onValueChange={(tab) =>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { Info, RefreshCw } from 'lucide-react'
|
import { RefreshCw } from 'lucide-react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { useState, useMemo } from 'react'
|
import { useState, useMemo } from 'react'
|
||||||
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
||||||
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
|
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
|
||||||
@@ -94,15 +93,6 @@ function OperationsComponent() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>Три раздела на одной странице</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
<strong>Ревизии</strong> — история конфигов и откат; <strong>Сравнение</strong> — diff
|
|
||||||
префиксов; <strong>Задачи</strong> — ingest, apply, rollback.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
trigger={
|
trigger={
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { AlertTriangle, Clock, Info, ListTodo, RefreshCw } from 'lucide-react'
|
import { AlertTriangle, Clock, ListTodo, RefreshCw } from 'lucide-react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
|
import { BadgeTabs, TabsContent } from '@/components/badge-tabs'
|
||||||
import { DataGridCard } from '@/components/data-grid-shell'
|
import { DataGridCard } from '@/components/data-grid-shell'
|
||||||
@@ -87,16 +86,6 @@ function ScheduleComponent() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>Как работает расписание</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Планировщик использует <code className="text-xs">refresh_interval_sec</code> и опционально{' '}
|
|
||||||
<code className="text-xs">cron_expr</code>. Ручной запуск —{' '}
|
|
||||||
<code className="text-xs">POST /v1/modules/{id}/refresh</code>.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
{loading ? <SectionCardsSkeleton count={3} /> : <SectionCards items={items} />}
|
{loading ? <SectionCardsSkeleton count={3} /> : <SectionCards items={items} />}
|
||||||
|
|
||||||
<DataGridCard
|
<DataGridCard
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert'
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
||||||
import { Input } from '@evobgp/ui/components/input'
|
import { Input } from '@evobgp/ui/components/input'
|
||||||
@@ -13,7 +12,7 @@ import { SelectField } from '@/components/select-field'
|
|||||||
import { DEV_API_TOKEN, normalizeApiToken, setToken, TOKEN_STORAGE_KEY } from '@/lib/api-client'
|
import { DEV_API_TOKEN, normalizeApiToken, setToken, TOKEN_STORAGE_KEY } from '@/lib/api-client'
|
||||||
import { authKeys, authSessionQueryOptions } from '@/queries/auth'
|
import { authKeys, authSessionQueryOptions } from '@/queries/auth'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { Info, Save } from 'lucide-react'
|
import { Save } from 'lucide-react'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
@@ -70,16 +69,6 @@ function SettingsComponent() {
|
|||||||
description="Параметры интерфейса и подключения браузера к API."
|
description="Параметры интерфейса и подключения браузера к API."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>Локальная разработка</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
При включённом demo-seed API принимает токен <code className="text-xs">dev</code> (роль{' '}
|
|
||||||
<code className="text-xs">operator</code>). Вводите только значение токена, без префикса{' '}
|
|
||||||
<code className="text-xs">Bearer</code> — он добавляется автоматически.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Подключение к API</CardTitle>
|
<CardTitle>Подключение к API</CardTitle>
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
import { createFileRoute, useSearch } from '@tanstack/react-router'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
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 { useEffect, useState } from 'react'
|
||||||
import { toast } from 'sonner'
|
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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@evobgp/ui/components/card'
|
||||||
import { Input } from '@evobgp/ui/components/input'
|
import { Input } from '@evobgp/ui/components/input'
|
||||||
import { Label } from '@evobgp/ui/components/label'
|
import { Label } from '@evobgp/ui/components/label'
|
||||||
@@ -104,15 +103,6 @@ function TenantSettingsComponent() {
|
|||||||
description="Параметры control plane для текущего tenant (API /v1/settings)"
|
description="Параметры control plane для текущего tenant (API /v1/settings)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>Operator-only</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Изменение значений через <code className="text-xs">PATCH /v1/settings</code> требует роли
|
|
||||||
operator. При отсутствии прав API вернёт 403.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<BadgeTabs
|
<BadgeTabs
|
||||||
value={search.tab}
|
value={search.tab}
|
||||||
onValueChange={(tab) =>
|
onValueChange={(tab) =>
|
||||||
@@ -137,14 +127,6 @@ function TenantSettingsComponent() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<Alert className="border-info/30 bg-info/5">
|
|
||||||
<Info className="text-info" />
|
|
||||||
<AlertTitle>Подстановка в конфиг</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Значения используются при генерации BIRD-конфигурации (router id, local AS, адреса).
|
|
||||||
Пиры и спикеры настраиваются в разделе «Сеть».
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<QueryState
|
<QueryState
|
||||||
data={partitioned}
|
data={partitioned}
|
||||||
isLoading={settingsQ.isLoading}
|
isLoading={settingsQ.isLoading}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user