1de244646b
Bug fixes: - GetFullStatus() returns deep copy; CrossDriveSummary/UnconfiguredApps/CrossDriveWarnings are always nil in the copy so the handler builds them fresh (fixes duplicate-apps bug) - Replace binary IsMountPoint check with tiered CheckBackupDestination() — path-not-exist, not-writable, system-drive (warning), disk >90-95% full; shown as warning vs critical - Remove dead settingsAppBackupHandler / POST /settings/app-backup route (toggle wrote to settings.json but nothing consumed the flag) Architecture: - Unified per-app backup rows: new AppBackupRow struct + buildAppBackupRows() replaces the two old sections with expandable rows showing all 3 layers per app - Sequential backup chaining: cross-drive runs immediately after restic (removed independent cross-drive-daily/cross-drive-weekly scheduler jobs) - Deploy page: remove "Csak kézi indítás" schedule option; add weekly consistency note Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
686 lines
28 KiB
HTML
686 lines
28 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 .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">🛡</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">✓</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: 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">Docker kötetek</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-unconfigured">⚠ Nincs beállítva</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}}
|
||
{{if and .HasHDDData .BackupEnabled}}
|
||
<option value="{{.StackName}}" data-paths="{{range $i, $p := .HDDPaths}}{{if $i}},{{end}}{{$p.HostPath}}{{end}}">{{.DisplayName}}</option>
|
||
{{end}}
|
||
{{end}}
|
||
</select>
|
||
</div>
|
||
<div class="restore-form-row">
|
||
<label class="restore-label">Pillanatkép:</label>
|
||
<select id="restore-snapshot" class="restore-select">
|
||
<option value="">— Betöltés... —</option>
|
||
</select>
|
||
</div>
|
||
<div id="restore-paths" class="restore-paths" style="display:none;">
|
||
<span class="restore-label">Visszaállítandó útvonalak:</span>
|
||
<ul id="restore-paths-list" class="repo-path-list"></ul>
|
||
</div>
|
||
<div class="restore-warning">
|
||
<strong>FIGYELMEZTETÉS</strong><br>
|
||
A visszaállítás FELÜLÍRJA a kiválasztott alkalmazás jelenlegi adatait a mentés pillanatának állapotával.
|
||
Ez a művelet NEM vonható vissza!<br>
|
||
Javasoljuk az alkalmazás leállítását a visszaállítás előtt.
|
||
</div>
|
||
<div class="restore-confirm">
|
||
<label>
|
||
<input type="checkbox" id="restore-confirm-cb" onchange="onRestoreConfirmChange()">
|
||
Megértettem, visszaállítás saját felelősségre.
|
||
</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
|
||
function onRestoreAppChange() {
|
||
var sel = document.getElementById('restore-app');
|
||
var opt = sel.options[sel.selectedIndex];
|
||
var pathsDiv = document.getElementById('restore-paths');
|
||
var pathsList = document.getElementById('restore-paths-list');
|
||
|
||
if (!opt.value) {
|
||
pathsDiv.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
var paths = (opt.getAttribute('data-paths') || '').split(',').filter(Boolean);
|
||
pathsList.innerHTML = '';
|
||
paths.forEach(function(p) {
|
||
var li = document.createElement('li');
|
||
li.className = 'mono';
|
||
li.textContent = p;
|
||
pathsList.appendChild(li);
|
||
});
|
||
pathsDiv.style.display = 'block';
|
||
|
||
// Load snapshots
|
||
fetch('/api/backup/snapshots')
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(data) {
|
||
var snapSel = document.getElementById('restore-snapshot');
|
||
snapSel.innerHTML = '<option value="">— Válasszon —</option>';
|
||
if (data.ok && data.data) {
|
||
data.data.forEach(function(s) {
|
||
var o = document.createElement('option');
|
||
o.value = s.short_id;
|
||
var t = new Date(s.time);
|
||
o.textContent = s.short_id + ' — ' + t.toLocaleString('hu-HU');
|
||
snapSel.appendChild(o);
|
||
});
|
||
}
|
||
});
|
||
|
||
document.getElementById('restore-confirm-cb').checked = false;
|
||
document.getElementById('restore-btn').disabled = true;
|
||
}
|
||
|
||
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}}
|
||
|
||
// Wire up snapshot selection change for restore confirm
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
var snapSel = document.getElementById('restore-snapshot');
|
||
if (snapSel) snapSel.addEventListener('change', onRestoreConfirmChange);
|
||
});
|
||
</script>
|
||
|
||
{{template "layout_end" .}}
|
||
{{end}}
|