v0.309.0: the fill warning reaches the household in its own language; the root-filesystem disk event pinned end to end (R-521 F7)

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-10-12 17:11:53 +02:00
parent 402fa41e45
commit 7626a28de3
14 changed files with 290 additions and 53 deletions
+34 -20
View File
@@ -38,7 +38,6 @@ import (
"gitea.dooplex.hu/admin/felhom-controller/internal/crashboot"
"gitea.dooplex.hu/admin/felhom-controller/internal/crypto"
"gitea.dooplex.hu/admin/felhom-controller/internal/fillwatch"
"gitea.dooplex.hu/admin/felhom-controller/internal/i18n"
"gitea.dooplex.hu/admin/felhom-controller/internal/infra"
"gitea.dooplex.hu/admin/felhom-controller/internal/integrations"
"gitea.dooplex.hu/admin/felhom-controller/internal/mailrelay"
@@ -1593,21 +1592,12 @@ func main() {
}
})
if notifier != nil {
fillWatcher.SetNotify(func(e fillwatch.Event) {
// The CUSTOMER's event — deliberately not operator-only. A customer can free space,
// delete files or add a drive, so this alert is theirs (D-c). The dynamic Hungarian
// message carries the label and the free space; the hub has NO customerMessages entry for
// these two types precisely so the template cannot discard them.
notifier.PushEvent(e.Band.EventType(), e.Band.Severity(), e.Message, map[string]any{
"path": e.Target.Path, "label": e.Target.Label,
"used_percent": e.Usage.UsedPercent, "avail_gb": e.Usage.AvailGB,
"total_gb": e.Usage.TotalGB, "band": e.Band.String(),
})
})
fillWatcher.SetNotify(fillEventPush(notifier))
}
// `09` §3 decision 36: the warning names the kept folders on the filling drive, with their sizes —
// space the household can free on the kept-data list. Nothing is deleted by the box (D3).
fillWatcher.SetExtra(func(t fillwatch.Target) string { return keptSpaceSentence(stackMgr, t.Path) })
fillWatcher.SetExtra(func(t fillwatch.Target, lang string) string { return keptSpaceSentence(stackMgr, t.Path, lang) })
fillWatcher.SetLang(sett.GetLanguage)
sched.Daily("fill-watch", "03:30", func(ctx context.Context) error { return fillWatcher.Check() })
sched.Every("fill-watch-interval", fillWatchInterval, func(ctx context.Context) error { return fillWatcher.Check() })
@@ -2532,28 +2522,52 @@ const fillWatchInterval = 10 * time.Minute
// DECOMMISSIONED drive is dropped — it is deliberately out of service, and its fill is not news. A
// DISCONNECTED one is kept in the list but will read as unreadable and be skipped by §8.4, which is
// the correct outcome: its absence already has its own alert.
// fillEventPusher is the one notifier method fillEventPush needs — an interface so the R-521 F7 test
// can record what is pushed without a hub.
type fillEventPusher interface {
PushEventHousehold(eventType, severity, message, household string, details interface{})
}
// fillEventPush turns a fillwatch crossing into the hub event.
//
// The CUSTOMER's event — deliberately not operator-only. A customer can free space, delete files or
// add a drive, so this alert is theirs (D-c). The dynamic Hungarian message carries the label and the
// free space; the hub has NO customerMessages entry for these two types precisely so the template
// cannot discard them. R-521 F7: the household's own rendering rides beside the Hungarian (R-558's
// `message_customer`), so an English household is told in English. Pinned by
// TestR521F7_RootFilesystemSendsTheDiskEventInBothLanguages.
func fillEventPush(n fillEventPusher) func(fillwatch.Event) {
return func(e fillwatch.Event) {
n.PushEventHousehold(e.Band.EventType(), e.Band.Severity(), e.Message, e.MessageHousehold, map[string]any{
"path": e.Target.Path, "label": e.Target.Label,
"used_percent": e.Usage.UsedPercent, "avail_gb": e.Usage.AvailGB,
"total_gb": e.Usage.TotalGB, "band": e.Band.String(),
})
}
}
func fillTargets(cfg *config.Config, sett *settings.Settings) []fillwatch.Target {
seen := make(map[string]bool)
var out []fillwatch.Target
add := func(path, label string) {
add := func(path, label, labelKey string) {
if path == "" || seen[path] {
return
}
seen[path] = true
out = append(out, fillwatch.Target{Path: path, Label: label})
out = append(out, fillwatch.Target{Path: path, Label: label, LabelKey: labelKey})
}
// The docker data-root as seen from inside the guest, and the system-data area.
add(system.DockerVolumePath, "Alkalmazások területe")
add(system.DockerVolumePath, "Alkalmazások területe", "event.fill.label_apps")
if cfg != nil {
add(cfg.Paths.SystemDataPath, "Rendszer- és mentési terület")
add(cfg.Paths.SystemDataPath, "Rendszer- és mentési terület", "event.fill.label_system")
}
if sett != nil {
for _, sp := range sett.GetStoragePaths() {
if sp.Decommissioned {
continue
}
add(sp.Path, sp.Label)
add(sp.Path, sp.Label, "")
}
}
return fillwatch.SortTargets(out)
@@ -4071,7 +4085,7 @@ func budapestLoc() *time.Location {
// keptSpaceSentence is the drive-full warning's kept-data sentence for one drive (Hungarian, like the
// warning it extends): each kept folder by app name and size. "" when the drive holds none.
func keptSpaceSentence(sm *stacks.Manager, drive string) string {
func keptSpaceSentence(sm *stacks.Manager, drive, lang string) string {
items := sm.ListKept([]string{drive})
if len(items) == 0 {
return ""
@@ -4080,5 +4094,5 @@ func keptSpaceSentence(sm *stacks.Manager, drive string) string {
for _, it := range items {
parts = append(parts, fmt.Sprintf("%s (%s)", it.DisplayName, appbackup.HumanizeBytes(it.SizeBytes)))
}
return util.Text(i18n.Default, "kept.diskwarn", strings.Join(parts, ", "))
return util.Text(lang, "kept.diskwarn", strings.Join(parts, ", "))
}