fix(ui): связывать OSPF узлы по подтверждённым ID

This commit is contained in:
Denozordec committed 2026-09-30 20:19:37 +07:00
1 parent 7a68ae8184
commit 259c5c3014
16 files changed
+123 -41

No files matched your search

+5
View File
@@ -66,3 +66,8 @@ assert.equal(parseCatalogRoutes(bfdServer, [{ ...rawRoute, "ospf-metric": "0" }]
assert.equal(parseCatalogRoutes(bfdServer, [{ ...rawRoute, "ospf-metric": "42" }])[0].cost, 42)
assert.equal(parseCatalogRoutes(bfdServer, [{ ...rawRoute, "ospf-metric": "42x" }])[0].cost, null)
console.log("OSPF strict interfaces/instances/routes: absent/zero/fractional/flags, no distance-as-cost passed")
assert.equal(parseBfdCatalogSessions(bfdServer, rawBfd("up"))[0].multihop, null)
assert.equal(parseBfdCatalogSessions(bfdServer, [{ ...rawBfd("up")[0], multihop: "false" }])[0].multihop, false)
assert.equal(parseCatalogRoutes(bfdServer, [rawRoute])[0].type, "Unknown")
assert.equal(parseCatalogRoutes(bfdServer, [{ ...rawRoute, "ospf-type": "intra-area" }])[0].type, "O")
+3 -3
View File
@@ -9,7 +9,7 @@ import type {
RosBfdSession, RosIpRoute,
OspfNeighborRead, OspfInterfaceRead, OspfInstanceRead, OspfRouteRead, BfdSessionRead,
} from "../types/server.js"
import { parseOspfGateway, parseOspfRouteType } from "../services/ospf-route-parse.js"
import { parseOspfGateway, parseOspfRouteType, parseOspfCatalogRouteType } from "../services/ospf-route-parse.js"
import { z } from "zod"
import { observedInteger, observedDurationMs, observedBoolean } from "../services/ospf-observed-values.js"
import { pollOspfCatalog, readOspfCatalogRaw, type OspfCatalogCollections } from "../services/ospf-catalog-poll.js"
@@ -123,7 +123,7 @@ export function parseBfdSessions(server: ServerRow, raw: RosBfdSession[], strict
export function parseBfdCatalogSessions(server: ServerRow, raw: RosBfdSession[]) {
return parseBfdSessions(server, raw, true).map((row, index) => {
const source = raw[index]
return { ...row, multiplier: observedInteger(source.multiplier, 0, 255), txInterval: observedDurationMs(source["actual-tx-interval"]), rxInterval: observedDurationMs(source["required-min-rx"]), holdTime: observedDurationMs(source["hold-time"]), packetsRx: observedInteger(source["packets-rx"]), packetsTx: observedInteger(source["packets-tx"]), stateChanges: observedInteger(source["state-changes"]) }
return { ...row, multihop: observedBoolean(source.multihop), multiplier: observedInteger(source.multiplier, 0, 255), txInterval: observedDurationMs(source["actual-tx-interval"]), rxInterval: observedDurationMs(source["required-min-rx"]), holdTime: observedDurationMs(source["hold-time"]), packetsRx: observedInteger(source["packets-rx"]), packetsTx: observedInteger(source["packets-tx"]), stateChanges: observedInteger(source["state-changes"]) }
})
}
export function parseCatalogNeighbors(server: ServerRow, raw: RosOspfNeighbor[], areaMap: Map<string, string>) {
@@ -208,7 +208,7 @@ export function parseCatalogInstances(server: ServerRow, raw: RosOspfInstance[])
export function parseCatalogRoutes(server: ServerRow, raw: RosIpRoute[]) {
return raw.flatMap((source, index) => {
const rows = parseOspfRoutes(server, [{ ...source, ".id": source[".id"] ?? String(index) }])
return rows.map(row => ({ ...row, cost: observedInteger(source["ospf-metric"], 0, 4294967295) }))
return rows.map(row => ({ ...row, type: parseOspfCatalogRouteType(source) ?? "Unknown", cost: observedInteger(source["ospf-metric"], 0, 4294967295) }))
})
}
+2 -2
View File
@@ -2,10 +2,10 @@ import type { MikrotikClient } from "./mikrotik.js"
import type { OspfNeighborRead, OspfInterfaceRead, OspfInstanceRead, BfdSessionRead, OspfRouteRead } from "../types/server.js"
export type NullableFields<T, K extends keyof T> = Omit<T, K> & { [P in K]: T[P] | null }
export type CatalogNeighbor = NullableFields<OspfNeighborRead, "priority" | "stateChanges">
export type CatalogBfdSession = NullableFields<BfdSessionRead, "multiplier" | "txInterval" | "rxInterval" | "holdTime" | "packetsRx" | "packetsTx" | "stateChanges">
export type CatalogBfdSession = NullableFields<BfdSessionRead, "multiplier" | "txInterval" | "rxInterval" | "holdTime" | "packetsRx" | "packetsTx" | "stateChanges" | "multihop">
export type CatalogInterface = NullableFields<OspfInterfaceRead, "cost" | "type" | "priority" | "helloInterval" | "deadInterval" | "disabled" | "inactive" | "useBfd">
export type CatalogInstance = NullableFields<OspfInstanceRead, "version" | "routerId" | "disabled" | "inactive">
export type CatalogRoute = NullableFields<OspfRouteRead, "cost">
export type CatalogRoute = Omit<NullableFields<OspfRouteRead, "cost">, "type"> & { type: OspfRouteRead["type"] | "Unknown" }
export interface OspfCatalogCollections {
neighbors: CatalogNeighbor[]; interfaces: CatalogInterface[]; instances: CatalogInstance[];
bfdSessions: CatalogBfdSession[]; routes: CatalogRoute[];
@@ -1,6 +1,6 @@
import assert from "node:assert/strict"
import type { RosIpRoute } from "../types/server.js"
import { parseOspfGateway, parseOspfRouteType } from "./ospf-route-parse.js"
import { parseOspfGateway, parseOspfRouteType, parseOspfCatalogRouteType } from "./ospf-route-parse.js"
function route(partial: Partial<RosIpRoute>): RosIpRoute {
return { ".id": "*1", "dst-address": "10.0.0.0/8", ...partial }
@@ -20,3 +20,11 @@ assert.deepEqual(parseOspfGateway(route({ gateway: "10.200.0.1%gre-msk-spb" })),
})
console.log("ospf-route-parse.test.ts: ok")
assert.equal(parseOspfCatalogRouteType(route({ ospf: "true" })), "Unknown")
assert.equal(parseOspfCatalogRouteType(route({ ospf: "true", "ospf-type": "future-type" })), "Unknown")
assert.equal(parseOspfCatalogRouteType(route({ ospf: "true", "ospf-type": "intra-area" })), "O")
assert.equal(parseOspfCatalogRouteType(route({ ospf: "true", "ospf-type": "inter-area" })), "O IA")
assert.equal(parseOspfCatalogRouteType(route({ "ospf-type": "ext-type-1" })), "O E1")
assert.equal(parseOspfCatalogRouteType(route({ "ospf-type": "nssa-ext-type-2" })), "O E2")
assert.equal(parseOspfCatalogRouteType(route({ static: "true" })), null)
+8
View File
@@ -26,3 +26,11 @@ export function parseOspfGateway(r: RosIpRoute): { nextHop: string; via: string
via: iface || (r.interface ?? "—"),
}
}
/** Only an explicit known subtype proves the intra/inter/external class. */
export function parseOspfCatalogRouteType(route: RosIpRoute): OspfRouteKind | "Unknown" | null {
if (parseOspfRouteType(route) === null) return null
const kinds: Record<string, OspfRouteKind> = { "intra-area": "O", internal: "O", "inter-area": "O IA", "ext-type-1": "O E1", "external-type-1": "O E1", "type-1": "O E1", "ext-1": "O E1", e1: "O E1", "nssa-ext-type-1": "O E1", "ext-type-2": "O E2", "external-type-2": "O E2", "type-2": "O E2", "ext-2": "O E2", e2: "O E2", "nssa-ext-type-2": "O E2" }
const raw = (route["ospf-type"] ?? route.type ?? "").trim().toLowerCase().replace(/^ospf[- ]/, "")
return kinds[raw] ?? "Unknown"
}