Refactor server status fetching to use a dynamic endpoint and improve JSON parsing error handling

This commit is contained in:
Fabian Schieder 2026-03-01 14:49:31 +01:00
parent 38ec4da90d
commit 4ebb0bc9c1

View File

@ -99,6 +99,8 @@ document.querySelectorAll('.card').forEach(card => {
const cards = Array.from(document.querySelectorAll('.card[data-status-url]')); const cards = Array.from(document.querySelectorAll('.card[data-status-url]'));
if (cards.length === 0) return; if (cards.length === 0) return;
const endpoint = './server_status.php';
const normalize = (url) => { const normalize = (url) => {
if (!url) return ''; if (!url) return '';
return String(url).replace(/\/+$/, ''); return String(url).replace(/\/+$/, '');
@ -106,14 +108,10 @@ document.querySelectorAll('.card').forEach(card => {
const parsePossiblyUtf16Json = (text) => { const parsePossiblyUtf16Json = (text) => {
if (!text) return null; if (!text) return null;
// If server accidentally delivered UTF-16LE bytes, the browser may decode it with lots of \u0000.
// Remove NULs and common BOM remnants.
const cleaned = String(text) const cleaned = String(text)
.replace(/^\uFEFF/, '') .replace(/^?\uFEFF/, '')
.replace(/\u0000/g, '') .replace(/\u0000/g, '')
.trim(); .trim();
return JSON.parse(cleaned); return JSON.parse(cleaned);
}; };
@ -121,6 +119,7 @@ document.querySelectorAll('.card').forEach(card => {
const byUrl = payload && payload.byUrl ? payload.byUrl : {}; const byUrl = payload && payload.byUrl ? payload.byUrl : {};
const byUrlNormalized = payload && payload.byUrlNormalized ? payload.byUrlNormalized : {}; const byUrlNormalized = payload && payload.byUrlNormalized ? payload.byUrlNormalized : {};
let applied = 0;
for (const card of cards) { for (const card of cards) {
const key = normalize(card.getAttribute('data-status-url')); const key = normalize(card.getAttribute('data-status-url'));
const svc = byUrl[key] || byUrl[key + '/'] || byUrlNormalized[key] || null; const svc = byUrl[key] || byUrl[key + '/'] || byUrlNormalized[key] || null;
@ -143,22 +142,29 @@ document.querySelectorAll('.card').forEach(card => {
badge.textContent = 'Unbekannt'; badge.textContent = 'Unbekannt';
badge.title = svc.detail || 'Unbekannt'; badge.title = svc.detail || 'Unbekannt';
} }
applied++;
} }
console.debug('[server-status] applied badges:', applied);
}; };
const fetchAndApply = async () => { const fetchAndApply = async () => {
const res = await fetch('/server_status.php', { cache: 'no-store' }); const res = await fetch(endpoint, { cache: 'no-store' });
if (!res.ok) throw new Error('status_fetch_failed'); if (!res.ok) throw new Error('status_fetch_failed_' + res.status);
// Use text() + robust JSON parse to survive wrong encoding (UTF-16/NULs).
const text = await res.text(); const text = await res.text();
const payload = parsePossiblyUtf16Json(text); let payload;
try {
payload = parsePossiblyUtf16Json(text);
} catch (e) {
console.warn('[server-status] JSON parse failed, first 120 chars:', text.slice(0, 120));
throw e;
}
if (!payload) throw new Error('status_json_empty'); if (!payload) throw new Error('status_json_empty');
applyPayload(payload); applyPayload(payload);
}; };
// Nach ~1s einmal aktualisieren, dann (falls nötig) 2 schnelle Retries.
const delays = [1000, 1000, 1500]; const delays = [1000, 1000, 1500];
for (const d of delays) { for (const d of delays) {
await new Promise(r => setTimeout(r, d)); await new Promise(r => setTimeout(r, d));
@ -166,7 +172,7 @@ document.querySelectorAll('.card').forEach(card => {
await fetchAndApply(); await fetchAndApply();
break; break;
} catch (e) { } catch (e) {
// keep unknown and retry console.warn('[server-status] update failed:', e);
} }
} }
})(); })();