759 Commits

Author SHA1 Message Date
librelad
7f034d3e02 fix(webui): reload app/service data after a config apply
config_update re-deploys apps (ports, subdomains, Open URLs, routing) but the
WebUI only unlocked the nav on completion — leaving stale URLs/routing until a
manual reload. apps-manager now refreshes apps + services and repaints the
current view when a config_update task completes, via a reusable
refreshAppsAndView() helper (also the basis for the upcoming task-refresh
registry).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 21:52:29 +01:00
librelad
6010727391 Merge claude/2 2026-05-28 21:47:04 +01:00
librelad
e3454dd10e feat(dashboard): whole-disk donut with storage breakdown on the frontpage
Replace the frontpage liquid-fill disk circle with a real donut split into
Apps · Docker · Other · Free, keeping disk % used in the centre. Apps come
from the per-app generator (root-device bytes), Docker from system df; both
are clamped within "used" so skew can't overflow. Live disk ticks redraw it,
and the card now clicks through to the full Storage breakdown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 21:47:04 +01:00
librelad
9c247ab3cf Merge claude/2 2026-05-28 21:38:47 +01:00
librelad
b020a3f43a feat(system): re-add Disk usage to the System trends grid
The trends grid lost its disk chart; add it back as a 6th card plotting
root-filesystem % over time, alongside CPU/Memory/Network/Load/Swap. The
'disk' history series and metric-detail entry already existed, so the
expand-to-detail flow works unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 21:38:47 +01:00
librelad
eef24582b2 Merge claude/1 2026-05-28 21:32:29 +01:00
librelad
9ca5cc6c7c feat(system): full, deletable images list on the Storage page
Replaces the read-only "Largest images" top-10 table with a Tasks-style list of
ALL Docker images, with select-one / select-multiple / clear-all removal that
mirrors the Tasks page UX (row checkboxes, master select-all, a button that
morphs Clear All ↔ Delete Selected (N), an eo confirm modal).

Deletion routes through the task system, NOT a new web API: a new
`libreportal system image rm [--force] <ids>` CLI subcommand (validates each
ref, loops runFileOp docker image rm, reports a tally) is invoked via the
system_image_rm task action — same pattern as Reclaim. The web backend change
is read-only (uncap the existing /storage image list). In-use images are
skipped by default with an opt-in "force-remove" toggle (warned). The page
stays put, toasts, and refreshes on the task's completion event.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 21:32:29 +01:00
librelad
b4607de950 Merge claude/2 2026-05-28 21:28:19 +01:00
librelad
e6cc84271c ux(system): one unified Storage donut — apps + Docker together
Put everything back in a single donut instead of an app-only one: a slice
per app plus Docker's images and build cache, with one legend listing them
all. Colours run continuously so app and Docker slices stay distinct, and
the Docker cards below reuse the same colours. The per-folder app
drill-down table stays. This is the "all the data, with app usage added"
view that was asked for.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 21:28:19 +01:00
librelad
beed825778 Merge claude/2 2026-05-28 20:59:13 +01:00
librelad
e5cbfba417 ux(system): make per-app usage the Storage page headline
The Storage page now leads with on-disk usage by app: the headline donut
is split by app (each a coloured slice, total app data in the centre),
and the "Storage by app" table is its legend — swatch + bar colours match
the slices, rows expand to the per-folder breakdown. Docker's engine
figures (images + build cache) drop to a secondary section below. This is
the integration that was asked for; the donut is your data, not Docker's
overhead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 20:59:13 +01:00
librelad
327fda8cd9 Merge claude/2 2026-05-28 20:51:06 +01:00
librelad
5f91d2717e ux(system): drop container writable-layer from the Storage view
Like named volumes, a container's writable layer is a near-zero scratch
number for LibrePortal (app data lives in bind mounts, shown per-app), so
sitting it next to per-app storage just confused things. Remove the
"Containers" slice/card and its backend summation, and reframe the Docker
breakdown as "Docker engine" overhead (images + build cache) — clearly
separate from your app data.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 20:51:06 +01:00
librelad
a31d77b751 Merge claude/2 2026-05-28 20:33:21 +01:00
librelad
7a0477ff8b feat(system): per-folder breakdown under each app on the Storage page
Extend the app-storage generator to record every bind mount's size and
in-container path, grouped into a per-app folder list. The "Storage by
app" rows are now expandable: click an app to see where its space goes
(e.g. /var/lib/mysql vs /data), with external-drive folders flagged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 20:33:21 +01:00
librelad
f5f3db900a Merge claude/2 2026-05-28 20:26:13 +01:00
librelad
17fe4d6ed5 ux(system): drop the Volumes category from the Storage view
LibrePortal apps keep data in bind mounts, so Docker named-volume
accounting is always ~empty and just reads as a confusing "0 B". Now that
per-app on-disk usage covers the real "what's filling my disk" question,
remove volumes end to end: the donut slice, category card, "Largest
volumes" table and the System-page count, plus the backend's volume
summation and top_volumes payload. Reclaim copy no longer references
volumes (it reassures about app data instead).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 20:26:13 +01:00
librelad
10b814cd93 Merge claude/1 2026-05-28 20:22:04 +01:00
librelad
5929ecb4c4 docs(install): clarify the signing-key comment now that it's the real key
The old comment implied the key was still a REPLACE_ME placeholder. Reword to
describe current behaviour (signature required for release installs) plus how to
rotate the key.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 20:22:04 +01:00
librelad
a2dd26b469 Merge claude/1 2026-05-28 20:20:38 +01:00
librelad
3b0b3a0a1f feat(release): activate release signing with the production minisign key
Replaces the REPLACE_ME placeholder public key in libreportal.pub and install.sh
with the real LibrePortal release-signing public key (id BC92526B3ECA7F41). The
secret half is held offline by the maintainer.

