Files
deploy-felhom-compose/controller/internal/web/templates/backups.html
T
2026-02-18 11:03:56 +01:00

701 lines
29 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 .Backup}}{{if .Backup.FlashSuccess}}
<div class="flash flash-success">{{.Backup.FlashSuccess}}</div>
{{end}}{{end}}
{{if .Backup}}{{if .Backup.FlashError}}
<div class="flash flash-error">{{.Backup.FlashError}}</div>
{{end}}{{end}}
{{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 0: Storage overview -->
<div class="backup-section-card">
<h3>Tárhely áttekintés</h3>
<div class="storage-overview-grid">
<div class="storage-bars">
{{with $.SystemInfo}}
<div class="storage-item">
<div class="storage-header">
<span class="storage-label">SSD (/)</span>
<span class="storage-value">{{fmtGB .DiskUsedGB}} / {{fmtGB .DiskTotalGB}} ({{printf "%.0f" .DiskPercent}}%)</span>
</div>
<div class="system-bar">
<div class="system-bar-fill {{usageColor .DiskPercent | printf "system-bar-%s"}}" style="width:{{printf "%.1f" .DiskPercent}}%"></div>
</div>
</div>
{{if .HDDConfigured}}
<div class="storage-item">
<div class="storage-header">
<span class="storage-label">Külső HDD</span>
<span class="storage-value">{{fmtGB .HDDUsedGB}} / {{fmtGB .HDDTotalGB}} ({{printf "%.0f" .HDDPercent}}%)</span>
</div>
<div class="system-bar">
<div class="system-bar-fill {{usageColor .HDDPercent | printf "system-bar-%s"}}" style="width:{{printf "%.1f" .HDDPercent}}%"></div>
</div>
</div>
{{end}}
{{end}}
</div>
<div class="storage-stats">
{{if .Backup.RepoStats}}
<div class="storage-stat-row">
<span class="storage-stat-label">Mentési tároló</span>
<span class="storage-stat-value mono">{{.Backup.RepoStats.TotalSize}}</span>
</div>
{{end}}
<div class="storage-stat-row">
<span class="storage-stat-label">DB mentések</span>
<span class="storage-stat-value mono">{{if .Backup.DumpFiles}}{{len .Backup.DumpFiles}} fájl{{else}}{{end}}</span>
</div>
<div class="storage-stat-row">
<span class="storage-stat-label">Pillanatképek</span>
<span class="storage-stat-value mono">{{if .Backup.RepoStats}}{{.Backup.RepoStats.SnapshotCount}}{{else}}0{{end}}</span>
</div>
</div>
</div>
</div>
<!-- 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 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: Unified per-app backup status -->
{{if .Backup.AppDataInfo}}
<div class="backup-section-card">
<h3>Alkalmazások mentési állapota</h3>
{{if .NoUserDataBackupWarning}}
<div class="alert alert-error" style="margin-bottom:1.5rem">
<strong>Felhasználói adatokról nincs biztonsági mentés.</strong><br>
A szerveren tárolt fotók, dokumentumok és egyéb fájlok jelenleg csak egy példányban léteznek.
Külső meghajtó csatlakoztatásával biztonsági másolat készíthető a 3-2-1 szabály szerint.
<a href="/settings" style="color:inherit;text-decoration:underline">Meghajtó beállítása →</a>
</div>
{{end}}
{{range .AppBackupRows}}
<div class="app-backup-row" data-status="{{.Status}}">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-{{.Status}}" title="{{.StatusText}}"></span>
<span class="app-backup-row-name">{{.DisplayName}}</span>
<div class="app-backup-row-meta">
{{if .HasHDDData}}
{{if .StorageLabel}}<span class="meta-badge meta-badge-storage">{{.StorageLabel}}</span>{{end}}
<span class="mono app-backup-size" style="font-size:.8rem">{{.HDDSizeHuman}}</span>
{{else}}
<span class="meta-badge">Auto</span>
{{end}}
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<!-- DB layer -->
<div class="backup-layer-row">
<span class="layer-label">Adatbázis mentés</span>
{{if .HasDB}}
<span class="layer-badge">Auto</span>
{{if .DBLastRun}}
<span class="layer-last">Utolsó: {{.DBLastRun}}
{{if eq .DBLastStatus "ok"}}<span class="text-ok"></span>
{{else if eq .DBLastStatus "error"}}<span class="text-error"></span>{{end}}
</span>
{{end}}
{{else}}
<span class="layer-na">— (nincs adatbázis)</span>
{{end}}
</div>
<!-- Volume layer -->
<div class="backup-layer-row">
<span class="layer-label">Konfiguráció</span>
<span class="layer-badge">Auto</span>
{{if .VolumeLastRun}}
<span class="layer-last">Utolsó: {{.VolumeLastRun}}
{{if eq .VolumeLastStatus "ok"}}<span class="text-ok"></span>
{{else if eq .VolumeLastStatus "error"}}<span class="text-error"></span>{{end}}
</span>
{{end}}
</div>
<!-- User data layer -->
<div class="backup-layer-row{{if not .HasHDDData}} layer-row-na{{end}}">
<span class="layer-label">Felhasználói adatok</span>
{{if .HasUserData}}
{{if .UserDataConfigured}}
<span class="layer-method">{{.UserDataMethod}}</span>
<span class="layer-dest">→ {{.UserDataDest}}</span>
<span class="layer-schedule">{{.UserDataSchedule}}</span>
{{if .UserDataLastRun}}
<span class="layer-last">Utolsó: {{.UserDataLastRun}}
<span class="{{if eq .UserDataLastStatus "ok"}}text-ok{{else if eq .UserDataLastStatus "error"}}text-error{{else if eq .UserDataLastStatus "running"}}text-muted{{end}}">
{{.UserDataStatusBadge}}
</span>
</span>
{{end}}
<div class="layer-actions">
<a href="/stacks/{{.StackName}}/deploy" class="btn btn-xs btn-outline">Beállítás</a>
<button class="btn btn-xs btn-outline"
onclick="triggerCrossDriveBackup('{{.StackName}}', this)">
Futtatás most</button>
</div>
{{else}}
<span class="layer-auto-ok">✓ Helyi mentés auto</span>
<span class="layer-unconfigured">⚠ Nincs 2. másolat</span>
<a href="/stacks/{{.StackName}}/deploy" class="btn btn-xs">Beállítás →</a>
{{end}}
{{else}}
<span class="layer-na">— (nincs HDD adat)</span>
{{end}}
</div>
</div>
{{if .Warnings}}
<div class="layer-warnings">
{{range .Warnings}}
<div class="backup-layer-warning">{{.}}</div>
{{end}}
</div>
{{end}}
</div>
</div>
{{end}}
{{if .Backup.CrossDriveSummary}}
<div class="cross-drive-actions" style="margin-top:1rem">
<button class="btn btn-sm btn-outline" onclick="triggerAllCrossDrive(this)">Összes HDD mentés futtatása most</button>
</div>
{{end}}
</div>
{{end}}
<!-- Section 5: 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 6: 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>
<!-- 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 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>
<!-- Section 7: Restore -->
{{if .Backup.AppDataInfo}}
<div class="backup-section-card">
<h3>Visszaállítás</h3>
<div class="restore-section">
<div class="restore-form-row">
<label class="restore-label">Alkalmazás:</label>
<select id="restore-app" class="restore-select" onchange="onRestoreAppChange()">
<option value="">— Válasszon —</option>
{{range .Backup.AppDataInfo}}
<option value="{{.StackName}}" data-has-hdd="{{.HasHDDData}}" data-has-db="{{.HasDBDump}}">{{.DisplayName}}</option>
{{end}}
</select>
</div>
<div class="restore-form-row">
<label class="restore-label">Pillanatkép:</label>
<select id="restore-snapshot" class="restore-select" onchange="onRestoreConfirmChange()">
<option value="">— Válasszon alkalmazást —</option>
</select>
</div>
<div id="restore-type-info" class="restore-info" style="display:none;margin-bottom:0.5rem">
</div>
<div id="restore-no-snapshots" class="restore-warning" style="display:none;">
Még nincs mentés felhasználói adattal.
</div>
<div class="restore-warning">
⚠ A visszaállítás felülírja az alkalmazás jelenlegi adatait a kiválasztott mentés állapotával.
Az alkalmazás a folyamat során automatikusan leáll és újraindul.
</div>
<div class="restore-confirm">
<label>
<input type="checkbox" id="restore-confirm-cb" onchange="onRestoreConfirmChange()">
Megértettem, visszaállítás indítása.
</label>
</div>
<div class="restore-actions">
<button type="button" class="btn btn-sm btn-danger" id="restore-btn" disabled onclick="submitRestore()">Visszaállítás indítása</button>
</div>
</div>
</div>
{{end}}
{{end}}
<script>
function toggleBackupDetail(header) {
var detail = header.nextElementSibling;
var icon = header.querySelector('.expand-icon');
if (detail.style.display === 'none') {
detail.style.display = 'block';
icon.textContent = '▼';
} else {
detail.style.display = 'none';
icon.textContent = '▶';
}
}
function triggerCrossDriveBackup(stackName, btn) {
btn.disabled = true;
btn.textContent = 'Fut...';
fetch('/api/stacks/' + stackName + '/cross-backup/run', {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 = 'Futtatás most';
return;
}
btn.textContent = 'Fut...';
setTimeout(function() { location.reload(); }, 5000);
})
.catch(function(e) {
alert('Hálózati hiba: ' + e.message);
btn.disabled = false;
btn.textContent = 'Futtatás most';
});
}
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;
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);
}
// 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) {
var t = new Date(s.time);
var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
return t.getFullYear() + '-' + pad(t.getMonth()+1) + '-' + pad(t.getDate()) +
' ' + huDays[t.getDay()] + ' ' + pad(t.getHours()) + ':' + pad(t.getMinutes()) +
' (' + s.short_id + ')';
}
function onRestoreAppChange() {
var sel = document.getElementById('restore-app');
var appName = sel.value;
var snapSel = document.getElementById('restore-snapshot');
var noSnaps = document.getElementById('restore-no-snapshots');
var typeInfo = document.getElementById('restore-type-info');
document.getElementById('restore-confirm-cb').checked = false;
document.getElementById('restore-btn').disabled = true;
noSnaps.style.display = 'none';
typeInfo.style.display = 'none';
if (!appName) {
snapSel.innerHTML = '<option value="">— Válasszon alkalmazást —</option>';
return;
}
// Determine restore type from data attributes
var opt = sel.options[sel.selectedIndex];
var hasHDD = opt.getAttribute('data-has-hdd') === 'true';
var hasDB = opt.getAttribute('data-has-db') === 'true';
if (hasHDD) {
typeInfo.innerHTML = '🔄 Teljes visszaállítás: adatbázis + konfiguráció + felhasználói adatok a kiválasztott pillanatképből.';
typeInfo.className = 'restore-info';
} else if (hasDB) {
typeInfo.innerHTML = ' Adatbázis és konfiguráció visszaállítása — az alkalmazásnak nincs külön felhasználói adata.';
typeInfo.className = 'restore-info restore-info-partial';
} else {
typeInfo.innerHTML = ' Csak konfiguráció visszaállítása (compose fájlok, beállítások).';
typeInfo.className = 'restore-info restore-info-partial';
}
typeInfo.style.display = 'block';
snapSel.innerHTML = '<option value="">— Betöltés... —</option>';
fetch('/api/backup/snapshots?stack=' + encodeURIComponent(appName))
.then(function(r) { return r.json(); })
.then(function(data) {
snapSel.innerHTML = '<option value="">— Válasszon —</option>';
if (data.ok && data.data && data.data.length > 0) {
data.data.forEach(function(s) {
var o = document.createElement('option');
o.value = s.short_id;
o.textContent = formatSnapshot(s);
snapSel.appendChild(o);
});
} else {
snapSel.innerHTML = '<option value="">— Nincs elérhető mentés —</option>';
noSnaps.style.display = 'block';
}
});
}
function onRestoreConfirmChange() {
var cb = document.getElementById('restore-confirm-cb');
var app = document.getElementById('restore-app').value;
var snap = document.getElementById('restore-snapshot').value;
document.getElementById('restore-btn').disabled = !(cb.checked && app && snap);
}
function submitRestore() {
var app = document.getElementById('restore-app').value;
var snap = document.getElementById('restore-snapshot').value;
if (!app || !snap) return;
var btn = document.getElementById('restore-btn');
btn.disabled = true;
btn.textContent = 'Visszaállítás folyamatban...';
var form = document.createElement('form');
form.method = 'POST';
form.action = '/backup/restore';
var f1 = document.createElement('input');
f1.type = 'hidden'; f1.name = 'stack_name'; f1.value = app;
form.appendChild(f1);
var f2 = document.createElement('input');
f2.type = 'hidden'; f2.name = 'snapshot_id'; f2.value = snap;
form.appendChild(f2);
document.body.appendChild(form);
form.submit();
}
// Auto-poll if backup is already running on page load
{{if .Backup}}{{if .Backup.Running}}
startBackupPolling();
{{end}}{{end}}
</script>
{{template "layout_end" .}}
{{end}}