fix: Tier2 backup status now detects drives removed from storage (not just disconnected)

Previously, removing a storage drive from the controller only marked it as
disconnected if the StoragePath entry still existed with Disconnected:true.
Drives removed entirely from storage_paths were invisible to the check,
causing Tier2 backup UI to show green "Sikeres" and scheduler to attempt
backups to a no-longer-managed destination.

New IsStoragePathKnown() method covers both cases. UI shows yellow
"Cél meghajtó leválasztva" and scheduler skips silently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 10:48:00 +01:00
parent dd79918234
commit 4fd907a09e
4 changed files with 28 additions and 1 deletions
+3 -1
View File
@@ -5,9 +5,11 @@
#### Fixed #### Fixed
- **system/mounts_linux.go**: `IsUSBDevice()` and `diskModel()` now strip findmnt bind-mount suffix (`[/subdir]`) before parsing device path — fixes USB badge and disk model not showing for drives mounted via the attach wizard - **system/mounts_linux.go**: `IsUSBDevice()` and `diskModel()` now strip findmnt bind-mount suffix (`[/subdir]`) before parsing device path — fixes USB badge and disk model not showing for drives mounted via the attach wizard
- **backup/crossdrive.go**: Disconnected source/destination drives now silently skip with WARN log instead of returning error — prevents noisy error aggregation in `RunAllScheduled()` and false "failed" counts - **backup/crossdrive.go**: Disconnected source/destination drives now silently skip with WARN log instead of returning error — prevents noisy error aggregation in `RunAllScheduled()` and false "failed" counts
- **web/handlers.go + backup/crossdrive.go**: Tier2 destination check now also covers drives **removed** from storage (not just marked disconnected) — `IsStoragePathKnown()` detects when destination path is no longer in any registered storage, UI shows yellow "Cél meghajtó leválasztva" and scheduler skips silently
#### Added #### Added
- **web/handlers.go**: New `Tier2DestDisconnected` field on `AppBackupRow` — detects when a Tier2 backup destination drive is disconnected, sets yellow status dot ("2. mentés szünetel") instead of red - **settings/settings.go**: New `IsStoragePathKnown(path)` method — returns whether a path belongs to any registered storage (connected, disconnected, or decommissioned); paths removed entirely return false
- **web/handlers.go**: New `Tier2DestDisconnected` field on `AppBackupRow` — detects when a Tier2 backup destination drive is disconnected or removed, sets yellow status dot ("2. mentés szünetel") instead of red
- **web/templates/backups.html**: New template branch for disconnected Tier2 destinations — shows "Cél meghajtó leválasztva" warning badge with grayed-out info, hides "Futtatás most" button - **web/templates/backups.html**: New template branch for disconnected Tier2 destinations — shows "Cél meghajtó leválasztva" warning badge with grayed-out info, hides "Futtatás most" button
### v0.32.4 — Controller telemetry: include controller in hub app telemetry (2026-02-27) ### v0.32.4 — Controller telemetry: include controller in hub app telemetry (2026-02-27)
+4
View File
@@ -100,6 +100,10 @@ func (r *CrossDriveRunner) RunAppBackup(ctx context.Context, stackName string) e
r.logger.Printf("[WARN] [backup] Cross-drive backup skipped for %s: destination drive disconnected (%s)", stackName, cfg.DestinationPath) r.logger.Printf("[WARN] [backup] Cross-drive backup skipped for %s: destination drive disconnected (%s)", stackName, cfg.DestinationPath)
return nil return nil
} }
if !r.sett.IsStoragePathKnown(cfg.DestinationPath) {
r.logger.Printf("[WARN] [backup] Cross-drive backup skipped for %s: destination not a registered storage (%s)", stackName, cfg.DestinationPath)
return nil
}
// Mark as running in settings // Mark as running in settings
_ = r.sett.UpdateCrossDriveStatus(stackName, func(c *settings.CrossDriveBackup) { _ = r.sett.UpdateCrossDriveStatus(stackName, func(c *settings.CrossDriveBackup) {
+14
View File
@@ -693,6 +693,20 @@ func (s *Settings) GetConnectedPaths() []StoragePath {
return result return result
} }
// IsStoragePathKnown returns whether a path belongs to any registered storage path
// (connected, disconnected, or decommissioned). A path removed entirely from
// storage_paths is NOT known.
func (s *Settings) IsStoragePathKnown(path string) bool {
s.mu.RLock()
defer s.mu.RUnlock()
for _, sp := range s.StoragePaths {
if path == sp.Path || strings.HasPrefix(path, sp.Path+"/") {
return true
}
}
return false
}
// GetStoppedStacks returns the list of stacks that were auto-stopped for a storage path. // GetStoppedStacks returns the list of stacks that were auto-stopped for a storage path.
func (s *Settings) GetStoppedStacks(path string) []string { func (s *Settings) GetStoppedStacks(path string) []string {
s.mu.RLock() s.mu.RLock()
+7
View File
@@ -938,6 +938,13 @@ func (s *Server) buildAppBackupRows(
} }
} }
// Also treat as disconnected if dest was removed from storage entirely
if cfg.DestinationPath != "" && !row.Tier2DestDisconnected {
if !s.settings.IsStoragePathKnown(cfg.DestinationPath) {
row.Tier2DestDisconnected = true
}
}
if row.Tier2DestDisconnected { if row.Tier2DestDisconnected {
// Disconnected destination — treat as paused, not failed // Disconnected destination — treat as paused, not failed
row.Status = "yellow" row.Status = "yellow"