Erreichbarkeits-Balken auf der Server-Seite
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

Ein Strich pro Stunde ueber 7 Tage, gruen/gelb/rot je nachdem, ob in der Stunde
alle, ein Teil oder keine Messung erreichbar war.

Die Daten lagen laengst da: player_history haelt 7 Tage vor, die API schnitt
aber auf 24 Stunden ab. Statt Rohdaten (ueber eine Woche rund 5000 Zeilen pro
Server) fasst uptimeBuckets() in SQL stundenweise zusammen — hoechstens 168
Zeilen.

- Stunden ohne Messung werden als Luecke gezeichnet, nicht als "online". Sonst
  saehe eine Bot-Auszeit aus wie ein einwandfrei laufender Server.
- Die alte 24h-Uptime-Zahl neben dem Peak ist raus: zweimal dieselbe Groesse
  ueber verschiedene Zeitraeume verwirrt nur.
- Tooltip zeigt Ortszeit. Die Eimer werden in UTC abgeglichen (SQLite rechnet
  so), angezeigt wird lokal — sonst stuende dort eine Stunde, die niemand
  wiedererkennt.

Zwei Fehler beim Bauen gefunden und behoben:
- .srv-card ist ein Grid-Item mit min-width:auto und wuchs durch die 168
  Striche auf 724 statt 335 px, die Karte sprengte auf dem Handy das Raster.
- "Uhr" stand hart deutsch im Tooltip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 04:09:19 +02:00
co-authored by Claude Fable 5
parent c0a56c1eb5
commit 1e2d295665
6 changed files with 126 additions and 13 deletions
+70 -12
View File
@@ -38,14 +38,73 @@ function HistoryChart({ history, max }) {
);
}
/** Uptime (%) und Spieler-Peak aus den 24h-Samples */
/**
* 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;
const online = history.filter((h) => h.online).length;
return {
uptime: Math.round((online / history.length) * 100),
peak: Math.max(0, ...history.map((h) => h.players ?? 0)),
};
return { peak: Math.max(0, ...history.map((h) => h.players ?? 0)) };
}
function ServerCard({ s }) {
@@ -99,15 +158,14 @@ function ServerCard({ s }) {
{stats && (
<div className="srv-stats">
<span>{t('server.players24h')}</span>
<span>
{t('server.peak')} <b>{stats.peak}</b> · {t('server.uptime')}{' '}
<b className={stats.uptime >= 99 ? 'up-good' : stats.uptime >= 90 ? '' : 'up-bad'}>
{stats.uptime}%
</b>
</span>
{/* Uptime steht jetzt am Balken darunter — zweimal dieselbe
Zahl in verschiedenen Zeiträumen verwirrt nur. */}
<span>{t('server.peak')} <b>{stats.peak}</b></span>
</div>
)}
<UptimeTrack buckets={s.uptime} />
{(s.connect_url || s.address) && (
<div className="srv-connect">
{s.connect_url && (