import * as net from "node:net"; import type { UptimeKumaMonitor } from "../integrations/uptimekuma/adapter.js"; /** The columns of a server row this needs, so it can be tested without a live database. */ export interface MatchableServer { id: number; name: string; hostname: string | null; ips: string[]; } const clean = (s: string) => s.trim().toLowerCase().replace(/\.$/, ""); const firstLabel = (s: string) => clean(s).split(".")[0]; /** * Which of these servers a monitor's target host belongs to — an IP is matched against * each server's reported addresses; anything else is matched as a hostname, against the * server's own hostname or its short name (so "elsa" and "elsa.lab.example" both work). * Returns null rather than guessing when nothing lines up. */ export function matchServerForTarget(target: string | null, servers: MatchableServer[]): MatchableServer | null { if (!target) return null; const t = target.trim(); if (!t) return null; if (net.isIP(t) !== 0) { const ip = t.toLowerCase(); return servers.find((s) => s.ips.some((a) => a.toLowerCase() === ip)) ?? null; } const name = clean(t); const short = firstLabel(t); return ( servers.find((s) => s.hostname && clean(s.hostname) === name) ?? servers.find((s) => clean(s.name) === short) ?? null ); } export interface MonitorWithServer extends UptimeKumaMonitor { matchedServer: { id: number; name: string } | null; } export function attachMatchedServers(monitors: UptimeKumaMonitor[], servers: MatchableServer[]): MonitorWithServer[] { return monitors.map((m) => { const match = matchServerForTarget(m.target, servers); return { ...m, matchedServer: match ? { id: match.id, name: match.name } : null }; }); } /** A server row as selected with {id, name, hostname, ips: } — turns it into what matchServerForTarget needs. */ export interface ServerRow { id: number; name: string; hostname: string | null; ips: string | null; } function parseServerIps(stored: string | null): string[] { if (!stored) return []; try { const value = JSON.parse(stored); return Array.isArray(value) ? value.filter((v): v is string => typeof v === "string") : []; } catch { return []; } } export function toMatchableServers(rows: ServerRow[]): MatchableServer[] { return rows.map((r) => ({ id: r.id, name: r.name, hostname: r.hostname, ips: parseServerIps(r.ips) })); } export interface MonitorSummary { total: number; up: number; down: number; pending: number; maintenance: number; unknown: number; } export function summarizeMonitors(monitors: UptimeKumaMonitor[]): MonitorSummary { const summary: MonitorSummary = { total: monitors.length, up: 0, down: 0, pending: 0, maintenance: 0, unknown: 0 }; for (const m of monitors) summary[m.status]++; return summary; }