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

202 lines
9.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="container backup-layout">
<div class="mobile-overlay" id="mobile-overlay"></div>
<div class="sidebar" id="sidebar">
<div class="sidebar-section" id="backup-sidebar-list">
<div class="category active" data-backup-tab="dashboard">
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="9"></rect>
<rect x="14" y="3" width="7" height="5"></rect>
<rect x="14" y="12" width="7" height="9"></rect>
<rect x="3" y="16" width="7" height="5"></rect>
</svg>
Dashboard
</div>
<div class="category" data-backup-tab="backups">
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
Backups
</div>
<div class="category" data-backup-tab="locations">
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
Locations
</div>
<div class="category" data-backup-tab="configuration">
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
Configuration
</div>
</div>
</div>
<div class="main">
<div class="backup-page" id="backup-page">
<div class="config-section backup-page-section">
<div class="page-header" id="backup-page-header">
<div class="page-header-icon-slot" id="backup-page-header-icon"></div>
<div class="page-header-title">
<h1 id="backup-section-title">Dashboard</h1>
<p id="backup-section-subtitle"></p>
</div>
<div class="page-header-actions">
<button class="backup-refresh-btn" id="backup-refresh-btn" title="Refresh">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"></polyline>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
</svg>
Refresh
</button>
<button class="backup-primary-btn" id="backup-primary-action"></button>
<div class="backup-export-menu" id="backup-export-menu" role="menu" hidden>
<button type="button" class="backup-export-menu-item" data-action="export-passwords" role="menuitem">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Repository Passwords
</button>
</div>
</div>
</div>
<div class="backup-page-body">
<span class="backup-engine-badge" id="backup-engine-badge" hidden>restic</span>
<section class="backup-tabpanel active" id="backup-panel-dashboard">
<div class="backup-summary-row" id="backup-summary-row"></div>
<div class="backup-cards-row">
<div class="backup-card">
<div class="backup-card-header">
<h2>Per-app status</h2>
<span class="backup-card-hint">Latest backup per app on this host</span>
</div>
<div class="backup-app-grid" id="backup-app-grid"></div>
</div>
<div class="backup-card">
<div class="backup-card-header">
<h2>Locations</h2>
<span class="backup-card-hint">Active destinations</span>
</div>
<div class="backup-repo-list" id="backup-repo-list-summary"></div>
</div>
</div>
<div class="backup-card">
<div class="backup-card-header">
<h2>System config</h2>
<span class="backup-card-hint">Global settings, WebUI login &amp; backup-location credentials</span>
</div>
<div class="backup-app-tile-meta" id="backup-system-status" style="margin:0 0 10px">
<span class="backup-status-dot none"></span><span>No backup yet</span>
</div>
<p class="backup-card-hint" style="margin:0 0 12px">
Snapshot the LibrePortal system config to every enabled location so a bare-metal
restore is self-sufficient — without it, the credentials needed to reach your own
backups live only on this box. Runs automatically with “Backup all apps” too.
</p>
<div class="backup-system-actions" style="display:flex;gap:8px;flex-wrap:wrap">
<button type="button" class="backup-primary-btn" data-action="backup-system">Back up now</button>
<button type="button" class="backup-secondary-btn" data-action="restore-system">Restore…</button>
</div>
</div>
</section>
<section class="backup-tabpanel" id="backup-panel-backups">
<div class="backup-filters">
<input class="backup-filter-input" id="backup-snapshot-filter" placeholder="Filter by app, host, or backup id…">
<select class="backup-filter-select form-control" id="backup-snapshot-repo">
<option value="">All locations</option>
</select>
</div>
<div class="backup-snapshot-table-wrap">
<table class="backup-snapshot-table">
<thead>
<tr>
<th>App</th>
<th>Host</th>
<th>Location</th>
<th>When</th>
<th>ID</th>
<th class="backup-col-actions">Actions</th>
</tr>
</thead>
<tbody id="backup-snapshot-tbody"></tbody>
</table>
</div>
</section>
<section class="backup-tabpanel" id="backup-panel-locations">
<div class="backup-location-list" id="backup-location-list"></div>
</section>
<section class="backup-tabpanel" id="backup-panel-configuration">
<div id="backup-configuration-body"></div>
</section>
</div>
</div>
</div>
</div>
</div>
<div class="backup-modal" id="backup-restore-modal">
<div class="backup-modal-inner">
<div class="backup-modal-header">
<h3>Restore backup</h3>
<button class="backup-modal-close" data-close-modal>×</button>
</div>
<div class="backup-modal-body" id="backup-restore-modal-body"></div>
<div class="backup-modal-footer">
<button class="backup-secondary-btn" data-close-modal>Cancel</button>
<button class="backup-primary-btn" id="backup-restore-confirm">Restore</button>
</div>
</div>
</div>
<div class="backup-modal" id="backup-delete-modal">
<div class="backup-modal-inner">
<div class="backup-modal-header">
<h3>Delete backup</h3>
<button class="backup-modal-close" data-close-modal>×</button>
</div>
<div class="backup-modal-body" id="backup-delete-modal-body"></div>
<div class="backup-modal-footer">
<button class="backup-secondary-btn" data-close-modal>Cancel</button>
<button class="backup-danger-btn" id="backup-delete-confirm">Delete</button>
</div>
</div>
</div>
<div class="backup-modal" id="backup-engine-modal">
<div class="backup-modal-inner backup-modal-wide">
<div class="backup-modal-header">
<h3 id="backup-engine-modal-title">Backup engine details</h3>
<button class="backup-modal-close" data-close-modal>×</button>
</div>
<div class="backup-modal-body" id="backup-engine-modal-body"></div>
<div class="backup-modal-footer">
<button class="backup-secondary-btn" data-close-modal>Close</button>
</div>
</div>
</div>
<div class="backup-modal" id="backup-add-location-modal">
<div class="backup-modal-inner">
<div class="backup-modal-header">
<h3>Add a backup location</h3>
<button class="backup-modal-close" data-close-modal>×</button>
</div>
<div class="backup-modal-body" id="backup-add-location-modal-body"></div>
<div class="backup-modal-footer">
<button class="backup-secondary-btn" data-close-modal>Cancel</button>
<button class="backup-primary-btn" id="backup-add-location-confirm">Add location</button>
</div>
</div>
</div>