From 985178ea8464df0d8cbd09b787eab771a323fdd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Br=C3=BCckner?= Date: Thu, 4 Jun 2026 15:27:21 +0200 Subject: [PATCH] fix(checkmk): per-host state lookup via /objects/host/{name}, remove batch collection call --- server.ts | 83 +++++++++++++++++++++++-------------------------------- 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/server.ts b/server.ts index ce3f773..a3b2338 100644 --- a/server.ts +++ b/server.ts @@ -740,51 +740,13 @@ async function startServer() { return; } - // Step 2: fetch live monitoring state for all hosts in one call. - let hostnameToState: Map; - try { - const monRes = await fetch( - `${CHECKMK_API_URL}/domain-types/host/collections/all`, - { headers } - ); - if (!monRes.ok) throw new Error(checkmkHttpHint(monRes.status)); - const monData = await monRes.json(); - - // Log raw response so we can see the actual structure (first 500 chars). - db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)') - .run(uid('log'), now, 'system', - `CheckMK monitoring raw response: ${JSON.stringify(monData).substring(0, 500)}`); - - hostnameToState = new Map(); - // Try multiple possible container keys used by different CheckMK versions. - const candidates: any[] = Array.isArray(monData?.value) - ? monData.value - : Array.isArray(monData?.members) - ? monData.members.map((m: any) => m?.value ?? m) - : []; - for (const host of candidates) { - const name: string | undefined = host?.id ?? host?.extensions?.name; - const state: number | undefined = host?.extensions?.state; - if (name !== undefined && state !== undefined) hostnameToState.set(name, state); - } - - if (hostnameToState.size === 0 && ipToHostname.size > 0) { - db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)') - .run(uid('log'), now, 'system', - `CheckMK: parsed 0 monitoring hosts from response (candidates array length: ${candidates.length}). ` + - 'Check the raw response entry above to identify the correct JSON path.'); - } - } catch (err: any) { - const msg = `CheckMK sync failed — could not fetch monitoring states: ${err?.message ?? err}`; - console.error('[CheckMK]', msg); - db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)') - .run(uid('log'), now, 'system', msg); - return; - } - - // Step 3: update each device based on IP lookup + live state + // Step 2: for each device, look up its CheckMK hostname by IP, then query the + // individual monitoring object at /objects/host/{name} for the live state. + // The collection endpoint (/domain-types/host/collections/all) only returns + // minimal fields without state, so per-host calls are required. const rows = db.prepare('SELECT id, hostname, ip, status FROM devices').all() as { id: string; hostname: string; ip: string; status: string }[]; const counts = { online: 0, offline: 0, unknown: 0 }; + let loggedFirstExtensions = false; for (const dev of rows) { const cmkHost = ipToHostname.get(dev.ip); @@ -797,14 +759,37 @@ async function startServer() { counts.unknown++; continue; } - const state = hostnameToState.get(cmkHost); - const newStatus = state === 0 ? 'online' : state === 1 || state === 2 ? 'offline' : 'unknown'; - db.prepare('UPDATE devices SET status = ?, lastCheckedAt = ? WHERE id = ?').run(newStatus, now, dev.id); - if (dev.status !== newStatus) { + try { + const hostRes = await fetch( + `${CHECKMK_API_URL}/objects/host/${encodeURIComponent(cmkHost)}`, + { headers } + ); + if (!hostRes.ok) throw new Error(checkmkHttpHint(hostRes.status)); + const hostData = await hostRes.json(); + + // Log raw extensions once so the state field path can be verified. + if (!loggedFirstExtensions) { + loggedFirstExtensions = true; + db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)') + .run(uid('log'), now, 'system', + `CheckMK per-host raw extensions for "${cmkHost}": ${JSON.stringify(hostData?.extensions ?? hostData).substring(0, 400)}`); + } + + const state: number = hostData?.extensions?.state ?? -1; + const newStatus = state === 0 ? 'online' : state === 1 || state === 2 ? 'offline' : 'unknown'; + db.prepare('UPDATE devices SET status = ?, lastCheckedAt = ? WHERE id = ?').run(newStatus, now, dev.id); + if (dev.status !== newStatus) { + db.prepare('INSERT INTO logs (id, timestamp, type, message, deviceId) VALUES (?, ?, ?, ?, ?)') + .run(uid('log'), now, 'status', `CheckMK: ${dev.hostname} (${dev.ip}) status changed to ${newStatus} (was: ${dev.status}).`, dev.id); + } + counts[newStatus as 'online' | 'offline' | 'unknown']++; + } catch (err: any) { + const msg = `CheckMK: status sync failed for ${dev.hostname} (${dev.ip}) — ${err?.message ?? err}`; + console.error('[CheckMK]', msg); db.prepare('INSERT INTO logs (id, timestamp, type, message, deviceId) VALUES (?, ?, ?, ?, ?)') - .run(uid('log'), now, 'status', `CheckMK: ${dev.hostname} (${dev.ip}) status changed to ${newStatus} (was: ${dev.status}).`, dev.id); + .run(uid('log'), now, 'system', msg, dev.id); + counts.unknown++; } - counts[newStatus as 'online' | 'offline' | 'unknown']++; } db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)')