Adds the install-time Beginner/Advanced choice the user described, with
the linked dev-mode escape hatch and global body-class machinery that
any surface can hang advanced/dev-only DOM off.
Three-tier mental model, two flags in the data model:
Beginner default. nothing extra shown.
Advanced .lp-advanced DOM revealed; advanced wizard steps shown
Adv+Dev .lp-dev DOM also revealed; dev-only fields visible
Linking rule (enforced inside LpUi):
- enabling dev auto-enables advanced (dev w/o advanced is incoherent)
- disabling advanced auto-disables dev
Wire shape:
CFG_INSTALL_LEVEL beginner | advanced (general_basic)
CFG_DEV_MODE existing, unchanged behaviour
window.LpUi.{advanced,dev} {get(), set(), apply()}
localStorage keys lp.ui.advanced, lp.ui.dev, lp.ui.seeded
body classes lp-ui--advanced, lp-ui--dev
events lp-ui-advanced-changed, lp-ui-dev-changed
global CSS gates body:not(.lp-ui--advanced) .lp-advanced { hide }
body:not(.lp-ui--dev) .lp-dev { hide }
Setup wizard:
- New step 1 "Choose your experience" with Beginner/Advanced cards.
Beginner is preselected so race-through gets the safe default.
- Picking a level updates totalSteps live (4 for beginner, 5 for
advanced) so the progress bar reflects the choice.
- Metrics step (Prometheus + Grafana) is gated to Advanced — beginner
never sees it, never gets asked, never installs them by accident.
- Submit payload now carries install_level; setup-routes.js validates
it against the enum (beginner|advanced).
- scripts/setup/setup_apply.sh writes it to CFG_INSTALL_LEVEL via
updateConfigOption.
- On submit, LpUi.advanced.set is called immediately so the next
surface (running-tasks page) is already in the right mode — no
refresh needed.
WebUI bootstrap:
- js/utils/lp-ui.js loads first thing in index.html (before any other
bootstrap) so body.lp-ui--advanced is applied pre-paint — no FOUC
of advanced content on a fresh tab.
- On first run, seeds lp.ui.advanced from CFG_INSTALL_LEVEL.
Subsequent loads honour the user's per-browser override.
- Mirrors CFG_DEV_MODE → lp.ui.dev on the seed pass.
Dev-mode unlock:
- Existing 10-click LibrePortal-logo easter egg unchanged.
- NEW: same 10-click unlock on the Advanced toggle (in services-manager).
Reuses the countdown-toast pattern; on the 10th click delegates to
the topbar's _setDevMode so there's one canonical setter and the
config_update task path stays singular.
- TopbarComponent now exposes its instance as window.topbar so the
toggle's tap handler can reach _setDevMode.
- topbar._setDevMode also calls LpUi.dev.set(enabled) so the body
class flips immediately (no reload needed to see dev-only DOM).
Convention rolled out:
- Services tab's .service-rich panel was already gated on
body.lp-ui--advanced.
- .lp-advanced / .lp-dev are now first-class hide classes any
component can tag DOM with — see style.css globals.
Signed-off-by: librelad <librelad@digitalangels.vip>
125 lines
5.4 KiB
JavaScript
125 lines
5.4 KiB
JavaScript
// window.LpUi — global UI-mode flags shared across the WebUI.
|
|
//
|
|
// Two orthogonal axes:
|
|
// advanced — show extra-technical detail (mounts, limits, internals,
|
|
// raw IDs, advanced config fields). Off by default
|
|
// ("Beginner"); user can flip on from any surface that
|
|
// exposes the Advanced toggle.
|
|
// dev — show developer-only fields and surfaces (the **DEV**
|
|
// config fields, Installation Mode picker, etc.). Locked
|
|
// behind the 10-tap unlock easter egg (LibrePortal logo
|
|
// OR the Advanced toggle). Auto-enables when on a git/
|
|
// local install (the existing topbar autodetect path).
|
|
//
|
|
// Linking rule: dev IMPLIES advanced.
|
|
// - Enabling dev auto-enables advanced (you can't see dev fields if
|
|
// the advanced layer is hidden).
|
|
// - Disabling advanced auto-disables dev (dev-without-advanced is
|
|
// an incoherent state — clean it up).
|
|
//
|
|
// Wire shape:
|
|
// localStorage keys lp.ui.advanced ('0' | '1')
|
|
// lp.ui.dev ('0' | '1') — UI-side mirror of CFG_DEV_MODE
|
|
// body classes lp-ui--advanced
|
|
// lp-ui--dev
|
|
// events window 'lp-ui-advanced-changed' { advanced }
|
|
// window 'lp-ui-dev-changed' { dev }
|
|
//
|
|
// Loaded eagerly (NOT lazy) via a <script> tag in index.html so the body
|
|
// class is applied before any page renders — no flash of unhidden content
|
|
// on a fresh load. Components that want to react to the flag should
|
|
// listen for the change events rather than poll.
|
|
|
|
(function setupLpUi() {
|
|
if (window.LpUi && window.LpUi.advanced && window.LpUi.dev) return;
|
|
|
|
const ADV_KEY = 'lp.ui.advanced';
|
|
const DEV_KEY = 'lp.ui.dev';
|
|
const SEED_KEY = 'lp.ui.seeded'; // marks that we've consulted CFG_INSTALL_LEVEL once
|
|
|
|
function safeGet(k) {
|
|
try { return localStorage.getItem(k); } catch { return null; }
|
|
}
|
|
function safeSet(k, v) {
|
|
try { localStorage.setItem(k, v); } catch { /* private mode */ }
|
|
}
|
|
|
|
function applyBodyClasses() {
|
|
if (!document.body) return;
|
|
document.body.classList.toggle('lp-ui--advanced', advanced.get());
|
|
document.body.classList.toggle('lp-ui--dev', dev.get());
|
|
}
|
|
|
|
const advanced = {
|
|
get() { return safeGet(ADV_KEY) === '1'; },
|
|
set(on) {
|
|
const v = !!on;
|
|
const before = this.get();
|
|
if (v === before) return;
|
|
safeSet(ADV_KEY, v ? '1' : '0');
|
|
document.body && document.body.classList.toggle('lp-ui--advanced', v);
|
|
// Dev implies advanced — turning advanced off also clears dev.
|
|
if (!v && dev.get()) dev.set(false);
|
|
window.dispatchEvent(new CustomEvent('lp-ui-advanced-changed', { detail: { advanced: v } }));
|
|
},
|
|
apply() { document.body && document.body.classList.toggle('lp-ui--advanced', this.get()); },
|
|
};
|
|
|
|
const dev = {
|
|
get() { return safeGet(DEV_KEY) === '1'; },
|
|
set(on) {
|
|
const v = !!on;
|
|
const before = this.get();
|
|
if (v === before) return;
|
|
safeSet(DEV_KEY, v ? '1' : '0');
|
|
document.body && document.body.classList.toggle('lp-ui--dev', v);
|
|
// Dev implies advanced — turning dev on lifts advanced too.
|
|
if (v && !advanced.get()) advanced.set(true);
|
|
window.dispatchEvent(new CustomEvent('lp-ui-dev-changed', { detail: { dev: v } }));
|
|
},
|
|
apply() { document.body && document.body.classList.toggle('lp-ui--dev', this.get()); },
|
|
};
|
|
|
|
// Seed lp.ui.advanced from CFG_INSTALL_LEVEL the first time we run.
|
|
// After that, the local flip is authoritative — we don't want the
|
|
// user's choice in the WebUI overridden every reload by the install-
|
|
// time default.
|
|
async function seedFromConfigIfNeeded() {
|
|
if (safeGet(SEED_KEY) === '1') return;
|
|
try {
|
|
const r = await fetch(`/data/config/generated/configs.json?t=${Date.now()}`);
|
|
if (!r.ok) { safeSet(SEED_KEY, '1'); return; }
|
|
const data = await r.json();
|
|
const level = (data?.config?.CFG_INSTALL_LEVEL?.value || 'beginner').toLowerCase();
|
|
const wantAdvanced = (level === 'advanced');
|
|
// Only seed if the user hasn't already set a value.
|
|
if (safeGet(ADV_KEY) === null) {
|
|
safeSet(ADV_KEY, wantAdvanced ? '1' : '0');
|
|
}
|
|
// Mirror CFG_DEV_MODE into the body class on first load so dev
|
|
// gating works before the topbar autodetect runs. The topbar
|
|
// still owns toggling CFG_DEV_MODE itself; we just read it.
|
|
const devOn = (data?.config?.CFG_DEV_MODE?.value === 'true' || data?.config?.CFG_DEV_MODE?.value === true);
|
|
safeSet(DEV_KEY, devOn ? '1' : '0');
|
|
safeSet(SEED_KEY, '1');
|
|
applyBodyClasses();
|
|
} catch {
|
|
safeSet(SEED_KEY, '1');
|
|
}
|
|
}
|
|
|
|
window.LpUi = { advanced, dev };
|
|
|
|
// Apply ASAP — once body exists drop the classes in. If the script
|
|
// loads before body, defer; otherwise do it immediately.
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
applyBodyClasses();
|
|
seedFromConfigIfNeeded();
|
|
}, { once: true });
|
|
} else {
|
|
applyBodyClasses();
|
|
seedFromConfigIfNeeded();
|
|
}
|
|
})();
|