mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Fix alert disable notification suppression
This commit is contained in:
parent
d6e8bffaeb
commit
0dd3fc779b
6 changed files with 140 additions and 7 deletions
|
|
@ -1822,7 +1822,7 @@ export function Alerts() {
|
|||
alertsActivation.config()?.observationWindowHours;
|
||||
|
||||
const alertConfig = {
|
||||
enabled: true,
|
||||
enabled: alertsActivation.config()?.enabled ?? true,
|
||||
activationState: existingActivationState ?? undefined,
|
||||
activationTime: existingActivationTime,
|
||||
observationWindowHours: existingObservationWindowHours,
|
||||
|
|
|
|||
|
|
@ -163,18 +163,20 @@ func (h *AlertHandlers) UpdateAlertConfig(w http.ResponseWriter, r *http.Request
|
|||
updatedConfig := h.getMonitor(r.Context()).GetAlertManager().GetConfig()
|
||||
|
||||
// Update notification manager with schedule settings
|
||||
h.getMonitor(r.Context()).GetNotificationManager().SetCooldown(updatedConfig.Schedule.Cooldown)
|
||||
notificationMgr := h.getMonitor(r.Context()).GetNotificationManager()
|
||||
notificationMgr.SetEnabled(updatedConfig.Enabled && updatedConfig.ActivationState == alerts.ActivationActive)
|
||||
notificationMgr.SetCooldown(updatedConfig.Schedule.Cooldown)
|
||||
|
||||
groupWindow = updatedConfig.Schedule.Grouping.Window
|
||||
if groupWindow == 0 && updatedConfig.Schedule.GroupingWindow != 0 {
|
||||
groupWindow = updatedConfig.Schedule.GroupingWindow
|
||||
}
|
||||
h.getMonitor(r.Context()).GetNotificationManager().SetGroupingWindow(groupWindow)
|
||||
h.getMonitor(r.Context()).GetNotificationManager().SetGroupingOptions(
|
||||
notificationMgr.SetGroupingWindow(groupWindow)
|
||||
notificationMgr.SetGroupingOptions(
|
||||
updatedConfig.Schedule.Grouping.ByNode,
|
||||
updatedConfig.Schedule.Grouping.ByGuest,
|
||||
)
|
||||
h.getMonitor(r.Context()).GetNotificationManager().SetNotifyOnResolve(updatedConfig.Schedule.NotifyOnResolve)
|
||||
notificationMgr.SetNotifyOnResolve(updatedConfig.Schedule.NotifyOnResolve)
|
||||
|
||||
// Save to persistent storage
|
||||
if err := h.getMonitor(r.Context()).GetConfigPersistence().SaveAlertConfig(updatedConfig); err != nil {
|
||||
|
|
@ -215,6 +217,9 @@ func (h *AlertHandlers) ActivateAlerts(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Update config
|
||||
h.getMonitor(r.Context()).GetAlertManager().UpdateConfig(config)
|
||||
h.getMonitor(r.Context()).GetNotificationManager().SetEnabled(
|
||||
config.Enabled && config.ActivationState == alerts.ActivationActive,
|
||||
)
|
||||
|
||||
// Save to persistent storage
|
||||
if err := h.getMonitor(r.Context()).GetConfigPersistence().SaveAlertConfig(config); err != nil {
|
||||
|
|
|
|||
|
|
@ -143,14 +143,16 @@ func TestUpdateAlertConfig(t *testing.T) {
|
|||
mockMonitor := new(MockAlertMonitor)
|
||||
mockManager := new(MockAlertManager)
|
||||
mockPersist := new(MockConfigPersistence)
|
||||
notificationMgr := notifications.NewNotificationManager("")
|
||||
defer notificationMgr.Stop()
|
||||
|
||||
mockMonitor.On("GetAlertManager").Return(mockManager)
|
||||
mockMonitor.On("GetConfigPersistence").Return(mockPersist)
|
||||
mockMonitor.On("GetNotificationManager").Return(¬ifications.NotificationManager{})
|
||||
mockMonitor.On("GetNotificationManager").Return(notificationMgr)
|
||||
|
||||
h := NewAlertHandlers(nil, mockMonitor, nil)
|
||||
|
||||
cfg := alerts.AlertConfig{Enabled: true}
|
||||
cfg := alerts.AlertConfig{Enabled: true, ActivationState: alerts.ActivationPending}
|
||||
mockManager.On("UpdateConfig", testifymock.Anything).Return()
|
||||
mockManager.On("GetConfig").Return(cfg)
|
||||
mockPersist.On("SaveAlertConfig", testifymock.Anything).Return(nil)
|
||||
|
|
@ -162,6 +164,42 @@ func TestUpdateAlertConfig(t *testing.T) {
|
|||
h.UpdateAlertConfig(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.False(t, notificationMgr.IsEnabled())
|
||||
}
|
||||
|
||||
func TestActivateAlerts_EnablesNotificationManager(t *testing.T) {
|
||||
mockMonitor := new(MockAlertMonitor)
|
||||
mockManager := new(MockAlertManager)
|
||||
mockPersist := new(MockConfigPersistence)
|
||||
notificationMgr := notifications.NewNotificationManager("")
|
||||
defer notificationMgr.Stop()
|
||||
|
||||
mockMonitor.On("GetAlertManager").Return(mockManager)
|
||||
mockMonitor.On("GetConfigPersistence").Return(mockPersist)
|
||||
mockMonitor.On("GetNotificationManager").Return(notificationMgr)
|
||||
|
||||
initialConfig := alerts.AlertConfig{Enabled: true, ActivationState: alerts.ActivationPending}
|
||||
updatedConfig := alerts.AlertConfig{Enabled: true, ActivationState: alerts.ActivationActive}
|
||||
mockManager.On("GetConfig").Return(initialConfig).Once()
|
||||
mockManager.On("UpdateConfig", testifymock.AnythingOfType("alerts.AlertConfig")).Run(func(args testifymock.Arguments) {
|
||||
cfg := args.Get(0).(alerts.AlertConfig)
|
||||
if cfg.ActivationState != alerts.ActivationActive {
|
||||
t.Fatalf("expected activation state to be active, got %q", cfg.ActivationState)
|
||||
}
|
||||
}).Return()
|
||||
mockPersist.On("SaveAlertConfig", testifymock.Anything).Return(nil)
|
||||
mockManager.On("GetActiveAlerts").Return([]alerts.Alert{}).Maybe()
|
||||
mockManager.On("GetConfig").Return(updatedConfig)
|
||||
|
||||
h := NewAlertHandlers(nil, mockMonitor, nil)
|
||||
|
||||
req := httptest.NewRequest("POST", "/api/alerts/activate", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.ActivateAlerts(w, req)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.True(t, notificationMgr.IsEnabled())
|
||||
}
|
||||
|
||||
func TestGetActiveAlerts(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -4221,6 +4221,7 @@ func New(cfg *config.Config) (*Monitor, error) {
|
|||
if alertConfig, err := m.configPersist.LoadAlertConfig(); err == nil {
|
||||
m.alertManager.UpdateConfig(*alertConfig)
|
||||
// Apply schedule settings to notification manager
|
||||
m.notificationMgr.SetEnabled(alertConfig.Enabled && alertConfig.ActivationState == alerts.ActivationActive)
|
||||
m.notificationMgr.SetCooldown(alertConfig.Schedule.Cooldown)
|
||||
groupWindow := alertConfig.Schedule.Grouping.Window
|
||||
if groupWindow == 0 && alertConfig.Schedule.GroupingWindow != 0 {
|
||||
|
|
|
|||
|
|
@ -651,6 +651,51 @@ func (n *NotificationManager) GetQueue() *NotificationQueue {
|
|||
return n.queue
|
||||
}
|
||||
|
||||
// SetEnabled toggles notification delivery globally for this runtime instance.
|
||||
func (n *NotificationManager) SetEnabled(enabled bool) {
|
||||
var (
|
||||
queue *NotificationQueue
|
||||
changed bool
|
||||
)
|
||||
|
||||
n.mu.Lock()
|
||||
changed = n.enabled != enabled
|
||||
n.enabled = enabled
|
||||
if !enabled {
|
||||
for i := range n.pendingAlerts {
|
||||
n.pendingAlerts[i] = nil
|
||||
}
|
||||
n.pendingAlerts = n.pendingAlerts[:0]
|
||||
if n.groupTimer != nil {
|
||||
n.groupTimer.Stop()
|
||||
n.groupTimer = nil
|
||||
}
|
||||
queue = n.queue
|
||||
}
|
||||
n.mu.Unlock()
|
||||
|
||||
if changed {
|
||||
log.Info().Bool("enabled", enabled).Msg("Updated notification manager enabled state")
|
||||
}
|
||||
|
||||
if !enabled && queue != nil {
|
||||
if err := queue.CancelByTypes([]string{
|
||||
"email", "email_resolved", "email_escalation",
|
||||
"webhook", "webhook_resolved", "webhook_escalation",
|
||||
"apprise", "apprise_resolved", "apprise_escalation",
|
||||
}); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to cancel queued notifications after global disable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled reports whether notification delivery is currently enabled.
|
||||
func (n *NotificationManager) IsEnabled() bool {
|
||||
n.mu.RLock()
|
||||
defer n.mu.RUnlock()
|
||||
return n.enabled
|
||||
}
|
||||
|
||||
// SendAlert sends notifications for an alert
|
||||
func (n *NotificationManager) SendAlert(alert *alerts.Alert) {
|
||||
n.mu.Lock()
|
||||
|
|
@ -3367,9 +3412,13 @@ func (n *NotificationManager) ProcessQueuedNotification(notif *QueuedNotificatio
|
|||
|
||||
func (n *NotificationManager) resolveQueuedEmailConfig() (EmailConfig, error) {
|
||||
n.mu.RLock()
|
||||
enabled := n.enabled
|
||||
config := copyEmailConfig(n.emailConfig)
|
||||
n.mu.RUnlock()
|
||||
|
||||
if !enabled {
|
||||
return EmailConfig{}, fmt.Errorf("%w: notifications are disabled", ErrNotificationCancelled)
|
||||
}
|
||||
if !config.Enabled {
|
||||
return EmailConfig{}, fmt.Errorf("%w: email notifications are disabled", ErrNotificationCancelled)
|
||||
}
|
||||
|
|
@ -3379,9 +3428,13 @@ func (n *NotificationManager) resolveQueuedEmailConfig() (EmailConfig, error) {
|
|||
|
||||
func (n *NotificationManager) resolveQueuedAppriseConfig() (AppriseConfig, error) {
|
||||
n.mu.RLock()
|
||||
enabled := n.enabled
|
||||
config := copyAppriseConfig(n.appriseConfig)
|
||||
n.mu.RUnlock()
|
||||
|
||||
if !enabled {
|
||||
return AppriseConfig{}, fmt.Errorf("%w: notifications are disabled", ErrNotificationCancelled)
|
||||
}
|
||||
if !config.Enabled {
|
||||
return AppriseConfig{}, fmt.Errorf("%w: Apprise notifications are disabled", ErrNotificationCancelled)
|
||||
}
|
||||
|
|
@ -3391,9 +3444,13 @@ func (n *NotificationManager) resolveQueuedAppriseConfig() (AppriseConfig, error
|
|||
|
||||
func (n *NotificationManager) resolveQueuedWebhookConfig(queued WebhookConfig) (WebhookConfig, error) {
|
||||
n.mu.RLock()
|
||||
enabled := n.enabled
|
||||
webhooks := copyWebhookConfigs(n.webhooks)
|
||||
n.mu.RUnlock()
|
||||
|
||||
if !enabled {
|
||||
return WebhookConfig{}, fmt.Errorf("%w: notifications are disabled", ErrNotificationCancelled)
|
||||
}
|
||||
if queued.ID != "" {
|
||||
for _, webhook := range webhooks {
|
||||
if webhook.ID != queued.ID {
|
||||
|
|
|
|||
|
|
@ -2954,6 +2954,38 @@ func TestProcessQueuedNotification_WebhookUsesCurrentConfig(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestProcessQueuedNotification_CancelledWhenNotificationsDisabled(t *testing.T) {
|
||||
t.Setenv("PULSE_DATA_DIR", t.TempDir())
|
||||
|
||||
nm := NewNotificationManager("")
|
||||
defer nm.Stop()
|
||||
nm.SetEnabled(false)
|
||||
|
||||
currentWebhook := WebhookConfig{
|
||||
ID: "wh-disabled",
|
||||
Name: "disabled",
|
||||
URL: "https://example.invalid/webhook",
|
||||
Method: http.MethodPost,
|
||||
Enabled: true,
|
||||
}
|
||||
nm.AddWebhook(currentWebhook)
|
||||
|
||||
configJSON, err := json.Marshal(currentWebhook)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal queued webhook: %v", err)
|
||||
}
|
||||
|
||||
err = nm.ProcessQueuedNotification(&QueuedNotification{
|
||||
ID: "test-webhook-global-disabled",
|
||||
Type: "webhook",
|
||||
Config: configJSON,
|
||||
Alerts: []*alerts.Alert{testQueuedAlert()},
|
||||
})
|
||||
if !errors.Is(err, ErrNotificationCancelled) {
|
||||
t.Fatalf("expected queued webhook to be cancelled when notifications are globally disabled, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEmailConfig_DisableCancelsQueuedEmailNotifications(t *testing.T) {
|
||||
t.Setenv("PULSE_DATA_DIR", t.TempDir())
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue