From 232adeb23fc19189e434611e58cf4175a326a243 Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sat, 8 Aug 2026 22:54:44 +0200 Subject: [PATCH] feat(admin): render dismissible announcement banners in the admin UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fifth piece of #419: wires up the announcements endpoint added in the previous commit. A banner container sits outside the tab-content divs (index.html) so it stays visible across all tabs, not just one. Fetched once on page load alongside settings/version/devices; dismissing calls the server-side dismiss endpoint (not a client-only localStorage flag, so it stays dismissed across sessions/devices) and removes it from the DOM immediately. Manually verified end-to-end against a running instance: the banner container renders, the admin-gate notice appears by default, dismissing it removes it from subsequent /api/announcements responses. No Go test coverage — this is frontend-only wiring of already-tested endpoints. Refs #419 --- pkg/service/handlers/web/index.html | 2 ++ pkg/service/handlers/web/js/script.js | 41 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/pkg/service/handlers/web/index.html b/pkg/service/handlers/web/index.html index b86a696..5fc93e8 100644 --- a/pkg/service/handlers/web/index.html +++ b/pkg/service/handlers/web/index.html @@ -31,6 +31,8 @@ Admin & Setup console: migration, settings, accounts, and diagnostics.

+
+
+
+ `).join(""); + } catch (error) { + console.error("Failed to fetch announcements", error); + } +} + +// dismissAnnouncement records the dismissal server-side (so it stays +// dismissed across sessions/devices — not a client-only localStorage flag) +// and removes it from the DOM immediately rather than waiting on a refetch. +async function dismissAnnouncement(id) { + try { + await fetch(`/api/announcements/${encodeURIComponent(id)}/dismiss`, {method: "POST"}); + } catch (error) { + console.error("Failed to dismiss announcement", error); + } + + const el = document.querySelector(`[data-announcement-id="${id}"]`); + if (el) el.remove(); +} + async function fetchVersion() { try { const response = await fetch("/api/setup/version"); @@ -1797,6 +1837,7 @@ function formatXML(xml) { document.addEventListener("DOMContentLoaded", async () => { const cfg = await fetchSettings(); fetchVersion(); + fetchAnnouncements(); const deviceCount = await fetchDevices(); // Only sweep automatically on a cold start (no devices known yet). When // devices are already in the store, rely on the cached list plus the