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
+1 -63
View File
@@ -6,6 +6,7 @@ import { apiGet } from '../api.js';
import { SkeletonCards } from '../components/Skeleton.jsx';
import { IconGame, IconMapPin, IconClock, IconCheck } from '../icons.jsx';
import { useT, useFmt } from '../i18n.jsx';
import UptimeTrack from '../components/UptimeTrack.jsx';
const UHRZEIT = { hour: '2-digit', minute: '2-digit' };
@@ -38,69 +39,6 @@ function HistoryChart({ history, max }) {
);
}
/**
* Erreichbarkeits-Balken: ein Strich pro Stunde der letzten 7 Tage.
*
* Der Server liefert nur Stunden, in denen wirklich gemessen wurde. Fehlende
* werden hier als Lücke gezeichnet statt als „online" — sonst sähe eine
* Bot-Auszeit aus wie ein einwandfrei laufender Server.
*/
function UptimeTrack({ buckets }) {
const { t } = useT();
const { tag, datum } = useFmt();
if (!buckets?.length) return null;
const proStunde = new Map(buckets.map((b) => [b.stunde, b]));
const jetzt = new Date();
jetzt.setMinutes(0, 0, 0);
const striche = [];
let gemessen = 0;
let erreichbar = 0;
for (let i = 167; i >= 0; i--) {
const d = new Date(jetzt.getTime() - i * 3600000);
// Schlüssel wie strftime('%Y-%m-%dT%H') in SQLite — das rechnet in UTC,
// hier also bewusst auch. Angezeigt wird gleich die Ortszeit, sonst
// stünde im Tooltip eine Stunde, die niemand wiedererkennt.
const key = d.toISOString().slice(0, 13);
const b = proStunde.get(key);
let art = 'leer';
if (b) {
gemessen += b.proben;
erreichbar += b.an;
art = b.an === b.proben ? 'gut' : b.an === 0 ? 'aus' : 'teil';
}
striche.push({ key, zeit: d, art, b });
}
const quote = gemessen > 0 ? (erreichbar / gemessen) * 100 : null;
const titel = (s) => {
const wann = datum(s.zeit, { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
return s.b
? `${wann}${Math.round((s.b.an / s.b.proben) * 100)} %`
: `${wann}${t('server.noData')}`;
};
return (
<div className="up-track-wrap">
<div className="up-track">
{striche.map((s) => (
<span className={`up-tick ${s.art}`} key={s.key} title={titel(s)} />
))}
</div>
<div className="up-track-foot">
<span>{t('server.days7')}</span>
{quote != null && (
<span className={quote >= 99 ? 'up-good' : quote >= 90 ? '' : 'up-bad'}>
{quote.toLocaleString(tag, { minimumFractionDigits: 1, maximumFractionDigits: 1 })} %
</span>
)}
<span>{t('server.now')}</span>
</div>
</div>
);
}
/** Spieler-Peak aus den 24h-Samples (Uptime kommt aus dem Balken oben) */
function historyStats(history) {
if (history.length === 0) return null;