De-clutter each component into sub-system folders (apps: core/ port-manager/
services/ tools/ routing/; admin: config/ overview/ system/ ssh/ peers/) with
the standard js/ css/ html/ icons/ layout inside; single-page components
(backup/dashboard/tasks/updater) get js/ css/ html/. Single-feature icon sets
moved into their sub-system (vpn -> apps/core/icons, config/cpu/os ->
admin/{config,system}/icons); shared app + category icons stay in core/icons.
feature.json + index.js stay at each component root (the scanned descriptor +
entry). Every controller/CSS/fragment/icon path reference rewritten; verified
no stale refs, all JS valid.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
263 lines
13 KiB
HTML
263 lines
13 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>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>
|
||
<span class="backup-card-hint" id="backup-next-run" title="Next scheduled backup run (from the app backup crontab)">—</span>
|
||
</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>
|
||
<!-- Each backup is rendered by renderSnapshots() as a .task-item
|
||
card, matching the per-app Backups tab so the two surfaces
|
||
share one visual language. -->
|
||
<div class="backup-snapshot-rows" id="backup-snapshot-list"></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 backup taken on another host out of a shared backup location and lays it down here. The destination's existing copy of the app is backed up 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-delete-location-modal">
|
||
<div class="backup-modal-inner">
|
||
<div class="backup-modal-header">
|
||
<h3>Delete backup location</h3>
|
||
<button class="backup-modal-close" data-close-modal>×</button>
|
||
</div>
|
||
<div class="backup-modal-body" id="backup-delete-location-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-location-confirm">Delete location</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>
|