From 748a3c00adb375522758e968fbe600ab6496c7a1 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 30 Sep 2026 18:04:14 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BD=D0=B5=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=B2=D0=BB=D1=8F=D1=82=D1=8C=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B4=D1=83=D0=BC=D0=B0=D0=BD=D0=BD=D1=8B=D0=B9=20cost=20?= =?UTF-8?q?=D1=81=D0=BE=D1=81=D0=B5=D0=B4=D0=B0=20OSPF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/ospf/page.tsx | 10 +++++----- components/data-grids/ospf-neighbors-data-grid.tsx | 4 ++-- docs/ux-ui-progress.md | 7 ++++++- docs/ux-ui-stage-10.md | 4 ++++ tests/e2e/ospf-states.spec.ts | 1 + 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/(main)/ospf/page.tsx b/app/(main)/ospf/page.tsx index 3eebd016..88212a02 100644 --- a/app/(main)/ospf/page.tsx +++ b/app/(main)/ospf/page.tsx @@ -64,7 +64,7 @@ interface OspfNeighbor { remoteRouterId: string area: string state: OspfNeighborState - cost: number + cost: number | null uptime: string priority: number dataUnknown?: boolean @@ -152,7 +152,7 @@ function backendToNeighbor(b: BackendNeighbor, ifaceMap: Map): O remoteRouterId: b.routerId, area: b.areaId !== b.area ? `${b.area} (${b.areaId})` : b.area, state, - cost: ifaceMap.get(costKey) ?? 10, + cost: ifaceMap.get(costKey) ?? null, uptime: b.uptime ?? "—", priority: b.priority, } @@ -298,7 +298,7 @@ const EDGE_COLOR: Record = { } interface GraphNode { id: string; label: string; short: string; site: string; x: number; y: number } -interface GraphEdge { from: string; to: string; state: OspfNeighbor["state"]; cost: number } +interface GraphEdge { from: string; to: string; state: OspfNeighbor["state"]; cost: number | null } // Mock topology (static layout for mock mode) const MOCK_GRAPH_NODES: GraphNode[] = [ @@ -459,7 +459,7 @@ function TopologyGraph({ graphNodes, graphEdges, highlightId, selectedId, showDo {e.cost} + fill={color} fontFamily="ui-monospace,monospace">{e.cost ?? "—"} ) @@ -613,7 +613,7 @@ function NodeDetailPanel({

{adj.peerLabel}

-

{adj.iface} · cost {adj.cost}

+

{adj.iface} · cost {adj.cost ?? "—"}

{adj.uptime}

{adj.state} diff --git a/components/data-grids/ospf-neighbors-data-grid.tsx b/components/data-grids/ospf-neighbors-data-grid.tsx index d95a8e1e..2249b215 100644 --- a/components/data-grids/ospf-neighbors-data-grid.tsx +++ b/components/data-grids/ospf-neighbors-data-grid.tsx @@ -30,7 +30,7 @@ export interface OspfNeighborRow { remoteRouterId: string area: string state: OspfNeighborState - cost: number + cost: number | null uptime: string priority: number } @@ -149,7 +149,7 @@ function OspfNeighborsDataGrid({ accessorKey: "cost", header: ({ column }) => , cell: ({ row }) => ( - {row.original.cost} + {row.original.cost ?? "—"} ), meta: { headerTitle: "Cost", diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 23a0a460..45708f24 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.13, состояния OSPF/BFD; далее неизвестные показатели/URL/layout | +| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.14, состояния и cost соседей OSPF; далее показатели/URL/layout | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат | +## Блок 10.14 — неизвестный cost соседа OSPF (2026-09-30) + +- При отсутствии интерфейса в каталоге cost соседа остаётся null: таблица, линия топологии и панель узла показывают «—». Значение 10 больше не выдумывается; известные costs и сортировка сохранены. +- 11 E2E states/routing-tables и TypeScript прошли. Production-сборки проверены в 10.13; повторная полная регрессия не требовалась. Далее nullable показатели строгого backend-каталога, URL и layout; backend defaults ещё требуют проверки. 96% окна использовано; локальный коммит без push. + ## Блок 10.13 — подтверждённые состояния OSPF/BFD (2026-09-30) - Восемь состояний соседства RouterOS распознаются независимо от регистра. Attempt, Init, ExStart, Exchange и Loading сохраняются как переходные состояния; неизвестные значения показываются нейтрально, без подмены Down. Топология различает Pending/Unknown и подтверждённый отказ. При неизвестном состоянии сводка не выдаёт полный подсчёт состояний. diff --git a/docs/ux-ui-stage-10.md b/docs/ux-ui-stage-10.md index 4a9f3378..5219caf9 100644 --- a/docs/ux-ui-stage-10.md +++ b/docs/ux-ui-stage-10.md @@ -92,3 +92,7 @@ Lock/readiness, существующее mm:servers:write, валидация п ## 10.13. Состояния соседства и BFD Все восемь состояний из [RouterOS OSPF](https://help.mikrotik.com/docs/spaces/ROS/pages/331612216/routing%2Bospf) сохранены. Переходные состояния — amber/Pending, неизвестные — neutral/Unknown, подтверждённый Down — red. Строгий каталог не подставляет Down при отсутствующем BFD state; старые endpoints совместимы. 14 E2E, два unit-теста, TS и обе production-сборки прошли. Далее неизвестные показатели, URL и визуальная приёмка. + +## 10.14. Неизвестный cost соседа + +Отсутствующий интерфейс не означает cost=10: таблица/линия/панель показывают «—». 11 E2E и TS прошли. Backend defaults/nullable показатели остаются следующим блоком. diff --git a/tests/e2e/ospf-states.spec.ts b/tests/e2e/ospf-states.spec.ts index c669bcfb..2f469d63 100644 --- a/tests/e2e/ospf-states.spec.ts +++ b/tests/e2e/ospf-states.spec.ts @@ -9,6 +9,7 @@ test("OSPF и BFD: переходные и неизвестные состоян await page.getByRole("button", { name: "Соседи", exact: true }).click() const rows = page.locator("tr[data-row-id]") await expect(rows).toHaveCount(9) + for (const row of await rows.all()) await expect(row.locator("td").nth(5)).toHaveText("—") for(const state of ["Full", "2-Way", "ExStart", "Exchange", "Loading", "Attempt", "Init", "Down", "Неизвестно"]) await expect(rows.getByText(state, { exact: true })).toHaveCount(1) await page.getByRole("button", { name: "BFD", exact: true }).click() await expect(rows).toHaveCount(5)