const TOKEN_KEY = "hexawetter_admin_token"; const el = (id) => document.getElementById(id); function getToken() { return sessionStorage.getItem(TOKEN_KEY); } function setToken(token) { if (token) sessionStorage.setItem(TOKEN_KEY, token); else sessionStorage.removeItem(TOKEN_KEY); } async function api(path, options = {}) { const headers = { ...(options.headers || {}) }; const token = getToken(); if (token) headers.Authorization = `Bearer ${token}`; const res = await fetch(path, { ...options, headers }); if (res.status === 401) { setToken(null); showLogin(); throw new Error("Sitzung abgelaufen"); } if (!res.ok) { const text = await res.text(); throw new Error(`${res.status}: ${text}`); } return res.json(); } function formatBytes(bytes) { if (!bytes) return "0 B"; const units = ["B", "KB", "MB", "GB"]; let v = bytes, u = 0; while (v >= 1024 && u < units.length - 1) { v /= 1024; u++; } return `${v.toFixed(u === 0 ? 0 : 1)} ${units[u]}`; } function showLogin() { el("loginSection").classList.remove("admin-hidden"); el("dashboardSection").classList.add("admin-hidden"); el("logoutBtn").classList.add("admin-hidden"); } function showDashboard() { el("loginSection").classList.add("admin-hidden"); el("dashboardSection").classList.remove("admin-hidden"); el("logoutBtn").classList.remove("admin-hidden"); } function renderServices(services) { el("serviceGrid").innerHTML = (services || []).map((s) => `
${s.name} ${s.status}${s.latency_ms ? ` · ${s.latency_ms} ms` : ""} ${s.http_status ? `HTTP ${s.http_status}` : ""} ${s.error ? `${s.error}` : ""}
`).join(""); } function renderStorage(storage) { el("storageGrid").innerHTML = Object.entries(storage || {}).map(([key, info]) => `
${formatBytes(info.bytes || 0)} ${key}: ${info.files || 0} Dateien
`).join(""); } function renderInfra(infra) { el("infraGrid").innerHTML = Object.entries(infra || {}).map(([key, status]) => `
${status} ${key}
`).join(""); } async function loadDashboard() { const data = await api("/api/admin/dashboard"); el("adminTime").textContent = `Stand: ${new Date(data.time_utc).toLocaleString("de-DE")}`; renderInfra(data.infrastructure); renderServices(data.services); renderStorage(data.storage); el("configBlock").textContent = JSON.stringify(data.config, null, 2); const settings = await api("/api/admin/settings"); const defaults = settings.defaults; const overrides = settings.runtime_overrides || {}; el("setRateLimit").value = overrides.rate_limit_per_minute ?? defaults.rate_limit_per_minute; el("setCacheForecast").value = overrides.cache_ttl_forecast ?? defaults.cache_ttl.forecast; el("setCacheObs").value = overrides.cache_ttl_observations ?? defaults.cache_ttl.observations; el("setCacheWarn").value = overrides.cache_ttl_warnings ?? defaults.cache_ttl.warnings; el("setRadarRetention").value = overrides.radar_retention_hours ?? defaults.radar_retention_hours; } el("loginForm").addEventListener("submit", async (e) => { e.preventDefault(); el("loginError").textContent = ""; try { const data = await api("/api/admin/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: el("adminUser").value, password: el("adminPass").value }), }); setToken(data.access_token); showDashboard(); await loadDashboard(); } catch (err) { el("loginError").textContent = err.message.includes("401") ? "Ungültige Zugangsdaten" : err.message; } }); el("logoutBtn").addEventListener("click", () => { setToken(null); showLogin(); }); el("refreshBtn").addEventListener("click", () => loadDashboard().catch((e) => alert(e.message))); el("clearCacheBtn").addEventListener("click", async () => { if (!confirm("Redis-Cache wirklich leeren?")) return; await api("/api/admin/cache/clear", { method: "POST" }); alert("Cache geleert"); await loadDashboard(); }); el("settingsForm").addEventListener("submit", async (e) => { e.preventDefault(); const payload = { rate_limit_per_minute: Number(el("setRateLimit").value), cache_ttl_forecast: Number(el("setCacheForecast").value), cache_ttl_observations: Number(el("setCacheObs").value), cache_ttl_warnings: Number(el("setCacheWarn").value), radar_retention_hours: Number(el("setRadarRetention").value), }; try { await api("/api/admin/settings", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); el("settingsMsg").textContent = "Einstellungen in Redis gespeichert."; } catch (err) { el("settingsMsg").textContent = err.message; } }); async function init() { try { const status = await fetch("/api/admin/status").then((r) => r.json()); if (!status.admin_enabled) { el("loginError").textContent = "Admin ist nicht konfiguriert. Bitte ADMIN_PASSWORD_HASH und ADMIN_JWT_SECRET in .env setzen."; return; } } catch { /* ignore */ } if (getToken()) { try { showDashboard(); await loadDashboard(); return; } catch { setToken(null); } } showLogin(); } init();