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:
@@ -309,13 +309,12 @@ func (s *Server) templateFuncMap() template.FuncMap {
|
||||
// moment the customer decides whether to restore — a UTC machine timestamp is not something a
|
||||
// customer can reason about. Absolute rather than relative here on purpose: "3 napja" is fine on
|
||||
// a status card, but a restore decision deserves the actual date.
|
||||
"fmtTimeStr": func(s string) string {
|
||||
t, err := time.Parse(time.RFC3339, s)
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
return t.In(loc).Format("2006-01-02 15:04")
|
||||
},
|
||||
//
|
||||
// R-102: it delegates to the package-level fmtRFC3339Local so a HANDLER can render the same
|
||||
// date the same way. The Tier-2 unit restore names the copy's date in its confirm (template)
|
||||
// and again in its outcome (Go); two renderings of one decision that could disagree is how a
|
||||
// customer ends up confirming one date and being told another.
|
||||
"fmtTimeStr": fmtRFC3339Local,
|
||||
"fmtTime": func(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "–"
|
||||
@@ -497,3 +496,14 @@ func (s *Server) templateFuncMap() template.FuncMap {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// fmtRFC3339Local renders an RFC3339 STRING as an absolute Budapest-local date-time, returning the
|
||||
// input unchanged when it does not parse. The ONE implementation behind the `fmtTimeStr` template
|
||||
// helper and every Go-side caller. See the comment on the funcmap entry for why it is shared.
|
||||
func fmtRFC3339Local(s string) string {
|
||||
t, err := time.Parse(time.RFC3339, s)
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
return t.In(getTimezone()).Format("2006-01-02 15:04")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user