HexaWetter v1.2.0: Security, Admin, Radar, UI
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user