Phase 3 complete: per-app backup toggles, restore, storage overview
- Storage overview on backup page (SSD/HDD bars, repo stats) - Restic password visibility + hub sync for disaster recovery - App data discovery (HDD bind mounts, Docker volumes) - Per-app backup toggle checkboxes with settings persistence - Dynamic backup paths: enabled app HDD data included in restic snapshots - Limited app restore from snapshots (self-service recovery) - Snapshots API endpoint for restore dropdown - Version bump to 0.8.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -237,14 +237,31 @@ func isPingConfigured(uuid string) bool {
|
||||
return uuid != "" && !strings.HasPrefix(uuid, "CHANGEME")
|
||||
}
|
||||
|
||||
func (s *Server) backupsHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
func (s *Server) backupsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := s.baseData("backups", "Biztonsági mentés")
|
||||
|
||||
// System info for storage overview bars
|
||||
data["SystemInfo"] = system.GetInfo(s.cfg.Paths.HDDPath, s.cpuCollector)
|
||||
|
||||
if s.backupMgr != nil {
|
||||
nextDBDump := scheduler.NextDailyRun(s.cfg.Backup.DBDumpSchedule)
|
||||
nextBackup := scheduler.NextDailyRun(s.cfg.Backup.ResticSchedule)
|
||||
fullStatus := s.backupMgr.GetFullStatus(nextDBDump, nextBackup)
|
||||
|
||||
// Pass flash messages from query params (set by redirect handlers)
|
||||
if flash := r.URL.Query().Get("flash"); flash != "" {
|
||||
fullStatus.FlashSuccess = flash
|
||||
}
|
||||
if flashErr := r.URL.Query().Get("flash_error"); flashErr != "" {
|
||||
fullStatus.FlashError = flashErr
|
||||
}
|
||||
|
||||
data["Backup"] = fullStatus
|
||||
|
||||
// Restic password for display
|
||||
if pw, err := s.backupMgr.GetResticPassword(); err == nil {
|
||||
data["ResticPassword"] = pw
|
||||
}
|
||||
} else {
|
||||
data["Backup"] = nil
|
||||
}
|
||||
@@ -252,6 +269,69 @@ func (s *Server) backupsHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
s.render(w, "backups", data)
|
||||
}
|
||||
|
||||
func (s *Server) settingsAppBackupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
if s.backupMgr == nil {
|
||||
http.Redirect(w, r, "/backups", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Get current app data info to know which stacks have HDD data
|
||||
nextDBDump := scheduler.NextDailyRun(s.cfg.Backup.DBDumpSchedule)
|
||||
nextBackup := scheduler.NextDailyRun(s.cfg.Backup.ResticSchedule)
|
||||
fullStatus := s.backupMgr.GetFullStatus(nextDBDump, nextBackup)
|
||||
|
||||
prefs := make(map[string]bool)
|
||||
for _, app := range fullStatus.AppDataInfo {
|
||||
if app.HasHDDData {
|
||||
prefs[app.StackName] = r.FormValue("backup_"+app.StackName) == "on"
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.settings.SetAppBackupBulk(prefs); err != nil {
|
||||
s.logger.Printf("[ERROR] Failed to save app backup prefs: %v", err)
|
||||
http.Redirect(w, r, "/backups?flash_error=Hiba+a+ment%C3%A9skor", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[INFO] App backup preferences updated: %v", prefs)
|
||||
|
||||
// Trigger cache refresh so the page shows updated data
|
||||
go s.backupMgr.RefreshCache(nextDBDump, nextBackup)
|
||||
|
||||
http.Redirect(w, r, "/backups?flash=Alkalmaz%C3%A1s+ment%C3%A9si+be%C3%A1ll%C3%ADt%C3%A1sok+mentve.", http.StatusFound)
|
||||
}
|
||||
|
||||
func (s *Server) backupRestoreHandler(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
stackName := r.FormValue("stack_name")
|
||||
snapshotID := r.FormValue("snapshot_id")
|
||||
|
||||
if stackName == "" || snapshotID == "" {
|
||||
http.Redirect(w, r, "/backups?flash_error=Hi%C3%A1nyz%C3%B3+param%C3%A9terek", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
if s.backupMgr == nil {
|
||||
http.Redirect(w, r, "/backups?flash_error=Ment%C3%A9s+nincs+be%C3%A1ll%C3%ADtva", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
s.logger.Printf("[WARN] Restore requested: stack=%s, snapshot=%s from %s", stackName, snapshotID, r.RemoteAddr)
|
||||
|
||||
if err := s.backupMgr.RestoreApp(stackName, snapshotID); err != nil {
|
||||
s.logger.Printf("[ERROR] Restore failed: %v", err)
|
||||
errMsg := url.QueryEscape("Visszaállítás sikertelen: " + err.Error())
|
||||
http.Redirect(w, r, "/backups?flash_error="+errMsg, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
msg := url.QueryEscape(stackName + " visszaállítva (" + snapshotID + ").")
|
||||
http.Redirect(w, r, "/backups?flash="+msg, http.StatusFound)
|
||||
}
|
||||
|
||||
func (s *Server) settingsData() map[string]interface{} {
|
||||
data := s.baseData("settings", "Beállítások")
|
||||
data["CustomerID"] = s.cfg.Customer.ID
|
||||
|
||||
Reference in New Issue
Block a user