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
- 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.
121 lines
4.0 KiB
Svelte
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>
|