v0.15.1: Backup page Részletek overhaul with per-drive tier sections
Replace Tároló section with collapsible Részletek containing 3 tiers: - Tier 1: per-drive restic repo stats with storage labels - Tier 2: cross-drive items grouped by destination, split by method - Tier 3: remote backup placeholder Restore UI now shows tier + drive labels in snapshot dropdown. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -373,82 +373,143 @@
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- Section 6: Repository -->
|
||||
<!-- Section 6: Részletek (Details) -->
|
||||
<div class="repo-card">
|
||||
<h3>Tároló</h3>
|
||||
<h3>Részletek</h3>
|
||||
|
||||
<!-- Tier 1: Local restic backup (per-drive) -->
|
||||
<div class="repo-tier">
|
||||
<h4 class="repo-tier-title">1. mentés — Helyi mentés (restic)</h4>
|
||||
<div class="repo-info-rows">
|
||||
<!-- Tier 1: Helyi mentés (collapsible, open by default) -->
|
||||
<div class="details-tier">
|
||||
<div class="details-tier-header" onclick="toggleTier(this)">
|
||||
<span class="expand-icon">▼</span>
|
||||
<h4 class="repo-tier-title">1. szint — Helyi mentés (restic)</h4>
|
||||
</div>
|
||||
<div class="details-tier-body">
|
||||
{{if .PerDriveRepoStats}}
|
||||
{{range .PerDriveRepoStats}}
|
||||
<div class="drive-detail-card">
|
||||
<div class="drive-detail-header">{{.DriveLabel}}</div>
|
||||
<div class="repo-info-rows">
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">Méret:</span>
|
||||
<span class="repo-value">{{if .TotalSize}}{{.TotalSize}}{{else}}—{{end}}</span>
|
||||
</div>
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">Pillanatképek:</span>
|
||||
<span class="repo-value">{{.SnapshotCount}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if gt (len .PerDriveRepoStats) 1}}
|
||||
<div class="repo-info-rows" style="margin-top:0.5rem;padding-top:0.5rem;border-top:1px solid var(--border-color)">
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">Összesen:</span>
|
||||
<span class="repo-value">{{if .Backup.RepoStats}}{{.Backup.RepoStats.TotalSize}} · {{.Backup.RepoStats.SnapshotCount}} pillanatkép{{end}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{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 class="repo-info-rows">
|
||||
<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>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">Adatbázis mentések:</span>
|
||||
<span class="repo-value">{{if .Backup.DumpFiles}}{{len .Backup.DumpFiles}} dump fájl{{if gt .DBDumpTotalBytes 0}} — {{fmtBytes .DBDumpTotalBytes}}{{end}}{{else}}Nincs dump fájl{{end}}</span>
|
||||
{{end}}
|
||||
|
||||
<div class="repo-info-rows" style="margin-top:0.5rem">
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">Adatbázis mentések:</span>
|
||||
<span class="repo-value">{{if .Backup.DumpFiles}}{{len .Backup.DumpFiles}} dump fájl{{if gt .DBDumpTotalBytes 0}} — {{fmtBytes .DBDumpTotalBytes}}{{end}}{{else}}Nincs dump fájl{{end}}</span>
|
||||
</div>
|
||||
<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-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>
|
||||
|
||||
<!-- 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>
|
||||
</div>
|
||||
|
||||
<!-- Tier 2: Másodlagos másolat (collapsible, collapsed by default) -->
|
||||
<div class="details-tier">
|
||||
<div class="details-tier-header" onclick="toggleTier(this)">
|
||||
<span class="expand-icon">▶</span>
|
||||
<h4 class="repo-tier-title">2. szint — Másodlagos másolat</h4>
|
||||
</div>
|
||||
<div class="details-tier-body" style="display:none">
|
||||
{{if .Tier2DriveGroups}}
|
||||
{{range .Tier2DriveGroups}}
|
||||
<div class="drive-detail-card">
|
||||
<div class="drive-detail-header">{{.DestLabel}} <span class="relative-time mono">({{.DestPath}})</span></div>
|
||||
{{if .ResticItems}}
|
||||
<div class="method-group">
|
||||
<div class="method-group-label">Restic:</div>
|
||||
{{range .ResticItems}}
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">{{.DisplayName}}</span>
|
||||
<span class="repo-value">{{if .SizeHuman}}{{.SizeHuman}}{{else}}—{{end}}</span>
|
||||
</div>
|
||||
{{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>
|
||||
|
||||
<!-- Tier 2: Cross-drive backup destinations -->
|
||||
{{if .Tier2Dests}}
|
||||
<div class="repo-tier">
|
||||
<h4 class="repo-tier-title">2. mentés — Másodlagos másolat</h4>
|
||||
{{range .Tier2Dests}}
|
||||
<div class="repo-info-rows">
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">Cél:</span>
|
||||
<span class="repo-value mono">{{index . "Path"}}{{if index . "Label"}} <span class="relative-time">({{index . "Label"}})</span>{{end}}</span>
|
||||
</div>
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">Módszer:</span>
|
||||
<span class="repo-value">{{index . "Method"}}</span>
|
||||
</div>
|
||||
{{if index . "SizeHuman"}}
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">Méret:</span>
|
||||
<span class="repo-value">{{index . "SizeHuman"}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .RsyncItems}}
|
||||
<div class="method-group">
|
||||
<div class="method-group-label">Rsync:</div>
|
||||
{{range .RsyncItems}}
|
||||
<div class="repo-info-row">
|
||||
<span class="repo-label">{{.DisplayName}}</span>
|
||||
<span class="repo-value">{{if .SizeHuman}}{{.SizeHuman}}{{else}}—{{end}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<div class="tier-empty-state">Nincs 2. szintű mentés konfigurálva.</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tier 3: Távoli mentés (collapsible, collapsed by default, placeholder) -->
|
||||
<div class="details-tier">
|
||||
<div class="details-tier-header" onclick="toggleTier(this)">
|
||||
<span class="expand-icon">▶</span>
|
||||
<h4 class="repo-tier-title" style="opacity:.6">3. szint — Távoli mentés (offsite)</h4>
|
||||
</div>
|
||||
<div class="details-tier-body" style="display:none">
|
||||
<div class="tier-empty-state">B2 / S3 / SFTP — hamarosan elérhető</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- Section 7: Restore -->
|
||||
@@ -508,6 +569,18 @@ function toggleBackupDetail(header) {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTier(header) {
|
||||
var body = header.nextElementSibling;
|
||||
var icon = header.querySelector('.expand-icon');
|
||||
if (body.style.display === 'none') {
|
||||
body.style.display = 'block';
|
||||
icon.textContent = '▼';
|
||||
} else {
|
||||
body.style.display = 'none';
|
||||
icon.textContent = '▶';
|
||||
}
|
||||
}
|
||||
|
||||
function triggerCrossDriveBackup(stackName, btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Fut...';
|
||||
@@ -607,9 +680,16 @@ var huDays = ['vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek
|
||||
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()) +
|
||||
var label = t.getFullYear() + '-' + pad(t.getMonth()+1) + '-' + pad(t.getDate()) +
|
||||
' ' + huDays[t.getDay()] + ' ' + pad(t.getHours()) + ':' + pad(t.getMinutes()) +
|
||||
' (' + s.short_id + ')';
|
||||
var tierLabel = s.tier === 2 ? '2. szint' : '1. szint';
|
||||
if (s.drive_label) {
|
||||
label += ' — ' + tierLabel + ', ' + s.drive_label;
|
||||
} else {
|
||||
label += ' — ' + tierLabel;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
function onRestoreAppChange() {
|
||||
@@ -653,12 +733,32 @@ function onRestoreAppChange() {
|
||||
.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);
|
||||
});
|
||||
// Group by tier
|
||||
var tier1 = data.data.filter(function(s) { return s.tier !== 2; });
|
||||
var tier2 = data.data.filter(function(s) { return s.tier === 2; });
|
||||
|
||||
if (tier1.length > 0) {
|
||||
var grp1 = document.createElement('optgroup');
|
||||
grp1.label = '1. szint — Helyi mentés (ajánlott)';
|
||||
tier1.forEach(function(s) {
|
||||
var o = document.createElement('option');
|
||||
o.value = s.short_id;
|
||||
o.textContent = formatSnapshot(s);
|
||||
grp1.appendChild(o);
|
||||
});
|
||||
snapSel.appendChild(grp1);
|
||||
}
|
||||
if (tier2.length > 0) {
|
||||
var grp2 = document.createElement('optgroup');
|
||||
grp2.label = '2. szint — Másodlagos másolat';
|
||||
tier2.forEach(function(s) {
|
||||
var o = document.createElement('option');
|
||||
o.value = s.short_id;
|
||||
o.textContent = formatSnapshot(s);
|
||||
grp2.appendChild(o);
|
||||
});
|
||||
snapSel.appendChild(grp2);
|
||||
}
|
||||
} else {
|
||||
snapSel.innerHTML = '<option value="">— Nincs elérhető mentés —</option>';
|
||||
noSnaps.style.display = 'block';
|
||||
|
||||
@@ -1600,6 +1600,65 @@ a.stat-card:hover {
|
||||
gap: .15rem;
|
||||
}
|
||||
|
||||
/* Details tier (collapsible sections in Részletek) */
|
||||
.details-tier {
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin-top: .5rem;
|
||||
}
|
||||
.details-tier:first-child {
|
||||
border-top: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
.details-tier-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
cursor: pointer;
|
||||
padding: .5rem 0;
|
||||
user-select: none;
|
||||
}
|
||||
.details-tier-header:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
.details-tier-header .expand-icon {
|
||||
font-size: .7rem;
|
||||
color: var(--text-muted);
|
||||
min-width: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.details-tier-header .repo-tier-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.details-tier-body {
|
||||
padding-bottom: .75rem;
|
||||
}
|
||||
.drive-detail-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius);
|
||||
padding: .75rem;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.drive-detail-header {
|
||||
font-size: .85rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.method-group {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
.method-group-label {
|
||||
font-size: .8rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.tier-empty-state {
|
||||
font-size: .85rem;
|
||||
color: var(--text-muted);
|
||||
padding: .5rem 0;
|
||||
}
|
||||
|
||||
.relative-time {
|
||||
color: var(--text-muted);
|
||||
font-size: .8rem;
|
||||
|
||||
Reference in New Issue
Block a user