v0.11.8 — Per-App Cross-Drive Backup (3-2-1 rule)
New feature: backup app data to a secondary storage drive to satisfy
the "different media" requirement of the 3-2-1 backup rule.
- settings.go: CrossDriveBackup struct, AppBackupPrefs.CrossDrive field,
getter/setter methods, GetOrCreateCrossDrivePassword, preserves
cross-drive config when toggling nightly backup
- crossdrive.go (new): CrossDriveRunner with rsync and restic backends.
Validates destination (mount point, writable), prevents source/dest
overlap, per-app concurrency lock, persists last_run/status/size.
- main.go: wire CrossDriveRunner, register cross-drive-daily (03:30)
and cross-drive-weekly (04:30 Sundays) scheduler jobs
- router.go: 4 new API endpoints — save config, trigger run, get status,
run-all. Router now accepts Settings and CrossDriveRunner.
- server.go: Server struct accepts CrossDriveRunner, new web route
POST /settings/cross-backup/{name}
- handlers.go: deployHandler populates CrossDriveConfig, BackupDestPaths,
BackupDestWarning, AppBackupEnabled. settingsCrossBackupHandler saves
config. backupsHandler builds CrossDriveSummary, UnconfiguredApps,
CrossDriveWarnings for backup page.
- deploy.html: "Biztonsági mentés" card with destination/method/schedule
dropdowns, last-run status, manual trigger button, flash messages.
- backups.html: "Másolatok másik meghajtóra" section with per-app
status rows, unconfigured app warnings, "Összes futtatása most" button.
- style.css: margin-bottom fix for .deploy-stale-data, new cross-drive
card and list styles.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -283,6 +283,57 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Section 4b: Cross-drive backups -->
|
||||
{{if or .Backup.CrossDriveSummary .Backup.UnconfiguredApps}}
|
||||
<div class="backup-section-card">
|
||||
<h3>Másolatok másik meghajtóra</h3>
|
||||
<p class="backup-section-desc">Alkalmazás adatok biztonsági másolata külső meghajtóra (3-2-1 szabály).</p>
|
||||
|
||||
{{if .Backup.CrossDriveWarnings}}
|
||||
<div style="margin-bottom:1rem">
|
||||
{{range .Backup.CrossDriveWarnings}}
|
||||
<div class="alert alert-warning" style="margin-bottom:.5rem">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Backup.CrossDriveSummary}}
|
||||
<div class="cross-drive-list" style="margin-bottom:1rem">
|
||||
{{range .Backup.CrossDriveSummary}}
|
||||
<div class="cross-drive-item">
|
||||
<div class="cross-drive-header">
|
||||
<a href="/stacks/{{.StackName}}/deploy" class="cross-drive-name">{{.DisplayName}}</a>
|
||||
<div class="cross-drive-meta">
|
||||
<span class="meta-badge">{{.MethodLabel}}</span>
|
||||
{{if .DestLabel}}<span class="meta-badge meta-badge-storage">→ {{.DestLabel}}</span>
|
||||
{{else if .DestPath}}<span class="meta-badge meta-badge-storage">→ {{.DestPath}}</span>{{end}}
|
||||
{{if eq .LastStatus "ok"}}<span class="meta-badge meta-badge-ok">✅ {{.LastRunShort}}</span>
|
||||
{{else if eq .LastStatus "error"}}<span class="meta-badge meta-badge-fail">❌ Hiba</span>
|
||||
{{else if eq .LastStatus "running"}}<span class="meta-badge">⏳ Fut...</span>
|
||||
{{else}}<span class="meta-badge" style="color:var(--text-muted)">⏰ {{.ScheduleLabel}}</span>{{end}}
|
||||
{{if .SizeHuman}}<span class="mono" style="font-size:.8rem;color:var(--text-muted)">{{.SizeHuman}}</span>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Backup.UnconfiguredApps}}
|
||||
<div style="font-size:.85rem;color:var(--yellow);margin-bottom:1rem">
|
||||
⚠️ {{len .Backup.UnconfiguredApps}} alkalmazáshoz nincs beállítva:
|
||||
{{range .Backup.UnconfiguredApps}}
|
||||
<a href="/stacks/{{.StackName}}/deploy" style="color:var(--accent-blue)">{{.DisplayName}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="cross-drive-actions">
|
||||
<button class="btn btn-sm btn-primary" onclick="triggerAllCrossDrive(this)">Összes futtatása most</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Section 5: Snapshots -->
|
||||
<div class="backup-section-card">
|
||||
<h3>Pillanatképek</h3>
|
||||
@@ -432,6 +483,28 @@
|
||||
{{end}}
|
||||
|
||||
<script>
|
||||
function triggerAllCrossDrive(btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Indítás...';
|
||||
fetch('/api/backup/cross-drive/run-all', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (!d.ok) {
|
||||
alert('Hiba: ' + (d.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Összes futtatása most';
|
||||
return;
|
||||
}
|
||||
btn.textContent = '⏳ Mentések futnak...';
|
||||
setTimeout(function() { location.reload(); }, 5000);
|
||||
})
|
||||
.catch(function(e) {
|
||||
alert('Hálózati hiba: ' + e.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Összes futtatása most';
|
||||
});
|
||||
}
|
||||
|
||||
function triggerBackupFromPage() {
|
||||
const btn = document.getElementById('backup-page-btn');
|
||||
btn.disabled = true;
|
||||
|
||||
Reference in New Issue
Block a user