feat(cluster): Cluster-Knoten ohne Agent erkennen (#022, #018)

Feldtest auf zwei echten Proxmox-VE-9.2-Hosts (#018): Installation, Timer,
Zwischenspeicher und Nachsenden laufen wie vorgesehen. `pvesubscription get`
kennt kein JSON, ohne Subscription fehlen fast alle Felder, `pveversion`
enthaelt den Kernel doppelt - alles im Agent beruecksichtigt.

Dabei zeigte sich: in Proxmox hat jeder Knoten eine eigene Subscription.
Der Agent meldet darum die Cluster-Mitglieder aus `pvecm nodes`, und eine
elfte Warnregel meldet Knoten, von denen sich kein Agent meldet (#022).

Ausserdem behoben: die Bilder des Handbuchs zeigten allesamt die
Anmeldeseite, weil Firefox eine selbst erzeugte Cookie-Datei stillschweigend
verwirft. Das Skript nutzt jetzt einen lokalen Vermittler und bricht ab,
wenn alle Aufnahmen gleich gross sind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-18 18:05:08 +02:00
co-authored by Claude Opus 5
parent dc685980d8
commit 7728fcb565
39 changed files with 484 additions and 82 deletions
+36
View File
@@ -22,6 +22,8 @@ export interface AlertKandidat {
export interface HostDaten {
id: string;
hostname: string;
clusterName: string | null;
clusterMitglieder: string[];
kundeId: string | null;
kundeName: string | null;
subKey: string | null;
@@ -287,6 +289,39 @@ const hostOhneKunde: Regel = {
},
};
const clusterKnotenOhneAgent: Regel = {
name: "cluster-knoten-ohne-agent",
beschreibung: "Ein Cluster-Knoten meldet sich nicht beim Dashboard",
standardSchwellwerte: {},
pruefe(kontext) {
// Alle gemeldeten Hostnamen – ein Knoten gilt als bekannt, wenn er selbst meldet.
const meldende = new Set(kontext.hosts.map((host) => host.hostname.toLowerCase()));
const fehlende = new Map<string, { cluster: string; melder: HostDaten }>();
for (const host of kontext.hosts) {
if (!host.clusterName) continue;
for (const mitglied of host.clusterMitglieder) {
const name = mitglied.trim();
if (!name || meldende.has(name.toLowerCase())) continue;
if (!fehlende.has(name.toLowerCase())) {
fehlende.set(name.toLowerCase(), { cluster: host.clusterName, melder: host });
}
}
}
return [...fehlende.entries()].map(([name, eintrag]) => ({
fingerprint: `cluster-knoten-ohne-agent:${eintrag.cluster}:${name}`,
regel: "cluster-knoten-ohne-agent",
schwere: "WARNUNG" as AlertSchwere,
titel: "Cluster-Knoten ohne Agent",
beschreibung: `${name} gehört zum Cluster ${eintrag.cluster} (gemeldet von ${eintrag.melder.hostname}), meldet sich aber nicht – dort fehlt der Agent`,
hostId: eintrag.melder.id,
kundeId: eintrag.melder.kundeId,
laufzeitId: null,
}));
},
};
const proxmoxRechnungFaellig: Regel = {
name: "proxmox-rechnung-faellig",
beschreibung: "Proxmox-Rechnung ist unbezahlt und wird fällig",
@@ -378,6 +413,7 @@ export const REGELN: Regel[] = [
serverIdGeaendert,
keyMehrfach,
hostOhneKunde,
clusterKnotenOhneAgent,
proxmoxRechnungFaellig,
kundenrechnungFehlt,
kundenrechnungUnbezahlt,