package backup import "time" // Restore op-status (Part B): a lightweight, in-memory surface for the ASYNC restore family so the // backups page can show a progress banner (running → success/failure) instead of blocking the HTTP // request until the restore completes. It is display-only and mutex-guarded on the Manager's `mu`; // it does NOT gate concurrency (that stays the restore functions' internal single-flight acquire). // In-memory only — lost on a controller restart (same precedent as notification cooldowns); a page // load mid-op after a restart simply shows no banner. // RestoreOpResult is the terminal record of the most recent restore op. type RestoreOpResult struct { Op string `json:"op"` // "restore" | "tier2-restore" | "offbox-restore" Stack string `json:"stack"` OK bool `json:"ok"` Message string `json:"message"` FinishedAt time.Time `json:"finished_at"` } // RestoreOpStatus is the shape served at GET /api/backup/restore-status. type RestoreOpStatus struct { Running bool `json:"running"` Op string `json:"op,omitempty"` Stack string `json:"stack,omitempty"` StartedAt time.Time `json:"started_at,omitempty"` Last *RestoreOpResult `json:"last,omitempty"` } // BeginRestoreOp marks a restore op in flight (called by the handler just before launching the // background goroutine). Idempotent enough for display; concurrency is enforced elsewhere. func (m *Manager) BeginRestoreOp(op, stack string) { m.mu.Lock() defer m.mu.Unlock() m.opRunning = true m.opName = op m.opStack = stack m.opStartedAt = time.Now() } // EndRestoreOp records the terminal result (called from the goroutine on completion, success or // failure). Message carries the error (failure) or a human note like the scratch path (offbox). func (m *Manager) EndRestoreOp(ok bool, message string) { m.mu.Lock() defer m.mu.Unlock() m.opLast = &RestoreOpResult{ Op: m.opName, Stack: m.opStack, OK: ok, Message: message, FinishedAt: time.Now(), } m.opRunning = false } // RestoreStatus returns a deep copy of the current restore op-status for the page/API. func (m *Manager) RestoreStatus() RestoreOpStatus { m.mu.Lock() defer m.mu.Unlock() st := RestoreOpStatus{ Running: m.opRunning, Op: m.opName, Stack: m.opStack, StartedAt: m.opStartedAt, } if m.opLast != nil { cp := *m.opLast st.Last = &cp } return st }