Phase 2: monitoring warnings, dashboard alerts & notification system

- Monitoring page: "Távoli monitoring" section showing healthcheck ping UUID
  configuration status (configured/not configured) for each of the 5 pings
- Alert manager: persistent dashboard banners on all pages generated from
  health check results, missing pings, and backup status
- Notification system: controller-side notifier sends events to hub relay,
  with cooldown tracking and event-type filtering
- Notification preferences UI: email, event checkboxes, cooldown settings
  on the settings page with test email functionality
- Settings refactored: shared settingsData() helper, NotificationPrefs
  struct with getter/setter and defaults

New files:
- controller/internal/web/alerts.go (AlertManager)
- controller/internal/notify/notifier.go (hub notification client)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 19:29:45 +01:00
parent ded59b687e
commit 3eee330ed5
10 changed files with 837 additions and 23 deletions
+45 -2
View File
@@ -26,8 +26,19 @@ type Settings struct {
DBValidations map[string]DBValidationCache `json:"db_validations,omitempty"`
}
// NotificationPrefs is a placeholder for Phase 2 notification settings.
type NotificationPrefs struct{}
// NotificationPrefs holds customer notification preferences.
type NotificationPrefs struct {
Email string `json:"email,omitempty"`
EnabledEvents []string `json:"enabled_events,omitempty"`
CooldownHours int `json:"cooldown_hours,omitempty"` // default: 6
}
// DefaultEnabledEvents are the events enabled by default for new customers.
var DefaultEnabledEvents = []string{
"disk_warning",
"backup_failed",
"update_available",
}
// DBValidationCache holds cached DB dump validation results.
type DBValidationCache struct {
@@ -127,3 +138,35 @@ func (s *Settings) SetDBValidation(filename string, cache DBValidationCache) err
s.DBValidations[filename] = cache
return s.save()
}
// GetNotificationPrefs returns a copy of the notification preferences.
func (s *Settings) GetNotificationPrefs() *NotificationPrefs {
s.mu.RLock()
defer s.mu.RUnlock()
if s.Notifications == nil {
return &NotificationPrefs{
EnabledEvents: DefaultEnabledEvents,
CooldownHours: 6,
}
}
prefs := *s.Notifications
if prefs.CooldownHours == 0 {
prefs.CooldownHours = 6
}
if prefs.EnabledEvents == nil {
prefs.EnabledEvents = DefaultEnabledEvents
}
// Return a copy of the slice
events := make([]string, len(prefs.EnabledEvents))
copy(events, prefs.EnabledEvents)
prefs.EnabledEvents = events
return &prefs
}
// SetNotificationPrefs updates notification preferences and saves to disk.
func (s *Settings) SetNotificationPrefs(prefs *NotificationPrefs) error {
s.mu.Lock()
defer s.mu.Unlock()
s.Notifications = prefs
return s.save()
}