From 399622cc70de206adb022e1e5acb375b847af74f Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 6 Apr 2026 00:41:59 +0700 Subject: [PATCH] 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. --- internal/birdfmt/protocol_summary.go | 28 +++++-- internal/birdfmt/protocol_summary_test.go | 11 +++ internal/birdfmt/protocols.go | 2 +- .../components/app/scroll-pre-block.svelte | 29 +++++++ web/src/lib/dialog-layout.ts | 21 +++++ web/src/routes/operations/+page.svelte | 77 +++++++++++-------- 6 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 web/src/lib/components/app/scroll-pre-block.svelte create mode 100644 web/src/lib/dialog-layout.ts diff --git a/internal/birdfmt/protocol_summary.go b/internal/birdfmt/protocol_summary.go index 931136e..43dd7c2 100644 --- a/internal/birdfmt/protocol_summary.go +++ b/internal/birdfmt/protocol_summary.go @@ -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 diff --git a/internal/birdfmt/protocol_summary_test.go b/internal/birdfmt/protocol_summary_test.go index d7c512d..c416597 100644 --- a/internal/birdfmt/protocol_summary_test.go +++ b/internal/birdfmt/protocol_summary_test.go @@ -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) + } +} diff --git a/internal/birdfmt/protocols.go b/internal/birdfmt/protocols.go index 5c57a87..bfa91e7 100644 --- a/internal/birdfmt/protocols.go +++ b/internal/birdfmt/protocols.go @@ -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") { diff --git a/web/src/lib/components/app/scroll-pre-block.svelte b/web/src/lib/components/app/scroll-pre-block.svelte new file mode 100644 index 0000000..8df9161 --- /dev/null +++ b/web/src/lib/components/app/scroll-pre-block.svelte @@ -0,0 +1,29 @@ + + +
+
{text}
+
diff --git a/web/src/lib/dialog-layout.ts b/web/src/lib/dialog-layout.ts new file mode 100644 index 0000000..4a4f2ab --- /dev/null +++ b/web/src/lib/dialog-layout.ts @@ -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'; diff --git a/web/src/routes/operations/+page.svelte b/web/src/routes/operations/+page.svelte index 4305972..0615c95 100644 --- a/web/src/routes/operations/+page.svelte +++ b/web/src/routes/operations/+page.svelte @@ -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 @@ - - + + Ревизия {previewRevision?.id.slice(0, 8)}… Срендеренный конфиг BIRD 2 (фрагменты из control plane) и материализованные префиксы. {#if previewLoading} -

Загрузка…

+
Загрузка…
{:else} -
- +
+ Конфиг BIRD Префиксы - + {@const frags = asPreviewFragments(previewData)} {#if Object.keys(frags).length === 0}

Нет фрагментов превью (старая ревизия или пустой render).

@@ -670,22 +678,20 @@ Совет: откройте _bird_full_expanded.conf — один текст с bird.conf и содержимым всех include.

-
-
{frags[birdPreviewPath] ?? ''}
-
+ {/if}
- +

Префиксов: {prefixesData.length}

{#each prefixesData as pfx}

{pfx}

@@ -702,39 +708,46 @@ - - + + Вывод birdc (протоколы) - Фрагмент ответа на этом API-хосте; при длинном выводе обрезан на сервере. + + Фрагмент ответа на этом API-хосте; при длинном выводе обрезан на сервере. Таблица сохраняет выравнивание + колонок (горизонтальная прокрутка). + - -
{birdStatus?.protocols_excerpt ?? ''}
-
+
+ +
- - + + Задача: {jobDetail?.kind} {#if jobDetail} -
-
- ID{jobDetail.job_id} +
+
+ ID{jobDetail.job_id} Статус{jobDetail.status} Создана{formatDate(jobDetail.created_at)} Начата{formatDate(jobDetail.started_at)} Завершена{formatDate(jobDetail.finished_at)} {#if jobDetail.error} - Ошибка{jobDetail.error} + Ошибка{jobDetail.error} {/if}
{#if jobDetail.meta && Object.keys(jobDetail.meta).length > 0} -
-

Meta

-
{JSON.stringify(jobDetail.meta, null, 2)}
+
+

Meta

+
{/if}