Symptom: after any commit / deploy on this box, the WebUI would log
users out ~60 seconds after they logged back in. Looked like a
short session timeout; was actually the auth file being deleted.
Cause: my recent update.sh change added --delete to the frontend
rsync so source-tree file removals propagate to the live install.
Excludes only protected data/. .auth.json sits at the top of
frontend/ (never in the source repo — it's the persisted credentials
+ JWT secret), so --delete nuked it on every deploy. The next
container start regenerated it with a fresh secret; all existing
cookies (signed with the old secret) became invalid. The dashboard's
60-second auto-refresh hits /data/system/*.json which is auth-gated,
gets 401, and the global 401 interceptor in auth-manager.js shows
the re-login overlay. Hence 'logged out after 60 seconds'.
Fix: extend the rsync exclude list with:
--exclude '.*' (any top-level dotfile — covers .auth.json
and future runtime state of the same shape)
--exclude '*.lock' (lockfiles like setup.lock if any ever land
outside data/)
--exclude '*.bak' (backup files from manual edits)
data/ exclude kept. JWT lifetime stays at 30 days as designed.
Also: feat(webui): icon on the 'Open Locations' button in the
backup → Migrate tab's empty state. Matches the location-pin icon
used by the sidebar's Locations entry so the visual carries over
when the user clicks through.
Signed-off-by: librelad <librelad@digitalangels.vip>
256 lines
13 KiB
HTML
256 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>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">
|
||
<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>
|
||
<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>
|