controller v0.121.0: backups page truth pass — remove dead Részletek card, real Tier-3 off-box state, SQLite-honest DB messaging

MinAgent: 0.81.0 (unchanged). Controller-only; no agent-API change, no backup-engine
behavior change. Fixes the self-contradicting /backups page (v0.120.0 live):

- Remove the dead "Részletek" card (redundant; kills never-set Tier2DriveGroups/
  ResticPassword fields + restic-pw element + toggleTier/toggleResticPw/copyResticPw JS).
- Per-app "3. mentés" row shows real off-box state via pure tier3State
  (unconfigured/off/escrow_pending/active) — "Hamarosan" placeholder gone.
- SQLite-honest DB messaging via pure dbSectionState (dumps/pending/embedded).
- Populate Tier1LastRun/Tier1LastStatus from ListRestorePoints; Tier-1/Tier-2 labels
  via timeAgoStr (relative), confirm() dialog keeps raw timestamp.
- Terminology split: "Távoli mentés (3. mentés)" (off-box, +#offbox-section anchor)
  vs "Távoli rendszermentés" (PBS whole-CT).
- Deploy page: add "Mentési beállítások →" link.

+9 internal/web tests (pure helper tables + buildAppBackupRows wiring + template
renders), 4 companion red-proofs run→fail→revert.
This commit is contained in:
2026-07-12 12:26:30 +02:00
parent 92d670e8a6
commit 9d05fa5c35
9 changed files with 604 additions and 174 deletions
+48 -111
View File
@@ -125,8 +125,8 @@
{{if .Backup}}
<!-- Off-box (NAS) backup — Part B: encrypted restic repo over SFTP (the off-site 3-2-1 leg). -->
<h3 class="backup-tier-divider">Külső (NAS) mentés — titkosított, off-site</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazások adatainak + adatbázis-kiírásainak titkosított mentése a saját NAS-odra (restic, SFTP-n). A NAS csak titkosított adatot lát. Ez a 3-2-1 „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<div class="card" style="margin-bottom:1.5rem">
{{if .Offbox}}
<div class="stats-grid backup-page-cards">
@@ -245,20 +245,25 @@
{{if and .GuestBackup .GuestBackup.Offsite}}
<div class="stat-card stat-running">
<div class="stat-value">&#10003;</div>
<div class="stat-label">Távoli mentés<br><span class="relative-time">külön hardveren (PBS)</span></div>
<div class="stat-label">Távoli rendszermentés<br><span class="relative-time">külön hardveren (PBS)</span></div>
</div>
{{else}}
<div class="stat-card" style="border-left-color: var(--text-3);">
<div class="stat-value" style="background:var(--text-3);-webkit-background-clip:text;background-clip:text;"></div>
<div class="stat-label">Távoli mentés<br><span class="relative-time">nincs beállítva</span></div>
<div class="stat-label">Távoli rendszermentés<br><span class="relative-time">nincs beállítva</span></div>
</div>
{{end}}
<div class="stat-card stat-total">
{{if eq .DBSectionState "embedded"}}
<div class="stat-value"></div>
<div class="stat-label">Adatbázis mentve<br><span class="relative-time">beágyazott DB-k a kötetmentésben</span></div>
{{else}}
<div class="stat-value">
{{if .Backup.LastDBDump}}{{len .Backup.LastDBDump.Results}}{{else}}{{len .Backup.DumpFiles}}{{end}}
</div>
<div class="stat-label">Adatbázis mentve</div>
{{end}}
</div>
</div>
@@ -360,8 +365,10 @@
</tbody>
</table>
</div>
{{else if eq .DBSectionState "pending"}}
<div class="backup-table-empty">Még nem készült adatbázis-mentés — az első ütemezett mentés éjjel fut le.</div>
{{else}}
<div class="backup-table-empty">Nem található adatbázis mentés.</div>
<div class="backup-table-empty">A telepített alkalmazások beágyazott adatbázist használnak (pl. SQLite) — adatbázisuk a fájl- és kötetmentés része, külön adatbázis-kiírás nem szükséges.</div>
{{end}}
</div>
@@ -406,7 +413,7 @@
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
{{if .Tier1LastRun}}
<span class="layer-last">Utolsó: {{.Tier1LastRun}}
<span class="layer-last">Utolsó: {{timeAgoStr .Tier1LastRun}}
{{if eq .Tier1LastStatus "ok"}}<span class="text-ok"><svg class="ico ico-sm"><use href="#i-check"/></svg></span>
{{else if eq .Tier1LastStatus "error"}}<span class="text-error"><svg class="ico ico-sm"><use href="#i-x"/></svg></span>{{end}}
</span>
@@ -429,7 +436,7 @@
<span class="layer-dest" style="opacity:.6">→ {{.Tier2Dest}}</span>
<span class="tag tag-warn">Cél meghajtó leválasztva</span>
{{if .Tier2LastRun}}
<span class="layer-last" style="opacity:.6">Utolsó: {{.Tier2LastRun}}</span>
<span class="layer-last" style="opacity:.6">Utolsó: {{timeAgoStr .Tier2LastRun}}</span>
{{end}}
<span class="tier-contents" style="opacity:.6">{{.BackupContents}}</span>
<div class="layer-actions">
@@ -440,7 +447,7 @@
<span class="layer-dest" style="opacity:.6">→ {{.Tier2Dest}}</span>
<span class="tag tag-warn">Cél meghajtó inaktív</span>
{{if .Tier2LastRun}}
<span class="layer-last" style="opacity:.6">Utolsó: {{.Tier2LastRun}}</span>
<span class="layer-last" style="opacity:.6">Utolsó: {{timeAgoStr .Tier2LastRun}}</span>
{{end}}
<span class="tier-contents" style="opacity:.6">{{.BackupContents}}</span>
<div class="layer-actions">
@@ -451,7 +458,7 @@
<span class="layer-dest">→ {{.Tier2Dest}}</span>
<span class="layer-schedule">{{.Tier2Schedule}}</span>
{{if .Tier2LastRun}}
<span class="layer-last">Utolsó: {{.Tier2LastRun}}
<span class="layer-last">Utolsó: {{timeAgoStr .Tier2LastRun}}
<span class="{{if eq .Tier2LastStatus "ok"}}text-ok{{else if eq .Tier2LastStatus "error"}}text-error{{else if eq .Tier2LastStatus "running"}}text-muted{{end}}">
{{.Tier2StatusBadge}}
</span>
@@ -480,13 +487,41 @@
</div>
{{end}}
</div>
<!-- Tier 3: Remote backup (future) -->
<!-- Tier 3: Off-box (NAS) backup — real state (see tier3State). -->
{{if eq .Tier3State "active"}}
<div class="backup-layer-row">
<span class="tier-label">3. mentés</span>
<span class="layer-badge {{if eq $.Offbox.LastStatus "ok"}}text-ok{{else if eq $.Offbox.LastStatus "error"}}text-error{{else if eq $.Offbox.LastStatus "running"}}text-muted{{end}}">{{if eq $.Offbox.LastStatus "ok"}}Sikeres{{else if eq $.Offbox.LastStatus "error"}}Hiba{{else if eq $.Offbox.LastStatus "running"}}Fut...{{else}}—{{end}}</span>
<span class="tier-location">restic → {{$.Offbox.Host}}</span>
{{if $.Offbox.LastRun}}<span class="layer-last">Utolsó: {{timeAgoStr $.Offbox.LastRun}}</span>{{end}}
<span class="tier-contents">Helyreállítási egység, titkosítva</span>
</div>
{{else if eq .Tier3State "escrow_pending"}}
<div class="backup-layer-row">
<span class="tier-label">3. mentés</span>
<span class="layer-badge text-error">Kulcsletétre vár</span>
<span class="tier-contents">A távoli mentés a titkosítási kulcs letétbe helyezéséig szünetel</span>
</div>
{{else if eq .Tier3State "off"}}
<div class="backup-layer-row" style="opacity:.5">
<span class="tier-label">3. mentés</span>
<span class="layer-badge" style="background:var(--bg-2);color:var(--text-3)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
<span class="layer-badge">Kikapcsolva</span>
<span class="tier-contents">Ez az alkalmazás nincs kijelölve távoli mentésre</span>
<div class="layer-actions">
<a href="#offbox-section" class="btn btn-xs btn-outline">Bekapcsolás</a>
</div>
</div>
{{else}}
<div class="backup-layer-row" style="opacity:.5">
<span class="tier-label">3. mentés</span>
<span class="layer-badge" style="background:var(--bg-2);color:var(--text-3)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
{{end}}
</div>
{{if .Warnings}}
<div class="layer-warnings">
@@ -502,78 +537,6 @@
</div>
{{end}}
<!-- Section 6: Részletek (Details) -->
<div class="repo-card">
<h3>Részletek</h3>
<!-- Tier 1: Helyi mentés (collapsible, open by default) -->
<div class="details-tier">
<div class="details-tier-header" onclick="toggleTier(this)">
<span class="expand-icon"></span>
<h4 class="repo-tier-title">1. szint — Helyi mentés (adatbázis + konfiguráció)</h4>
</div>
<div class="details-tier-body">
<div class="repo-info-rows" style="margin-top:0.5rem">
<div class="repo-info-row">
<span class="repo-label">Adatbázis mentések:</span>
<span class="repo-value">{{if .Backup.DumpFiles}}{{len .Backup.DumpFiles}} dump fájl{{if gt .DBDumpTotalBytes 0}} — {{fmtBytes .DBDumpTotalBytes}}{{end}}{{else}}Nincs dump fájl{{end}}</span>
</div>
</div>
<!-- Encryption key -->
{{if $.ResticPassword}}
<div class="repo-encryption">
<span class="repo-label">Titkosítási kulcs:</span>
<div class="repo-encryption-row">
<input type="password" id="restic-pw" class="restic-pw-field mono" value="{{$.ResticPassword}}" readonly>
<button type="button" class="btn btn-sm" onclick="toggleResticPw()">Megjelenítés</button>
<button type="button" class="btn btn-sm" onclick="copyResticPw()">Másolás</button>
</div>
<div class="repo-encryption-warn">
Mentse el biztonságos helyre! A kulcs nélkül a biztonsági mentések NEM állíthatók vissza.
</div>
</div>
{{end}}
</div>
</div>
<!-- Tier 2: Másodlagos másolat (collapsible, collapsed by default) -->
<div class="details-tier">
<div class="details-tier-header" onclick="toggleTier(this)">
<span class="expand-icon"></span>
<h4 class="repo-tier-title">2. szint — Másodlagos másolat</h4>
</div>
<div class="details-tier-body" style="display:none">
{{if .Tier2DriveGroups}}
{{range .Tier2DriveGroups}}
<div class="drive-detail-card">
<div class="drive-detail-header">{{.DestLabel}} <span class="relative-time mono">({{.DestPath}})</span></div>
{{range .Items}}
<div class="repo-info-row">
<span class="repo-label">{{.DisplayName}}</span>
<span class="repo-value">{{if .SizeHuman}}{{.SizeHuman}}{{else}}—{{end}}</span>
</div>
{{end}}
</div>
{{end}}
{{else}}
<div class="tier-empty-state">Nincs 2. szintű mentés konfigurálva.</div>
{{end}}
</div>
</div>
<!-- Tier 3: Távoli mentés (collapsible, collapsed by default, placeholder) -->
<div class="details-tier">
<div class="details-tier-header" onclick="toggleTier(this)">
<span class="expand-icon"></span>
<h4 class="repo-tier-title" style="opacity:.6">3. szint — Távoli mentés (offsite)</h4>
</div>
<div class="details-tier-body" style="display:none">
<div class="tier-empty-state">B2 / S3 / SFTP — hamarosan elérhető</div>
</div>
</div>
</div>
<!-- Section 7: Restore -->
{{if .Backup.AppDataInfo}}
<div class="backup-section-card">
@@ -671,18 +634,6 @@ function toggleBackupDetail(header) {
}
}
function toggleTier(header) {
var body = header.nextElementSibling;
var icon = header.querySelector('.expand-icon');
if (body.style.display === 'none') {
body.style.display = 'block';
icon.textContent = '▼';
} else {
body.style.display = 'none';
icon.textContent = '▶';
}
}
function triggerBackupFromPage() {
const btn = document.getElementById('backup-page-btn');
btn.disabled = true;
@@ -756,20 +707,6 @@ function pollGuestBackup(out, btn) {
}, 5000);
}
// Restic password toggle/copy
function toggleResticPw() {
var el = document.getElementById('restic-pw');
el.type = el.type === 'password' ? 'text' : 'password';
}
function copyResticPw() {
var el = document.getElementById('restic-pw');
navigator.clipboard.writeText(el.value).then(function() {
var btn = event.target;
btn.textContent = 'Másolva!';
setTimeout(function() { btn.textContent = 'Másolás'; }, 2000);
});
}
// Restore section
var huDays = ['vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat'];
function formatSnapshot(s) {