HexaWetter v1.2.0: Security, Admin, Radar, UI

This commit is contained in:
TheOnlyMace
2026-06-18 23:59:08 +02:00
parent 7ba9687619
commit 3589f7fdcf
72 changed files with 695 additions and 409 deletions

View File

@@ -35,6 +35,67 @@ function formatBytes(bytes) {
return `${v.toFixed(u === 0 ? 0 : 1)} ${units[u]}`;
}
async function fetchBlobUrl(path) {
const token = getToken();
const res = await fetch(path, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
if (!res.ok) throw new Error(`${res.status}`);
const blob = await res.blob();
return URL.createObjectURL(blob);
}
async function downloadAdminFile(path, filename) {
const url = await fetchBlobUrl(path);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
async function loadRadarData() {
try {
const local = await api("/api/admin/radar/files");
el("radarFiles").innerHTML = local.files.length ? local.files.slice().reverse().map((file) => `
<tr>
<td>${file.name}</td>
<td>${formatBytes(file.size_bytes)}</td>
<td>${new Date(file.modified_utc).toLocaleString("de-DE")}</td>
<td><button type="button" class="btn btn-secondary" data-download="${file.download_url}" data-name="${file.name}">Download</button></td>
</tr>
`).join("") : `<tr><td colspan="4" class="muted">Noch keine lokalen Radar-Dateien.</td></tr>`;
el("radarFiles").querySelectorAll("[data-download]").forEach((btn) => {
btn.addEventListener("click", () => downloadAdminFile(btn.dataset.download, btn.dataset.name));
});
} catch {
el("radarFiles").innerHTML = `<tr><td colspan="4" class="muted">Rohdaten nicht verfügbar</td></tr>`;
}
try {
const rendered = await api("/api/admin/radar/rendered?limit=12");
el("renderedCount").textContent = `${rendered.count} Frame(s)`;
const frames = rendered.frames || [];
if (!frames.length) {
el("renderedFrames").innerHTML = `<p class="muted">Noch keine gerenderten Frames.</p>`;
return;
}
el("renderedFrames").innerHTML = frames.map((f, i) => `
<figure class="rendered-frame" id="renderedFrame${i}">
<div class="muted" style="padding:40px;text-align:center">Lade…</div>
<figcaption>${f.filename}</figcaption>
</figure>
`).join("");
await Promise.allSettled(frames.map(async (f, i) => {
const url = await fetchBlobUrl(f.image_url);
const figure = el(`renderedFrame${i}`);
if (!figure) return;
figure.innerHTML = `<img src="${url}" alt="Radar ${f.filename}" loading="lazy" /><figcaption>${f.filename}</figcaption>`;
}));
} catch {
el("renderedCount").textContent = "";
el("renderedFrames").innerHTML = `<p class="muted">Renderer nicht verfügbar.</p>`;
}
}
function showLogin() {
el("loginSection").classList.remove("admin-hidden");
el("dashboardSection").classList.add("admin-hidden");
@@ -92,6 +153,7 @@ async function loadDashboard() {
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;
await loadRadarData();
}
el("loginForm").addEventListener("submit", async (e) => {
@@ -124,6 +186,23 @@ el("clearCacheBtn").addEventListener("click", async () => {
await loadDashboard();
});
el("syncRadarBtn").addEventListener("click", async () => {
const button = el("syncRadarBtn");
button.disabled = true;
const label = button.textContent;
button.textContent = "Sync läuft…";
try {
const result = await api("/api/admin/radar/sync?limit=6", { method: "POST" });
alert(`Sync abgeschlossen: ${result.downloaded?.length || 0} neu, ${result.skipped?.length || 0} übersprungen`);
await loadRadarData();
} catch (err) {
alert(err.message);
} finally {
button.disabled = false;
button.textContent = label;
}
});
el("settingsForm").addEventListener("submit", async (e) => {
e.preventDefault();
const payload = {