refactor: enhance UI components with PanelCard integration and improved layout consistency
CI / changes (push) Successful in 13s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 1m6s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m17s
CI / changes (push) Successful in 13s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 1m6s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 4m17s
Updated multiple components to utilize the new PanelCard for better organization and presentation of content. Refactored DataGridShell, DataGridCard, and various analytics components to streamline layouts and enhance user experience. Adjusted styles for consistency across components, including pagination and toolbar elements, ensuring a cohesive interface throughout the application.
This commit is contained in:
@@ -1,14 +1,6 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { Info } from 'lucide-react'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@evobgp/ui/components/card'
|
||||
import { cn } from '@evobgp/ui/lib/utils'
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -16,6 +8,8 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@evobgp/ui/components/tooltip'
|
||||
|
||||
import { PanelCard } from '@/components/panel-card'
|
||||
|
||||
export function AnalyticsCardShell({
|
||||
title,
|
||||
description,
|
||||
@@ -33,32 +27,36 @@ export function AnalyticsCardShell({
|
||||
className?: string
|
||||
children: ReactNode
|
||||
}) {
|
||||
const titleNode = (
|
||||
<span className="flex items-center gap-2">
|
||||
{title}
|
||||
{info ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
className="inline-flex text-muted-foreground transition-colors hover:text-foreground"
|
||||
aria-label="Подробнее"
|
||||
>
|
||||
<Info className="size-3.5" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs text-xs">
|
||||
{info}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<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">
|
||||
<div className="min-w-0 space-y-1">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
{title}
|
||||
{info ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
className="inline-flex text-muted-foreground transition-colors hover:text-foreground"
|
||||
aria-label="Подробнее"
|
||||
>
|
||||
<Info className="size-3.5" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs text-xs">
|
||||
{info}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</CardTitle>
|
||||
{description ? <CardDescription>{description}</CardDescription> : null}
|
||||
</div>
|
||||
{actions ? <div className="shrink-0">{actions}</div> : null}
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-5 p-5">{children}</CardContent>
|
||||
{footer ? <CardFooter className="gap-2 border-t p-4">{footer}</CardFooter> : null}
|
||||
</Card>
|
||||
<PanelCard
|
||||
title={titleNode}
|
||||
description={description}
|
||||
actions={actions}
|
||||
footer={footer}
|
||||
className={cn('overflow-hidden', className)}
|
||||
contentClassName="flex flex-col gap-4 py-4"
|
||||
footerClassName={footer ? 'gap-2 p-3' : undefined}
|
||||
>
|
||||
{children}
|
||||
</PanelCard>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ import { cn } from '@evobgp/ui/lib/utils'
|
||||
|
||||
export function AnalyticsProgress({
|
||||
label,
|
||||
hint,
|
||||
value,
|
||||
className,
|
||||
}: {
|
||||
label: string
|
||||
hint?: string
|
||||
value: number
|
||||
className?: string
|
||||
}) {
|
||||
@@ -21,9 +23,10 @@ export function AnalyticsProgress({
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
<span className="font-medium tabular-nums">{clamped}%</span>
|
||||
</div>
|
||||
<Progress value={clamped} className="gap-0">
|
||||
{hint ? <p className="text-xs leading-snug text-muted-foreground">{hint}</p> : null}
|
||||
<Progress value={clamped} className="w-full gap-0">
|
||||
<ProgressTrack className="h-2">
|
||||
<ProgressIndicator className="bg-foreground" />
|
||||
<ProgressIndicator />
|
||||
</ProgressTrack>
|
||||
</Progress>
|
||||
</div>
|
||||
|
||||
@@ -50,14 +50,14 @@ export function DashboardNetworkCapacityCard({
|
||||
|
||||
const deltaLabel =
|
||||
mode === 'peers'
|
||||
? `${peers.filter((p) => p.enabled !== false && p.session_state === 'Established').length} Established`
|
||||
: `${speakers.filter((s) => s.live?.agent_ok).length} online`
|
||||
? `${peers.filter((p) => p.enabled !== false && p.session_state === 'Established').length} установлено`
|
||||
: `${speakers.filter((s) => s.live?.agent_ok).length} в сети`
|
||||
|
||||
return (
|
||||
<AnalyticsCardShell
|
||||
title="Загрузка BGP"
|
||||
description="Текущая утилизация сессий по пирам и спикерам"
|
||||
info="Каждый столбец — enabled peer или speaker. Высота отражает Established/online."
|
||||
info="Каждый столбец — включённый пир или спикер. Высота отражает установленную сессию или доступность."
|
||||
actions={
|
||||
<AnalyticsSegmentControl
|
||||
value={mode}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AnalyticsKpiRow } from '@/components/analytics/analytics-kpi-row'
|
||||
import { AnalyticsProgress } from '@/components/analytics/analytics-progress'
|
||||
import {
|
||||
deploymentProgress,
|
||||
deploymentProgressMeta,
|
||||
recentPlatformActivity,
|
||||
} from '@/lib/metrics'
|
||||
import { runningJobCount } from '@/queries/overview'
|
||||
@@ -48,6 +49,7 @@ export function DashboardPlatformCard({
|
||||
peersEnabled > 0 ? Math.round((peersEstablished / peersEnabled) * 100) : null
|
||||
|
||||
const deploy = useMemo(() => deploymentProgress(speakers), [speakers])
|
||||
const deployMeta = useMemo(() => deploymentProgressMeta(deploy), [deploy])
|
||||
const activity = useMemo(
|
||||
() => recentPlatformActivity(jobs, revisions, peers, speakers),
|
||||
[jobs, revisions, peers, speakers],
|
||||
@@ -74,7 +76,7 @@ export function DashboardPlatformCard({
|
||||
label:
|
||||
bgpPct === null
|
||||
? 'нет включённых пиров'
|
||||
: `${peersEstablished} Established`,
|
||||
: `${peersEstablished} установлено`,
|
||||
tone: (bgpPct !== null && bgpPct >= 90
|
||||
? 'success'
|
||||
: bgpPct !== null && bgpPct < 70
|
||||
@@ -87,16 +89,14 @@ export function DashboardPlatformCard({
|
||||
value: loading ? '—' : String(riskCount),
|
||||
delta: {
|
||||
direction: (riskCount > 0 ? 'down' : 'up') as 'up' | 'down',
|
||||
label: riskCount > 0 ? `${failedJobs} задач, ${peersMismatch} mismatch` : 'в норме',
|
||||
label: riskCount > 0 ? `${failedJobs} задач, ${peersMismatch} расхождений` : 'в норме',
|
||||
tone: (riskCount > 0 ? 'destructive' : 'success') as 'destructive' | 'success',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const progressLabel =
|
||||
deploy.mode === 'revision'
|
||||
? `Синхронизация ревизий (${deploy.synced}/${deploy.total})`
|
||||
: `Спикеры online (${deploy.synced}/${deploy.total})`
|
||||
const progressLabel = deployMeta.label
|
||||
const progressHint = deployMeta.hint
|
||||
|
||||
return (
|
||||
<AnalyticsCardShell
|
||||
@@ -122,7 +122,11 @@ export function DashboardPlatformCard({
|
||||
}
|
||||
>
|
||||
<AnalyticsKpiRow items={kpis} />
|
||||
<AnalyticsProgress label={progressLabel} value={loading ? 0 : deploy.percent} />
|
||||
<AnalyticsProgress
|
||||
label={progressLabel}
|
||||
hint={progressHint}
|
||||
value={loading ? 0 : deploy.percent}
|
||||
/>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">Недавняя активность</span>
|
||||
|
||||
@@ -18,15 +18,15 @@ export function MonitoringHealthCard({
|
||||
return (
|
||||
<AnalyticsCardShell
|
||||
title="Доступность системы"
|
||||
description="Health и readiness checks"
|
||||
info="Donut отражает результат GET /v1/health и checks из GET /v1/ready."
|
||||
description="Проверки живучести и готовности"
|
||||
info="Диаграмма отражает результат GET /v1/health и проверок из GET /v1/ready."
|
||||
>
|
||||
{loading ? (
|
||||
<div className="flex h-48 items-center justify-center text-sm text-muted-foreground">
|
||||
Загрузка…
|
||||
</div>
|
||||
) : (
|
||||
<ChartDonutMetric slices={slices} centerLabel="Checks" centerValue={total} />
|
||||
<ChartDonutMetric slices={slices} centerLabel="Проверки" centerValue={total} />
|
||||
)}
|
||||
</AnalyticsCardShell>
|
||||
)
|
||||
|
||||
@@ -21,29 +21,29 @@ export function NetworkOverviewAnalyticsCard({
|
||||
return (
|
||||
<AnalyticsCardShell
|
||||
title="Сводка BGP"
|
||||
description="Established, online и mismatch по live-данным"
|
||||
description="Установленные сессии, доступность спикеров и расхождения по live-данным"
|
||||
info="Снимок текущего состояния пиров и спикеров."
|
||||
>
|
||||
<AnalyticsKpiRow
|
||||
items={[
|
||||
{
|
||||
label: 'Пиры Established',
|
||||
label: 'Пиры с установленной сессией',
|
||||
value: loading ? '—' : `${net.peersEstablished}/${net.peersEnabled}`,
|
||||
delta: {
|
||||
direction: net.peersMismatch > 0 ? 'down' : 'up',
|
||||
label: net.peersMismatch > 0 ? `${net.peersMismatch} mismatch` : 'сессии в норме',
|
||||
label: net.peersMismatch > 0 ? `${net.peersMismatch} расхождений` : 'сессии в норме',
|
||||
tone: net.peersMismatch > 0 ? 'warning' : 'success',
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Спикеры online',
|
||||
label: 'Спикеры в сети',
|
||||
value: loading ? '—' : `${net.speakersOnline}/${net.speakersTotal}`,
|
||||
delta: {
|
||||
direction: net.speakersOnline < net.speakersTotal ? 'down' : 'up',
|
||||
label:
|
||||
net.speakersOnline < net.speakersTotal
|
||||
? `${net.speakersTotal - net.speakersOnline} offline`
|
||||
: 'все online',
|
||||
? `${net.speakersTotal - net.speakersOnline} не в сети`
|
||||
: 'все в сети',
|
||||
tone: net.speakersOnline < net.speakersTotal ? 'warning' : 'success',
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user