From af05ad5cdada091251b15f27e649f2472c5c2959 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 30 Sep 2026 19:49:47 +0700 Subject: [PATCH] =?UTF-8?q?fix(api):=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7?= =?UTF-8?q?=D1=8B=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B4=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=B4=D1=91=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B8=20OSPF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(main)/ospf/page.tsx | 60 +++++++------------ backend/src/routes/ospf-catalog.test.ts | 19 +++++- backend/src/routes/ospf.ts | 21 ++++++- backend/src/services/ospf-catalog-poll.ts | 7 ++- .../src/services/ospf-observed-values.test.ts | 6 +- backend/src/services/ospf-observed-values.ts | 6 ++ .../data-grids/ospf-routes-data-grid.tsx | 4 +- docs/ux-ui-progress.md | 9 ++- docs/ux-ui-stage-10.md | 4 ++ lib/ospf-cost.test.ts | 3 +- lib/ospf-cost.ts | 4 +- lib/ospf-response.ts | 6 +- tests/e2e/ospf-interface-values.spec.ts | 26 ++++++++ 13 files changed, 120 insertions(+), 55 deletions(-) create mode 100644 tests/e2e/ospf-interface-values.spec.ts diff --git a/app/(main)/ospf/page.tsx b/app/(main)/ospf/page.tsx index d4885c07..c9825098 100644 --- a/app/(main)/ospf/page.tsx +++ b/app/(main)/ospf/page.tsx @@ -46,13 +46,13 @@ interface OspfItem { serverId: string interfaceName: string area: string - cost: number - active: boolean + cost: number | null + active: boolean | null dataUnknown?: boolean - type?: string - useBfd?: boolean - helloInterval?: number - deadInterval?: number + type?: string | null + useBfd?: boolean | null + helloInterval?: number | null + deadInterval?: number | null } interface OspfNeighbor { @@ -75,7 +75,7 @@ interface OspfRoute { id: string destination: string type: "O" | "O IA" | "O E1" | "O E2" - cost: number + cost: number | null nextHop: string via: string serverId: string @@ -106,31 +106,10 @@ interface BfdSession { // ─── backend types ──────────────────────────────────────────────────────────── -interface BackendNeighbor { - id: string; serverId: number; serverName: string; serverSite: string; serverCountry: string - address: string; routerId: string; instance: string; area: string; areaId: string - interface: string; state: string; uptime: string | null; stateChanges: number | null; priority: number | null -} - -interface BackendInterface { - id: string; serverId: number; serverName: string; serverSite: string; serverCountry: string - instance: string; area: string; areaId: string; interface: string; cost: number - type: string; disabled: boolean; inactive: boolean; priority: number - helloInterval: number; deadInterval: number; useBfd: boolean -} - - -interface BackendBfdSession { - id: string; serverId: number; serverName: string; serverSite: string; serverCountry: string - localAddr: string; remoteAddr: string; interface: string; state: string; uptime: string | null - multihop: boolean; multiplier: number | null; txInterval: number | null; rxInterval: number | null; holdTime: number | null - packetsRx: number | null; packetsTx: number | null; stateChanges: number | null -} - -interface BackendOspfRoute { - id: string; serverId: number; serverName: string; serverSite: string - destination: string; type: OspfRoute["type"]; cost: number; nextHop: string; via: string; area: string -} +type BackendNeighbor = OspfCatalog["neighbors"][number] +type BackendInterface = OspfCatalog["interfaces"][number] +type BackendBfdSession = OspfCatalog["bfdSessions"][number] +type BackendOspfRoute = OspfCatalog["routes"][number] function isRefInterfaceName(name: string): boolean { @@ -168,7 +147,7 @@ function backendToItem(b: BackendInterface): OspfItem { interfaceName: b.interface, area: b.areaId, cost: b.cost, - active: !b.disabled && !b.inactive, + active: b.disabled === true || b.inactive === true ? false : b.disabled === false && b.inactive === false ? true : null, type: b.type, useBfd: b.useBfd, helloInterval: b.helloInterval, @@ -637,7 +616,7 @@ function NodeDetailPanel({ {iface.area === "0.0.0.0" ? "backbone" : `area ${iface.area}`} - cost {iface.cost} + cost {iface.cost ?? "—"} ))} @@ -696,7 +675,7 @@ function InterfacesTab({ return Object.values(byRouter).map(r => ({ ...r, areas: Object.entries(r.areas) - .map(([area, areaItems]) => ({ area, items: [...areaItems].sort((a, b) => a.cost - b.cost) })) + .map(([area, areaItems]) => ({ area, items: [...areaItems].sort((a, b) => (a.cost ?? Infinity) - (b.cost ?? Infinity)) })) .sort((a, b) => a.area.localeCompare(b.area)), })) }, [items]) @@ -765,7 +744,7 @@ function InterfacesTab({ function onDrop(routerKey: string, area: string, targetKey: string) { if (!dragging || dragging === targetKey) { setDragging(null); setDragOver(null); return } setItems(prev => { - const areaItems = prev.filter(i => i.routerKey === routerKey && i.area === area).sort((a, b) => a.cost - b.cost) + const areaItems = prev.filter(i => i.routerKey === routerKey && i.area === area).sort((a, b) => (a.cost ?? Infinity) - (b.cost ?? Infinity)) const fromIdx = areaItems.findIndex(i => i.key === dragging) const toIdx = areaItems.findIndex(i => i.key === targetKey) if (fromIdx < 0 || toIdx < 0) return prev @@ -785,6 +764,7 @@ function InterfacesTab({ return (
+ {isLive && filterServerId !== ALL_SERVERS_ID && !dataReady &&

Оптимизация недоступна до полного успешного опроса и подтверждения costs/состояний интерфейсов с правом записи.

} {operationError && Оптимизация OSPF не подтверждена{operationError}}