feat: improve BGP protocol summary handling by introducing isBGPProtocolSummaryRow function. Update SummarizeProtocolsOutput and related tests to ensure evobgp_* static names are not counted as BGP sessions, enhancing accuracy in protocol summaries.
CI / changes (push) Successful in 5s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 23s
CI / bird2 (push) Has been cancelled
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been cancelled
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been cancelled
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been cancelled
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been cancelled
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been cancelled
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been cancelled
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been cancelled
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been cancelled
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Has started running
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Has been cancelled
CI / docker-bird (push) Has been cancelled
CI / changes (push) Successful in 5s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 23s
CI / bird2 (push) Has been cancelled
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been cancelled
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been cancelled
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been cancelled
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been cancelled
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been cancelled
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been cancelled
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been cancelled
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been cancelled
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Has started running
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Has been cancelled
CI / docker-bird (push) Has been cancelled
This commit is contained in:
@@ -11,6 +11,24 @@ type ProtocolsSummary struct {
|
||||
RawLineCount int
|
||||
}
|
||||
|
||||
// isBGPProtocolSummaryRow is true for BIRD "show protocols" summary rows where the
|
||||
// second column (Proto) is BGP. Substring checks are unsafe: names like evobgp_* contain "bgp".
|
||||
func isBGPProtocolSummaryRow(line string) bool {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
return false
|
||||
}
|
||||
low := strings.ToLower(line)
|
||||
if strings.HasPrefix(low, "name") || strings.HasPrefix(low, "table") {
|
||||
return false
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(fields[1], "BGP")
|
||||
}
|
||||
|
||||
// SummarizeProtocolsOutput extracts BGP session heuristics from birdc output.
|
||||
func SummarizeProtocolsOutput(output string) ProtocolsSummary {
|
||||
var s ProtocolsSummary
|
||||
@@ -22,14 +40,12 @@ func SummarizeProtocolsOutput(output string) ProtocolsSummary {
|
||||
continue
|
||||
}
|
||||
low := strings.ToLower(line)
|
||||
if strings.HasPrefix(low, "name") || strings.HasPrefix(low, "table") {
|
||||
if !isBGPProtocolSummaryRow(line) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(low, "bgp") {
|
||||
s.BGPSessionsTotal++
|
||||
if strings.Contains(low, "established") {
|
||||
s.BGPEstablished++
|
||||
}
|
||||
s.BGPSessionsTotal++
|
||||
if strings.Contains(low, "established") {
|
||||
s.BGPEstablished++
|
||||
}
|
||||
}
|
||||
return s
|
||||
|
||||
@@ -12,3 +12,14 @@ uplink BGP --- start 10:00:01 Established
|
||||
t.Fatalf("got %+v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSummarizeProtocolsOutput_evoBGPNameNotCountedAsBGP(t *testing.T) {
|
||||
sample := `Name Proto Table State Since Info
|
||||
evobgp_prefixes_v4 Static master4 up 17:32:14.631
|
||||
evobgp_prefixes_v6 Static master6 up 17:32:14.631
|
||||
`
|
||||
s := SummarizeProtocolsOutput(sample)
|
||||
if s.BGPSessionsTotal != 0 || s.BGPEstablished != 0 {
|
||||
t.Fatalf("evobgp_* static names must not match substring bgp: got %+v", s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func CountEstablishedBGPSessions(showProtocolsOutput string) int {
|
||||
if line == "" || strings.HasPrefix(line, "name") || strings.HasPrefix(strings.ToLower(line), "table") {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(line), "bgp") {
|
||||
if !isBGPProtocolSummaryRow(line) {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(strings.ToLower(line), "established") {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils.js';
|
||||
|
||||
/** preserve — вывод CLI (колонки birdc); wrap — конфиги и JSON с длинными строками */
|
||||
type Variant = 'preserve' | 'wrap';
|
||||
|
||||
let {
|
||||
variant = 'preserve',
|
||||
text,
|
||||
class: className = ''
|
||||
}: {
|
||||
variant?: Variant;
|
||||
text: string;
|
||||
class?: string;
|
||||
} = $props();
|
||||
|
||||
const shell =
|
||||
'border-border bg-muted/40 relative isolate min-w-0 overflow-auto rounded-lg border [scrollbar-gutter:stable] overscroll-contain';
|
||||
|
||||
const prePreserve =
|
||||
'text-foreground m-0 block w-max min-w-full p-4 font-mono text-[0.8125rem] leading-normal whitespace-pre select-text';
|
||||
|
||||
const preWrap =
|
||||
'text-foreground m-0 block min-w-0 w-full max-w-none p-4 font-mono text-[0.8125rem] leading-relaxed whitespace-pre-wrap break-words select-text';
|
||||
</script>
|
||||
|
||||
<div class={cn(shell, className)} data-slot="scroll-pre-block">
|
||||
<pre class={variant === 'preserve' ? prePreserve : preWrap}>{text}</pre>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Общая разметка для модалок с прокручиваемым контентом (превью BIRD, вывод birdc, логи).
|
||||
* DialogContent по умолчанию — grid + p-4; здесь переопределяем на flex-колонку без внешних отступов.
|
||||
*/
|
||||
export const dialogContentDocument =
|
||||
'!flex w-[min(100vw-2rem,56rem)] max-h-[min(92vh,880px)] flex-col gap-0 overflow-hidden !p-0 sm:max-w-4xl';
|
||||
|
||||
export const dialogHeaderDocument =
|
||||
'shrink-0 space-y-1.5 border-b border-border/70 px-6 pt-5 pb-3 pr-14 text-left';
|
||||
|
||||
export const dialogBodyDocument =
|
||||
'flex min-h-0 min-w-0 flex-1 flex-col gap-3 overflow-hidden px-6 py-4';
|
||||
|
||||
/** Компактные модалки (детали задачи, формы): единая шапка и тело */
|
||||
export const dialogContentPanel =
|
||||
'!flex max-h-[min(90vh,40rem)] w-full max-w-lg flex-col gap-0 overflow-hidden !p-0 sm:max-w-lg';
|
||||
|
||||
export const dialogHeaderPanel =
|
||||
'shrink-0 space-y-1.5 border-b border-border/70 px-6 pt-5 pb-3 pr-14 text-left';
|
||||
|
||||
export const dialogBodyPanel = 'min-h-0 flex-1 overflow-y-auto px-6 py-4';
|
||||
@@ -40,6 +40,16 @@
|
||||
TableRow
|
||||
} from '$lib/components/ui/table/index.js';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
|
||||
import ScrollPreBlock from '$lib/components/app/scroll-pre-block.svelte';
|
||||
import {
|
||||
dialogBodyDocument,
|
||||
dialogBodyPanel,
|
||||
dialogContentDocument,
|
||||
dialogContentPanel,
|
||||
dialogHeaderDocument,
|
||||
dialogHeaderPanel
|
||||
} from '$lib/dialog-layout.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||
import Play from '@lucide/svelte/icons/play';
|
||||
@@ -631,25 +641,23 @@
|
||||
|
||||
<!-- Preview dialog -->
|
||||
<Dialog bind:open={previewDialog}>
|
||||
<DialogContent
|
||||
class="!flex max-h-[min(96vh,880px)] w-[min(100vw-2rem,56rem)] flex-col gap-4 overflow-hidden sm:max-w-4xl"
|
||||
>
|
||||
<DialogHeader class="shrink-0 space-y-1.5 pr-8">
|
||||
<DialogContent class={dialogContentDocument}>
|
||||
<DialogHeader class={dialogHeaderDocument}>
|
||||
<DialogTitle>Ревизия {previewRevision?.id.slice(0, 8)}…</DialogTitle>
|
||||
<DialogDescription>
|
||||
Срендеренный конфиг BIRD 2 (фрагменты из control plane) и материализованные префиксы.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{#if previewLoading}
|
||||
<p class="text-muted-foreground py-4 text-center text-sm">Загрузка…</p>
|
||||
<div class="text-muted-foreground px-6 py-10 text-center text-sm">Загрузка…</div>
|
||||
{:else}
|
||||
<div class="flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<Tabs bind:value={previewSubTab} class="flex min-h-0 flex-1 flex-col gap-3">
|
||||
<div class={cn(dialogBodyDocument, 'min-h-[min(44vh,400px)]')}>
|
||||
<Tabs bind:value={previewSubTab} class="flex min-h-0 min-w-0 flex-1 flex-col gap-3">
|
||||
<TabsList class="grid w-full max-w-md shrink-0 grid-cols-2">
|
||||
<TabsTrigger value="bird">Конфиг BIRD</TabsTrigger>
|
||||
<TabsTrigger value="prefixes">Префиксы</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="bird" class="mt-0 flex min-h-0 flex-1 flex-col gap-2 outline-none">
|
||||
<TabsContent value="bird" class="mt-0 flex min-h-0 min-w-0 flex-1 flex-col gap-2 outline-none">
|
||||
{@const frags = asPreviewFragments(previewData)}
|
||||
{#if Object.keys(frags).length === 0}
|
||||
<p class="text-muted-foreground text-sm">Нет фрагментов превью (старая ревизия или пустой render).</p>
|
||||
@@ -670,22 +678,20 @@
|
||||
Совет: откройте <code class="bg-muted rounded px-1">_bird_full_expanded.conf</code> — один текст с
|
||||
<code class="bg-muted rounded px-1">bird.conf</code> и содержимым всех include.
|
||||
</p>
|
||||
<div
|
||||
class="border-border bg-muted/40 text-foreground relative isolate min-h-[12rem] min-w-0 flex-1 overflow-x-auto overflow-y-auto rounded-lg border [scrollbar-gutter:stable] overscroll-contain"
|
||||
>
|
||||
<pre
|
||||
class="text-foreground m-0 block min-w-0 w-full max-w-none p-3 font-mono text-xs leading-relaxed whitespace-pre-wrap break-words select-text"
|
||||
>{frags[birdPreviewPath] ?? ''}</pre>
|
||||
</div>
|
||||
<ScrollPreBlock
|
||||
variant="wrap"
|
||||
text={frags[birdPreviewPath] ?? ''}
|
||||
class="min-h-[12rem] min-w-0 flex-1"
|
||||
/>
|
||||
{/if}
|
||||
</TabsContent>
|
||||
<TabsContent value="prefixes" class="mt-0 flex min-h-0 flex-1 flex-col gap-2 outline-none">
|
||||
<TabsContent value="prefixes" class="mt-0 flex min-h-0 min-w-0 flex-1 flex-col gap-2 outline-none">
|
||||
<p class="text-sm shrink-0">
|
||||
<span class="font-medium">Префиксов:</span>
|
||||
{prefixesData.length}
|
||||
</p>
|
||||
<div
|
||||
class="border-border bg-muted/40 min-h-[10rem] min-w-0 flex-1 overflow-x-auto overflow-y-auto rounded-lg border p-3 [scrollbar-gutter:stable] overscroll-contain"
|
||||
class="border-border bg-muted/40 flex min-h-[10rem] min-w-0 flex-1 flex-col overflow-auto rounded-lg border p-3 [scrollbar-gutter:stable] overscroll-contain"
|
||||
>
|
||||
{#each prefixesData as pfx}
|
||||
<p class="font-mono text-xs">{pfx}</p>
|
||||
@@ -702,39 +708,46 @@
|
||||
|
||||
<!-- BIRD protocols excerpt -->
|
||||
<Dialog bind:open={birdProtocolsOpen}>
|
||||
<DialogContent class="flex max-h-[min(90vh,720px)] flex-col gap-3 sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogContent class={dialogContentDocument}>
|
||||
<DialogHeader class={dialogHeaderDocument}>
|
||||
<DialogTitle>Вывод birdc (протоколы)</DialogTitle>
|
||||
<DialogDescription>Фрагмент ответа на этом API-хосте; при длинном выводе обрезан на сервере.</DialogDescription>
|
||||
<DialogDescription>
|
||||
Фрагмент ответа на этом API-хосте; при длинном выводе обрезан на сервере. Таблица сохраняет выравнивание
|
||||
колонок (горизонтальная прокрутка).
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea class="border-border bg-muted/40 max-h-[min(60vh,480px)] rounded-md border">
|
||||
<pre class="m-0 p-3 font-mono text-xs leading-relaxed whitespace-pre-wrap break-words select-text">{birdStatus?.protocols_excerpt ?? ''}</pre>
|
||||
</ScrollArea>
|
||||
<div class="px-6 pb-6 pt-0">
|
||||
<ScrollPreBlock
|
||||
variant="preserve"
|
||||
text={birdStatus?.protocols_excerpt ?? ''}
|
||||
class="h-[min(65vh,560px)] w-full"
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- Job detail dialog -->
|
||||
<Dialog bind:open={jobDetailDialog}>
|
||||
<DialogContent class="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogContent class={dialogContentPanel}>
|
||||
<DialogHeader class={dialogHeaderPanel}>
|
||||
<DialogTitle>Задача: {jobDetail?.kind}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{#if jobDetail}
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<span class="text-muted-foreground">ID</span><span class="font-mono">{jobDetail.job_id}</span>
|
||||
<div class={dialogBodyPanel}>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-1 text-sm">
|
||||
<span class="text-muted-foreground">ID</span><span class="font-mono break-all">{jobDetail.job_id}</span>
|
||||
<span class="text-muted-foreground">Статус</span><span><Badge variant={jobStatusVariant(jobDetail.status)}>{jobDetail.status}</Badge></span>
|
||||
<span class="text-muted-foreground">Создана</span><span>{formatDate(jobDetail.created_at)}</span>
|
||||
<span class="text-muted-foreground">Начата</span><span>{formatDate(jobDetail.started_at)}</span>
|
||||
<span class="text-muted-foreground">Завершена</span><span>{formatDate(jobDetail.finished_at)}</span>
|
||||
{#if jobDetail.error}
|
||||
<span class="text-muted-foreground">Ошибка</span><span class="text-destructive">{jobDetail.error}</span>
|
||||
<span class="text-muted-foreground">Ошибка</span><span class="text-destructive break-words">{jobDetail.error}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if jobDetail.meta && Object.keys(jobDetail.meta).length > 0}
|
||||
<div>
|
||||
<p class="text-muted-foreground mb-1">Meta</p>
|
||||
<pre class="bg-muted rounded p-2 text-xs overflow-auto max-h-40">{JSON.stringify(jobDetail.meta, null, 2)}</pre>
|
||||
<div class="mt-4 space-y-2">
|
||||
<p class="text-muted-foreground text-sm font-medium">Meta</p>
|
||||
<ScrollPreBlock variant="wrap" text={JSON.stringify(jobDetail.meta, null, 2)} class="max-h-52 w-full" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user