9e8b48a32b
- Add 4-branch template guard to handle zero-value DumpValidation (shows "–" instead of misleading "Hiba" when validation never ran) - Add debug logging to ValidateDump for all code paths - Add cross-check re-validation in RefreshCache to heal stale lastDBDump validation state from disk Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
314 lines
12 KiB
HTML
314 lines
12 KiB
HTML
{{define "backups"}}
|
||
{{template "layout_start" .}}
|
||
|
||
<div class="page-header">
|
||
<h2>Biztonsági mentés</h2>
|
||
<span class="domain-badge">{{.Domain}}</span>
|
||
</div>
|
||
|
||
{{if not .Backup}}
|
||
<div class="backup-empty-state">
|
||
<div class="backup-empty-icon">🛡</div>
|
||
<h3>Biztonsági mentés nincs beállítva</h3>
|
||
<p>A biztonsági mentés funkció nem aktív.<br>
|
||
Kérjük, vegye fel a kapcsolatot a Felhom csapattal a beállításhoz.</p>
|
||
</div>
|
||
{{else}}
|
||
|
||
<!-- Section 1: Status overview cards -->
|
||
<div class="stats-grid backup-page-cards">
|
||
{{if .Backup.LastBackup}}
|
||
{{if .Backup.LastBackup.Success}}
|
||
<div class="stat-card stat-running">
|
||
<div class="stat-value">✓</div>
|
||
<div class="stat-label">Helyi mentés aktív</div>
|
||
</div>
|
||
{{else}}
|
||
<div class="stat-card stat-stopped">
|
||
<div class="stat-value">✗</div>
|
||
<div class="stat-label">Helyi mentés sikertelen</div>
|
||
</div>
|
||
{{end}}
|
||
{{else}}
|
||
<div class="stat-card">
|
||
<div class="stat-value">–</div>
|
||
<div class="stat-label">Helyi mentés</div>
|
||
</div>
|
||
{{end}}
|
||
|
||
<div class="stat-card" style="border-left-color: var(--gray);">
|
||
<div class="stat-value" style="background:var(--gray);-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>
|
||
|
||
<div class="stat-card stat-total">
|
||
<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>
|
||
</div>
|
||
|
||
<div class="stat-card stat-total">
|
||
<div class="stat-value">
|
||
{{if .Backup.RepoStats}}{{.Backup.RepoStats.TotalSize}}{{else}}–{{end}}
|
||
</div>
|
||
<div class="stat-label">Tároló méret
|
||
{{if .Backup.RepoStats}}<br><span class="relative-time">{{.Backup.RepoStats.SnapshotCount}} pillanatkép</span>{{end}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 2: Schedule -->
|
||
<div class="schedule-card">
|
||
<h3>Ütemezés</h3>
|
||
<div class="schedule-rows">
|
||
<div class="schedule-row">
|
||
<span class="schedule-task">Adatbázis mentés</span>
|
||
<span class="schedule-time">{{.Backup.DBDumpSchedule}}</span>
|
||
<span class="schedule-next">Következő: {{nextRunLabel .Backup.NextDBDump}}</span>
|
||
</div>
|
||
<div class="schedule-row">
|
||
<span class="schedule-task">Restic pillanatkép</span>
|
||
<span class="schedule-time">{{.Backup.ResticSchedule}}</span>
|
||
<span class="schedule-next">Következő: {{nextRunLabel .Backup.NextBackup}}</span>
|
||
</div>
|
||
<div class="schedule-row">
|
||
<span class="schedule-task">Karbantartás</span>
|
||
<span class="schedule-time">{{pruneLabel .Backup.PruneSchedule}}</span>
|
||
<span class="schedule-next">Következő: {{nextPruneLabel .Backup.PruneSchedule}}</span>
|
||
</div>
|
||
</div>
|
||
<div class="schedule-summary">
|
||
{{if .Backup.LastBackup}}
|
||
<div class="schedule-summary-row">
|
||
<span>Utolsó sikeres mentés:</span>
|
||
<span class="schedule-summary-value">{{fmtTime .Backup.LastBackup.LastRun}} ({{timeAgo .Backup.LastBackup.LastRun}})</span>
|
||
</div>
|
||
<div class="schedule-summary-row">
|
||
<span>Mentés időtartam:</span>
|
||
<span class="schedule-summary-value">{{fmtDuration .Backup.LastBackup.Duration}}</span>
|
||
</div>
|
||
{{else}}
|
||
<div class="schedule-summary-row">
|
||
<span>Utolsó sikeres mentés:</span>
|
||
<span class="schedule-summary-value relative-time">Még nem futott</span>
|
||
</div>
|
||
{{end}}
|
||
<div class="schedule-summary-row">
|
||
<span>Megőrzés:</span>
|
||
<span class="schedule-summary-value">{{.Backup.Retention.KeepDaily}} napi · {{.Backup.Retention.KeepWeekly}} heti · {{.Backup.Retention.KeepMonthly}} havi</span>
|
||
</div>
|
||
</div>
|
||
<div class="schedule-actions">
|
||
<button class="btn btn-sm btn-primary" onclick="triggerBackupFromPage()" id="backup-page-btn"
|
||
{{if .Backup.Running}}disabled{{end}}>
|
||
{{if .Backup.Running}}Mentés folyamatban...{{else}}Mentés most{{end}}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 3: Databases -->
|
||
<div class="backup-section-card">
|
||
<h3>Adatbázisok</h3>
|
||
{{if or .Backup.DumpFiles .Backup.DiscoveredDBs}}
|
||
<div class="backup-table-wrap">
|
||
<table class="db-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Alkalmazás</th>
|
||
<th>Típus</th>
|
||
<th>Méret</th>
|
||
<th>Utolsó</th>
|
||
<th>Érvényesítés</th>
|
||
<th>Állapot</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{if .Backup.LastDBDump}}
|
||
{{range .Backup.LastDBDump.Results}}
|
||
<tr>
|
||
<td>{{.DB.StackName}}</td>
|
||
<td><span class="db-type-badge db-type-{{.DB.DBType}}">{{dbTypeLabel .DB.DBType}}</span></td>
|
||
<td class="mono">{{if .Error}}–{{else}}{{fmtBytes .Size}}{{end}}</td>
|
||
<td class="mono">{{if .Error}}–{{else}}{{fmtTimeShort $.Backup.LastDBDump.LastRun}}{{end}}</td>
|
||
<td>
|
||
{{if .Error}}
|
||
<span class="validation-badge validation-na">–</span>
|
||
{{else if .Validation.Valid}}
|
||
<span class="validation-badge validation-ok">{{.Validation.TableCount}} tábla</span>
|
||
{{else if .Validation.Error}}
|
||
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">Hiba</span>
|
||
{{else}}
|
||
<span class="validation-badge validation-na" title="Az érvényesítés nem futott le">–</span>
|
||
{{end}}
|
||
</td>
|
||
<td>
|
||
{{if .Error}}
|
||
<span class="validation-badge validation-fail" title="{{.Error}}">Hiba</span>
|
||
{{else}}
|
||
<span class="validation-badge validation-ok">OK</span>
|
||
{{end}}
|
||
</td>
|
||
</tr>
|
||
{{end}}
|
||
{{else}}
|
||
{{range .Backup.DumpFiles}}
|
||
<tr>
|
||
<td>{{.StackName}}</td>
|
||
<td><span class="db-type-badge db-type-{{.DBType}}">{{dbTypeLabel .DBType}}</span></td>
|
||
<td class="mono">{{fmtBytes .Size}}</td>
|
||
<td class="mono">{{fmtTimeShort .ModTime}}</td>
|
||
<td>
|
||
{{if .Validation.Valid}}
|
||
<span class="validation-badge validation-ok">{{.Validation.TableCount}} tábla</span>
|
||
{{else if .Validation.Error}}
|
||
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">Hiba</span>
|
||
{{else}}
|
||
<span class="validation-badge validation-na">–</span>
|
||
{{end}}
|
||
</td>
|
||
<td><span class="validation-badge validation-ok">OK</span></td>
|
||
</tr>
|
||
{{end}}
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{{else}}
|
||
<div class="backup-table-empty">Nem található adatbázis mentés.</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
<!-- Section 4: Snapshots -->
|
||
<div class="backup-section-card">
|
||
<h3>Pillanatképek</h3>
|
||
{{if .Backup.SnapshotHistory}}
|
||
<div class="backup-table-wrap">
|
||
<table class="snapshot-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Azonosító</th>
|
||
<th>Időpont</th>
|
||
<th>Méret</th>
|
||
<th>Új fájl</th>
|
||
<th>Változott</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{{range .Backup.SnapshotHistory}}
|
||
<tr>
|
||
<td class="mono">{{shortID .SnapshotID}}</td>
|
||
<td class="mono">{{fmtTime .Time}}</td>
|
||
<td class="mono">{{if .HasStats}}+{{.DataAdded}}{{else}}–{{end}}</td>
|
||
<td class="mono">{{if .HasStats}}{{.FilesNew}}{{else}}–{{end}}</td>
|
||
<td class="mono">{{if .HasStats}}{{.FilesChanged}}{{else}}–{{end}}</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div class="snapshot-footer">
|
||
Összesen: {{len .Backup.SnapshotHistory}} pillanatkép
|
||
{{if .Backup.RepoStats}} · {{.Backup.RepoStats.TotalSize}}{{end}}
|
||
</div>
|
||
{{else}}
|
||
<div class="backup-table-empty">Még nincs pillanatkép.</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
<!-- Section 5: Repository -->
|
||
<div class="repo-card">
|
||
<h3>Tároló</h3>
|
||
<div class="repo-info-rows">
|
||
<div class="repo-info-row">
|
||
<span class="repo-label">Helyszín:</span>
|
||
<span class="repo-value mono">{{.Backup.RepoPath}} (helyi)</span>
|
||
</div>
|
||
{{if .Backup.RepoStats}}
|
||
<div class="repo-info-row">
|
||
<span class="repo-label">Méret:</span>
|
||
<span class="repo-value">{{.Backup.RepoStats.TotalSize}}</span>
|
||
</div>
|
||
<div class="repo-info-row">
|
||
<span class="repo-label">Pillanatképek:</span>
|
||
<span class="repo-value">{{.Backup.RepoStats.SnapshotCount}}</span>
|
||
</div>
|
||
{{end}}
|
||
<div class="repo-info-row">
|
||
<span class="repo-label">Integritás:</span>
|
||
<span class="repo-value">
|
||
{{if .Backup.LastCheckTime.IsZero}}
|
||
<span class="relative-time">Még nem ellenőrzött</span>
|
||
{{else if .Backup.LastCheckOK}}
|
||
<span class="backup-status-ok">Rendben</span> <span class="relative-time">({{fmtTime .Backup.LastCheckTime}})</span>
|
||
{{else}}
|
||
<span class="backup-status-fail">Hiba</span> <span class="relative-time">({{fmtTime .Backup.LastCheckTime}})</span>
|
||
{{end}}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="repo-paths">
|
||
<span class="repo-label">Mentett útvonalak:</span>
|
||
<ul class="repo-path-list">
|
||
{{range .Backup.BackupPaths}}
|
||
<li class="mono">{{.}}</li>
|
||
{{end}}
|
||
</ul>
|
||
</div>
|
||
<div class="repo-remote">
|
||
<span class="repo-label">Távoli másolat:</span>
|
||
<div class="repo-remote-status">
|
||
<span class="relative-time">Nincs beállítva</span>
|
||
<span class="relative-time">(B2/S3/SFTP támogatás hamarosan)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{end}}
|
||
|
||
<script>
|
||
function triggerBackupFromPage() {
|
||
const btn = document.getElementById('backup-page-btn');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Mentés indítása...';
|
||
fetch('/api/backup/run', { method: 'POST' })
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.ok) {
|
||
btn.textContent = 'Mentés folyamatban...';
|
||
btn.classList.add('loading');
|
||
startBackupPolling();
|
||
} else {
|
||
btn.textContent = data.error || 'Hiba';
|
||
setTimeout(() => { btn.textContent = 'Mentés most'; btn.disabled = false; }, 3000);
|
||
}
|
||
})
|
||
.catch(() => {
|
||
btn.textContent = 'Hiba';
|
||
setTimeout(() => { btn.textContent = 'Mentés most'; btn.disabled = false; }, 3000);
|
||
});
|
||
}
|
||
|
||
function startBackupPolling() {
|
||
const poll = setInterval(() => {
|
||
fetch('/api/backup/status')
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data.ok && data.data && !data.data.running) {
|
||
clearInterval(poll);
|
||
window.location.reload();
|
||
}
|
||
})
|
||
.catch(() => {});
|
||
}, 3000);
|
||
}
|
||
|
||
// Auto-poll if backup is already running on page load
|
||
{{if .Backup}}{{if .Backup.Running}}
|
||
startBackupPolling();
|
||
{{end}}{{end}}
|
||
</script>
|
||
|
||
{{template "layout_end" .}}
|
||
{{end}}
|