This activates the signature-required path everywhere it was wired but inert:
install.sh now REQUIRES a valid tarball signature on release installs, the
updater (fetch.sh) requires it on update, and the integrity check (verify.sh)
will report a real "Verified" state once a signed release is installed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 20:20:38 +01:00
librelad
1c3531b932 Merge claude/2 2026-05-28 20:06:34 +01:00
librelad
4b39cf770b feat(system): per-app on-disk storage on the Storage page
Docker only tracks where an app's data lives (its bind mounts), not how
big a bind-mounted host dir is — so named-volume accounting reads ~0 for
LibrePortal, whose app data lives in bind mounts. Add a generator that
reads each app's mount map from `docker inspect` and `du`s the directories
(via runFileOp, so it runs as the data-owning user and isn't blocked by
rootless UID mapping). `du -x` keeps each measurement on its own
filesystem, so data on a separate disk is reported as a distinct
"external" total. The generator self-throttles to ~10 min since du is
heavier than the per-minute metrics. Surfaced as a "Storage by app"
section on the Storage page.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 20:06:34 +01:00
librelad
271b489029 Merge claude/1 2026-05-28 19:41:22 +01:00
librelad
b28268a61f feat(system): "Verified" integrity check against the signed release manifest
Adds per-file integrity attestation on top of the existing signed-tarball
release flow. make_release now generates a SHA256SUMS manifest over the shipped
tree and (when a key is configured) signs it, riding both inside the release
tarball so they land in the install tree with no extra download.

lpVerifyInstall (scripts/source/verify.sh) re-hashes the install tree against
that manifest and verifies the manifest's minisign signature against the
root-owned footprint pubkey, yielding states: verified / modified / tampered /
unsigned / unverifiable / development. webuiSystemVerify writes verify_status.json
(throttled daily, force on demand, also after each update apply), surfaced as an
Integrity line + "Verify now" button on the Admin → Overview Updates card and a
row in the update details panel. `libreportal verify` exposes the same check on
the CLI.

