fix(ui): показывать частичные сбои опроса VXLAN

This commit is contained in:
Denozordec committed 2026-09-30 09:42:43 +07:00
1 parent 4dc2ee2129
commit e163a9ed9d
9 files changed
+120 -41

No files matched your search

+8 -5
View File
@@ -2,21 +2,24 @@ import { eq } from "drizzle-orm"
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import { db } from "../db/index.js"
import { servers } from "../db/schema.js"
import { listVxlanTunnels, listVxlanTunnelsForServer } from "../services/vxlan-live.js"
import { listVxlanResult, listVxlanTunnelsForServer } from "../services/vxlan-live.js"
import { ServerIdParamSchema, type ServerIdParams } from "../types/server.js"
const vxlanRoutes: FastifyPluginAsyncZod = async (app) => {
app.get("/vxlan", async (_req, reply) => {
const tunnels = await listVxlanTunnels()
return reply.send({ tunnels })
return reply.send(await listVxlanResult())
})
app.get("/servers/:id/vxlan", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
const params = req.params as ServerIdParams
const server = (await db.select().from(servers).where(eq(servers.id, params.id)).limit(1))[0]
if (!server) return reply.status(404).send({ error: "Server not found" })
const tunnels = await listVxlanTunnelsForServer(server)
return reply.send({ tunnels })
try {
const tunnels = await listVxlanTunnelsForServer(server)
return reply.send({ tunnels })
} catch {
return reply.status(502).send({ error: "Не удалось опросить VXLAN на сервере" })
}
})
}
+14 -1
View File
@@ -1,5 +1,5 @@
import assert from "node:assert/strict"
import { mapVxlanRow } from "./vxlan-live.js"
import { mapVxlanRow, pollVxlanServers } from "./vxlan-live.js"
const server = {
id: 7,
@@ -41,3 +41,16 @@ assert.deepEqual(row.remoteVteps, ["10.0.1.1", "10.0.2.1"])
assert.equal(row.vtepIp, "10.0.0.1")
console.log("vxlan-live.test.ts: ok")
const mixed = await pollVxlanServers([server, { ...server, id: 8, name: "failed" }], async s => {
if (s.id === 8) throw new Error("device unavailable")
return [row]
})
assert.deepEqual(mixed.tunnels, [row])
assert.deepEqual(mixed.failures, [{ serverId: "8", serverName: "failed", error: "device unavailable" }])
const empty = await pollVxlanServers([server], async () => [])
assert.deepEqual(empty, { tunnels: [], failures: [] })
const failed = await pollVxlanServers([server], async () => { throw new Error("all failed") })
assert.equal(failed.tunnels.length, 0)
assert.equal(failed.failures.length, 1)
console.log("vxlan polling: mixed/empty/all-failed ok")
+22 -19
View File
@@ -93,33 +93,36 @@ async function fetchVxlanForServer(server: ServerRow): Promise<VxlanTunnelLive[]
const client = MikrotikClient.fromServer(server)
const [vxRaw, vtepRaw] = await Promise.all([
client.get<RosVxlan[]>("/interface/vxlan"),
client.get<RosVxlanVtep[]>("/interface/vxlan/vteps").catch(() => [] as RosVxlanVtep[]),
client.get<RosVxlanVtep[]>("/interface/vxlan/vteps"),
])
const list = Array.isArray(vxRaw) ? vxRaw : []
const vteps = Array.isArray(vtepRaw) ? vtepRaw : []
if (!Array.isArray(vxRaw) || !Array.isArray(vtepRaw)) throw new Error("Некорректный ответ VXLAN устройства")
const list = vxRaw
const vteps = vtepRaw
return list.map((vx, idx) => mapVxlanRow(server, vx, vteps, idx))
}
export async function listVxlanTunnels(): Promise<VxlanTunnelLive[]> {
export async function pollVxlanServers(
serverRows: ServerRow[],
fetchServer: (server: ServerRow) => Promise<VxlanTunnelLive[]> = fetchVxlanForServer,
): Promise<{ tunnels: VxlanTunnelLive[]; failures: Array<{ serverId: string; serverName: string; error: string }> }> {
const results = await Promise.all(serverRows.map(async server => {
try { return { tunnels: await fetchServer(server), failures: [] } }
catch (error) { return { tunnels: [], failures: [{ serverId: String(server.id), serverName: server.name, error: error instanceof Error ? error.message : String(error) }] } }
}))
return { tunnels: results.flatMap(result => result.tunnels), failures: results.flatMap(result => result.failures) }
}
export async function listVxlanResult() {
const enabledServers = await db.select().from(servers).where(eq(servers.enabled, true))
const results = await Promise.all(
enabledServers.map(async (server) => {
try {
return await fetchVxlanForServer(server)
} catch {
return [] as VxlanTunnelLive[]
}
}),
)
return results.flat()
return pollVxlanServers(enabledServers)
}
export async function listVxlanTunnels(): Promise<VxlanTunnelLive[]> {
return (await listVxlanResult()).tunnels
}
export async function listVxlanTunnelsForServer(server: ServerRow): Promise<VxlanTunnelLive[]> {
try {
return await fetchVxlanForServer(server)
} catch {
return []
}
return fetchVxlanForServer(server)
}
export async function countVxlanTunnels(): Promise<number> {