Docker images / prepare-release (push) Successful in 7s
Docker images / backend-test (push) Successful in 1m40s
Docker images / frontend-image (push) Successful in 2m50s
Docker images / updater-image (push) Successful in 42s
Docker images / backend-image (push) Successful in 2m40s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 10s
- Added support for fetching and displaying server data in BGP, VXLAN, and OSPF pages, improving the overall user experience. - Introduced new backend routes for OSPF and VXLAN, allowing for better data management and retrieval. - Implemented mapping functions for backend server data to frontend types, ensuring consistency across components. - Enhanced the sidebar to display counts for BGP sessions, VXLAN tunnels, and containers, providing users with quick insights into their network status. - Updated tests to cover new functionalities and ensure reliability. Co-authored-by: Cursor <[email protected]>
137 lines
3.5 KiB
TypeScript
137 lines
3.5 KiB
TypeScript
import { eq } from "drizzle-orm"
|
|
import { db } from "../db/index.js"
|
|
import { servers } from "../db/schema.js"
|
|
import { MikrotikClient } from "./mikrotik.js"
|
|
|
|
type ServerRow = typeof servers.$inferSelect
|
|
|
|
interface RosVxlan {
|
|
".id"?: string
|
|
name?: string
|
|
vni?: string
|
|
port?: string
|
|
"local-address"?: string
|
|
"vtep-address"?: string
|
|
running?: string
|
|
disabled?: string
|
|
comment?: string
|
|
l2mtu?: string
|
|
arp?: string
|
|
"arp-proxy"?: string
|
|
"mac-learning"?: string
|
|
learning?: string
|
|
}
|
|
|
|
interface RosVxlanVtep {
|
|
".id"?: string
|
|
interface?: string
|
|
"remote-ip"?: string
|
|
}
|
|
|
|
export type VxlanTunnelLive = {
|
|
id: string
|
|
rosId: string
|
|
name: string
|
|
vni: number
|
|
port: number
|
|
dstPort: number
|
|
serverId: string
|
|
vtepIp: string
|
|
remoteVteps: string[]
|
|
l2mtu: number
|
|
arpProxy: boolean
|
|
macLearning: boolean
|
|
comment: string
|
|
enabled: boolean
|
|
status: "up" | "down"
|
|
}
|
|
|
|
function rosYes(v: string | undefined): boolean {
|
|
return v === "true" || v === "yes"
|
|
}
|
|
|
|
function parseIntSafe(v: string | undefined, fallback: number): number {
|
|
const n = Number.parseInt(v ?? "", 10)
|
|
return Number.isFinite(n) ? n : fallback
|
|
}
|
|
|
|
export function mapVxlanRow(
|
|
server: ServerRow,
|
|
vx: RosVxlan,
|
|
vteps: RosVxlanVtep[],
|
|
idx: number,
|
|
): VxlanTunnelLive {
|
|
const name = (vx.name ?? "").trim() || `vxlan-${idx + 1}`
|
|
const rosId = String(vx[".id"] ?? name)
|
|
const disabled = rosYes(vx.disabled)
|
|
const running = rosYes(vx.running)
|
|
const port = parseIntSafe(vx.port, 8472)
|
|
const remoteVteps = vteps
|
|
.filter((v) => (v.interface ?? "").trim() === name)
|
|
.map((v) => (v["remote-ip"] ?? "").trim())
|
|
.filter(Boolean)
|
|
return {
|
|
id: `${server.id}-${rosId}`,
|
|
rosId,
|
|
name,
|
|
vni: parseIntSafe(vx.vni, 0),
|
|
port: 0,
|
|
dstPort: port,
|
|
serverId: String(server.id),
|
|
vtepIp: (vx["local-address"] ?? vx["vtep-address"] ?? "").trim(),
|
|
remoteVteps,
|
|
l2mtu: parseIntSafe(vx.l2mtu, 1500),
|
|
arpProxy: rosYes(vx["arp-proxy"]) || vx.arp === "proxy-arp" || vx.arp === "enabled",
|
|
macLearning: vx["mac-learning"] != null ? rosYes(vx["mac-learning"]) : vx.learning !== "false",
|
|
comment: vx.comment ?? "",
|
|
enabled: !disabled,
|
|
status: !disabled && running ? "up" : "down",
|
|
}
|
|
}
|
|
|
|
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[]),
|
|
])
|
|
const list = Array.isArray(vxRaw) ? vxRaw : []
|
|
const vteps = Array.isArray(vtepRaw) ? vtepRaw : []
|
|
return list.map((vx, idx) => mapVxlanRow(server, vx, vteps, idx))
|
|
}
|
|
|
|
export async function listVxlanTunnels(): Promise<VxlanTunnelLive[]> {
|
|
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()
|
|
}
|
|
|
|
export async function listVxlanTunnelsForServer(server: ServerRow): Promise<VxlanTunnelLive[]> {
|
|
try {
|
|
return await fetchVxlanForServer(server)
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
export async function countVxlanTunnels(): Promise<number> {
|
|
try {
|
|
const result = await Promise.race([
|
|
listVxlanTunnels(),
|
|
new Promise<null>((resolve) => setTimeout(() => resolve(null), 8_000)),
|
|
])
|
|
if (!result) return 0
|
|
return result.length
|
|
} catch {
|
|
return 0
|
|
}
|
|
}
|