librelad 14bc0c3386 ui(backup): tile-click → Back-up checklist modal; LibrePortal icon on System tile; 2-up grid
Reshape the dashboard's Backup status grid into a click-to-pick UI:

- Removed the inline Back-up / Restore buttons from the System config
  tile. Same shape as an app tile now; LibrePortal app icon instead of
  the server-stack glyph.
- Grid is 2 columns (was auto-fill min 220px). Tiles are wider, read
  better, and the System tile no longer needs to span a full row to fit
  inline buttons.
- Click any tile (System or app) → opens a new "Back up" modal:
    * System config first (key=__system__, LibrePortal icon)
    * Every installed app, alphabetical
    * Checkbox per row + 'Select all' / 'Clear' shortcuts
    * The tile clicked is pre-ticked
- Confirm queues backup tasks:
    * Everything ticked  → single `libreportal backup all` (which also
      runs `backup system`) — one task instead of N
    * Subset            → one task per ticked item (`backup system`
      and/or `backup app create <slug>`)

Restore for System config used to live on the dashboard's inline
'Restore' button. It's now reachable via the Backups tab — system
snapshots appear in the snapshot list with the standard per-row
Restore action — same path apps already use. No new UI required;
just one fewer dashboard button.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-27 01:05:44 +01:00

259 lines
13 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="migrate">
<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12h18"></path>
<polyline points="12 5 19 12 12 19"></polyline>
<path d="M3 5v14"></path>
</svg>
Migrate
</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>Backup status <span class="tooltip" title="Latest backup per app + System config. Back up System first — it's needed to restore the rest." style="font-size:.75em;opacity:.7;cursor:help"></span></h2>
</div>
<!-- The "System config" tile is rendered FIRST inside this grid
by renderDashboard() / renderSystemTile(), styled the same
as an app tile but with a server icon and two inline action
buttons (back up / restore). Folding both into one grid
gives a single at-a-glance checklist instead of two
parallel sections. -->
<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>
</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-migrate">
<div class="backup-card backup-migrate-card">
<div class="backup-card-header">
<h2>Cross-host migrate <span class="tooltip" title="Pulls a snapshot taken on another host out of a shared backup location and lays it down here. The destination's existing copy of the app is snapshotted first (rollback safety), then replaced." style="font-size:.75em;opacity:.7;cursor:help"></span></h2>
<span class="backup-card-hint">Restore an app from another LibrePortal</span>
</div>
<div class="backup-migrate-empty" id="backup-migrate-empty" hidden>
<!-- Bordered callout panel — matches the per-app task-card
visual so the empty state reads as a contained block
rather than floating centred text. -->
<div class="backup-empty-state" style="border: 1px solid var(--border-color, #2a2a2a); background: var(--surface-2, rgba(255,255,255,0.02)); border-radius: 10px; padding: 28px 24px; margin: 12px 0;">
<div style="margin-bottom: 6px; opacity: .7; display: flex; justify-content: center;">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<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>
</div>
<div style="margin-bottom: 14px;">
No backups from other hosts visible in any enabled location.<br>
Add a <strong>shared backup location</strong> on both hosts to enable cross-host migrate.
</div>
<button type="button" class="backup-primary-btn" data-action="go-to-locations">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<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>
Open Locations
</button>
</div>
</div>
<div class="backup-migrate-body" id="backup-migrate-body"></div>
</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-pick-modal">
<div class="backup-modal-inner">
<div class="backup-modal-header">
<h3>Back up</h3>
<button class="backup-modal-close" data-close-modal>×</button>
</div>
<div class="backup-modal-body" id="backup-pick-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-pick-confirm">Back up selected</button>
</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-migrate-modal">
<div class="backup-modal-inner">
<div class="backup-modal-header">
<h3>Migrate from another LibrePortal</h3>
<button class="backup-modal-close" data-close-modal>×</button>
</div>
<div class="backup-modal-body" id="backup-migrate-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-migrate-confirm">Start migrate</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>