1120 Commits

Author SHA1 Message Date
librelad
8006ddba75 fix(webui): give the app-detail Updates tab the standard tab chrome
The per-app Updates tab rendered a bare version/badge bar + detail with no
title, no dividers and no recessed container — unlike the Config / Backups /
Tasks tabs.

- Add a .updater-title block (⬆️ Updates + description, Check/Update actions,
  bottom-border divider) mirroring .backup-title.
- Wrap the body in a .updater-detail-container recessed dark panel (same recipe
  as .backup-snapshots-container / .tasks-container).
- Separate the Version/Security/Recovery/History sections with divider lines
  (scoped to the container; fleet row-details keep their gap-only spacing).
- renderAppDetail() gains an opt-in Version section so the version/badge reads
  as a section in the panel; fleet rows omit it (the row head shows it already).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 00:56:01 +01:00
librelad
6ced7c4c71 Merge claude/1 2026-06-03 00:23:10 +01:00
librelad
25dc51d63e fix(webui): make Overview sub-tab areas match the app-detail layout
Migrate and Backups detached their sub-tab strip from the content (16px gap)
and Backups had no top-level header and a transparent (card-less) body — so
both read as different from the per-app Config sub-tabs.

- Migrate: nest .tabs-content inside the same .tabs-wrapper as .tabs-list (the
  canonical app-detail structure) and drop the gap, so the strip joins the
  content card with no space and clean corners.
- Backups: inject the shared "Backups" .config-title header above the embedded
  BackupPage; drop the sub-tab strip's bottom gap; give the content (.main) the
  connected .tabs-content surface (card bg + rounded bottom) and round the
  strip's outer top corners — so it reads as one tabs-within-tabs unit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 00:23:10 +01:00
librelad
58b76af311 Merge claude/1 2026-06-02 23:29:16 +01:00
librelad
4d54d6a9b0 feat(webui): unify Overview tab headers as in-content, app-detail style
Drop the floating fleet header; every Overview tab now renders its heading
inside the pane, under the tab strip, in the per-app detail .config-title
format (emoji + title + description) — matching the Migrate tab.

Introduces a small modular system so the area can't drift:
- OV_TAB_META is the single source of truth for each tab's icon/title/blurb.
- renderHeader(id) is the only thing that turns it into markup; renderTab()
  prepends it for the string tabs and mountMigrate() injects it once for the
  static Migrate pane. Body renderers now only ever produce the body.

Retires the now-dead floating-header plumbing: updateHeader(), the
ov-backups-active/ov-migrate-active hide toggles + CSS, and the .overview-header
rules. The tabbed interface owns the top padding the header used to provide.

Backups is the documented exception — it embeds the full BackupPage, which
supplies its own header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 23:29:16 +01:00
librelad
0f9a76503c Merge claude/1 2026-06-02 23:04:44 +01:00
librelad
f6f29bf68b feat(webui): match Migrate tab to app-detail tab design
Give the fleet Overview › Migrate tab the same in-content header + sub-tab
styling the per-app detail tabs use:

- Add a .config-title header (icon + title + description) inside the Migrate
  pane, above the Restore/Peers sub-tabs, and hide the generic floating fleet
  header for Migrate (mirrors how Backups already supplies its own heading).
- Make the .tab-button icon↔label gap explicit (flex + 6px gap) so sub-tabs
  render identically whether or not the markup has whitespace between the
  emoji and name spans; align the Backups sub-tab strip gap to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 23:04:44 +01:00
librelad
bb6db43392 Merge claude/1 2026-06-02 19:03:42 +01:00
librelad
e88d46ffeb fix(os): skip apt OS-update step when running as the de-sudo manager
installDebianUbuntu ran apt (bare on line 14, via sudo on 17/20) during
the startPreInstall pass. Under the hardened de-sudo model the runtime is
the manager (libreportal, non-root) and the LP_SYSTEM sudoers allowlist
scopes systemctl/ufw/sysctl/loginctl/service but NOT apt — so every apt
call failed (exit 100, 'Updating System Operating system.').

