diff --git a/api/server_status.php b/api/server_status.php
new file mode 100644
index 0000000..9c616e4
--- /dev/null
+++ b/api/server_status.php
@@ -0,0 +1,56 @@
+= 0 && $age <= $cacheTtlSeconds) {
+ $serverStatus = $decoded['data'];
+ }
+ }
+ }
+}
+
+if (!is_array($serverStatus)) {
+ $serverStatus = build_server_status($serverStatusTargets);
+ @file_put_contents($cacheFile, json_encode(['ts' => time(), 'data' => $serverStatus], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
+}
+
+$byUrlNormalized = [];
+foreach ($serverStatus as $svc) {
+ if (!empty($svc['url']) && is_string($svc['url'])) {
+ $byUrlNormalized[rtrim($svc['url'], '/')] = $svc;
+ }
+}
+
+$payload = json_encode([
+ 'ts' => time(),
+ 'byUrlNormalized' => $byUrlNormalized,
+], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+
+if ($payload === false) {
+ http_response_code(500);
+ $payload = '{"error":"json_encode_failed"}';
+}
+
+echo $payload;
+
diff --git a/assets/js/main.js b/assets/js/main.js
index 5c00330..24383d0 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -94,41 +94,37 @@ document.querySelectorAll('.card').forEach(card => {
});
});
-// ── Server Status Badges (async) ───────────────────────────
+// ── Server Status Badges (async, no reload) ───────────────
(async function () {
const cards = Array.from(document.querySelectorAll('.card[data-status-url]'));
if (cards.length === 0) return;
- const endpoint = './server_status.php';
+ const endpoint = '/api/server_status.php';
const normalize = (url) => {
if (!url) return '';
return String(url).replace(/\/+$/, '');
};
- const parsePossiblyUtf16Json = (text) => {
- if (!text) return null;
- const cleaned = String(text)
- .replace(/^?\uFEFF/, '')
+ const safeJsonParse = (text) => {
+ const cleaned = String(text || '')
+ .replace(/^\uFEFF/, '')
.replace(/\u0000/g, '')
.trim();
+ if (!cleaned) return null;
return JSON.parse(cleaned);
};
- const applyPayload = (payload) => {
- const byUrl = payload && payload.byUrl ? payload.byUrl : {};
- const byUrlNormalized = payload && payload.byUrlNormalized ? payload.byUrlNormalized : {};
-
- let applied = 0;
+ const apply = (byUrl) => {
for (const card of cards) {
const key = normalize(card.getAttribute('data-status-url'));
- const svc = byUrl[key] || byUrl[key + '/'] || byUrlNormalized[key] || null;
+ const svc = byUrl[key] || byUrl[key + '/'] || null;
if (!svc) continue;
- const state = svc.state || 'unknown';
const badge = card.querySelector('[data-status-badge="1"]');
if (!badge) continue;
+ const state = svc.state || 'unknown';
badge.classList.remove('status-badge--unknown', 'status-badge--up', 'status-badge--down');
badge.classList.add('status-badge--' + state);
@@ -142,37 +138,22 @@ document.querySelectorAll('.card').forEach(card => {
badge.textContent = 'Unbekannt';
badge.title = svc.detail || 'Unbekannt';
}
- applied++;
}
-
- console.debug('[server-status] applied badges:', applied);
};
- const fetchAndApply = async () => {
+ // Exactly ~1s delay, then fetch.
+ await new Promise(r => setTimeout(r, 1000));
+
+ try {
const res = await fetch(endpoint, { cache: 'no-store' });
- if (!res.ok) throw new Error('status_fetch_failed_' + res.status);
+ if (!res.ok) return;
const text = await res.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');
+ const payload = safeJsonParse(text);
+ if (!payload || !payload.byUrlNormalized) return;
- applyPayload(payload);
- };
-
- const delays = [1000, 1000, 1500];
- for (const d of delays) {
- await new Promise(r => setTimeout(r, d));
- try {
- await fetchAndApply();
- break;
- } catch (e) {
- console.warn('[server-status] update failed:', e);
- }
+ apply(payload.byUrlNormalized);
+ } catch (e) {
+ // keep "Unbekannt"
}
})();
diff --git a/index.php b/index.php
index 16d001b..2bf4e91 100644
--- a/index.php
+++ b/index.php
@@ -15,65 +15,13 @@ require_once __DIR__ . '/includes/lib/server_status.php';
require_once __DIR__ . '/includes/lib/view_helpers.php';
// ── Serverstatus ─────────────────────────────────────────────────────────
-// Performance: Beim ersten Rendern sofort antworten (=> Unbekannt).
-// Nach ~1s lädt die Seite einmal neu mit ?status=1 und rendert dann serverseitig Online/Offline.
-$shouldComputeStatus = isset($_GET['status']) && $_GET['status'] === '1';
-
+// Performance: initial sofort rendern (=> Unbekannt). Badges werden clientseitig nachgeladen.
$serverStatusByUrl = [];
-if ($shouldComputeStatus) {
- $cacheTtlSeconds = 30;
- $cacheKey = 'server_status_v5';
- $cacheFile = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $cacheKey . '.json';
-
- $serverStatus = null;
- if (is_file($cacheFile)) {
- $raw = @file_get_contents($cacheFile);
- if ($raw !== false) {
- $decoded = json_decode($raw, true);
- if (is_array($decoded) && isset($decoded['ts'], $decoded['data']) && is_array($decoded['data'])) {
- $age = time() - (int)$decoded['ts'];
- if ($age >= 0 && $age <= $cacheTtlSeconds) {
- $serverStatus = $decoded['data'];
- }
- }
- }
- }
-
- if (!is_array($serverStatus)) {
- $serverStatus = build_server_status($serverStatusTargets);
- @file_put_contents($cacheFile, json_encode(['ts' => time(), 'data' => $serverStatus], JSON_UNESCAPED_SLASHES));
- }
-
- foreach ($serverStatus as $svc) {
- if (!empty($svc['url']) && is_string($svc['url'])) {
- $serverStatusByUrl[$svc['url']] = $svc;
- }
- }
-}
require __DIR__ . '/includes/views/layout_head.php';
require __DIR__ . '/includes/views/header.php';
?>
-
-
-
-
$items): ?>