import { eq, gt } from "drizzle-orm"; import { db } from "../db/client.js"; import { dnsProviders, integrations, maintenanceWindows, servers } from "../db/schema.js"; export type ActiveWindow = typeof maintenanceWindows.$inferSelect; /** Windows whose end is still in the future. ISO strings compare correctly as text, so this is a plain string comparison. */ export async function listActiveWindows(now: Date = new Date()): Promise { return db.select().from(maintenanceWindows).where(gt(maintenanceWindows.endsAt, now.toISOString())); } /** * The identity a health condition or alert belongs to, as "server:3" / * "integration:1" / "dns_provider:2", so it can be matched against active windows. * Derived from the condition key (which already encodes it) rather than stored, * so conditions persisted by an earlier version still map correctly. */ export function subjectOfConditionKey(key: string): string | null { let m = /^(?:offline|disk):server:(\d+)(?::|$)/.exec(key); if (m) return `server:${m[1]}`; m = /^disk:proxmox:(\d+):/.exec(key); if (m) return `integration:${m[1]}`; m = /^(?:synology-volume|synology-disk|disk:synology):(\d+):/.exec(key); if (m) return `integration:${m[1]}`; m = /^automation:(?:semaphore|gitea):(\d+):/.exec(key); if (m) return `integration:${m[1]}`; return null; } export async function activeSubjects(now: Date = new Date()): Promise> { return new Set((await listActiveWindows(now)).map((w) => `${w.targetType}:${w.targetId}`)); } export async function isInMaintenance(targetType: ActiveWindow["targetType"], targetId: number, now: Date = new Date()): Promise { return (await activeSubjects(now)).has(`${targetType}:${targetId}`); } /** * Integration-failure alerts are tracked per service TYPE ("proxmox", * "cloudflare", ...), not per configured instance, so a window on any * integration or DNS provider of that type silences that type's failure alerts. * (With two integrations of one type, a real failure on the un-windowed one is * silenced too while the window is open — a known consequence of that * granularity, stated on the Maintenance page.) */ export async function isSourceInMaintenance(source: string, now: Date = new Date()): Promise { const windows = await listActiveWindows(now); if (windows.length === 0) return false; for (const w of windows) { if (w.targetType === "integration") { const [row] = await db.select({ type: integrations.type }).from(integrations).where(eq(integrations.id, w.targetId)).limit(1); if (row?.type === source) return true; } else if (w.targetType === "dns_provider") { const [row] = await db.select({ type: dnsProviders.providerType }).from(dnsProviders).where(eq(dnsProviders.id, w.targetId)).limit(1); if (row?.type === source) return true; } } return false; } /** Display name for a window's target, or null if the target no longer exists. */ export async function describeTarget(targetType: ActiveWindow["targetType"], targetId: number): Promise<{ name: string; kind: string } | null> { if (targetType === "server") { const [r] = await db.select({ name: servers.name }).from(servers).where(eq(servers.id, targetId)).limit(1); return r ? { name: r.name, kind: "Server" } : null; } if (targetType === "integration") { const [r] = await db.select({ name: integrations.name, type: integrations.type }).from(integrations).where(eq(integrations.id, targetId)).limit(1); return r ? { name: r.name, kind: `Integration (${r.type})` } : null; } const [r] = await db.select({ name: dnsProviders.name, type: dnsProviders.providerType }).from(dnsProviders).where(eq(dnsProviders.id, targetId)).limit(1); return r ? { name: r.name, kind: `DNS provider (${r.type})` } : null; }