Watchdog: Embeds statt Textzeilen, dazu eine Uebersicht im Panel
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:
@@ -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;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '../icons.jsx';
|
||||
import { moduleIcon } from '../module-icons.jsx';
|
||||
import ModulePage from '../components/ModulePage.jsx';
|
||||
import UptimeTrack from '../components/UptimeTrack.jsx';
|
||||
import { findSettings, SETTING_INDEX } from '../setting-index.js';
|
||||
import { Markdown } from '../markdown.jsx';
|
||||
|
||||
@@ -341,6 +342,7 @@ export default function Settings({ me }) {
|
||||
const [team, setTeam] = useState([]);
|
||||
const [audit, setAudit] = useState([]);
|
||||
const [serverStats, setServerStats] = useState(null);
|
||||
const [watchdog, setWatchdog] = useState(null);
|
||||
const [owner, setOwner] = useState(null);
|
||||
const [auditAlle, setAuditAlle] = useState(false);
|
||||
// Module (An/Aus für alle Funktionen)
|
||||
@@ -467,6 +469,7 @@ export default function Settings({ me }) {
|
||||
apiGet('/api/auditlog').then((d) => setAudit(d.entries)).catch(() => {});
|
||||
// Fuers Dashboard: Mitgliederzahl und Nachrichten pro Tag
|
||||
apiGet('/api/serverstats').then(setServerStats).catch(() => {});
|
||||
apiGet('/api/watchdog').then(setWatchdog).catch(() => {});
|
||||
apiGet('/api/ssoapps').then((d) => setSsoApps(d.apps)).catch(() => {});
|
||||
apiGet('/api/services').then((d) => setServices(d.services)).catch(() => {});
|
||||
apiGet('/api/pages/all/list').then((d) => setPages(d.pages)).catch(() => {});
|
||||
@@ -2337,6 +2340,31 @@ export default function Settings({ me }) {
|
||||
<input type="text" placeholder="https://git.d4rkst3r.de, https://bot.d4rkst3r.de/health" value={form.watchdog_urls} onChange={(e) => setForm({ ...form, watchdog_urls: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{watchdog?.adressen?.length > 0 && (
|
||||
<div className="settings-section">
|
||||
<h2 className="settings-title">// Erreichbarkeit</h2>
|
||||
<p className="section-intro">
|
||||
Ein Strich je Stunde über die letzten 7 Tage — grün erreichbar, rot
|
||||
Ausfall, grau keine Messung. Geprüft wird alle {watchdog.intervall} Minuten.
|
||||
{!watchdog.aktiv && ' Der Wächter ist gerade ausgeschaltet, es kommen keine neuen Messpunkte dazu.'}
|
||||
</p>
|
||||
{watchdog.adressen.map((a) => (
|
||||
<div className="wd-card card" key={a.url}>
|
||||
<div className="wd-head">
|
||||
<span className={`wd-dot ${a.online === null ? 'wartet' : a.online ? 'an' : 'aus'}`} />
|
||||
<a className="wd-url" href={a.url} target="_blank" rel="noreferrer">{a.url}</a>
|
||||
<span className="wd-meta">
|
||||
{a.online === null && 'noch nicht geprüft'}
|
||||
{a.online === true && `${a.status ?? 'OK'}${a.ms != null ? ` · ${a.ms} ms` : ''}`}
|
||||
{a.online === false && `seit ${vorWieLange(new Date(a.seit).toISOString())} weg`}
|
||||
</span>
|
||||
</div>
|
||||
<UptimeTrack buckets={a.uptime} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user