From 21999506814f81dd543279b3fd15a500f1d1ac31 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 30 Sep 2026 18:06:18 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D1=8F=D1=82=D1=8C=20=D0=B4=D0=B8=D0=B0=D0=BF=D0=B0=D0=B7=D0=BE?= =?UTF-8?q?=D0=BD=20AS=20=D0=B2=20=D0=BA=D0=B0=D1=82=D0=B0=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=D0=B5=20BGP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ux-ui-progress.md | 7 ++++++- docs/ux-ui-stage-10.md | 4 ++++ lib/bgp/response.ts | 3 ++- tests/e2e/bgp-data.spec.ts | 24 ++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 45708f24..fdbf1518 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -26,11 +26,16 @@ | 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже | | 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 | | 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 | -| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.14, состояния и cost соседей OSPF; далее показатели/URL/layout | +| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.15, OSPF и проверка диапазона AS; далее показатели/URL/layout | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат | +## Блок 10.15 — диапазон AS в клиенте BGP (2026-09-30) + +- Клиентская схема согласована со строгим backend parser: localAs/remoteAs — null или целое 1..4294967295. Ноль и переполнение отклоняют ответ; предыдущий снимок сохраняется как неизвестный, повторный успешный опрос восстанавливает данные. +- 13 BGP data E2E и TypeScript прошли, включая обе AS × ноль/переполнение. На nullable показатели OSPF, URL и визуальную приёмку это не влияет; они остаются следующим основным объёмом. Локальный коммит без push. + ## Блок 10.14 — неизвестный cost соседа OSPF (2026-09-30) - При отсутствии интерфейса в каталоге cost соседа остаётся null: таблица, линия топологии и панель узла показывают «—». Значение 10 больше не выдумывается; известные costs и сортировка сохранены. diff --git a/docs/ux-ui-stage-10.md b/docs/ux-ui-stage-10.md index 5219caf9..64c612ed 100644 --- a/docs/ux-ui-stage-10.md +++ b/docs/ux-ui-stage-10.md @@ -96,3 +96,7 @@ Lock/readiness, существующее mm:servers:write, валидация п ## 10.14. Неизвестный cost соседа Отсутствующий интерфейс не означает cost=10: таблица/линия/панель показывают «—». 11 E2E и TS прошли. Backend defaults/nullable показатели остаются следующим блоком. + +## 10.15. Диапазон AS клиента BGP + +Схема клиента принимает null либо 1..4294967295, согласованно со строгим backend parser. Неверный ответ сохраняет кэш как неизвестный. 13 data E2E и TS прошли. diff --git a/lib/bgp/response.ts b/lib/bgp/response.ts index bdb527a9..84747d5c 100644 --- a/lib/bgp/response.ts +++ b/lib/bgp/response.ts @@ -2,10 +2,11 @@ import { z } from "zod" import { vxlanServersSchema } from "@/lib/vxlan-response" const counter = z.number().int().nonnegative() +const autonomousSystem = z.number().int().min(1).max(4294967295).nullable() export const bgpSessionSchema = z.object({ id: z.string().trim().min(1), serverId: z.number().int().positive(), serverName: z.string(), serverSite: z.string(), serverCountry: z.string(), - name: z.string(), peerIp: z.string(), remoteAs: counter.nullable(), localAs: counter.nullable(), + name: z.string(), peerIp: z.string(), remoteAs: autonomousSystem, localAs: autonomousSystem, localId: z.string(), remoteId: z.string(), state: z.string().trim().min(1), type: z.enum(["eBGP", "iBGP", "Unknown"]), uptime: z.string().nullable(), holdTime: counter.nullable(), keepalive: counter.nullable(), prefixesRx: counter.nullable(), prefixesTx: counter.nullable(), inputMessages: counter.nullable(), outputMessages: counter.nullable(), diff --git a/tests/e2e/bgp-data.spec.ts b/tests/e2e/bgp-data.spec.ts index 3afffccd..7fa5edd3 100644 --- a/tests/e2e/bgp-data.spec.ts +++ b/tests/e2e/bgp-data.spec.ts @@ -93,3 +93,27 @@ test("BGP: неизвестные поля и наблюдаемый ноль н await expect(page.getByLabel("Сводка префиксов BGP")).toContainText("нет данных API") await expect(page.getByText("Недостаточно данных о префиксах для сравнения", { exact: true })).toBeVisible() }) + +for (const field of ["localAs", "remoteAs"]) for (const invalid of [0, 4294967296]) { + test("BGP: невалидная AS сохраняет снимок " + field + "=" + invalid, async ({ page }) => { + let broken = false + await page.route("**/health", r => r.fulfill({ json: { ok: true } })) + await page.route("**/api/**", r => { + const path = new URL(r.request().url()).pathname + return r.fulfill({ json: path === "/api/servers" ? [server(1)] : path === "/api/bgp/catalog" ? { sessions: [{ ...session(1), ...(broken ? { [field]: invalid } : {}) }], failures: [] } : path === "/api/auth/config" ? { required: false } : {} }) + }) + await page.goto("/bgp") + const rows = page.locator("tr[data-row-id]") + await expect(rows).toHaveCount(1) + broken = true + await page.getByRole("button", { name: "Обновить", exact: true }).click() + const alert = page.getByRole("alert").filter({ hasText: "Не удалось загрузить BGP" }) + await expect(alert).toContainText("Показаны последние полученные данные") + await expect(rows).toHaveCount(1) + await expect(rows).toContainText("Неизвестно") + broken = false + await alert.getByRole("button", { name: "Повторить загрузку BGP" }).click() + await expect(alert).toHaveCount(0) + await expect(rows).toContainText("Established") + }) +}