Honest framing: this is a self-check (run by the software it verifies), so red
fires only for genuine modified/tampered states; the badge tooltip points to
out-of-band `minisign -Vm` for an independent guarantee.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 19:41:22 +01:00
librelad
9324c6a4f4 Merge claude/2 2026-05-28 19:28:17 +01:00
librelad
89a2b300b8 ux(overview): slim the System card to 3 compact rows
Drop the OS row and compact Memory/Uptime so the System card matches
the density of its sibling cards. Full detail stays on the deep system
stats page.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 19:28:17 +01:00
librelad
f792cf55f6 Merge claude/2 2026-05-28 19:06:02 +01:00
librelad
49cf7e8bec ux(system): move Reclaim button top-right, make it actually free space
Three fixes from testing the storage page:

- Placement: the "Reclaim space" button moves into the page header,
  top-right (matching the metric page), instead of sitting in the body.

- It now actually reclaims: build cache needs -a to drop (docker reports
  0 B "reclaimable" without it, but it's pure cache — safe to clear), so
  the CLI uses `docker builder prune -af`. Previously the safe scope
  freed ~nothing on a box whose reclaimable was mostly cache.

- Honest "Reclaimable" number: /api/system/storage was counting the
  whole build cache AND unused tagged images, overstating what the safe
  prune frees (e.g. 340 MB shown, ~96 MB per docker, button cleared 0).
  Reclaimable now = dangling images + build cache only; stopped
  containers and volumes are never counted (the safe prune never touches
  them). Headline now matches the button's effect.

Also simplify the CLI output (drop the jargony scope notice and the
reclaimed-total greps) and re-enable the now-persistent header button
after the post-reclaim refreshes.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 19:06:02 +01:00
librelad
816b96fe97 Merge claude/2 2026-05-28 18:50:28 +01:00
librelad
3031c6cab9 feat(system): "Reclaim space" action on the Storage page
Adds a `libreportal system reclaim` CLI command and an orange "Reclaim
space" button on /admin/config/system/storage (the v2 prune control the
page always hinted at).

Scope is deliberately SAFE: build cache + dangling (untagged) images
only (docker builder prune -f + docker image prune -f via the
rootless-aware runFileOp). It never touches volumes (app data) or
tagged/in-use images, so nothing an app relies on is removed.

Wiring mirrors system_update: a systemReclaim() action + system_reclaim
route case run the command verbatim through the task processor. The
button confirms via showConfirmation, shows a spinner, and re-reads
storage usage as the prune lands. Button styled with --status-warning to
match the Reclaimable stat it sits under, with a note clarifying scope.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 18:50:27 +01:00
librelad
f6fecd023a Merge claude/1 2026-05-28 18:38:28 +01:00
librelad
7ba281a390 ux(backup): redesign the snapshot details panel
The expanded snapshot detail reused the shared .task-meta/.meta-item
layout, which forces each field onto one nowrap line and clips long
values (the full date string, repo paths) mid-string. Give the backup
snapshot its own scoped label-over-value grid plus full-width Tags/Paths
blocks that wrap, surface app=/host=/engine= tags as their own fields,
and show a readable date (full timestamp on hover). Applied to both the
global Snapshots tab and the per-app Backups card so they match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 18:38:28 +01:00
librelad
93d4aaabbb Merge claude/1 2026-05-28 18:25:25 +01:00
librelad
d448d34f67 feat(backup): auto-refresh the backup page when a backup/restore finishes
The page has no live feed, so a completed backup/restore wasn't reflected
until a manual Refresh or re-navigation. Subscribe to the TaskEventBus
'taskCompleted' event and repaint on backup/restore completions. Debounced
to coalesce the burst when several per-app tasks finish together; only the
mounted instance reacts and a stale listener removes itself. The Refresh
button stays as a manual pull.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 18:25:24 +01:00
librelad
7a302d1af0 Merge claude/1 2026-05-28 18:20:21 +01:00
librelad
7dbeb307c9 ux(backup): drop the "Backup all apps" header action on Dashboard and Migrate
The primary header button defaulted to "Backup all apps" on every tab that
wasn't Locations/Configuration, so it showed on Dashboard and Migrate where
it isn't wanted (Dashboard backs up via the status-grid tiles; Migrate is
about moving hosts, not backing up). Keep it on the Backups tab and hide the
header action entirely on Dashboard and Migrate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 18:20:21 +01:00
librelad
dca885738d Merge claude/1 2026-05-28 16:47:17 +01:00
librelad
8e6691b7d3 feat(system): surface the Docker storage breakdown on the System page
Promote a compact Storage summary (breakdown donut + per-category legend
+ reclaimable) onto the System index, replacing the thin Docker strip and
its easily-missed "Open breakdown" link; it links through to the full
breakdown page. Drop the Disk usage trend chart, which duplicated the
Disk gauge's root-mount %.

