feat: log login events in logbook; improve CheckMK error reporting

Successful logins now write a system-type logbook entry.
CheckMK sync reports configuration errors, host-fetch failures,
and per-device sync failures as logbook entries instead of silently
dropping them; inherits effective_attributes IP fallback.
This commit is contained in:
Brückner
2026-06-04 14:21:05 +02:00
parent 1289e2476c
commit e13e11ce6a

View File

@ -125,6 +125,10 @@ async function startServer() {
const token = jwt.sign({ userId: row.id, email: row.email }, JWT_SECRET, { expiresIn: JWT_EXPIRY });
const user: User = { id: row.id, name: row.name, role: row.role, email: row.email };
const logId = uid("log");
db.prepare(`INSERT INTO logs (id, timestamp, type, message, deviceId, userId) VALUES (?, ?, ?, ?, ?, ?)`)
.run(logId, new Date().toISOString(), 'system', `${row.name} logged in.`, null, row.id);
res.json({ token, user });
} catch (err: any) {
res.status(500).json({ error: err.message });
@ -686,36 +690,51 @@ async function startServer() {
// in Settings take effect on the next cycle without a server restart.
// -------------------------------------------------------------
async function syncCheckMkStatuses() {
const now = new Date().toISOString();
if (getSetting('checkmk_enabled') !== 'true') return;
const CHECKMK_API_URL = getSetting('checkmk_api_url') || process.env.CHECKMK_API_URL;
const CHECKMK_API_USER = getSetting('checkmk_api_user') || process.env.CHECKMK_API_USER || 'automation';
const CHECKMK_API_SECRET = getSetting('checkmk_api_secret') || process.env.CHECKMK_API_SECRET;
if (!CHECKMK_API_URL || !CHECKMK_API_SECRET) return;
if (!CHECKMK_API_URL || !CHECKMK_API_SECRET) {
db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)')
.run(uid('log'), now, 'system', 'CheckMK sync skipped — API URL or secret not configured. Check Settings.');
return;
}
const authHeader = `Bearer ${CHECKMK_API_USER} ${CHECKMK_API_SECRET}`;
// Step 1: build IP → hostname map from CheckMK host configurations
// Falls back to effective_attributes for IPs inherited from a parent folder.
let ipToHostname: Map<string, string>;
try {
const cfgRes = await fetch(
`${CHECKMK_API_URL}/domain-types/host_config/collections/all`,
{ headers: { Authorization: `Bearer ${CHECKMK_API_USER} ${CHECKMK_API_SECRET}`, Accept: 'application/json' } }
{ headers: { Authorization: authHeader, Accept: 'application/json' } }
);
if (!cfgRes.ok) throw new Error(`HTTP ${cfgRes.status}`);
if (!cfgRes.ok) throw new Error(`HTTP ${cfgRes.status}${await cfgRes.text()}`);
const cfgData = await cfgRes.json();
ipToHostname = new Map<string, string>();
for (const host of cfgData?.value ?? []) {
const ip: string | undefined = host?.extensions?.attributes?.ipaddress;
const ext = host?.extensions;
const ip: string | undefined =
ext?.attributes?.ipaddress || ext?.effective_attributes?.ipaddress;
const name: string | undefined = host?.id;
if (ip && name) ipToHostname.set(ip, name);
}
} catch (err) {
console.error('[CheckMK] Failed to fetch host configs:', err);
} catch (err: any) {
const msg = `CheckMK sync failed — could not fetch host list: ${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 2: update each device based on IP lookup and log status changes
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 now = new Date().toISOString();
for (const dev of rows) {
const cmkHost = ipToHostname.get(dev.ip);
@ -731,9 +750,9 @@ async function startServer() {
try {
const res = await fetch(
`${CHECKMK_API_URL}/objects/host/${encodeURIComponent(cmkHost)}`,
{ headers: { Authorization: `Bearer ${CHECKMK_API_USER} ${CHECKMK_API_SECRET}`, Accept: 'application/json' } }
{ headers: { Authorization: authHeader, Accept: 'application/json' } }
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
if (!res.ok) throw new Error(`HTTP ${res.status}${await res.text()}`);
const data = await res.json();
const hardState: number = data?.extensions?.state ?? -1;
const newStatus = hardState === 0 ? 'online' : 'offline';
@ -743,13 +762,15 @@ async function startServer() {
.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) {
console.error(`[CheckMK] Status sync failed for ${dev.hostname} (${dev.ip}):`, err);
} 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, 'system', msg, dev.id);
counts.unknown++;
}
}
// Summary log entry for every sync run
db.prepare('INSERT INTO logs (id, timestamp, type, message) VALUES (?, ?, ?, ?)')
.run(uid('log'), now, 'system',
`CheckMK sync completed — ${counts.online} online, ${counts.offline} offline, ${counts.unknown} unknown (${rows.length} devices total).`);