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>
250 lines
12 KiB
HTML
250 lines
12 KiB
HTML
<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>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 backup-system-card">
|
||
<div class="backup-card-header">
|
||
<h2>
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:.7;vertical-align:-3px;margin-right:7px">
|
||
<rect x="2" y="3" width="20" height="6" rx="1"></rect>
|
||
<rect x="2" y="9" width="20" height="6" rx="1"></rect>
|
||
<line x1="6" y1="6" x2="6.01" y2="6"></line>
|
||
<line x1="6" y1="12" x2="6.01" y2="12"></line>
|
||
</svg>System config
|
||
</h2>
|
||
<span class="backup-card-hint">Global settings, WebUI login & 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-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>
|
||
<div class="backup-empty-state">
|
||
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 style="margin-top:12px">
|
||
<button type="button" class="backup-primary-btn" data-action="go-to-locations">Open Locations</button>
|
||
</div>
|
||
</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-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>
|