fix(checkmk): use monitoring collection endpoint, batch state fetch, clearer Settings hints

This commit is contained in:
Brückner
2026-06-04 14:48:15 +02:00
parent 59f11356ec
commit 626871213d
2 changed files with 38 additions and 25 deletions

View File

@ -712,14 +712,15 @@ async function startServer() {
} }
const authHeader = `Bearer ${CHECKMK_API_USER} ${CHECKMK_API_SECRET}`; const authHeader = `Bearer ${CHECKMK_API_USER} ${CHECKMK_API_SECRET}`;
const headers = { Authorization: authHeader, Accept: 'application/json' };
// Step 1: build IP → hostname map from CheckMK host configurations // Step 1: build IP → hostname map from host configuration
// Falls back to effective_attributes for IPs inherited from a parent folder. // Checks both attributes (explicitly set) and effective_attributes (inherited).
let ipToHostname: Map<string, string>; let ipToHostname: Map<string, string>;
try { try {
const cfgRes = await fetch( const cfgRes = await fetch(
`${CHECKMK_API_URL}/domain-types/host_config/collections/all`, `${CHECKMK_API_URL}/domain-types/host_config/collections/all`,
{ headers: { Authorization: authHeader, Accept: 'application/json' } } { headers }
); );
if (!cfgRes.ok) throw new Error(checkmkHttpHint(cfgRes.status)); if (!cfgRes.ok) throw new Error(checkmkHttpHint(cfgRes.status));
const cfgData = await cfgRes.json(); const cfgData = await cfgRes.json();
@ -739,7 +740,32 @@ async function startServer() {
return; return;
} }
// Step 2: update each device based on IP lookup and log status changes // Step 2: fetch live monitoring state for all hosts in one call.
// /domain-types/host/collections/all returns monitoring objects with extensions.state
// where 0=UP, 1=DOWN, 2=UNREACHABLE.
let hostnameToState: Map<string, number>;
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();
hostnameToState = new Map<string, number>();
for (const host of monData?.value ?? []) {
const name: string | undefined = host?.id;
const state: number | undefined = host?.extensions?.state;
if (name !== undefined && state !== undefined) hostnameToState.set(name, state);
}
} 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
const rows = db.prepare('SELECT id, hostname, ip, status FROM devices').all() as { id: string; hostname: string; ip: string; status: string }[]; 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 }; const counts = { online: 0, offline: 0, unknown: 0 };
@ -754,28 +780,14 @@ async function startServer() {
counts.unknown++; counts.unknown++;
continue; continue;
} }
try { const state = hostnameToState.get(cmkHost);
const res = await fetch( const newStatus = state === 0 ? 'online' : state === 1 || state === 2 ? 'offline' : 'unknown';
`${CHECKMK_API_URL}/objects/host/${encodeURIComponent(cmkHost)}`, db.prepare('UPDATE devices SET status = ?, lastCheckedAt = ? WHERE id = ?').run(newStatus, now, dev.id);
{ headers: { Authorization: authHeader, Accept: 'application/json' } } if (dev.status !== newStatus) {
);
if (!res.ok) throw new Error(checkmkHttpHint(res.status));
const data = await res.json();
const hardState: number = data?.extensions?.state ?? -1;
const newStatus = hardState === 0 ? 'online' : 'offline';
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']++;
} 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 (?, ?, ?, ?, ?)') db.prepare('INSERT INTO logs (id, timestamp, type, message, deviceId) VALUES (?, ?, ?, ?, ?)')
.run(uid('log'), now, 'system', msg, dev.id); .run(uid('log'), now, 'status', `CheckMK: ${dev.hostname} (${dev.ip}) status changed to ${newStatus} (was: ${dev.status}).`, dev.id);
counts.unknown++;
} }
counts[newStatus as 'online' | 'offline' | 'unknown']++;
} }
db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)') db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)')

View File

@ -449,7 +449,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
/> />
</FieldRow> </FieldRow>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
<FieldRow label="Automation User" hint="CheckMK automation user (default: automation)"> <FieldRow label="Automation User" hint="Setup → Users → Automation user (e.g. automation)">
<Input <Input
value={checkmkApiUser} value={checkmkApiUser}
onChange={setCheckmkApiUser} onChange={setCheckmkApiUser}
@ -460,6 +460,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
</FieldRow> </FieldRow>
<FieldRow <FieldRow
label="Automation Secret" label="Automation Secret"
hint="Setup → Users → Automation user → Automation secret"
badge={checkmkSecretSet ? <ConfiguredBadge /> : undefined} badge={checkmkSecretSet ? <ConfiguredBadge /> : undefined}
> >
<SecretInput <SecretInput