diff --git a/containers/libreportal/frontend/css/services.css b/containers/libreportal/frontend/css/services.css index b483c37..17dc8db 100644 --- a/containers/libreportal/frontend/css/services.css +++ b/containers/libreportal/frontend/css/services.css @@ -231,7 +231,8 @@ Rich container detail panel — limits, image, healthcheck, networks, mounts. Rendered inside .task-details above the log container so it's discoverable from the existing "Logs" - expand action. + expand action. Gated behind the global Advanced UI mode so + a Beginner doesn't see a wall of technical detail. ============================================================ */ .service-rich { display: flex; @@ -239,6 +240,72 @@ gap: 14px; margin: 8px 0 14px; } +body:not(.lp-ui--advanced) .service-rich { display: none; } + +/* ============================================================ + Beginner / Advanced toggle in the Services tab title row. + Project-wide visual; same component will be reused wherever + else surfaces grow an Advanced-only section. + ============================================================ */ +.services-title { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} +.services-title-main { flex: 1; min-width: 0; } + +.lp-ui-advanced-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + cursor: pointer; + user-select: none; + flex-shrink: 0; +} +.lp-ui-advanced-toggle input { + position: absolute; + opacity: 0; + pointer-events: none; +} +.lp-ui-advanced-toggle-track { + position: relative; + width: 34px; + height: 18px; + background: rgba(var(--text-rgb), 0.18); + border-radius: 999px; + transition: background .15s ease; + flex-shrink: 0; +} +.lp-ui-advanced-toggle-thumb { + position: absolute; + top: 2px; + left: 2px; + width: 14px; + height: 14px; + background: var(--text-primary); + border-radius: 50%; + transition: transform .15s ease, background .15s ease; +} +.lp-ui-advanced-toggle input:checked + .lp-ui-advanced-toggle-track { + background: var(--accent); +} +.lp-ui-advanced-toggle input:checked + .lp-ui-advanced-toggle-track .lp-ui-advanced-toggle-thumb { + transform: translateX(16px); + background: var(--text-on-accent, #0a1426); +} +.lp-ui-advanced-toggle-label { + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.02em; + color: rgba(var(--text-rgb), 0.7); + transition: color .15s ease; +} +.lp-ui-advanced-toggle:hover .lp-ui-advanced-toggle-label { color: var(--text-primary); } +.lp-ui-advanced-toggle input:focus-visible + .lp-ui-advanced-toggle-track { + outline: 2px solid rgba(var(--accent-rgb), 0.6); + outline-offset: 2px; +} .service-rich-grid { display: grid; diff --git a/containers/libreportal/frontend/js/components/app/services-manager.js b/containers/libreportal/frontend/js/components/app/services-manager.js index 5c47559..5075f60 100644 --- a/containers/libreportal/frontend/js/components/app/services-manager.js +++ b/containers/libreportal/frontend/js/components/app/services-manager.js @@ -1,3 +1,44 @@ +// Global "UI mode" — Beginner vs Advanced. First foothold of a +// project-wide pattern: any surface that wants to render extra-technical +// detail (mounts, limits, internals, raw IDs, …) gates it on +// body.lp-ui--advanced. Default is OFF (Beginner) so a newcomer isn't +// overwhelmed; flipping the toggle reveals everything site-wide. +// +// The flag persists per-browser via localStorage. The install will later +// seed the initial value from CFG_INSTALL_LEVEL (Beginner | Advanced) +// chosen on the very first setup screen — once that lands, the body +// class is set server-side at template render time so there's no FOUC. +// Until then, the user picks it from any surface that exposes a toggle +// (currently the Services tab). +(function setupLpUi() { + if (window.LpUi && window.LpUi.advanced) return; + const KEY = 'lp.ui.advanced'; + const advanced = { + get() { + try { return localStorage.getItem(KEY) === '1'; } catch { return false; } + }, + set(on) { + const v = !!on; + try { localStorage.setItem(KEY, v ? '1' : '0'); } catch { /* private mode */ } + document.body && document.body.classList.toggle('lp-ui--advanced', v); + window.dispatchEvent(new CustomEvent('lp-ui-advanced-changed', { detail: { advanced: v } })); + }, + apply() { + if (!document.body) return; + document.body.classList.toggle('lp-ui--advanced', this.get()); + }, + }; + window.LpUi = { ...(window.LpUi || {}), advanced }; + // Apply as early as possible — once the body exists, we drop the class + // in so any already-rendered surface (e.g. another tab re-mounted with + // .service-rich present) reflects the saved mode immediately. + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => advanced.apply(), { once: true }); + } else { + advanced.apply(); + } +})(); + // Services tab on the app detail page. // // Each row renders a single docker compose service with: @@ -87,10 +128,23 @@ class ServicesManager { _titleBlock(appName) { const display = (appName || '').replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); + const adv = window.LpUi?.advanced?.get() ? 'checked' : ''; + // The toggle is the visible surface for the global "Advanced UI" mode + // ([[window.LpUi.advanced]]). Flipping it here unhides the rich + // container detail (limits, mounts, networks, healthcheck) across + // every service row. The body class drives the CSS show/hide, so + // other surfaces that opt into the same mode get it for free. return `
Inspect, restart and tail logs for the docker compose services that make up ${escapeHtml(display)}
+Inspect, restart and tail logs for the docker compose services that make up ${escapeHtml(display)}
+