An app whose Tier-2 copy holds no file legs but a full recovery-unit mirror - 45 of the 53 catalog templates - was told to press a button on a DIFFERENT page. Since R-102 the data it is asking for is restorable from the copy it is looking at. New POST /backup/tier2/unit-restore and backupTier2UnitRestoreHandler: same guards, same restoreOpBlocked() refusal (R-351b), same async shape as the file restore beside it, plus a fail-closed pre-flight so the app is never stopped for a mirror that could not be opened. The outcome reuses unitRestoreOutcomeMsg and adds which copy overwrote the live data. The row offers the action where the refusal was, in a danger style, as a SEPARATE button. The two are not merged: one adds what is missing, the other overwrites. The confirm carries that difference in words and names the copy's date - and says so differently when that date is only an ATTEMPT (R-101). It is built from named Go constants rather than assembled inside an HTML attribute, so a test can assert it verbatim; fmtTimeStr now delegates to a package-level fmtRFC3339Local so the confirm and the outcome cannot render the same date two ways. tier2NoCoverageMsg is NARROWED to the case that remains - no legs and no openable unit - and still names the route that works. tier2UnitNotCoveredMsg is NOT deleted: it is appended where the FILE restore ran and is still exactly true of it. Tests C1-C2 and D1-D6 plus four more. Red-proofs: C1 (widen CanRestore to include HasUnit -> the unit-only cases fail), D6 (drop EndRestoreOp from the handler goroutine -> 'the restore never published a result').
This commit is contained in:
@@ -537,6 +537,10 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// C2: in-place, additive-only file restore from the Tier-2 copy (class-C user files)
|
||||
case path == "/backup/tier2/restore" && r.Method == http.MethodPost:
|
||||
s.backupTier2RestoreHandler(w, r)
|
||||
// R-102/R-103: the DESTRUCTIVE twin — a full recovery-unit restore read from the SECOND DRIVE's
|
||||
// mirror. Separate route because it is a separate promise: this one overwrites live data.
|
||||
case path == "/backup/tier2/unit-restore" && r.Method == http.MethodPost:
|
||||
s.backupTier2UnitRestoreHandler(w, r)
|
||||
// Off-box (NAS) restic-SFTP backup (Part B)
|
||||
case path == "/backup/offbox/config" && r.Method == http.MethodPost:
|
||||
s.offboxConfigHandler(w, r)
|
||||
|
||||
Reference in New Issue
Block a user