mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Default legacy alert schedule settings on load
Back-port v5 fix af486b6f5 to v6. LoadAlertConfig now back-fills
Schedule.Cooldown=5, MaxAlertsHour=10, NotifyOnResolve=true and the
Grouping defaults for legacy alerts.json files saved before these
fields existed, probing raw JSON so an explicit 0/false is preserved.
Without this, a v5 user migrating to v6 loads with zeroed cooldown and
rate limit, silently losing notification-spam protection.
This commit is contained in:
parent
5a21f0c288
commit
545a6f22a0
2 changed files with 109 additions and 10 deletions
|
|
@ -88,7 +88,17 @@ type alertSchedulePresence struct {
|
|||
}
|
||||
|
||||
type alertScheduleFieldPresence struct {
|
||||
NotifyOnResolve *bool `json:"notifyOnResolve"`
|
||||
Cooldown *int `json:"cooldown"`
|
||||
MaxAlertsHour *int `json:"maxAlertsHour"`
|
||||
NotifyOnResolve *bool `json:"notifyOnResolve"`
|
||||
Grouping *alertScheduleGroupingPresence `json:"grouping"`
|
||||
}
|
||||
|
||||
type alertScheduleGroupingPresence struct {
|
||||
Enabled *bool `json:"enabled"`
|
||||
Window *int `json:"window"`
|
||||
ByNode *bool `json:"byNode"`
|
||||
ByGuest *bool `json:"byGuest"`
|
||||
}
|
||||
|
||||
type resolvedConfigPersistencePaths struct {
|
||||
|
|
@ -927,7 +937,15 @@ func (c *ConfigPersistence) LoadAlertConfig() (*alerts.AlertConfig, error) {
|
|||
Disk: &alerts.HysteresisThreshold{Trigger: 90, Clear: 85},
|
||||
},
|
||||
Schedule: alerts.ScheduleConfig{
|
||||
Cooldown: 5,
|
||||
MaxAlertsHour: 10,
|
||||
NotifyOnResolve: true,
|
||||
Grouping: alerts.GroupingConfig{
|
||||
Enabled: true,
|
||||
Window: 30,
|
||||
ByNode: true,
|
||||
ByGuest: false,
|
||||
},
|
||||
},
|
||||
StorageDefault: alerts.HysteresisThreshold{Trigger: 85, Clear: 80},
|
||||
TimeThresholds: map[string]int{
|
||||
|
|
@ -973,15 +991,48 @@ func (c *ConfigPersistence) LoadAlertConfig() (*alerts.AlertConfig, error) {
|
|||
config.Enabled = true
|
||||
}
|
||||
|
||||
// Load-specific: NotifyOnResolve defaults to true (send recovery notifications).
|
||||
// Since bool zero-value is false, we check raw JSON to distinguish
|
||||
// "missing field" (old configs) from "explicitly set to false".
|
||||
if !config.Schedule.NotifyOnResolve {
|
||||
var presence alertSchedulePresence
|
||||
// Ignore error here as we've already unmarshaled successfully above.
|
||||
if json.Unmarshal(data, &presence) == nil {
|
||||
if presence.Schedule == nil || presence.Schedule.NotifyOnResolve == nil {
|
||||
config.Schedule.NotifyOnResolve = true
|
||||
// Load-specific: back-fill legacy alert schedule defaults for configs saved
|
||||
// before these fields existed (pre-5.1.12). Because Go unmarshals a missing
|
||||
// field to its zero value, we probe the raw JSON to distinguish "field
|
||||
// absent" (apply default) from "explicitly set to 0/false" (preserve it).
|
||||
defaultSchedule := alerts.ScheduleConfig{
|
||||
Cooldown: 5,
|
||||
MaxAlertsHour: 10,
|
||||
NotifyOnResolve: true,
|
||||
Grouping: alerts.GroupingConfig{
|
||||
Enabled: true,
|
||||
Window: 30,
|
||||
ByNode: true,
|
||||
ByGuest: false,
|
||||
},
|
||||
}
|
||||
var presence alertSchedulePresence
|
||||
// Ignore error here as we've already unmarshaled successfully above.
|
||||
if json.Unmarshal(data, &presence) == nil {
|
||||
sched := presence.Schedule
|
||||
if sched == nil || sched.Cooldown == nil {
|
||||
config.Schedule.Cooldown = defaultSchedule.Cooldown
|
||||
}
|
||||
if sched == nil || sched.MaxAlertsHour == nil {
|
||||
config.Schedule.MaxAlertsHour = defaultSchedule.MaxAlertsHour
|
||||
}
|
||||
if sched == nil || sched.NotifyOnResolve == nil {
|
||||
config.Schedule.NotifyOnResolve = defaultSchedule.NotifyOnResolve
|
||||
}
|
||||
if sched == nil || sched.Grouping == nil {
|
||||
config.Schedule.Grouping = defaultSchedule.Grouping
|
||||
} else {
|
||||
if sched.Grouping.Enabled == nil {
|
||||
config.Schedule.Grouping.Enabled = defaultSchedule.Grouping.Enabled
|
||||
}
|
||||
if sched.Grouping.Window == nil {
|
||||
config.Schedule.Grouping.Window = defaultSchedule.Grouping.Window
|
||||
}
|
||||
if sched.Grouping.ByNode == nil {
|
||||
config.Schedule.Grouping.ByNode = defaultSchedule.Grouping.ByNode
|
||||
}
|
||||
if sched.Grouping.ByGuest == nil {
|
||||
config.Schedule.Grouping.ByGuest = defaultSchedule.Grouping.ByGuest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ 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)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -114,6 +121,47 @@ func TestLoadAlertConfig_Normalization(t *testing.T) {
|
|||
assert.False(t, cfg.Schedule.NotifyOnResolve)
|
||||
},
|
||||
},
|
||||
{
|
||||
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 and grouping 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)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NodeDefaults Temperature nil",
|
||||
input: map[string]interface{}{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue