66 lines
2.9 KiB
TypeScript
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 : [])
|
|
}
|