Files
EvoBGP/web/src/lib/components/overview/OverviewNetworkStatusCard.svelte
T
Denozordec a1ada06a76
CI / changes (push) Successful in 9s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 24s
CI / web (push) Successful in 29s
CI / go (push) Successful in 43s
CI / bird2 (push) Successful in 15s
CI / release (push) Successful in 3m29s
feat(api): add live status tracking for speakers and BGP sessions
- Introduced new schemas for `SpeakerLiveStatus`, `BgpSessionLive`, and `LiveSpeakerPoll` in OpenAPI documentation to support live status queries.
- Enhanced the `/v1/speakers` endpoint to include a `live` query parameter, allowing retrieval of real-time speaker and BGP status.
- Updated the HTTP API to collect and return live status data for speakers, improving monitoring capabilities.
- Modified frontend components to display live status information, enhancing user visibility into speaker health and BGP session states.
- Added a new endpoint `/v1/bird/status` for retrieving the local BIRD status, further enriching the network monitoring features.
2026-05-21 17:36:50 +07:00

121 lines
4.0 KiB
Svelte

<script lang="ts">
import { resolve } from '$app/paths';
import type { PeerRow, SpeakerRow } from '$lib/api/types.js';
import {
aggregateNetworkMetrics,
collectNetworkIssues,
deriveNetworkOverallStatus,
networkOverallStatusHint,
networkOverallStatusLabel
} from '$lib/network/network-metrics.js';
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
import { Button } from '$lib/ui/core/button/index.js';
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription
} from '$lib/ui/core/card/index.js';
import CheckCircle from '@lucide/svelte/icons/check-circle';
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
import XCircle from '@lucide/svelte/icons/x-circle';
import ArrowRight from '@lucide/svelte/icons/arrow-right';
import NetworkIcon from '@lucide/svelte/icons/network';
type Props = {
peers: PeerRow[];
speakers: SpeakerRow[];
loading?: boolean;
initialLoading?: boolean;
error?: string | null;
};
let { peers, speakers, loading = false, initialLoading = false, error = null }: Props = $props();
const metrics = $derived(aggregateNetworkMetrics(peers, speakers));
const overallStatus = $derived(deriveNetworkOverallStatus(metrics));
const overallHint = $derived(networkOverallStatusHint(overallStatus, metrics));
const issues = $derived(collectNetworkIssues(peers, speakers, 3));
</script>
<Card>
<CardHeader
class="flex flex-col gap-3 border-b py-3 sm:flex-row sm:items-center sm:justify-between"
>
<div class="min-w-0 flex-1">
<CardTitle class="flex items-center gap-2 text-base">
<NetworkIcon class="size-4" />
Сеть (BGP)
</CardTitle>
<CardDescription>Live-статус пиров и спикеров</CardDescription>
</div>
<Button variant="outline" size="sm" href={resolve('/network?tab=overview')}>
Подробнее
<ArrowRight class="size-3.5" />
</Button>
</CardHeader>
<CardContent class="space-y-3 p-4 pt-4">
{#if error}
<p class="text-sm text-destructive">{error}</p>
{:else if initialLoading || loading}
<p class="text-sm text-muted-foreground">Загрузка live-метрик…</p>
{:else if overallStatus === 'ok'}
<Alert class="border-success/30 bg-success/5 py-3">
<CheckCircle class="text-success" />
<AlertTitle class="text-sm">{networkOverallStatusLabel(overallStatus)}</AlertTitle>
<AlertDescription class="text-xs">{overallHint}</AlertDescription>
</Alert>
{:else if overallStatus === 'warn'}
<Alert class="border-warning/30 bg-warning/5 py-3">
<AlertTriangle class="text-warning" />
<AlertTitle class="text-sm">{networkOverallStatusLabel(overallStatus)}</AlertTitle>
<AlertDescription class="text-xs">
{overallHint}
{#if issues.length > 0}
<ul class="mt-2 list-inside list-disc">
{#each issues as issue (issue.id)}
<li>{issue.message}</li>
{/each}
</ul>
{/if}
</AlertDescription>
</Alert>
{:else}
<Alert variant="destructive" class="py-3">
<XCircle />
<AlertTitle class="text-sm">{networkOverallStatusLabel(overallStatus)}</AlertTitle>
<AlertDescription class="text-xs">
{overallHint}
{#if issues.length > 0}
<ul class="mt-2 list-inside list-disc">
{#each issues as issue (issue.id)}
<li>{issue.message}</li>
{/each}
</ul>
{/if}
</AlertDescription>
</Alert>
{/if}
<div class="flex flex-wrap gap-4 text-sm">
<div>
<p class="text-muted-foreground">Пиры Established</p>
<p class="text-xl font-bold tabular-nums">
{initialLoading ? '—' : `${metrics.peersEstablished}/${metrics.peersEnabled}`}
</p>
</div>
<div>
<p class="text-muted-foreground">Спикеры online</p>
<p class="text-xl font-bold tabular-nums">
{initialLoading ? '—' : `${metrics.speakersOnline}/${metrics.speakersTotal}`}
</p>
</div>
<div>
<p class="text-muted-foreground">Drift</p>
<p class="text-xl font-bold tabular-nums">{initialLoading ? '—' : metrics.speakersDrift}</p>
</div>
</div>
</CardContent>
</Card>