import { desc, eq } from "drizzle-orm" import { db } from "../db/index.js" import { servers, serverSnapshots } from "../db/schema.js" import type { SnapshotInsert } from "../db/schema.js" import type { SnapshotRead } from "../types/server.js" import { MikrotikClient } from "./mikrotik.js" // ── pollServer ───────────────────────────────────────────────────────────────── /** * Connect to a RouterOS device, collect system info, persist a snapshot, * and sync the server's display name from system/identity. */ export async function pollServer(serverId: number): Promise { const server = db .select() .from(servers) .where(eq(servers.id, serverId)) .limit(1) .all()[0] if (!server) { throw new Error(`Server with id=${serverId} not found`) } const now = new Date().toISOString() const client = MikrotikClient.fromServer(server) const t0 = performance.now() const partialSnap: Partial = { serverId, polledAt: now, status: "offline", latencyMs: null, } try { // Fire all requests in parallel for speed const [identity, resource, ifaces, addresses] = await Promise.all([ client.getIdentity(), client.getResource(), client.getInterfaces(), client.getIpAddresses(), ]) const latencyMs = performance.now() - t0 // Parse RouterOS string values (RouterOS REST API returns everything as strings) const cpuLoad = parseInt(resource["cpu-load"], 10) const freeMem = parseInt(resource["free-memory"], 10) const totalMem = parseInt(resource["total-memory"], 10) Object.assign(partialSnap, { status: "online", latencyMs, identityName: identity.name, rosVersion: resource["version"], boardName: resource["board-name"], uptime: resource["uptime"], cpuLoad: isNaN(cpuLoad) ? null : cpuLoad, freeMemory: isNaN(freeMem) ? null : freeMem, totalMemory: isNaN(totalMem) ? null : totalMem, rawInterfaces: JSON.stringify(ifaces), rawIpAddresses: JSON.stringify(addresses), } satisfies Partial) // Keep server.name in sync with RouterOS identity db.update(servers) .set({ name: identity.name, updatedAt: now }) .where(eq(servers.id, serverId)) .run() } catch (err) { // Log but don't throw — we still persist the offline snapshot console.warn(`[poller] server id=${serverId} unreachable:`, (err as Error).message) } const [inserted] = db .insert(serverSnapshots) .values(partialSnap as SnapshotInsert) .returning() .all() return toSnapshotRead(inserted) } // ── helper ───────────────────────────────────────────────────────────────────── export function toSnapshotRead(s: typeof serverSnapshots.$inferSelect): SnapshotRead { return { id: s.id, serverId: s.serverId, polledAt: s.polledAt, status: s.status, latencyMs: s.latencyMs ?? null, rosVersion: s.rosVersion ?? null, boardName: s.boardName ?? null, uptime: s.uptime ?? null, cpuLoad: s.cpuLoad ?? null, freeMemory: s.freeMemory ?? null, totalMemory: s.totalMemory ?? null, identityName: s.identityName ?? null, } }