Detect privilege once: run apt directly when root (the install-time path,
which also bootstraps sudo on a bare box), and skip cleanly with a notice
when we're the unprivileged manager. OS/security updates are a host /
install-time concern there, deliberately kept out of the manager's reach.
Also routes the trailing sysctl mkdir/touch through the same prefix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 19:03:42 +01:00
librelad
e959468173 Merge claude/2 2026-06-02 16:21:39 +01:00
librelad
9986a8a814 fix(webui): stop rapid clicks selecting text on setup level cards
The Beginner/Advanced experience cards are tap targets, but had no
user-select guard. Rapid clicking — notably the Advanced card's 10-tap
dev-mode unlock — selected the card title/description text as a side
effect. Add user-select: none to .setup-level-card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-06-02 16:21:39 +01:00
librelad
11e79e6d81 Merge claude/1 2026-06-02 16:07:55 +01:00
librelad
f1e1330cd8 feat(webui): network-drift notifier (topbar badge + dashboard banner)
Surfaces network_status.json in the WebUI, attention-only: a rose badge
+ dashboard banner appear ONLY when conflicts_found, with a details panel
listing the stranded apps and a 'Heal now' button that runs the heal
through the task pipeline (libreportal system network heal). Re-reads on
task completion via taskRefresh.

Cloned from update-notifier; the badge anchors just after the update
badge so the two coexist in a stable order. New --page-network hue.
Wired into system-loader scripts[], topbar onTopbarReady, and index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 16:07:54 +01:00
librelad
a16c93721e Merge claude/1 2026-06-02 16:03:53 +01:00
librelad
20f8ca2eb5 feat(network): detect + heal apps stranded off the docker subnet
Closes the gap behind the vpn-recreate bug: when the shared network is
recreated with a different /24, every app's stored static IP is left
outside it and adoptDockerSubnet only realigns CFG, not the apps.

- networkScanConflicts (network_conflicts.sh): read-only scan diffing each
  active network_resources IP against docker's real subnet (via ipInSubnet).
  Per-service routing-aware — skips gateway-routed services whose ipv4 is
  commented out in the deployed compose, so gluetun apps don't false-positive.
  Distinguishes 'daemon down' (benign) from 'network missing' (real).

- webuiSystemNetworkCheck (webui_system_network.sh): self-throttled generator
  that writes frontend/data/system/network_status.json (modelled on
  verify_status.json). Wired into webuiSystemUpdate AND run unconditionally
  every ~60s from the task-processor poll (regen webui is mtime-gated and
  would never fire on drift, which touches no source file).

- networkHealConflicts (network_heal.sh) + 'libreportal system network
  check|heal [app]': the heal adopts docker's subnet in-process, then re-IPs
  stranded apps with reset_network=ip (ports preserved), gluetun first.
  Mutating path runs only through the task system (dual-mode, like update
  apply); read-only check runs inline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 16:03:53 +01:00
librelad
b59b909d54 Merge claude/1 2026-06-02 15:54:55 +01:00
librelad
b7a0743d8b feat(network): add ipInSubnet + IP-only network reset scope
Foundations for network-drift healing:

- ipInSubnet(ip, cidr): prefix-aware CIDR membership (pure bash), so
  stored IPs can be checked against docker's real subnet. Honours the
  actual prefix, so a healthy /16-subnet + /24-ip-range install is not
  mistaken for drift.

- dockerInstallApp now accepts reset_network="ip": re-roll the static IP
  from the current subnet but PRESERVE published host ports (clears only
  IP rows; LIBREPORTAL_RESET_IP_ONLY keeps port_allocate reusing existing
  ports). This is the heal path — a subnet move strands the IP, not the
  port, so we don't churn bookmarks/forwards/proxy upstreams. reset="true"
  still re-rolls both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 15:54:55 +01:00
librelad
55ca1b4270 Merge claude/1 2026-06-02 15:52:56 +01:00
librelad
d23ad87246 fix(network): correct adoptDockerSubnet comment + guard ipAllocation double-INSERT
Two latent issues uncovered while designing network-drift detection:

- adoptDockerSubnet's comment claimed apps' IPs stay inside docker's
  subnet after adoption. False: IPs are pinned to the old subnet's first
  three octets, so adopting a different /24 base strands every app IP
  out-of-subnet. Document the real behaviour + the heal paths.

- ipAllocation fell through from the existing-row branch to the
  unconditional INSERT, which would violate UNIQUE(app,type,service).
  Unreachable on today's reset path (rows are deleted first) but a hazard
  for any direct caller; add an explicit return after reuse/reset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 15:52:55 +01:00
librelad
287c13a311 Merge claude/1 2026-06-02 15:02:36 +01:00
librelad
14e6d4aba1 fix(network): converge when the docker network already exists
installDockerNetwork errored with 'network with name <x> already exists'
on re-runs: the requirement check sets DOCKER_NETWORK_SETUP_NEEDED=true
whenever 'docker network inspect' returns non-zero, but that also happens
when the rootless daemon socket isn't reachable yet — indistinguishable
from the network being genuinely absent. A prior install also leaves the
network behind, so the flag fires on every re-install.

