copy(backup): user-facing "snapshot" → "backup" across the UI

"Snapshot" is restic's term and leaks the tool's vocabulary into the
WebUI. Users think in "backups" — the on-page label even says "Backups"
already; only the secondary copy still said "snapshot". Renames the
remaining user-visible mentions while leaving code identifiers, API
keys, data attributes, CSS class names, and the ?snapshot= deep-link
param untouched (those are internal contracts and changing them would
churn for no user-visible win).

Renamed surfaces:
  - Per-app Backups tab header:
      "Snapshots for <app>" → "Backups for <app>"
      "across all configured repositories" → "across all configured locations"
  - BackupAppCard:
      "No snapshots yet"   → "No backups yet"
      "No snapshots found" → "No backups found"
      "Showing the most recent 50 of N snapshots" → "...of N backups"
      ID-chip tooltip "Snapshot ID" → "Backup ID"
      Detail panel "Snapshot ID:" → "Backup ID:"
  - Backup retention preset descriptions (KEEP_LAST/DAILY/WEEKLY/MONTHLY/
    YEARLY) — "snapshot per day/week/..." → "backup per day/week/..."
  - Personal preset hint: "6 monthly snapshots" → "6 monthly backups"
  - Restore confirmation modal hint: "snapshot restored in place" →
    "backup restored in place"
  - Config-warning banner copy adjusted so it doesn't introduce
    "snapshots" as a noun
  - Retention "Keep last" input suffix: "snapshots" → "backups"
  - Cross-host migrate tooltip: "snapshot" → "backup"

Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-28 00:42:08 +01:00
parent e0e4bddd57
commit 989123322b
4 changed files with 18 additions and 18 deletions

View File

@ -181,7 +181,7 @@
<div class="backup-section" id="backup-section">
<div class="backup-title">
<h3>💾 Backups</h3>
<p>Snapshots for <span id="backup-app-name">this app</span> across all configured repositories.</p>
<p>Backups for <span id="backup-app-name">this app</span> across all configured locations.</p>
</div>
<div class="backup-app-card" id="backup-app-card">
<div class="backup-app-card-status" id="backup-app-card-status">Loading…</div>

View File

@ -134,7 +134,7 @@
<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>
<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>

View File

