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>
Adds a foundational global UI-mode flag — Beginner (default) vs Advanced —
gated by a single toggle on the Services tab title row. First foothold
of a project-wide pattern: any surface that grows extra-technical detail
(mounts, limits, internals, raw IDs, …) will gate it on the same flag,
so a newcomer doesn't see a wall of operator information while a power
user gets everything site-wide with one flip.
How it's wired:
window.LpUi.advanced — { get(), set(on), apply() }
localStorage key — lp.ui.advanced ('0' | '1')
body class — lp-ui--advanced
event — window 'lp-ui-advanced-changed' { advanced }
Surfaces gate their advanced-only DOM via CSS:
body:not(.lp-ui--advanced) .service-rich { display: none; }
So flipping the toggle is instant and DOM-free — no re-render needed.
The Services tab's rich container panel (limits, image, healthcheck,
networks, mounts) is the first thing behind the flag; live CPU%/memory
chips in each row stay visible always because they read just as easily
as a status colour and are useful to everyone.
Title row gets a small slider toggle styled in the project's accent —
unobtrusive, labelled "Advanced". Default OFF (Beginner).
The same _renderRow reorders the log block above the rich-detail block
inside .task-details, so when Advanced is on AND a row is expanded, the
live log appears right where the "Logs" click landed rather than below
a wall of metadata. Helps with the old simple-click feel even when the
extra detail is showing.
Plumbed deliberately to be project-wide so the upcoming first-install
"Beginner vs Advanced" wizard step can seed the flag (planned:
CFG_INSTALL_LEVEL in general config → emit body class server-side at
template render time → no FOUC on a fresh load).
Signed-off-by: librelad <librelad@digitalangels.vip>
The Admin → System area was growing a parallel per-container surface
(/admin/config/system/app/<name>) alongside the existing per-app Services
tab on the app page. Two pages onto the same thing is the kind of
duplication that rots fast — they drift, users have to remember which
one to use, and the next person adding a feature has to decide twice.
This commit consolidates onto the existing Services tab (which already
has compose-service awareness, docker socket access, restart actions via
the task system, and live log streaming) and decommissions the parallel
admin sub-page:
- Delete system-app-page.js and its lazyLoad entry. The dispatch in
admin-system.js for the 'app' view now redirects to the app page's
Services tab so old bookmarks still resolve cleanly.
- System index per-app rows navigate to /app/<name>/services (not
/admin/config/system/app/<name>) and the row hint copy is updated
to match.
- Services tab gains the rich container detail the old admin page
rendered, fed by /api/system/containers + /containers/:id +
/containers/:id/stats:
* Inline live chips in each service header: CPU% and memory
(with limit + percent if a limit is set). Memory chip flips
amber at 80% and red at 95% of the configured limit.
* New "service-rich" panel inside the existing expandable
details section (above the log block, so the existing Logs
toggle reveals both):
- Image + image-id + uptime + restart count
- Memory / CPU / PIDs limits + restart policy
- Healthcheck pill + last 3 probes (collapsible per-probe)
- Networks table (name, IP, gateway, MAC)
- Mounts table with type badges (volume/bind/tmpfs)
* Live stats refresh every 5 s; existing status refresh stays
on 10 s. Both gated on the Services tab being active.
- Backups for the app already live on the existing /app/<name>/backups
tab (loadAppBackups → BackupAppCard.render), so the navigational
promise of "one place per-app" is already met — System index just
needed to route there.
- CSS: services.css picks up .service-live-chip (with warn/danger
colour cues) and the full .service-rich block (grid, tables, mount
badges, healthcheck pills).
Signed-off-by: librelad <librelad@digitalangels.vip>
A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys,
Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun
VPN routing, and a web dashboard to manage it all.
Free & open forever to self-host; optional paid hosted services fund it.
See PROMISE.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>