Re-check existence right before creating and converge: if the network is
already there, leave it in place and adopt its real subnet into CFG rather
than erroring. This also stops the spurious subnet randomization (and the
resulting CFG drift) that ran before the doomed create.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 15:02:36 +01:00
librelad
c04b6d43e5 Merge claude/1 2026-06-02 14:56:27 +01:00
librelad
d6e385390d feat(rootless): show progress notice before apt-get install
The 'Installing System Requirements' step ran apt-get install with no
output until checkSuccess reported afterwards, so it looked frozen
while packages were being fetched. Print a notice up front.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 14:56:27 +01:00
librelad
ef344081e5 Merge claude/1 2026-06-01 11:12:11 +01:00
librelad
164f782a95 fix(webui): address Migrate-refactor review findings
- Restore empty-state 'Open Locations' now deep-links to the backup center's
  Locations sub-tab: the embedded center honors /overview/backups/<sub> and
  switchTab()s to it after mount (was landing on Dashboard).
- PeersPage.notify + MigratePage.notify use the real window.notificationSystem
  (were calling a never-defined window.showNotification → console-only).
- Remove the now-dead Admin config-manager peers branch (Peers left Admin).
- Trim the dead migrate.json/peers fetch + hostnameToPeerName from BackupPage
  (no consumer after the migrate removal).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 11:12:11 +01:00
librelad
b37e2454e3 Merge claude/1 2026-06-01 10:46:30 +01:00
librelad
69bb5532b7 refactor(webui): move Peers out of Admin; harmonize Backups sub-tab strip
- Remove the Admin sidebar Peers entry; /peers and /admin/tools/peers now
  redirect to /overview/migrate/peers (its new home next to cross-host Restore).
- Re-skin the embedded Backups center's sub-tab strip from pills to the per-app
  Config .tabs-list/.tab-button segmented look (full-width bar, accent underline)
  so every nested sub-tab row is consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:46:30 +01:00
librelad
f46adefb26 Merge claude/1 2026-06-01 10:39:36 +01:00
librelad
25e25230fd fix(webui): drop deleted backup-migrate.js from the embedded center asset list
The embedded backup center's lazy bundle still listed backup-migrate.js, which
was just removed — its 404 failed the whole load chain. Drop it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:39:36 +01:00
librelad
d9899db796 Merge claude/1 2026-06-01 10:37:47 +01:00
librelad
c449641b9c refactor(webui): remove Migrate from the backup center (moved to Overview)
Migrate now lives at Overview › Migrate › Restore (standalone MigratePage). Strip
it out of BackupPage: drop the migrate sidebar item/panel/modal from the
fragment, the 'migrate' tab from the allowed set / titleFor / subtitleFor /
iconFor, the renderMigrate() call, and the migrate-host/app/confirm click
handlers; delete the now-orphaned backup-migrate.js. The backup center is now
Dashboard/Backups/Locations/Configuration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:37:46 +01:00
librelad
509eea3188 Merge claude/1 2026-06-01 10:28:58 +01:00
librelad
ebdae15838 feat(webui): deep-link the Migrate sub-tabs (/overview/migrate/{restore,peers})
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:28:58 +01:00
librelad
0c52abde78 Merge claude/1 2026-06-01 10:24:54 +01:00
librelad
4a964c42a2 feat(webui): add Migrate fleet tab (Restore + Peers sub-tabs)
New 5th Overview tab 'Migrate' with a nested segmented sub-tab row reusing the
per-app Config-tab .tabs-list/.tab-button design:
- Restore: a standalone MigratePage (cross-host migrate moved out of BackupPage
  into its own controller + fragment + modal; own data fetch + task dispatch).
- Peers: reuses the existing PeersPage (container-parameterized) + its template.
Both lazy-loaded on first open and disposed on apps-feature unmount. Additive —
migrate is still in the backup center and Peers still in Admin until the next
commits remove the duplicates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:24:54 +01:00
librelad
a27df62bf3 Merge claude/1 2026-06-01 01:55:09 +01:00
librelad
efdbed8e0c fix(webui): embedded backup-center review fixes
- BackupPage task-refresh run-guard is instance-relative now (window.backupPage
  OR window.overviewBackupPage), so the embedded center's own auto-refresh works
  instead of relying on OverviewManager's overlapping coverage.
- _ensureBackupAssets no longer memoizes a rejected promise — a transient
  script-load failure no longer bricks the backup center until reload; the next
  open retries.
