Default legacy alert schedule settings

This commit is contained in:
rcourtman 2026-04-05 20:51:59 +01:00
parent b524659f03
commit af486b6f55
2 changed files with 110 additions and 13 deletions

View file

@ -565,7 +565,15 @@ func (c *ConfigPersistence) LoadAlertConfig() (*alerts.AlertConfig, error) {
},
Overrides: make(map[string]alerts.ThresholdConfig),
Schedule: alerts.ScheduleConfig{
Cooldown: 5,
MaxAlertsHour: 10,
NotifyOnResolve: true,
Grouping: alerts.GroupingConfig{
Enabled: true,
Window: 30,
ByNode: true,
ByGuest: false,
},
},
}, nil
}
@ -579,9 +587,62 @@ func (c *ConfigPersistence) LoadAlertConfig() (*alerts.AlertConfig, error) {
// For empty config files ({}), enable alerts by default
// This handles the case where the file exists but is empty
if string(data) == "{}" {
if bytes.Equal(bytes.TrimSpace(data), []byte("{}")) {
config.Enabled = true
}
defaultSchedule := alerts.ScheduleConfig{
Cooldown: 5,
MaxAlertsHour: 10,
NotifyOnResolve: true,
Grouping: alerts.GroupingConfig{
Enabled: true,
Window: 30,
ByNode: true,
ByGuest: false,
},
}
var scheduleProbe struct {
Schedule *struct {
Cooldown *int `json:"cooldown"`
MaxAlertsHour *int `json:"maxAlertsHour"`
NotifyOnResolve *bool `json:"notifyOnResolve"`
Grouping *struct {
Enabled *bool `json:"enabled"`
Window *int `json:"window"`
ByNode *bool `json:"byNode"`
ByGuest *bool `json:"byGuest"`
} `json:"grouping"`
} `json:"schedule"`
}
if err := json.Unmarshal(data, &scheduleProbe); err == nil {
if scheduleProbe.Schedule == nil || scheduleProbe.Schedule.Cooldown == nil {
config.Schedule.Cooldown = defaultSchedule.Cooldown
}
if scheduleProbe.Schedule == nil || scheduleProbe.Schedule.MaxAlertsHour == nil {
config.Schedule.MaxAlertsHour = defaultSchedule.MaxAlertsHour
}
if scheduleProbe.Schedule == nil || scheduleProbe.Schedule.NotifyOnResolve == nil {
config.Schedule.NotifyOnResolve = defaultSchedule.NotifyOnResolve
}
if scheduleProbe.Schedule == nil || scheduleProbe.Schedule.Grouping == nil {
config.Schedule.Grouping = defaultSchedule.Grouping
} else {
if scheduleProbe.Schedule.Grouping.Enabled == nil {
config.Schedule.Grouping.Enabled = defaultSchedule.Grouping.Enabled
}
if scheduleProbe.Schedule.Grouping.Window == nil {
config.Schedule.Grouping.Window = defaultSchedule.Grouping.Window
}
if scheduleProbe.Schedule.Grouping.ByNode == nil {
config.Schedule.Grouping.ByNode = defaultSchedule.Grouping.ByNode
}
if scheduleProbe.Schedule.Grouping.ByGuest == nil {
config.Schedule.Grouping.ByGuest = defaultSchedule.Grouping.ByGuest
}
}
}
// Storage: Allow Trigger=0 to disable storage alerting
if config.StorageDefault.Trigger < 0 {
config.StorageDefault.Trigger = 85
@ -727,18 +788,6 @@ func (c *ConfigPersistence) LoadAlertConfig() (*alerts.AlertConfig, error) {
}
config.BackupDefaults.IgnoreVMIDs = normalized
}
// Default NotifyOnResolve to true if not explicitly set in saved config.
// Older configs (pre-5.1.12) don't have this field, so Go unmarshals it
// as false — but the intended default is true (recovery notifications ON).
var scheduleProbe struct {
Schedule struct {
NotifyOnResolve *bool `json:"notifyOnResolve"`
} `json:"schedule"`
}
if err := json.Unmarshal(data, &scheduleProbe); err == nil && scheduleProbe.Schedule.NotifyOnResolve == nil {
config.Schedule.NotifyOnResolve = true
}
config.MetricTimeThresholds = alerts.NormalizeMetricTimeThresholds(config.MetricTimeThresholds)
config.DockerIgnoredContainerPrefixes = alerts.NormalizeDockerIgnoredPrefixes(config.DockerIgnoredContainerPrefixes)

View file

@ -26,6 +26,54 @@ func TestLoadAlertConfig_Normalization(t *testing.T) {
input: map[string]interface{}{},
verify: func(t *testing.T, cfg *alerts.AlertConfig) {
assert.True(t, cfg.Enabled)
assert.Equal(t, 5, cfg.Schedule.Cooldown)
assert.Equal(t, 10, cfg.Schedule.MaxAlertsHour)
assert.True(t, cfg.Schedule.NotifyOnResolve)
assert.True(t, cfg.Schedule.Grouping.Enabled)
assert.Equal(t, 30, cfg.Schedule.Grouping.Window)
assert.True(t, cfg.Schedule.Grouping.ByNode)
assert.False(t, cfg.Schedule.Grouping.ByGuest)
},
},
{
name: "Legacy schedule missing cooldown and grouping defaults",
input: map[string]interface{}{
"schedule": map[string]interface{}{
"notifyOnResolve": false,
},
},
verify: func(t *testing.T, cfg *alerts.AlertConfig) {
assert.Equal(t, 5, cfg.Schedule.Cooldown)
assert.Equal(t, 10, cfg.Schedule.MaxAlertsHour)
assert.False(t, cfg.Schedule.NotifyOnResolve)
assert.True(t, cfg.Schedule.Grouping.Enabled)
assert.Equal(t, 30, cfg.Schedule.Grouping.Window)
assert.True(t, cfg.Schedule.Grouping.ByNode)
assert.False(t, cfg.Schedule.Grouping.ByGuest)
},
},
{
name: "Explicit zero cooldown preserved",
input: map[string]interface{}{
"schedule": map[string]interface{}{
"cooldown": 0,
"notifyOnResolve": true,
"grouping": map[string]interface{}{
"enabled": false,
"window": 0,
"byNode": false,
"byGuest": true,
},
},
},
verify: func(t *testing.T, cfg *alerts.AlertConfig) {
assert.Equal(t, 0, cfg.Schedule.Cooldown)
assert.Equal(t, 10, cfg.Schedule.MaxAlertsHour)
assert.True(t, cfg.Schedule.NotifyOnResolve)
assert.False(t, cfg.Schedule.Grouping.Enabled)
assert.Equal(t, 0, cfg.Schedule.Grouping.Window)
assert.False(t, cfg.Schedule.Grouping.ByNode)
assert.True(t, cfg.Schedule.Grouping.ByGuest)
},
},
{