diff --git a/CHANGELOG.md b/CHANGELOG.md index 657cdbd..56467b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ ## Changelog +### What was just completed (2026-02-18 session 46) +- **v0.12.9 — Tier 2 for All Apps + Status Dot Update:** + + **Fix 1: Tier 2 now configurable for ALL apps — not just HDD apps (`crossdrive.go`)** + - Removed `len(mounts) == 0` error gate from `RunAppBackup()` — empty mounts = config-only backup + - rsync: DB dump copy (`_db/`) + config rsync (`_config/`) still runs even with zero HDD mounts + - restic: config dir + DB dump dir still appended even without mount paths + - Non-HDD apps (Mealie, Gokapi, etc.) can now be protected against drive failure via Tier 2 + + **Fix 2: Status dot logic updated, HasHDDData gate removed (`handlers.go`)** + - `buildAppBackupRows()`: "auto" (gray) status removed — all apps start yellow ("Csak helyi mentés") + - Green requires Tier 2 configured + last status "ok" (not just "configured but never run") + - Tier2 section is now unconditional — no `if app.HasHDDData` gate + - Cross-drive summary loop: removed `if !app.HasHDDData { continue }` — all apps in summary + + **Fix 3: Backup page template updates (`backups.html`)** + - Tier 2 row shown for all apps (removed `{{if .HasHDDData}}` gate) + - Meta badge: non-HDD apps show "Konfig" or "Konfig + DB" instead of "Auto" + - Tier 3 placeholder row added (grayed out "Hamarosan / távoli offsite") + - Button text: "Összes HDD mentés" → "Összes 2. mentés futtatása most" + + **Fix 4: Deploy page cross-drive section visible for all deployed apps (`deploy.html`)** + - Removed `{{if .StorageInfo}}` double-gate — section now shows for all deployed apps + - Updated heading: "Másolat másik meghajtóra (felhasználói adatok)" → "2. mentés — másolat másik meghajtóra" + - Updated hint: "mint az alkalmazás adattárolója" → "a meghibásodás elleni védelem érdekében" + + **Files modified (4):** `internal/backup/crossdrive.go`, `internal/web/handlers.go`, `internal/web/templates/backups.html`, `internal/web/templates/deploy.html` + ### What was just completed (2026-02-18 session 45) - **v0.12.8 — Complete Cross-Drive Backup + Per-Tier UI:** diff --git a/controller/internal/backup/crossdrive.go b/controller/internal/backup/crossdrive.go index 223e88d..389fdd7 100644 --- a/controller/internal/backup/crossdrive.go +++ b/controller/internal/backup/crossdrive.go @@ -95,12 +95,8 @@ func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) e return fmt.Errorf("destination validation failed: %w", err) } - // Resolve HDD mounts for this app + // Resolve HDD mounts for this app (may be empty for config-only apps) mounts := r.stackProvider.GetStackHDDMounts(stackName) - if len(mounts) == 0 { - r.updateStatus(stackName, "error", "no HDD data paths found for this app", time.Since(start), "") - return fmt.Errorf("no HDD data paths found for %s", stackName) - } // Safety: destination must not overlap with any source for _, m := range mounts { diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index 9eb4dd4..6e72727 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -407,9 +407,6 @@ func (s *Server) backupsHandler(w http.ResponseWriter, r *http.Request) { } for _, app := range fullStatus.AppDataInfo { - if !app.HasHDDData { - continue - } cfg, hasCfg := crossConfigs[app.StackName] if !hasCfg || cfg == nil { fullStatus.UnconfiguredApps = append(fullStatus.UnconfiguredApps, backup.CrossDriveSummaryItem{ @@ -519,7 +516,7 @@ type AppBackupRow struct { Tier1LastStatus string // "ok", "error", "" Tier1DBStatus string // "ok", "error", "" — separate DB dump status for warning - // Tier 2: Cross-drive backup (only for apps with HDD data) + // Tier 2: Cross-drive backup (configurable for all apps) Tier2Configured bool Tier2Method string // "rsync", "restic" Tier2MethodLabel string // "rsync", "restic" @@ -602,71 +599,67 @@ func (s *Server) buildAppBackupRows( Tier1DBStatus: tier1DBStatus, } - // Default status = auto (no user data, just config) - row.Status = "auto" - row.StatusText = "Automatikus mentés" + // Status dot — start as yellow (1 tier only) + row.Status = "yellow" + row.StatusText = "Csak helyi mentés (1 szint)" - if app.HasHDDData { - cfg, hasCfg := crossConfigs[app.StackName] + cfg, hasCfg := crossConfigs[app.StackName] - if !hasCfg || cfg == nil || !cfg.Enabled { - // HDD data backed up via nightly restic (mandatory), but no second copy - row.Tier2Configured = false - row.Status = "yellow" - row.StatusText = "Nincs második másolat (csak helyi mentés)" - } else { - row.Tier2Configured = true - row.Tier2Method = cfg.Method - row.Tier2MethodLabel = cfg.Method // "rsync" or "restic" - row.Tier2Browsable = cfg.Method == "rsync" - row.Tier2Dest = destLabels[cfg.DestinationPath] - if row.Tier2Dest == "" { - row.Tier2Dest = cfg.DestinationPath + if !hasCfg || cfg == nil || !cfg.Enabled { + // Only Tier 1 — no second copy + row.Tier2Configured = false + } else { + row.Tier2Configured = true + row.Tier2Method = cfg.Method + row.Tier2MethodLabel = cfg.Method // "rsync" or "restic" + row.Tier2Browsable = cfg.Method == "rsync" + row.Tier2Dest = destLabels[cfg.DestinationPath] + if row.Tier2Dest == "" { + row.Tier2Dest = cfg.DestinationPath + } + switch cfg.Schedule { + case "daily": + row.Tier2Schedule = "Naponta" + case "weekly": + row.Tier2Schedule = "Hetente" + default: + row.Tier2Schedule = cfg.Schedule + } + if cfg.LastRun != "" { + if t, err := time.Parse(time.RFC3339, cfg.LastRun); err == nil { + row.Tier2LastRun = t.In(loc).Format("01-02 15:04") } - switch cfg.Schedule { - case "daily": - row.Tier2Schedule = "Naponta" - case "weekly": - row.Tier2Schedule = "Hetente" - default: - row.Tier2Schedule = cfg.Schedule - } - if cfg.LastRun != "" { - if t, err := time.Parse(time.RFC3339, cfg.LastRun); err == nil { - row.Tier2LastRun = t.In(loc).Format("01-02 15:04") - } - } - row.Tier2LastStatus = cfg.LastStatus - row.Tier2LastError = cfg.LastError - row.Tier2SizeHuman = cfg.LastSizeHuman - switch cfg.LastStatus { - case "ok": - row.Tier2StatusBadge = "Sikeres" - case "error": - row.Tier2StatusBadge = "Hiba" - row.Status = "yellow" - row.StatusText = "Utolsó mentés sikertelen" - case "running": - row.Tier2StatusBadge = "Fut..." - default: - row.Tier2StatusBadge = "—" - } - - // Destination health check - if cfg.Enabled && cfg.DestinationPath != "" { - if err := s.crossDriveRunner.ValidateDestination(cfg.DestinationPath); err != nil { - if strings.Contains(err.Error(), "does not exist") || strings.Contains(err.Error(), "not writable") { - row.Status = "red" - row.StatusText = "Mentési cél nem elérhető" - } else { - row.Status = "yellow" - row.StatusText = "Figyelmeztetés" - } - row.Warnings = append(row.Warnings, err.Error()) - } else if row.Status != "yellow" { - row.Status = "green" - row.StatusText = "Mentés rendben" + } + row.Tier2LastStatus = cfg.LastStatus + row.Tier2LastError = cfg.LastError + row.Tier2SizeHuman = cfg.LastSizeHuman + switch cfg.LastStatus { + case "ok": + row.Tier2StatusBadge = "Sikeres" + row.Status = "green" + row.StatusText = "Mentés rendben" + case "error": + row.Tier2StatusBadge = "Hiba" + // Status stays yellow + row.StatusText = "Utolsó mentés sikertelen" + case "running": + row.Tier2StatusBadge = "Fut..." + default: + row.Tier2StatusBadge = "—" + // Tier2 configured but never run — stay yellow + } + + // Destination health check — can downgrade green to yellow/red + if cfg.DestinationPath != "" { + if err := s.crossDriveRunner.ValidateDestination(cfg.DestinationPath); err != nil { + if strings.Contains(err.Error(), "does not exist") || strings.Contains(err.Error(), "not writable") { + row.Status = "red" + row.StatusText = "Mentési cél nem elérhető" + } else if row.Status != "red" { + row.Status = "yellow" + row.StatusText = "Figyelmeztetés" } + row.Warnings = append(row.Warnings, err.Error()) } } } diff --git a/controller/internal/web/templates/backups.html b/controller/internal/web/templates/backups.html index 4037a70..aa22a7e 100644 --- a/controller/internal/web/templates/backups.html +++ b/controller/internal/web/templates/backups.html @@ -257,7 +257,7 @@ {{if .StorageLabel}}{{end}} {{.HDDSizeHuman}} {{else}} - + {{end}} @@ -281,7 +281,6 @@ {{end}} - {{if .HasHDDData}}
Másolat másik meghajtóra (felhasználói adatok):
+2. mentés — másolat másik meghajtóra:
{{if .BackupDestWarning}}