Extract the donut + segment builders onto SystemStoragePage so the index
summary and the full page share one renderer. This also fixes a donut
stacking bug: the SVG used the final cumulative fraction for every
slice's dashoffset instead of each slice's own running offset, so the
ring only partially filled. It now fills proportionally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 16:47:17 +01:00
librelad
324240dd90 Merge claude/2 2026-05-28 16:46:23 +01:00
librelad
b7679bb384 fix(admin/system): clear the "no samples" overlay once live data arrives
The metric detail page showed the empty overlay ("No samples in this
range yet — check back in a minute") whenever the initial history fetch
returned zero points, but nothing ever hid it again. Live ticks then
pushed samples in, drew the chart and filled the now/peak/avg/min stats
— while the overlay stayed up, contradicting the visible data.

Make _renderChart the single authority for the overlay: hidden whenever
there are points, shown only when there are none. Live data clears it as
soon as the first sample lands; switching to a genuinely empty range
brings it back.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 16:46:23 +01:00
librelad
284356228b Merge claude/2 2026-05-28 16:28:42 +01:00
librelad
7178dddae7 ux(admin/system): make the Load gauge capacity-aware, not alarmist
The Load ring was driven by load1_percent = min(100, load1/cores*100)
and coloured by the generic gauge thresholds (red at >=90%). On a
low-core box that pinned it red whenever load merely approached the core
count — which is normal "fully used" territory, not a problem.

Drive the ring from raw load1 with max = cores*2 (so load == cores sits
mid-gauge) and colour by load-per-core: green below capacity, orange
around capacity (>=1.0x), red only once load clearly exceeds it (>=1.7x,
tasks genuinely queuing). cpu.cores rides the live SSE payload, so the
colour is correct on live ticks too.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 16:28:42 +01:00
librelad
ec5b1735dc Merge claude/2 2026-05-28 16:16:22 +01:00
librelad
9c6cef5a05 ux(admin/system): give the host info strip a "Host" section head
The OS/Kernel/Uptime/CPU/Swap strip was the only section on the System
page rendered without a .sys-section-head, so it had no title and butted
directly against the charts above it (no top spacing). Add a "Host" head
— matching the Docker / Per-app pattern — which supplies both the label
and the section's 26px top margin. "Host" rather than "System" since the
page H1 is already "System".

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 16:16:22 +01:00
librelad
280bb11d5e Merge claude/1 2026-05-28 16:12:48 +01:00
librelad
f0dc73e332 fix(admin): Manage backups button navigates via the real SPA router
The admin overview Manage backups action called window.librePortalSPA,
a global that is never assigned, so the optional-chaining call silently
no-op'd. The router is window.spaClean; point the call at it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 16:12:48 +01:00
librelad
00de75ffc3 Merge claude/1 2026-05-28 14:49:18 +01:00
librelad
ed319b0f94 fix(backup): configs backup gets its own task identity, not "Backup All Apps"
System-config backups (libreportal backup system) carry no app slug, so the
notification descriptor resolved a blank subject + no icon, and a system-only
pick collapsed to `backup all` when no apps were installed. Give them the
LibrePortal icon + a "Configs" subject, add backup-system to the system-task
logo detection, and guard the whole-fleet collapse on having >=1 app. Rename
the visible subject from "System config" to "Configs" throughout the backup UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 14:49:18 +01:00
librelad
494dc499b3 Merge claude/2 2026-05-28 14:42:22 +01:00