quality / changes (push) Successful in 8s
quality / go (push) Skipped
quality / bird2 (push) Skipped
quality / commitlint (push) Skipped
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 26s
quality / web (push) Successful in 1m24s
CD / quality (push) Successful in 2m0s
CD / publish (push) Successful in 3m7s
Панель переписана по доске BgpDashboard: действия в шапке, KPI-полоса, вкладка «Обзор», быстрые действия карточками, график префиксов по последним ревизиям, таблица модулей и лента активности. Графики загрузки BGP и потока операций убраны с панели — они живут в «Сети» и «Мониторинге». Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
56 lines
2.9 KiB
TypeScript
56 lines
2.9 KiB
TypeScript
import { useMemo } from 'react'
|
|
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
|
|
|
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from '@evobgp/ui/components/chart'
|
|
import { Frame, FrameDescription, FrameHeader, FramePanel, FrameTitle } from '@/components/reui/frame'
|
|
import { fmtWhen } from '@/components/portal/format'
|
|
import type { RevisionRow } from '@/types/api'
|
|
|
|
const chartConfig = {
|
|
prefixes: { label: 'Префиксов', color: 'var(--chart-1)' },
|
|
} satisfies ChartConfig
|
|
|
|
/** Префиксов в опубликованной конфигурации по последним ревизиям (доска «Панель»). */
|
|
export function DashboardPrefixTrend({ revisions, loading }: { revisions: RevisionRow[]; loading?: boolean }) {
|
|
const data = useMemo(
|
|
() =>
|
|
[...revisions]
|
|
.sort((a, b) => a.created_at.localeCompare(b.created_at))
|
|
.slice(-12)
|
|
.map((r) => ({ id: r.id, when: fmtWhen(r.created_at), prefixes: r.materialized_prefix_count })),
|
|
[revisions],
|
|
)
|
|
|
|
return (
|
|
<Frame className="[--frame-panel-header-py-adjust:4px] w-full min-w-0">
|
|
<FrameHeader className="flex-col items-start gap-0.5">
|
|
<FrameTitle>Префиксов в опубликованной конфигурации</FrameTitle>
|
|
<FrameDescription className="text-pretty">Последние {data.length || 12} ревизий</FrameDescription>
|
|
</FrameHeader>
|
|
<FramePanel className="p-3">
|
|
{data.length < 2 ? (
|
|
<div className="text-muted-foreground flex h-48 items-center justify-center text-sm">
|
|
{loading ? 'Загрузка…' : 'Недостаточно ревизий для графика'}
|
|
</div>
|
|
) : (
|
|
<ChartContainer config={chartConfig} className="aspect-auto h-64 w-full">
|
|
<AreaChart data={data} margin={{ left: 4, right: 12, top: 8 }}>
|
|
<defs>
|
|
<linearGradient id="prefix-fill" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="5%" stopColor="var(--color-prefixes)" stopOpacity={0.35} />
|
|
<stop offset="95%" stopColor="var(--color-prefixes)" stopOpacity={0.02} />
|
|
</linearGradient>
|
|
</defs>
|
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
<XAxis dataKey="id" tickLine={false} axisLine={false} tickMargin={8} minTickGap={24} />
|
|
<YAxis domain={([min, max]) => [Math.floor(min * 0.98), Math.ceil(max * 1.01)]} tickLine={false} axisLine={false} width={48} tickFormatter={(v: number) => v.toLocaleString('ru-RU')} />
|
|
<ChartTooltip content={<ChartTooltipContent labelFormatter={(_, p) => p?.[0]?.payload?.when ?? ''} />} />
|
|
<Area dataKey="prefixes" type="monotone" stroke="var(--color-prefixes)" strokeWidth={2} fill="url(#prefix-fill)" />
|
|
</AreaChart>
|
|
</ChartContainer>
|
|
)}
|
|
</FramePanel>
|
|
</Frame>
|
|
)
|
|
}
|