Watchdog: Embeds statt Textzeilen, dazu eine Uebersicht im Panel
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

Die DM-Alarme waren als einzige Meldung noch reiner Text — direkt neben dem
Raid-Alarm, der ein Embed ist. Jetzt beide im selben Look, mit Ausfalldauer
bzw. Fehlergrund und Antwortzeit als Felder.

Uebersicht im Panel (System, unter den Watchdog-URLs): je Adresse ein
Erreichbarkeits-Balken ueber 7 Tage, gruen/rot/grau wie bei den Game-Servern,
dazu Status, Antwortzeit und seit wann etwas weg ist.

Dafuer schreibt der Waechter jetzt mit — vorher lebte sein Zustand nur im
Speicher und war nach jedem Neustart weg. Neue Tabelle watchdog_history,
gleiche Bauart und gleiche 7-Tage-Aufbewahrung wie player_history, stundenweise
zusammengefasst statt roh ausgeliefert.

Wichtig dabei: aufgezeichnet wird jeder Check, nicht nur die Wechsel. Sonst
saehen ruhige Stunden im Balken aus wie Messluecken.

Der Balken ist aus Server.jsx in components/UptimeTrack.jsx gewandert, damit
oeffentliche Seite und Panel denselben benutzen. Er zeigt die Antwortzeit im
Tooltip nur, wo sie erhoben wird — bei Game-Servern gibt es keine.

Der Endpunkt /api/watchdog liegt hinter der Rechtepruefung, nicht oeffentlich:
in der Adressliste koennen interne Dienste stehen.

Zwei fehlende Importe in api.js gefunden und ergaenzt (moduleEnabled, tuning) —
der erste Aufruf der Route waere sonst mit ReferenceError gestorben.

Geprueft: alle 171 SQL-Abfragen gegen das Schema, die drei neuen inklusive.
Im Browser mit drei Faellen — laeuft, ist weg, noch nie geprueft: Punktfarbe,
Quote, Ausfallstunden und Messluecken stimmen, die ungepruefte Adresse zeigt
korrekt gar keinen Balken. Oeffentliche Server-Seite nach dem Umbau
gegengeprueft.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 05:28:22 +02:00
co-authored by Claude Fable 5
parent 2d4afad3cb
commit fd2d31f07f
7 changed files with 222 additions and 78 deletions
+49 -12
View File
@@ -1,23 +1,31 @@
// Watchdog: prüft konfigurierte URLs und schickt dem Admin eine DM bei Ausfall.
// Watchdog: prüft konfigurierte URLs, meldet Ausfälle per DM und schreibt
// jeden Check mit — daraus baut die Übersicht im Panel ihren Uptime-Balken.
// URLs kommen aus dem Setting watchdog_urls (Config-Seite), leer = deaktiviert.
import { getSetting } from '../db.js';
import { getSetting, recordWatchdogSample, pruneWatchdogHistory } from '../db.js';
import { config } from '../config.js';
import { moduleEnabled } from '../modules.js';
import { brandEmbed } from '../embeds.js';
import { tuning, tuningMs, everyTuned } from '../tuning.js';
// URL → { fails, down, since }
const state = new Map();
function watchedUrls() {
/** Aktueller Stand je Adresse — für die Übersicht im Panel */
export function watchdogState() {
return state;
}
export function watchedUrls() {
return (getSetting('watchdog_urls') ?? '')
.split(',')
.map((s) => s.trim())
.filter((s) => /^https?:\/\//.test(s));
}
async function dmAdmin(client, content) {
const user = await client.users.fetch(config.adminDiscordId);
await user.send(content);
async function dmAdmin(client, embed) {
if (!config.adminDiscordId) return;
const user = await client.users.fetch(config.adminDiscordId).catch(() => null);
await user?.send({ embeds: [embed] }).catch(() => {});
}
/** Ein Prüfdurchlauf — exportiert für Tests und den Intervall-Timer */
@@ -28,6 +36,8 @@ export async function watchdogTick(client, { failsBeforeAlert = tuning('watchdog
let ok = false;
let detail = '';
let status = null;
const start = Date.now();
try {
const res = await fetch(url, {
method: 'GET',
@@ -36,29 +46,56 @@ export async function watchdogTick(client, { failsBeforeAlert = tuning('watchdog
headers: { 'User-Agent': 'd4rkbot-watchdog/1.0' },
});
ok = res.status < 500;
status = res.status;
detail = `HTTP ${res.status}`;
} catch (error) {
detail = error.cause?.code ?? error.name ?? 'Fehler';
}
const dauer = Date.now() - start;
// Immer mitschreiben, nicht nur bei Wechseln — der Balken braucht
// durchgehende Messpunkte, sonst sähen ruhige Stunden aus wie Lücken.
try {
recordWatchdogSample(url, ok, ok ? dauer : null);
} catch (error) {
console.error('[watchdog] Messpunkt nicht gespeichert:', error.message);
}
if (ok) {
if (s.down) {
const minutes = Math.max(1, Math.round((Date.now() - s.since) / 60000));
await dmAdmin(client, `✅ **${url}** ist wieder erreichbar (Downtime: ~${minutes} min).`).catch(() => {});
const minuten = Math.max(1, Math.round((Date.now() - s.since) / 60000));
await dmAdmin(client, brandEmbed(client, 'WATCHDOG')
.setTitle('✅ Wieder erreichbar')
.setDescription(`**${url}**`)
.addFields(
{ name: 'Ausfall', value: `~${minuten} min`, inline: true },
{ name: 'Antwort', value: `${detail} · ${dauer} ms`, inline: true },
));
console.log(`[watchdog] ${url} wieder ok`);
}
state.set(url, { fails: 0, down: false, since: null });
state.set(url, { fails: 0, down: false, since: null, status, ms: dauer, geprueft: Date.now() });
} else {
const fails = s.fails + 1;
if (!s.down && fails >= failsBeforeAlert) {
await dmAdmin(client, `🚨 **${url}** ist DOWN (${detail}) — seit ${failsBeforeAlert} Checks nicht erreichbar!`).catch(() => {});
await dmAdmin(client, brandEmbed(client, 'WATCHDOG', { secondary: true })
.setTitle('🚨 Nicht erreichbar')
.setDescription(`**${url}**`)
.addFields(
{ name: 'Fehler', value: detail, inline: true },
{ name: 'Versuche', value: `${fails} in Folge`, inline: true },
));
console.warn(`[watchdog] ${url} down (${detail})`);
state.set(url, { fails, down: true, since: Date.now() });
state.set(url, { fails, down: true, since: Date.now(), status, ms: null, geprueft: Date.now() });
} else {
state.set(url, { ...s, fails });
state.set(url, { ...s, fails, status, ms: null, geprueft: Date.now() });
}
}
}
// Alte Messpunkte wegräumen, gleiche Aufbewahrung wie beim Game-Server
try {
pruneWatchdogHistory();
} catch { /* nicht kritisch, beim nächsten Durchlauf wieder */ }
}
export function startWatchdog(client) {