feat: enhance dashboard and monitoring components with new analytics skeletons
CI / changes (push) Successful in 11s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 49s
CI / go (push) Successful in 1m2s
CI / bird2 (push) Successful in 16s
CI / release (push) Successful in 3m57s
CI / changes (push) Successful in 11s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 49s
CI / go (push) Successful in 1m2s
CI / bird2 (push) Successful in 16s
CI / release (push) Successful in 3m57s
Added the AnalyticsDashboardSkeleton component to improve loading states in the dashboard and monitoring pages. Refactored the dashboard to utilize the new skeleton for initial loading, replacing the previous SectionCardsSkeleton. Updated the overview queries to increase job limit from 10 to 100 for better data handling. Enhanced the network and operations components to incorporate new analytics cards, streamlining the user experience and improving data presentation.
This commit is contained in:
@@ -1,40 +1,31 @@
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useQueries } from '@tanstack/react-query'
|
||||
import {
|
||||
Boxes,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
GitBranch,
|
||||
Info,
|
||||
Radio,
|
||||
RefreshCw,
|
||||
Activity,
|
||||
XCircle,
|
||||
} from 'lucide-react'
|
||||
import { CheckCircle, Info, RefreshCw, XCircle } 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,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@evobgp/ui/components/card'
|
||||
import { Skeleton } from '@evobgp/ui/components/skeleton'
|
||||
|
||||
import {
|
||||
DashboardNetworkCapacityCard,
|
||||
DashboardOperationsFlowCard,
|
||||
DashboardPlatformCard,
|
||||
} from '@/components/analytics'
|
||||
import { DataGridCard } from '@/components/data-grid-shell'
|
||||
import { DashboardNetworkPanel } from '@/components/dashboard/dashboard-network-panel'
|
||||
import { DashboardQuickActions } from '@/components/dashboard/dashboard-quick-actions'
|
||||
import { DashboardRecentJobsGrid } from '@/components/dashboard/dashboard-recent-jobs-grid'
|
||||
import { DashboardRecentRevisionsGrid } from '@/components/dashboard/dashboard-recent-revisions-grid'
|
||||
import { PageHeader } from '@/components/page-header'
|
||||
import { SectionCards, type SectionCardItem } from '@/components/section-cards'
|
||||
import { SectionCardsSkeleton } from '@/components/skeletons'
|
||||
import { AnalyticsDashboardSkeleton } from '@/components/skeletons'
|
||||
|
||||
import {
|
||||
aggregateNetworkMetrics,
|
||||
moduleNameById,
|
||||
overviewHealthQueryOptions,
|
||||
overviewJobsQueryOptions,
|
||||
@@ -42,7 +33,6 @@ import {
|
||||
overviewPeersQueryOptions,
|
||||
overviewRevisionsQueryOptions,
|
||||
overviewSpeakersQueryOptions,
|
||||
runningJobCount,
|
||||
} from '@/queries/overview'
|
||||
|
||||
export const Route = createFileRoute('/_auth/dashboard')({
|
||||
@@ -50,7 +40,6 @@ export const Route = createFileRoute('/_auth/dashboard')({
|
||||
})
|
||||
|
||||
function DashboardComponent() {
|
||||
const navigate = useNavigate()
|
||||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
|
||||
|
||||
const results = useQueries({
|
||||
@@ -83,60 +72,10 @@ function DashboardComponent() {
|
||||
const speakers = speakersQ.data?.items ?? []
|
||||
const revisions = revisionsQ.data?.items ?? []
|
||||
const jobs = jobsQ.data?.items ?? []
|
||||
const modulesHasMore = modulesQ.data?.has_more ?? false
|
||||
const peersHasMore = peersQ.data?.has_more ?? false
|
||||
const speakersHasMore = speakersQ.data?.has_more ?? false
|
||||
const revisionsHasMore = revisionsQ.data?.has_more ?? false
|
||||
|
||||
const net = aggregateNetworkMetrics(peers, speakers)
|
||||
const running = runningJobCount(jobs)
|
||||
const nameById = moduleNameById(modules)
|
||||
|
||||
const countBadge = (n: number, hasMore: boolean, suffix: string) => (hasMore ? '200+' : suffix)
|
||||
|
||||
const items: SectionCardItem[] = [
|
||||
{
|
||||
label: 'Модули',
|
||||
value: initialLoading ? '—' : String(modules.length),
|
||||
icon: <Boxes className="size-4" />,
|
||||
hint: countBadge(modules.length, modulesHasMore, 'AS, CDN, домены, IP'),
|
||||
onClick: () => navigate({ to: '/modules' }),
|
||||
},
|
||||
{
|
||||
label: 'Пиры',
|
||||
value: initialLoading ? '—' : `${net.peersEstablished}/${net.peersEnabled}`,
|
||||
icon: <GitBranch className="size-4" />,
|
||||
hint: countBadge(peers.length, peersHasMore, 'Established / включённых'),
|
||||
badge: net.peersMismatch > 0 ? `mismatch ${net.peersMismatch}` : undefined,
|
||||
variant: net.peersMismatch > 0 ? 'warning' : 'default',
|
||||
onClick: () => navigate({ to: '/network', search: { tab: 'peers' } }),
|
||||
},
|
||||
{
|
||||
label: 'Спикеры',
|
||||
value: initialLoading ? '—' : `${net.speakersOnline}/${net.speakersTotal}`,
|
||||
icon: <Radio className="size-4" />,
|
||||
hint: countBadge(speakers.length, speakersHasMore, 'online / всего'),
|
||||
variant: net.speakersOnline < net.speakersTotal ? 'warning' : 'default',
|
||||
onClick: () => navigate({ to: '/network', search: { tab: 'overview' } }),
|
||||
},
|
||||
{
|
||||
label: 'Ревизии',
|
||||
value: initialLoading ? '—' : String(revisions.length),
|
||||
icon: <Activity className="size-4" />,
|
||||
hint: countBadge(revisions.length, revisionsHasMore, 'configs'),
|
||||
onClick: () => navigate({ to: '/operations', search: { tab: 'revisions' } }),
|
||||
},
|
||||
{
|
||||
label: 'Активных задач',
|
||||
value: initialLoading ? '—' : String(running),
|
||||
icon: <Clock className="size-4" />,
|
||||
hint: 'queued и running',
|
||||
onClick: () => navigate({ to: '/operations', search: { tab: 'jobs' } }),
|
||||
},
|
||||
]
|
||||
|
||||
const activityLoading =
|
||||
refreshing && jobs.length === 0 && revisions.length === 0 && peers.length === 0 && speakers.length === 0
|
||||
refreshing && jobs.length === 0 && revisions.length === 0
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5">
|
||||
@@ -170,9 +109,25 @@ function DashboardComponent() {
|
||||
loadError={modulesQ.isError || peersQ.isError ? 'Некоторые данные не загружены' : null}
|
||||
/>
|
||||
|
||||
{initialLoading ? <SectionCardsSkeleton count={5} /> : <SectionCards items={items} className="gap-3" />}
|
||||
{initialLoading ? (
|
||||
<AnalyticsDashboardSkeleton />
|
||||
) : (
|
||||
<div className="grid gap-4 lg:grid-cols-3 lg:grid-rows-2">
|
||||
<div className="lg:row-span-2">
|
||||
<DashboardPlatformCard
|
||||
modules={modules}
|
||||
peers={peers}
|
||||
speakers={speakers}
|
||||
jobs={jobs}
|
||||
revisions={revisions}
|
||||
/>
|
||||
</div>
|
||||
<DashboardNetworkCapacityCard peers={peers} speakers={speakers} jobs={jobs} />
|
||||
<DashboardOperationsFlowCard jobs={jobs} modules={modules} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-3">
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
<DataGridCard
|
||||
title="Недавние задачи"
|
||||
description="Последние фоновые операции"
|
||||
@@ -181,7 +136,7 @@ function DashboardComponent() {
|
||||
{activityLoading ? (
|
||||
<Skeleton className="m-3 h-24 w-auto" />
|
||||
) : (
|
||||
<DashboardRecentJobsGrid jobs={jobs} nameById={nameById} isLoading={refreshing} />
|
||||
<DashboardRecentJobsGrid jobs={jobs.slice(0, 10)} nameById={nameById} isLoading={refreshing} />
|
||||
)}
|
||||
</DataGridCard>
|
||||
|
||||
@@ -196,20 +151,6 @@ function DashboardComponent() {
|
||||
<DashboardRecentRevisionsGrid revisions={revisions} isLoading={refreshing} />
|
||||
)}
|
||||
</DataGridCard>
|
||||
|
||||
<Card className="h-full gap-0">
|
||||
<CardHeader className="border-b py-3">
|
||||
<CardTitle className="text-base">Состояние сети</CardTitle>
|
||||
<CardDescription>BGP-сессии и спикеры</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
{activityLoading ? (
|
||||
<Skeleton className="m-3 h-24 w-auto" />
|
||||
) : (
|
||||
<DashboardNetworkPanel peers={peers} speakers={speakers} />
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="gap-0">
|
||||
|
||||
Reference in New Issue
Block a user