Phase 3 — Alerts (P3-05/06/07) #7
@@ -68,6 +68,60 @@ func (e *Engine) raiseAndNotify(ctx context.Context, hostID, kind, severity, mes
|
||||
})
|
||||
}
|
||||
|
||||
// Acknowledge updates the alert row and fans out alert.acknowledged to
|
||||
// every enabled channel. Best-effort: store errors are logged but the
|
||||
// dispatch still fires only when the store update succeeds.
|
||||
func (e *Engine) Acknowledge(ctx context.Context, alertID, userID string, when time.Time) error {
|
||||
if err := e.store.Acknowledge(ctx, alertID, userID, when); err != nil {
|
||||
return err
|
||||
}
|
||||
a, lerr := e.store.GetAlert(ctx, alertID)
|
||||
if lerr != nil || a == nil {
|
||||
// Acknowledge already succeeded; dispatch is best-effort.
|
||||
return nil //nolint:nilerr
|
||||
}
|
||||
p := alertPayload(ctx, e.store, notification.EventAcknowledged, a)
|
||||
go e.hub.Dispatch(context.WithoutCancel(ctx), p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve marks the alert resolved and fans out alert.resolved.
|
||||
func (e *Engine) Resolve(ctx context.Context, alertID string, when time.Time) error {
|
||||
a, _ := e.store.GetAlert(ctx, alertID)
|
||||
if err := e.store.Resolve(ctx, alertID, when); err != nil {
|
||||
return err
|
||||
}
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
p := alertPayload(ctx, e.store, notification.EventResolved, a)
|
||||
go e.hub.Dispatch(context.WithoutCancel(ctx), p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// alertPayload builds a Payload from a stored Alert, looking up the host
|
||||
// name when HostID is set.
|
||||
func alertPayload(ctx context.Context, st *store.Store, ev notification.Event, a *store.Alert) notification.Payload {
|
||||
hostID, hostName := "", ""
|
||||
if a.HostID != nil {
|
||||
hostID = *a.HostID
|
||||
hostName = hostID
|
||||
if h, err := st.GetHost(ctx, hostID); err == nil && h != nil {
|
||||
hostName = h.Name
|
||||
}
|
||||
}
|
||||
return notification.Payload{
|
||||
Event: ev,
|
||||
AlertID: a.ID,
|
||||
Severity: a.Severity,
|
||||
Kind: a.Kind,
|
||||
HostID: hostID,
|
||||
HostName: hostName,
|
||||
Message: a.Message,
|
||||
RaisedAt: a.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// resolveAndNotify clears every open (or acknowledged) alert for
|
||||
// (host_id, kind) via store.AutoResolve, then fires alert.resolved
|
||||
// for each row that was actually open. Best-effort — errors are
|
||||
|
||||
@@ -119,7 +119,13 @@ func (s *Server) handleUIAlertAcknowledge(w stdhttp.ResponseWriter, r *stdhttp.R
|
||||
stdhttp.Error(w, "missing id", stdhttp.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := s.deps.Store.Acknowledge(r.Context(), id, u.ID, time.Now().UTC()); err != nil {
|
||||
var err error
|
||||
if s.deps.AlertEngine != nil {
|
||||
err = s.deps.AlertEngine.Acknowledge(r.Context(), id, u.ID, time.Now().UTC())
|
||||
} else {
|
||||
err = s.deps.Store.Acknowledge(r.Context(), id, u.ID, time.Now().UTC())
|
||||
}
|
||||
if err != nil {
|
||||
slog.Warn("ui alerts: ack", "err", err)
|
||||
}
|
||||
_ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{
|
||||
@@ -147,7 +153,13 @@ func (s *Server) handleUIAlertResolve(w stdhttp.ResponseWriter, r *stdhttp.Reque
|
||||
stdhttp.Error(w, "missing id", stdhttp.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := s.deps.Store.Resolve(r.Context(), id, time.Now().UTC()); err != nil {
|
||||
var err error
|
||||
if s.deps.AlertEngine != nil {
|
||||
err = s.deps.AlertEngine.Resolve(r.Context(), id, time.Now().UTC())
|
||||
} else {
|
||||
err = s.deps.Store.Resolve(r.Context(), id, time.Now().UTC())
|
||||
}
|
||||
if err != nil {
|
||||
slog.Warn("ui alerts: resolve", "err", err)
|
||||
}
|
||||
_ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{
|
||||
|
||||
Reference in New Issue
Block a user