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:
2026-02-19 08:23:33 +01:00
parent 0c0cacbe7c
commit 2befa6877b
7 changed files with 415 additions and 99 deletions
+47 -13
View File
@@ -525,33 +525,59 @@ func (s *Server) backupsHandler(w http.ResponseWriter, r *http.Request) {
data["ResticPassword"] = pw
}
// Tároló section: DB dump total size
// Részletek section: DB dump total size
var dbDumpTotalBytes int64
for _, f := range fullStatus.DumpFiles {
dbDumpTotalBytes += f.Size
}
data["DBDumpTotalBytes"] = dbDumpTotalBytes
// Tároló section: deduplicated Tier 2 destination list
tier2DestMap := make(map[string]map[string]string)
// Részletek section: enrich per-drive repo stats with storage labels
for i := range fullStatus.PerDriveRepoStats {
for _, sp := range storagePaths {
if strings.HasPrefix(fullStatus.PerDriveRepoStats[i].DrivePath, sp.Path) ||
fullStatus.PerDriveRepoStats[i].DrivePath == sp.Path {
fullStatus.PerDriveRepoStats[i].DriveLabel = sp.Label
break
}
}
if fullStatus.PerDriveRepoStats[i].DriveLabel == "" {
fullStatus.PerDriveRepoStats[i].DriveLabel = filepath.Base(fullStatus.PerDriveRepoStats[i].DrivePath)
}
}
data["PerDriveRepoStats"] = fullStatus.PerDriveRepoStats
// Részletek section: group Tier 2 items by destination drive
tier2GroupMap := make(map[string]*Tier2DriveGroup)
for _, item := range fullStatus.CrossDriveSummary {
if item.DestPath == "" {
continue
}
if _, exists := tier2DestMap[item.DestPath]; !exists {
tier2DestMap[item.DestPath] = map[string]string{
"Path": item.DestPath,
"Label": item.DestLabel,
"Method": item.MethodLabel,
"SizeHuman": item.SizeHuman,
grp, exists := tier2GroupMap[item.DestPath]
if !exists {
grp = &Tier2DriveGroup{
DestPath: item.DestPath,
DestLabel: item.DestLabel,
}
if grp.DestLabel == "" {
grp.DestLabel = filepath.Base(item.DestPath)
}
tier2GroupMap[item.DestPath] = grp
}
switch item.Method {
case "restic":
grp.ResticItems = append(grp.ResticItems, item)
case "rsync":
grp.RsyncItems = append(grp.RsyncItems, item)
default:
grp.RsyncItems = append(grp.RsyncItems, item)
}
}
var tier2DestList []map[string]string
for _, d := range tier2DestMap {
tier2DestList = append(tier2DestList, d)
var tier2Groups []Tier2DriveGroup
for _, grp := range tier2GroupMap {
tier2Groups = append(tier2Groups, *grp)
}
data["Tier2Dests"] = tier2DestList
data["Tier2DriveGroups"] = tier2Groups
} else {
data["Backup"] = nil
}
@@ -559,6 +585,14 @@ func (s *Server) backupsHandler(w http.ResponseWriter, r *http.Request) {
s.render(w, "backups", data)
}
// Tier2DriveGroup holds grouped Tier 2 cross-drive backup items for one destination drive.
type Tier2DriveGroup struct {
DestPath string
DestLabel string
ResticItems []backup.CrossDriveSummaryItem
RsyncItems []backup.CrossDriveSummaryItem
}
// AppBackupRow holds per-tier backup information for one app on the backup page.
type AppBackupRow struct {
StackName string