controller v0.264.0: the household is told when an update is undone or held, in its language
gates / gates (push) Successful in 25s

app_update_undone / app_update_held events (09 decision 15), on by
default and seeded once on existing boxes; R-606 update sentences as
key+args rendered per reader; R-646 startup applied-meta backfill for
apps current with the catalog; R-620 a disabled notifier WARNs once per
event type. Needs hub v0.120.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-23 13:51:21 +02:00
parent c3a2aba0d2
commit bc278944a3
27 changed files with 1086 additions and 86 deletions
+17 -1
View File
@@ -19,6 +19,7 @@ import (
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
cf "gitea.dooplex.hu/admin/felhom-controller/internal/cloudflare"
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
"gitea.dooplex.hu/admin/felhom-controller/internal/integrations"
"gitea.dooplex.hu/admin/felhom-controller/internal/metrics"
"gitea.dooplex.hu/admin/felhom-controller/internal/notify"
@@ -373,12 +374,27 @@ func (r *Router) rescanStacks(w http.ResponseWriter, req *http.Request) {
})
}
func (r *Router) getStack(w http.ResponseWriter, _ *http.Request, name string) {
func (r *Router) getStack(w http.ResponseWriter, req *http.Request, name string) {
stack, ok := r.stackMgr.GetStack(name)
if !ok {
writeJSON(w, http.StatusNotFound, apiResponse{OK: false, Error: "stack not found: " + name})
return
}
// v0.264.0 (R-606): the page polls this for the Update button's label and the outcome. Rendered in
// the reader's language; a Hungarian reader gets exactly the bytes it always got. GetStack returns a
// COPY, so nothing here touches the manager's own state.
if lang := r.langFor(req); lang != i18n.Default {
stack.UpdatePhaseLabel = stacks.UpdatePhaseLabelIn(lang, stack.UpdatePhase)
stack.UpdateError = stack.UpdateErrorIn(lang)
if stack.HoldReason != "" && r.backupMgr != nil {
if held, why := r.backupMgr.RestoreHoldForLang(name, lang); held && why != "" {
stack.HoldReason = why
if stack.UpdateErrorKey == "" && stack.UpdatePhase == stacks.UpdatePhaseFailed {
stack.UpdateError = why // a held update's UpdateError IS the hold sentence
}
}
}
}
writeJSON(w, http.StatusOK, apiResponse{OK: true, Data: stack})
}