fix(ui): показывать частичные сбои опроса VXLAN
This commit is contained in:
1 parent
4dc2ee2129
commit
e163a9ed9d
9 files changed
+120
-41
No files matched your search
@@ -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 на сервере" })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -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> {
|
||||
|
||||
Reference in new issue
Block a user