Files
MikrotikManager/backend/src/services/gre-tunnels-live.ts
T

66 lines
2.9 KiB
TypeScript

import { eq } from "drizzle-orm"
import { db } from "../db/index.js"
import { servers } from "../db/schema.js"
import { MikrotikClient } from "./mikrotik.js"
import { readInternetPathRows } from "./internet-path-snapshot.js"
import { readGreObservedState } from "./gre-observed-state.js"
type ServerRow = typeof servers.$inferSelect
interface RosGre {
".id"?: string
name?: string
"local-address"?: string
"remote-address"?: string
"allow-fast-path"?: string
"clamp-tcp-mss"?: string
mtu?: string
"keepalive"?: string
dscp?: string
running?: string
disabled?: string
comment?: string
}
export type GreTunnelLiveStatus = "up" | "down" | "degraded"
/** Как в UI `/alerts`: `${tunnelName} / ${serverName}`. */
export type GreTunnelLiveRow = {
targetLabel: string
status: GreTunnelLiveStatus
}
export function mapGreAlertRow(server: ServerRow, g: RosGre, idx = 0): GreTunnelLiveRow {
const { status } = readGreObservedState(g, g.name ?? g[".id"] ?? "")
const sn = String(server.name ?? "").trim() || String(server.host ?? "").trim() || String(server.id)
const tn = (String(g.name ?? "").trim() || String(g[".id"] ?? "").trim() || `gre-${idx + 1}`).trim()
const label = `${tn} / ${sn}`.trim()
return { targetLabel: label, status }
}
async function fetchGreForServer(server: ServerRow): Promise<GreTunnelLiveRow[]> {
const client = MikrotikClient.fromServer(server)
const greRaw = readInternetPathRows(await client.get<unknown>("/interface/gre"), "GRE alert source", [".id", "name"])
if (new Set(greRaw.map(row => row[".id"])).size !== greRaw.length) throw new Error("Повторяющиеся ID GRE")
return greRaw.map((g, idx) => mapGreAlertRow(server, g, idx))
}
interface GreAlertDependencies {
listEnabledServers: () => Promise<ServerRow[]>
readTunnels: (server: ServerRow) => Promise<GreTunnelLiveRow[]>
}
const alertDefaults: GreAlertDependencies = {
listEnabledServers: () => db.select().from(servers).where(eq(servers.enabled, true)),
readTunnels: fetchGreForServer,
}
/** GRE с включённых серверов — тот же смысл, что `/api/filters/gre-tunnels` (без правил фильтров). */
export async function fetchGreTunnelLiveRows(dependencies: GreAlertDependencies = alertDefaults): Promise<GreTunnelLiveRow[]> {
const devices = await dependencies.listEnabledServers()
const results = await Promise.allSettled(devices.map(server => Promise.resolve().then(() => dependencies.readTunnels(server))))
const failures = results.flatMap((result, index) => result.status === "rejected"
? [`${devices[index].name}: ${result.reason instanceof Error ? result.reason.message : "Ошибка опроса устройства"}`] : [])
if (failures.length) throw new Error("Не опрошены устройства GRE: " + failures.join("; "))
return results.flatMap(result => result.status === "fulfilled" ? result.value : [])
}