Files
deploy-felhom-compose/controller/internal/web/templates/backups.html
T
admin 37ff296a0d v0.4.5: Add dedicated Backup page (Biztonsági mentés)
New /backups page with full backup system visibility:
- Status overview cards (local/remote backup, DB count, repo size)
- Schedule section with next-run times and retention policy
- Database table with type, size, validation (table count), status
- Snapshot history table with per-snapshot stats
- Repository info card with paths, integrity status, remote placeholder
- "Mentés most" button with auto-refresh polling
- Empty state when backup not configured

Backend: SnapshotRecord history (ring buffer), DumpValidation,
ListDumpFiles, ListSnapshots, GetFullStatus, restic check tracking.
Server accepts scheduler for next-run time calculation.

Sidebar nav updated with 3rd item, dashboard backup card title clickable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 07:43:24 +01:00

312 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{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">&#128737;</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">&#10003;</div>
<div class="stat-label">Helyi mentés aktív</div>
</div>
{{else}}
<div class="stat-card stat-stopped">
<div class="stat-value">&#10007;</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}}
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">Hiba</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}}