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.
146 lines
4.9 KiB
TypeScript
146 lines
4.9 KiB
TypeScript
import { useNavigate } from '@tanstack/react-router'
|
|
import { useMemo } from 'react'
|
|
|
|
import { Button } from '@evobgp/ui/components/button'
|
|
|
|
import { AnalyticsActivityList } from '@/components/analytics/analytics-activity-list'
|
|
import { AnalyticsCardShell } from '@/components/analytics/analytics-card-shell'
|
|
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'
|
|
import type { JobRow, ModuleRow, PeerRow, RevisionRow, SpeakerRow } from '@/types/api'
|
|
|
|
export function DashboardPlatformCard({
|
|
modules,
|
|
peers,
|
|
speakers,
|
|
jobs,
|
|
revisions,
|
|
loading,
|
|
}: {
|
|
modules: ModuleRow[]
|
|
peers: PeerRow[]
|
|
speakers: SpeakerRow[]
|
|
jobs: JobRow[]
|
|
revisions: RevisionRow[]
|
|
loading?: boolean
|
|
}) {
|
|
const navigate = useNavigate()
|
|
|
|
const enabledModules = modules.filter((m) => m.enabled !== false).length
|
|
const peersEnabled = peers.filter((p) => p.enabled !== false).length
|
|
const peersEstablished = peers.filter(
|
|
(p) => p.enabled !== false && p.session_state === 'Established',
|
|
).length
|
|
const peersMismatch = peers.filter((p) => p.session_mismatch).length
|
|
const speakersOnline = speakers.filter((s) => s.live?.agent_ok).length
|
|
const failedJobs = jobs.filter((j) =>
|
|
['failed', 'error', 'cancelled'].includes(j.status.toLowerCase()),
|
|
).length
|
|
const running = runningJobCount(jobs)
|
|
const riskCount = peersMismatch + failedJobs + Math.max(0, speakers.length - speakersOnline)
|
|
|
|
const bgpPct =
|
|
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],
|
|
)
|
|
|
|
const kpis = [
|
|
{
|
|
label: 'Модули активны',
|
|
value: loading ? '—' : `${enabledModules}/${modules.length || 0}`,
|
|
delta: {
|
|
direction: 'neutral' as const,
|
|
label: `${modules.length} всего`,
|
|
tone: 'muted' as const,
|
|
},
|
|
},
|
|
{
|
|
label: 'BGP готовность',
|
|
value: loading || bgpPct === null ? '—' : `${bgpPct}%`,
|
|
delta: {
|
|
direction: (bgpPct !== null && bgpPct >= 90 ? 'up' : bgpPct !== null && bgpPct < 70 ? 'down' : 'neutral') as
|
|
| 'up'
|
|
| 'down'
|
|
| 'neutral',
|
|
label:
|
|
bgpPct === null
|
|
? 'нет включённых пиров'
|
|
: `${peersEstablished} установлено`,
|
|
tone: (bgpPct !== null && bgpPct >= 90
|
|
? 'success'
|
|
: bgpPct !== null && bgpPct < 70
|
|
? 'warning'
|
|
: 'muted') as 'success' | 'warning' | 'muted',
|
|
},
|
|
},
|
|
{
|
|
label: 'Риски',
|
|
value: loading ? '—' : String(riskCount),
|
|
delta: {
|
|
direction: (riskCount > 0 ? 'down' : 'up') as 'up' | 'down',
|
|
label: riskCount > 0 ? `${failedJobs} задач, ${peersMismatch} расхождений` : 'в норме',
|
|
tone: (riskCount > 0 ? 'destructive' : 'success') as 'destructive' | 'success',
|
|
},
|
|
},
|
|
]
|
|
|
|
const progressLabel = deployMeta.label
|
|
const progressHint = deployMeta.hint
|
|
|
|
return (
|
|
<AnalyticsCardShell
|
|
title="Состояние платформы"
|
|
description="Сводка модулей, BGP и фоновых задач"
|
|
info="Актуальный снимок без исторических трендов. Обновите данные кнопкой «Обновить» на странице."
|
|
footer={
|
|
<>
|
|
<Button
|
|
variant="outline"
|
|
className="flex-1"
|
|
onClick={() => navigate({ to: '/schedule' })}
|
|
>
|
|
Расписание
|
|
</Button>
|
|
<Button
|
|
className="flex-1"
|
|
onClick={() => navigate({ to: '/monitoring', search: { tab: 'system' } })}
|
|
>
|
|
Мониторинг
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
<AnalyticsKpiRow items={kpis} />
|
|
<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>
|
|
{!loading ? (
|
|
<span className="text-xs text-muted-foreground">{running} активных задач</span>
|
|
) : null}
|
|
</div>
|
|
{loading ? (
|
|
<p className="text-sm text-muted-foreground">Загрузка…</p>
|
|
) : (
|
|
<AnalyticsActivityList items={activity} />
|
|
)}
|
|
</div>
|
|
</AnalyticsCardShell>
|
|
)
|
|
}
|