The SSH Access page was boxed to max-width 860px and centered, unlike the
Overview and System admin pages (.admin-page) which span the full content
width. Drop the cap and match .admin-page padding so /admin/tools/ssh-access
looks like the rest of the Admin area.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
New 'System' admin page (sidebar Tools group) rendering the metrics the
collector now produces:
- live ring gauges for CPU, memory, disk and load
- SVG trend charts (CPU/mem/disk/network) with 1h/6h/24h range toggle
- host info + swap + docker summary strips
- per-app table: CPU/mem bars, network, status, CPU sparkline
Charts are hand-rolled SVG in charts.js (LPCharts) — no third-party libs or
CDN calls — themed entirely from the active theme's CSS variables. The
Overview System card now links here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- Sidebar now groups items: Overview at top, a 'Config' heading over the config
categories, and the existing 'Tools' heading over SSH Access.
- Breadcrumb reflects the group: config pages read 'Config' (was 'Admin'), SSH
reads 'Tools', Overview stays 'Admin'.
- SSH Access page restyled to the config page's section layout
(.config-category/.domains-wrapper sections) instead of backup-style cards, so
it matches the other Admin config pages.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Add an Admin Overview as the Admin landing (default when you open Admin): an
ops/health board distinct from the user Dashboard. Four cards built from data
we already generate — Updates (update_status.json, with one-click update),
Backups (backup dashboard.json), SSH & Security (access.json), System
(disk/memory/system_info) — each with a Manage link into the right section.
Styled like the backup dashboard (tiles/status dots).
Wire-up: 'Overview' is the top sidebar item and the default category
(handleConfig + sidebar), rendered by AdminOverview into #config-section via a
renderConfig('overview') special case. Every Admin page now shows the same
'Admin' breadcrumb header (Overview, SSH Access, and the config categories) for
a consistent Admin → Section feel. User Dashboard gets an 'Admin overview →'
link.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Rename the Config top-nav to 'Admin' and move SSH Access into its sidebar
under a 'Tools' group, instead of a separate top-level nav item. SSH Access is
rendered by SshPage into the config main pane via a renderConfig('ssh-access')
special case; the sidebar item (config-sidebar.js) routes there. SshPage now
mounts into any container (defaults to #config-section). /ssh redirects to
/config?=ssh-access for old links; the standalone ssh-content.html is removed.
Declutters the top bar and gives system/admin features one home that scales
(updates, users, Connect settings can become sidebar entries later).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
New /ssh page (topbar nav + SPA route + SshPage controller + ssh-content.html
+ ssh.css). Reads data/ssh/access.json and lets the admin: paste a public key
to authorize a machine, remove keys, and toggle key-only login — all via
'libreportal ssh ...' tasks through the backend's lockout guards. Reuses the
backup key-card styles for a consistent look. This is the inbound counterpart
to the backup location key card (outbound): same paste-a-key model, opposite
direction.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
When a location uses SSH key auth, show a key card: paste an existing private
key, or 'Generate keypair', then the card displays the public key to copy into
the remote server's authorized_keys (with Copy/Delete). Wires to the
ssh-key-set/generate/delete CLI; key mutations refresh locations.json so the
card reflects state immediately. applySshAuthVisibility toggles the card vs the
password field by auth mode. Private key only ever flows in (base64); only the
public key is ever shown.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
- Remove the per-tab 'How LibrePortal connects…' description lines; the tab
labels already say what each panel is, and the paragraphs read as misplaced
titles.
- Give the tab panels even, comfortable padding (tabs-content padding zeroed so
the panel owns it) instead of the cramped 2px sides.
- Round the tab strip's top corners (.tabs-list) so the strip + content read as
one card — .tabs-content already rounds the bottom, leaving the top square.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Reuse the shared .tabs-wrapper/.tab-button/.tab-panel components (same as an
app's Config/Tasks tabs) for the location editor instead of bespoke tab CSS:
emoji + label buttons, equal-width strip, accent active state. Panels toggle
via the .active class like the rest of the UI; only the panel padding is
trimmed so it nests inside the backup row.
Also drop the now-dead 'No advanced options' empty state — every type has at
least Engine + append-only in the Advanced tab.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The expanded location row was one long form. Split it into tabs so it opens
showing only the Connection fields. Retention moves from a stacked section
into its own tab, and the advanced overrides (URI/SSH port/append-only) get
their own tab instead of the inline disclosure from the previous pass.
Field grouping is metadata-driven: locFieldGroups partitions a type's fields
into Connection vs Advanced via the configs.json "advanced" flag (with
LOC_ADVANCED_SUFFIXES as the legacy fallback). Type changes rebuild both the
Connection and Advanced panels since advanced fields are type-dependent too.
Save still reads every field across all panels (hidden tabs stay in the DOM).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The Locations editor now renders field metadata from configs.json
(window.configData) instead of relying on the hardcoded BACKUP_LOC_FIELD_DEFS,
which drops to a fallback. Fields flagged advanced (URI override, SSH port,
append-only) move out of the main grid into a full-width "Advanced"
disclosure that's collapsed by default, so the common case stays simple.
Also load the unified config once on the backup page into window.configData
(metadata) + a flat window.systemConfigs (values). Previously systemConfigs
was only populated after a save — and with the full nested JSON, while the
code reads it as a flat map — so default-engine lookups and save-time change
detection silently misbehaved on first load. Both are now correct.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The config-grid toggle box used the input's 12px vertical padding, but its
24px pill made it render 48px tall vs the inputs' 44px, so it sat too tall
to read as inline. Trim vertical padding to 10px so the box is 44px.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Divider: .domains-divider was a bold 2px accent bar under every section header,
which read as a stray line. Drop it to a subtle 1px low-opacity neutral rule so
it separates without shouting.
Toggle: the boxed config toggle (.checkbox-label) used a different radius (10px),
fill (0.04) and border (0.10) than the .form-control inputs beside it (8px /
0.05 / 0.20), so it looked off and out of line. Match it to the input field box
exactly so toggles and inputs read as the same surface. The app-config
borderless toggle override is unaffected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Give the per-location Save changes / Delete location row some breathing room
from the bottom of the expanded card.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Buttons: the per-location Save changes / Delete location buttons had no icons,
unlike the apps-config action buttons. Add a save (floppy) icon and a trash
icon so they match the reference; colour comes from the nebula button groups
they already belong to.
Theme refactor: move the theme-specific [data-theme="nebula"] button/topbar/CTA
rules out of the shared css/themes.css and into themes/nebula/theme.css, where
the README says theme overrides belong. css/themes.css keeps only the generic,
non-theme-scoped defaults (solid status/accent buttons, danger-zone,
warning-banner) shared by dark-blue/light. No behaviour change: the nebula file
loads after css/themes.css so the moved rules still win.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Two follow-ups to the button restyle:
- On the nebula theme, primary/danger CTAs are translucent (rgba accent/danger
fill + white text + border), not the solid generic .btn-primary. The earlier
change only added the backup classes to the generic groups, so on nebula the
Add location / Save changes / Delete buttons fell back to a solid fill with
dark text. Add .backup-primary-btn and .backup-danger-btn to the
[data-theme="nebula"] groups too, so they match the config-page buttons.
- The per-location action row used justify-content: space-between, throwing the
two buttons to opposite edges. Switch to flex-start with a gap (like
.config-actions) and put Save changes (primary) before Delete location.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
The backup pages' primary buttons (Add location, Save changes) and Delete
location button used a local gradient + glow in backup.css, so they didn't
match the flat solid-accent buttons on the config page. Add .backup-primary-btn
and .backup-danger-btn to the shared nebula button groups in themes.css
(.btn-primary / .btn-uninstall) so they get the same solid accent/danger fill
and hover with !important across themes, and drop the local gradient/shadow/lift
from backup.css. Top-right and bottom-of-page backup buttons now match config.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Drop the Export button from the config-backup warning banner — it's now just the alert + dismiss (x). On the Configuration tab the top-right primary action becomes an 'Export' dropdown (first item: Repository Passwords, reusing the existing export-passwords action) so more export types can be added later. Other tabs keep Backup all apps / Add location. Menu opens from the trigger and closes on outside click, item click, or tab switch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Add a large amber alert-triangle icon to the 'keep your config backed up offline' banner and a close (x) button in its top-right. Dismissal is stored in localStorage (libreportal:backup-config-warning-dismissed) — a per-browser UI nudge, not server config — and hides both the banner and its divider until cleared.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Location config dropdowns (Type, Path, etc.) live inside .task-item cards,
whose :hover applies transform: translateY(-2px). A transformed element
becomes the containing block for position:fixed descendants, so the popup —
previously a child of the card — was positioned with viewport coords against
the card instead of the viewport (wrong placement) and perturbed layout
(content shifted left).
Portal the popup to <body> on open and detach on close, so position:fixed is
always relative to the viewport regardless of any transformed/overflow
ancestor. flip-up styling moves onto the popup element and the topbar's wider
popup is carried via a class, since the popup no longer nests in the wrapper.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Make the expanded location editor read like /config: Connection and Retention now use the section header + .domains-divider layout, and Connection gets a description. Move the retention 'Backup style' guidance into a tooltip and drop the always-visible hint line below it. Move the Enabled toggle out of the Connection fields into the collapsed location row header so a location can be enabled/disabled without expanding it; setLocationEnabled persists the change via the same config_update routing as saveSection.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Add a config-divider after the header-only Danger Zone banner on the features page so a line separates it from the feature fields. Drop the now-redundant 24px top padding on .config-actions since the divider above the Save/Reset buttons already provides that spacing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Emit a config-divider before the Danger Zone / "Show Advanced Options" container in the shared renderer, so a line separates the regular fields from the advanced toggle — mirroring the dividers above Save/Reset and inside #advanced-sections. Applies to every config category.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Add a thin divider above the Save/Reset buttons, and one at the top of
#advanced-sections so a line appears between the "Show Advanced Options"
toggle and the advanced fields only when they're revealed. Shared config
renderer, so it applies to every config category (backup included).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
Surface when LibrePortal is behind upstream and let users update from the
WebUI, reusing the proven git-update path instead of reinventing it.
Detection (host): webuiSystemUpdateCheck writes
frontend/data/system/update_status.json from a throttled git fetch +
behind-count + VERSION compare, off the existing per-minute
`webui generate system` cron. A new /VERSION file is the canonical version.
Display (frontend): update-notifier.js/.css render a global topbar badge
(every page) and a dashboard banner (prominent when behind, subtle "up to
date" with a manual check otherwise), plus a details panel.
Actions go through the task pipeline:
- `libreportal update apply` -> webuiRunUpdate (non-interactive: guards,
forced check, gitPerformUpdate, then dockerInstallApp libreportal)
- `libreportal update check` -> forced recheck
gitFolderResetAndBackup's body is extracted into gitPerformUpdate (no exit)
so the WebUI path can reuse it; the interactive CLI flow is unchanged.
Detection JSON verified against the repo (up-to-date and behind cases).
webuiRunUpdate's re-clone + redeploy still needs validation on a live host.
The latest-version source is git for now and is the single swap point for
get.libreportal.org later — the JSON contract and frontend stay unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>