diff --git a/internal/api/web/app.js b/internal/api/web/app.js index d47d139..1ef85ad 100644 --- a/internal/api/web/app.js +++ b/internal/api/web/app.js @@ -133,6 +133,7 @@ function showSignedOut(message) { document.getElementById("console").classList.add("hidden"); document.getElementById("stats").classList.add("hidden"); document.getElementById("account").classList.add("hidden"); + setSettingsOpen(false); document.getElementById("admins-panel").classList.add("hidden"); document.getElementById("security-panel").classList.add("hidden"); document.getElementById("token-banner").classList.add("hidden"); @@ -813,12 +814,42 @@ document.getElementById("query-select").addEventListener("change", () => { refresh().catch(showConsoleError); }); document.getElementById("rules-btn").addEventListener("click", runRules); + +function setSettingsOpen(open) { + const menu = document.getElementById("settings-menu"); + const button = document.getElementById("settings-btn"); + menu.classList.toggle("hidden", !open); + button.setAttribute("aria-expanded", open ? "true" : "false"); +} + +function closeSettingsMenu() { + const button = document.getElementById("settings-btn"); + if (button.getAttribute("aria-expanded") !== "true") return; + setSettingsOpen(false); + button.focus(); +} + +document.getElementById("settings-btn").addEventListener("click", () => { + const open = document.getElementById("settings-btn").getAttribute("aria-expanded") === "true"; + setSettingsOpen(!open); +}); +document.addEventListener("click", (event) => { + if (event.target.closest(".settings")) return; + if (document.getElementById("settings-btn").getAttribute("aria-expanded") !== "true") return; + setSettingsOpen(false); +}); +document.addEventListener("keydown", (event) => { + if (event.key !== "Escape") return; + closeSettingsMenu(); +}); + document.getElementById("token-btn").addEventListener("click", async () => { const button = document.getElementById("token-btn"); const has = Boolean(currentAdmin && currentAdmin.has_api_token); if (has && !window.confirm("Replace the current API token? Scripts using it will stop working.")) { return; } + closeSettingsMenu(); button.disabled = true; try { const created = await fetchJSON(has ? "/v1/admins/token/rotate" : "/v1/admins/token", { method: "POST" }); @@ -838,6 +869,7 @@ document.getElementById("token-dismiss").addEventListener("click", () => { document.getElementById("token-banner").classList.add("hidden"); }); document.getElementById("logout-btn").addEventListener("click", async () => { + closeSettingsMenu(); await fetch("/v1/auth/logout", { method: "POST" }); window.location.href = "/login"; }); @@ -920,6 +952,7 @@ async function renderSecurity() { } document.getElementById("security-btn").addEventListener("click", () => { + closeSettingsMenu(); const panel = document.getElementById("security-panel"); if (!panel.classList.contains("hidden")) { panel.classList.add("hidden"); @@ -928,6 +961,7 @@ document.getElementById("security-btn").addEventListener("click", () => { renderSecurity().catch(showConsoleError); }); document.getElementById("admins-btn").addEventListener("click", () => { + closeSettingsMenu(); const panel = document.getElementById("admins-panel"); if (!panel.classList.contains("hidden")) { panel.classList.add("hidden"); diff --git a/internal/api/web/index.html b/internal/api/web/index.html index 54435f3..b53be98 100644 --- a/internal/api/web/index.html +++ b/internal/api/web/index.html @@ -15,10 +15,19 @@

OpenSourceOM

diff --git a/internal/api/web/styles.css b/internal/api/web/styles.css index c45ca7d..418df3a 100644 --- a/internal/api/web/styles.css +++ b/internal/api/web/styles.css @@ -24,13 +24,78 @@ header { display: flex; justify-content: space-between; align-items: center; - gap: 1rem; + flex-wrap: wrap; + gap: 0.75rem 1.5rem; padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border); + position: relative; + z-index: 2; } header h1 { margin: 0; font-size: 1.4rem; } .tagline { margin: 0.25rem 0 0; color: var(--muted); } -.account { display: flex; align-items: center; gap: 0.6rem; color: var(--muted); } +.account { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 0.5rem 0.75rem; + margin-left: auto; + max-width: 100%; + color: var(--muted); +} +#account-label { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.settings { position: relative; } +#settings-btn { + display: inline-flex; + align-items: center; + gap: 0.45rem; +} +#settings-btn .chevron { + width: 0.4rem; + height: 0.4rem; + border-right: 1.5px solid currentColor; + border-bottom: 1.5px solid currentColor; + transform: translateY(-1px) rotate(45deg); +} +#settings-btn[aria-expanded="true"] { border-color: var(--accent); } +#settings-btn[aria-expanded="true"] .chevron { transform: translateY(2px) rotate(225deg); } +.settings-menu { + position: absolute; + top: calc(100% + 0.45rem); + right: 0; + z-index: 3; + min-width: 13rem; + display: flex; + flex-direction: column; + gap: 0.1rem; + padding: 0.35rem; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 10px; + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.35); +} +.account .settings-menu button { + display: block; + width: 100%; + text-align: left; + background: transparent; + border: 0; + border-radius: 6px; + padding: 0.5rem 0.7rem; + white-space: nowrap; +} +.account .settings-menu button:hover, +.account .settings-menu button:focus-visible { background: #1a2440; } +.settings-sep { + height: 1px; + margin: 0.25rem 0.45rem; + background: var(--border); +} .hidden { display: none; } .gate { @@ -232,6 +297,7 @@ h2 { margin: 0 0 0.75rem; font-size: 1rem; } #account.hidden, #admins-panel.hidden, #security-panel.hidden, -#token-banner.hidden { +#token-banner.hidden, +#settings-menu.hidden { display: none; }