fix(api): сохранять неизвестные показатели BFD и OSPF

This commit is contained in:
Denozordec committed 2026-09-30 19:42:07 +07:00
1 parent 47dcd89b3c
commit e3791f3496
13 files changed
+136 -44

No files matched your search

+5 -2
View File
@@ -1,8 +1,11 @@
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 interface OspfCatalogCollections {
neighbors: OspfNeighborRead[]; interfaces: OspfInterfaceRead[]; instances: OspfInstanceRead[];
bfdSessions: BfdSessionRead[]; routes: OspfRouteRead[];
neighbors: CatalogNeighbor[]; interfaces: OspfInterfaceRead[]; instances: OspfInstanceRead[];
bfdSessions: CatalogBfdSession[]; routes: OspfRouteRead[];
}
export const emptyOspfCatalog = (): OspfCatalogCollections => ({ neighbors: [], interfaces: [], instances: [], bfdSessions: [], routes: [] })
export async function readOspfCatalogRaw(client: Pick<MikrotikClient, "getOspfNeighbors" | "getOspfAreas" | "getOspfInterfaceTemplates" | "getOspfInstances" | "getBfdSessions" | "getIpRoutes">) {
@@ -0,0 +1,7 @@
import assert from "node:assert/strict"
import { observedInteger, observedDurationMs } from "./ospf-observed-values.js"
for (const raw of [undefined, "", " ", "1x", "-1", "1.5", "9007199254740992"]) assert.equal(observedInteger(raw), null)
assert.equal(observedInteger("0"), 0); assert.equal(observedInteger("255", 0, 255), 255); assert.equal(observedInteger("256", 0, 255), null)
for (const raw of [undefined, "", "-1s", "12junk", "200", "1s2s", "60:00", "00:99:00", "1s junk"]) assert.equal(observedDurationMs(raw), null)
for (const [raw, ms] of [["0ms", 0], ["0", 0], ["1us", 0.001], ["1s200ms", 1200], ["1h2m3s", 3723000], ["01:02:03.5", 3723500], ["02:03", 123000]] as const) assert.equal(observedDurationMs(raw), ms)
console.log("OSPF observed values: absent/zero/invalid/unsafe/units/clock passed")
@@ -0,0 +1,28 @@
/** Values read from RouterOS; absence is not a measured zero. */
export function observedInteger(value: string | undefined, min = 0, max = Number.MAX_SAFE_INTEGER): number | null {
if (value === undefined || !/^\d+$/.test(value.trim())) return null
const number = Number(value.trim())
return Number.isSafeInteger(number) && number >= min && number <= max ? number : null
}
/** RouterOS time in milliseconds. Bare numbers are ambiguous, except explicit zero. */
export function observedDurationMs(value: string | undefined): number | null {
const text = value?.trim()
if (!text) return null
if (text === "0") return 0
const clock = /^(?:(\d+):)?(\d{1,2}):(\d{2})(\.\d+)?$/.exec(text)
if (clock) {
const [, hours = "0", minutes, seconds, fraction = ""] = clock
if (Number(minutes) >= 60 || Number(seconds) >= 60) return null
const ms = (Number(hours) * 3600 + Number(minutes) * 60 + Number(seconds + fraction)) * 1000
return Number.isFinite(ms) && ms <= Number.MAX_SAFE_INTEGER ? ms : null
}
const units: Record<string, number> = { w: 604800000, d: 86400000, h: 3600000, m: 60000, s: 1000, ms: 1, us: 0.001, ns: 0.000001 }
const pattern = /(\d+(?:\.\d+)?)(ms|us|ns|w|d|h|m|s)/g
let cursor = 0, total = 0
const seen = new Set<string>()
for (const part of text.matchAll(pattern)) {
if (part.index !== cursor || seen.has(part[2])) return null
seen.add(part[2]); cursor += part[0].length; total += Number(part[1]) * units[part[2]]
}
return cursor === text.length && Number.isFinite(total) && total <= Number.MAX_SAFE_INTEGER ? total : null
}