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
|
// resolveAndNotify clears every open (or acknowledged) alert for
|
||||||
// (host_id, kind) via store.AutoResolve, then fires alert.resolved
|
// (host_id, kind) via store.AutoResolve, then fires alert.resolved
|
||||||
// for each row that was actually open. Best-effort — errors are
|
// 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)
|
stdhttp.Error(w, "missing id", stdhttp.StatusBadRequest)
|
||||||
return
|
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)
|
slog.Warn("ui alerts: ack", "err", err)
|
||||||
}
|
}
|
||||||
_ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{
|
_ = 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)
|
stdhttp.Error(w, "missing id", stdhttp.StatusBadRequest)
|
||||||
return
|
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)
|
slog.Warn("ui alerts: resolve", "err", err)
|
||||||
}
|
}
|
||||||
_ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{
|
_ = s.deps.Store.AppendAudit(r.Context(), store.AuditEntry{
|
||||||
|
|||||||
Reference in New Issue
Block a user