7 Commits

Author SHA1 Message Date
librelad
88b431ee86 style(migrate): tighten card header + give the empty state a real CTA
The Migrate tab carried two walls of explanation text — a 3-line hint
under the h2 ("Pulls a snapshot taken on another host…") and an even
longer empty-state paragraph ("Either no other LibrePortal has backed
up to a location this host can see, or this is the only host using its
locations…"). Both spelled out diagnosis the user can infer from the
empty list itself, and the tone didn't match the rest of the backup
page (cards elsewhere have a short title + a 4-6 word hint, with any
long explanation as a hover title attribute).

Three changes:

1. h2 down to "Cross-host migrate" with a small ℹ️ carrying the full
   explanation as a title= tooltip — matches the existing tooltip
   pattern in the Locations form (BACKUP_RETENTION_PRESET_META).
   The short subtitle "Restore an app from another LibrePortal" stays
   as backup-card-hint, mirroring "Per-app status / Latest backup per
   app on this host" elsewhere on the page.

2. The empty state is now the standard `<div class="backup-empty-state">`
   container (same shape Locations + Snapshots use), one trimmed line
   ("No backups from other hosts visible in any enabled location.
   Add a shared backup location on both hosts to enable cross-host
   migrate.") instead of two paragraphs.

3. Added an "Open Locations" CTA button inside the empty state — the
   #1 next-step for a user staring at this empty list is to add a
   shared location, which lives one tab over. New data-action
   "go-to-locations" wired through the existing event-delegation
   handler in backup-page.js calling switchTab('locations').

The renderMigrate JS still toggles #backup-migrate-empty.hidden — the
wrapper id is unchanged, only its inner markup tightened. No
behavioural change beyond the CTA + tab switch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 23:58:52 +01:00
librelad
52e4280a67 feat(webui): add 'Migrate' tab — restore an app from another LibrePortal
Phase 1 of the migration-system refresh. Surfaces Phase 0's kernel
(libreportal restore migrate ...) as a WebUI flow so users don't have
to drop to the CLI to pull an app from a peer's backups.

backend / data generator:
  scripts/webui/data/generators/backup/webui_backup_migrate.sh
    Walks every enabled backup location, lists every (other_host, app)
    pair with snapshot count + latest id/date, and emits a single
    destination summary block (installed apps, running apps, disk free)
    so the frontend can compute collisions and warnings without per-row
    API round-trips. Filters out our own hostname — we don't migrate to
    ourselves. Output: data/backup/generated/migrate.json.
    Hooked into the standard webuiLibrePortalUpdate refresh pipeline,
    so 'libreportal regen webui' (and the periodic task-processor poll)
    keep it fresh on their own.

frontend:
  - New 'Migrate' sidebar tab on /backup, sits between Locations and
    Configuration. Path-based URL: /backup/migrate.
  - Per-source-host cards listing every available app, with snapshot
    count + relative-time hint, collision dot when the app is already
    installed here, and per-app + per-host migrate buttons.
  - Confirm modal with two checkboxes matching the kernel's defaults:
      [✓] Back up the destination's existing copy first   (pre-migrate
          backup; auto-disabled when there's nothing to back up)
      [✓] Rewrite host-bound URLs to this host             (URL rewrite
          — uncheck only to keep source hostnames)
    On confirm, runs 'libreportal restore migrate app/system …' via the
    task system; opt-out checkboxes append --no-pre-backup / --keep-urls
    only when the user un-ticks, matching the kernel's default-on flags.
  - Empty state when no other hosts have visible backups, explaining
    the shared-backup-location prerequisite.

The CLI dispatcher hooks (Phase 0) wire restore migrate app/system to
migrateApplyApp/migrateApplySystem, so the WebUI gets pre-backup safety,
URL rewrite, and structured progress (when --json-progress is set; not
needed here yet — the task system's log tail is enough for v1).

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 17:32:01 +01:00
librelad
9f37f7655d polish(webui): spacing + icon for the System config backup card; doc the status
- Add .backup-system-card { margin-top: 20px } — the card stands alone below the
  two-column cards row (which has no bottom margin), so it was butting against it.
- Add a server-stack icon to the card header (matches the nebula stroke-icon style).
- DEVELOPMENT.md: document the dashboard "System config" card + its last-backup
  status (tag system=config → `system` in the dashboard JSON), the CLI/auto paths,
  and that the libreportal app is excluded from the per-app grid.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:43:24 +01:00
librelad
3283b3f7a3 feat(webui): track system-config backup status on the dashboard
Make the system config a tracked backup, not just action buttons:

- engine: resticSystemSnapshotsJson (tag system=config) + engineSystemSnapshotsJson
  dispatcher — query the system snapshots the way per-app status is queried.
- webui_backup_dashboard.sh: emit a "system": { latest_snapshot, latest_time }
  object (latest system snapshot on the primary location), and exclude the
  libreportal WebUI app from the per-app grid (it's intentionally not backed up, so
  it no longer shows a perpetual "No backup yet" tile).
- backup dashboard card: a status line (dot + "Last backed up <relative>" / "No
  backup yet"), populated in renderDashboard from d.system — mirrors the app tiles.

Verified: shell + JS parse; dashboard content assembles to valid JSON with the
system key; engine query defined + dispatched; frontend reads d.system into the
#backup-system-status element.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:38:39 +01:00
librelad
c2c10103b8 feat(webui): surface system-config backup/restore on the backup dashboard
Add a "System config" card to the backup dashboard with two actions wired through
the task processor (same path as "Backup all apps"):

- "Back up now"  -> libreportal backup system
- "Restore…"     -> libreportal restore system  (confirm dialog explains it lands
  in a staging folder and never overwrites live config)

Card copy explains why it matters (the backup-location creds otherwise live only on
the box). Click handlers + runBackupSystem/confirmRestoreSystem added; JS parses,
data-actions match handlers, commands match the CLI subcommands.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 00:31:23 +01:00
librelad
4568ec51ef feat(backup): Export dropdown in Configuration header; warning is dismiss-only
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>
2026-05-23 00:14:06 +01:00
librelad
875a60f90f LibrePortal v0.1.0 — initial release
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>
2026-05-21 20:37:54 +01:00