- spaClean.loadScript removes the failed <script> element on error so the
  getElementById dedupe can't make a retry resolve without re-fetching.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 01:55:09 +01:00
librelad
58eba94628 Merge claude/1 2026-06-01 01:40:30 +01:00
librelad
f1f0cf7516 fix(webui): scope embedded backup-center mount check to the pane
The 'already mounted?' guard checked #backup-section, but the per-app Backups
tab also defines that id — detect the prior mount via the pane's own
.backup-layout instead so the check is correct, not coincidental.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 01:40:29 +01:00
librelad
b374787486 Merge claude/1 2026-06-01 01:36:25 +01:00
librelad
c508a20605 feat(webui): embed the full backup center in the Overview Backups tab
Instead of a glance + 'Open backup center' button, the Backups tab now mounts
the real BackupPage (dashboard/snapshots/locations/migrate/configuration) inline,
with its sidebar restyled as a nested tab strip and its own header taking over.

- BackupPage gains an embedded mode (opts.embedded): no /backup URL coupling, so
  sub-tabs switch in-page under /overview/backups. Backward compatible.
- OverviewManager lazy-loads the backup bundle + fragment on first open, news a
  BackupPage({embedded:true}), and disposes it on apps-feature unmount. Colliding
  ids (#sidebar/#mobile-overlay) are stripped on inject.
- Revert the Admin backup-config surface — the embedded center (incl.
  Configuration) is now the single home for backup settings.
- The updater needs no equivalent: its sections were already unpacked into the
  Overview/Updates/Improvements tabs + the per-row expander.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 01:36:25 +01:00
librelad
e698724592 Merge claude/1 2026-06-01 00:37:59 +01:00
librelad
d5e2375f38 fix(webui): address review findings on the fleet Overview build
- HIGH: renderAppDetail gated the Roll back button on last_snapshot* fields no
  generator emits, so it never rendered. Derive recoverability from update
  history (updater_apply always snapshots first) so the affordance is reachable.
- MED: per-app Updates tab now repaints on update/rollback/check/hotfix task
  completion (mirrors the backups card) instead of going stale until re-click.
- MED: in-page tab switches now sync spaClean.currentRoute, so the sidebar
  Overview entry no longer no-ops after switching tabs.
- LOW: keyboard activation (Enter/Space) for the role=button expander heads,
  backup tiles, and sidebar Overview entry.
- LOW: preserve ALL expanded Updates rows across a background repaint, not just
  the single ?app= deep-link (split toggle into _openDetail/_closeDetail).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 00:37:59 +01:00
librelad
c1ab09f406 Merge claude/1 2026-06-01 00:10:30 +01:00
librelad
dfd4ffa268 feat(webui): drop Updates & Backups top-nav items; fold into App Center
The fleet Overview area (Overview · Updates · Improvements · Backups) and the
backup center now live under App Center, so the standalone top-nav items are
redundant. Top nav is now Dashboard · App Center · Admin · Tasks.

- Remove the Backups and Updates anchors from topbar.html.
- Remove the nav{} blocks from updater/backup feature.json + manifest (so they
  don't resurface when the nav kernel lands).
- Highlight App Center for /overview and /backup; drop the dead /updater branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 00:10:30 +01:00
librelad
d4e5cdca83 Merge claude/1 2026-06-01 00:01:59 +01:00
librelad
c7ae1414b9 feat(webui): redirect /updater into Overview; surface backup config in Admin
- _legacyRedirect: /updater[/tab] -> /overview[/tab] (security/recovery/history
  fold into the Updates expander -> /overview/updates). /backup is intentionally
  NOT redirected — it stays the operational backup center (locations/migrate/
  snapshots), reached from Overview › Backups.
- Re-point the per-app hotfix chip to /overview/improvements.
- Unhide the existing backup config category in the Admin sidebar so
  engine/schedule/retention config lives under Admin (same generated category
  the backup center binds, so edits stay in sync).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 00:01:59 +01:00
librelad
18ff440115 Merge claude/1 2026-05-31 23:54:21 +01:00
librelad
1460acb941 feat(webui): add per-app Updates tab (version/CVEs/recovery/history)
New 'Updates' tab in the app detail page, beside Backups. Reuses the headless
UpdaterPage + renderAppDetail() scoped to the single app, so the per-app and
fleet views share one data/render path. UpdaterPage is added to the apps script
bundle so it's available on app pages; the tab is disabled while a task runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 23:54:21 +01:00
librelad
32080e5aef Merge claude/1 2026-05-31 23:46:34 +01:00