@ -62,8 +62,8 @@ class BackupAppCard {
const allSnaps = this.flattenSnapshots();
if (!allSnaps.length) {
statusEl.innerHTML = `<span class="backup-status-dot none"></span> No snapshots yet`;
snapsEl.innerHTML = `<div class="backup-empty-state">No snapshots found for <strong>${this.escape(this.appName)}</strong>. Click "Backup now" to create the first one.</div>`;
statusEl.innerHTML = `<span class="backup-status-dot none"></span> No backups yet`;
snapsEl.innerHTML = `<div class="backup-empty-state">No backups found for <strong>${this.escape(this.appName)}</strong>. Click "Backup now" to create the first one.</div>`;
return;
}
@ -80,7 +80,7 @@ class BackupAppCard {
<div class="backup-snapshot-rows">
${allSnaps.slice(0, 50).map(s => this._renderRow(s, iconUrl)).join('')}
</div>
${allSnaps.length > 50 ? `<div class="backup-snapshot-overflow">Showing the most recent 50 of ${allSnaps.length} snapshots. Use the <a href="/backup">backup center</a> for the full list.</div>` : ''}
${allSnaps.length > 50 ? `<div class="backup-snapshot-overflow">Showing the most recent 50 of ${allSnaps.length} backups. Use the <a href="/backup">backup center</a> for the full list.</div>` : ''}
`;
// Deep-link: /app/<name>/backups?snapshot=<id> auto-expands that row
@ -99,7 +99,7 @@ class BackupAppCard {
<span class="task-title">${this.escape(this.formatRelative(s.time))}</span>
<span class="task-status backup-snapshot-loc-pill">${this.escape(s.locName)}</span>
<span class="task-time" title="${this.escape(this._fmtFull(s.time))}">${this.escape(this._fmtShort(s.time))}</span>
<span class="backup-snapshot-id-chip" title="Snapshot ID">${this.escape(sid)}</span>
<span class="backup-snapshot-id-chip" title="Backup ID">${this.escape(sid)}</span>
</div>
<div class="task-actions">
<button class="task-btn" data-action="restore-app-snapshot" data-loc="${this.escape(String(s.locIdx))}" data-snapshot="${this.escape(sid)}" title="Restore from this snapshot">
@ -114,7 +114,7 @@ class BackupAppCard {
</div>
<div class="task-details">
<div class="task-meta">
<div class="meta-item"><strong>Snapshot ID:</strong> <code>${this.escape(sid)}</code></div>
<div class="meta-item"><strong>Backup ID:</strong> <code>${this.escape(sid)}</code></div>
<div class="meta-item"><strong>Location:</strong> ${this.escape(s.locName)}</div>
<div class="meta-item"><strong>When:</strong> ${this.escape(this._fmtFull(s.time))}</div>
${s.hostname ? `<div class="meta-item"><strong>Host:</strong> ${this.escape(s.hostname)}</div>` : ''}
@ -213,7 +213,7 @@ class BackupAppCard {
async restoreSnapshot(locIdx, snapshot) {
const locName = this.locationsByIdx[locIdx]?.name || `Location ${locIdx}`;
if (!confirm(`Restore ${this.appName} from backup ${snapshot} at ${locName}? The app will be stopped, its folder wiped, the backup restored in place, then the app started again.`)) return;
if (!confirm(`Restore ${this.appName} from backup ${snapshot} at ${locName}?\n\nThe app will be stopped, its folder wiped, the backup restored in place, then the app started again.`)) return;
if (!this.taskManager) return;
await this.taskManager.createTask(`libreportal restore app start ${this.appName} ${snapshot} ${locIdx}`, 'restore', this.appName);
}

View File

@ -14,7 +14,7 @@ const BACKUP_RETENTION_PRESETS = {
const BACKUP_RETENTION_PRESET_META = {
'inherit-global': { label: 'Inherit global retention', hint: 'Use whatever the Configuration tab specifies. Pick something else here only when this location needs a different policy.' },
'self-hosting': { label: 'Self-hosting', hint: '30 days of daily backups. Plenty for a homelab — covers accidental deletes and app screw-ups.' },
'personal': { label: 'Personal', hint: '30 days of daily backups plus 6 monthly snapshots. Good for personal data where "what did this look like last summer" matters.' },
'personal': { label: 'Personal', hint: '30 days of daily backups plus 6 monthly backups. Good for personal data where "what did this look like last summer" matters.' },
'enterprise': { label: 'Enterprise', hint: '30 daily + 12 monthly + 5 yearly. Compliance-style retention with multi-year history.' },
'custom': { label: 'Custom…', hint: 'Define each retention tier yourself.' }
};
@ -44,11 +44,11 @@ const BACKUP_LOC_FIELD_DEFS = {
B2_ACCOUNT_KEY: { title: 'B2 account key', description: '' },
APPEND_ONLY: { title: 'Append-only', description: 'Ransomware-safe — refuse forget/prune for this location even if LibrePortal itself is compromised. Trades off automatic retention cleanup.' },
CUSTOM_RETENTION: { title: 'Use custom retention', description: 'Otherwise this location inherits the global retention.' },
KEEP_LAST: { title: 'Keep last', description: 'Snapshots to always retain.' },
KEEP_DAILY: { title: 'Keep daily', description: 'One snapshot per day for this many days.' },
KEEP_WEEKLY: { title: 'Keep weekly', description: 'One snapshot per week for this many weeks.' },
KEEP_MONTHLY: { title: 'Keep monthly', description: 'One snapshot per month for this many months.' },
KEEP_YEARLY: { title: 'Keep yearly', description: 'One snapshot per year for this many years.' }
KEEP_LAST: { title: 'Keep last', description: 'Backups to always retain.' },
KEEP_DAILY: { title: 'Keep daily', description: 'One backup per day for this many days.' },
KEEP_WEEKLY: { title: 'Keep weekly', description: 'One backup per week for this many weeks.' },
KEEP_MONTHLY: { title: 'Keep monthly', description: 'One backup per month for this many months.' },
KEEP_YEARLY: { title: 'Keep yearly', description: 'One backup per year for this many years.' }
};
// Fallback for the per-type field schema. The live source is the generator-
@ -504,7 +504,7 @@ class BackupPage {
subtitleFor(tab) {
return {
dashboard: "Check what's protected — and when it last ran.",
backups: 'Every snapshot across every enabled location.',
backups: 'Every backup across every enabled location.',
locations: 'Where backups are stored. Add, edit, or remove destinations.',
migrate: 'Restore an app from another LibrePortal that shares one of your backup locations.',
configuration: 'Schedule, retention, and engine settings.'
@ -1113,7 +1113,7 @@ class BackupPage {
</svg>
<div class="backup-warning-banner-text">
<strong>Keep your LibrePortal config backed up offline.</strong>
<span>Repository passwords live inside the config directory. Without that backup, snapshots cannot be decrypted by anyone including you.</span>
<span>Repository passwords live inside the config directory. Without that backup, the others cannot be decrypted by anyone including you.</span>
</div>
<button type="button" class="backup-warning-banner-close" data-action="dismiss-config-warning" title="Dismiss this warning" aria-label="Dismiss this warning">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@ -1349,7 +1349,7 @@ class BackupPage {
</div>
<div class="backup-retention-advanced" data-retention-advanced ${preset === 'custom' ? '' : 'hidden'}>
<div class="backup-form-grid">
${this.formInput(`${prefix}KEEP_LAST`, 'Keep last', values.last, 'number', '', 'snapshots')}
${this.formInput(`${prefix}KEEP_LAST`, 'Keep last', values.last, 'number', '', 'backups')}
${this.formInput(`${prefix}KEEP_DAILY`, 'Keep daily', values.daily, 'number', '', 'days')}
${this.formInput(`${prefix}KEEP_WEEKLY`, 'Keep weekly', values.weekly, 'number', '', 'weeks')}
${this.formInput(`${prefix}KEEP_MONTHLY`, 'Keep monthly', values.monthly, 'number', '', 'months')}
@ -1837,7 +1837,7 @@ class BackupPage {
if (!modal || !body) return;
body.innerHTML = `
<p>Restore <strong>${this.escape(app)}</strong> from backup <code>${this.escape(snapshot)}</code> at <strong>${this.escape(locName)}</strong>?</p>
<p class="backup-card-hint">The app will be stopped, its folder wiped, the snapshot restored in place, then the app started again. App-specific pre/post-restore hooks run if present.</p>
<p class="backup-card-hint">The app will be stopped, its folder wiped, the backup restored in place, then the app started again. App-specific pre/post-restore hooks run if present.</p>
`;
modal.dataset.app = app;
modal.dataset.locIdx = locIdx;