Add alert archiving
Release / build (amd64, darwin) (push) Failing after 2m46s
Release / build (amd64, linux) (push) Failing after 2m26s
Release / build (arm64, darwin) (push) Failing after 1m40s
Release / build (arm64, linux) (push) Failing after 10s
Release / release (push) Has been skipped
Release / chart (push) Failing after 11s
Release / docker (push) Failing after 19s

Alerts can be manually archived (POST /api/alerts/{id}/archive) or
unarchived (DELETE /api/alerts/{id}/archive). A background goroutine
auto-archives resolved alerts older than TERDUT_ARCHIVE_AFTER (default 7d).
GET /api/alerts hides archived alerts by default; ?archived=true shows them.
This commit is contained in:
Niklas Ye
2026-05-22 13:22:45 +02:00
parent 36468a68ed
commit debc4bf78c
8 changed files with 178 additions and 6 deletions
+57 -2
View File
@@ -20,7 +20,8 @@ const alertSelectFrom = `
SELECT a.id, a.fingerprint, a.name, a.status,
a.labels, a.annotations,
a.starts_at, a.ends_at, a.generator_url, a.received_at,
a.acknowledged_by, a.acknowledged_at, u.username
a.acknowledged_by, a.acknowledged_at, u.username,
a.archived_at
FROM alerts a
LEFT JOIN users u ON u.id = a.acknowledged_by`
@@ -39,6 +40,12 @@ func handleListAlerts(db *sql.DB) http.HandlerFunc {
where = append(where, "a.name = ?")
args = append(args, name)
}
if archived := q.Get("archived"); archived == "true" {
where = append(where, "a.archived_at IS NOT NULL")
} else {
where = append(where, "a.archived_at IS NULL")
}
if from := q.Get("from"); from != "" {
if t, err := time.Parse("2006-01-02", from); err == nil {
where = append(where, "a.received_at >= ?")
@@ -169,7 +176,7 @@ func scanAlert(s scanner) (models.Alert, error) {
var a models.Alert
var labelsJSON, annotationsJSON string
var startsAtUnix, receivedAtUnix int64
var endsAtUnix, ackAtUnix *int64
var endsAtUnix, ackAtUnix, archivedAtUnix *int64
var ackByID *int64
var ackByUser *string
@@ -179,6 +186,7 @@ func scanAlert(s scanner) (models.Alert, error) {
&startsAtUnix, &endsAtUnix,
&a.GeneratorURL, &receivedAtUnix,
&ackByID, &ackAtUnix, &ackByUser,
&archivedAtUnix,
); err != nil {
return a, err
}
@@ -197,5 +205,52 @@ func scanAlert(s scanner) (models.Alert, error) {
a.AcknowledgedByUser = ackByUser
a.AcknowledgedAt = &t
}
if archivedAtUnix != nil {
t := time.Unix(*archivedAtUnix, 0).UTC()
a.ArchivedAt = &t
}
return a, nil
}
func handleArchive(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
respond(w, http.StatusBadRequest, errResp("invalid alert id"))
return
}
res, err := db.ExecContext(r.Context(),
"UPDATE alerts SET archived_at = unixepoch() WHERE id = ?", id)
if err != nil {
respond(w, http.StatusInternalServerError, errResp("internal error"))
return
}
if n, _ := res.RowsAffected(); n == 0 {
respond(w, http.StatusNotFound, errResp("alert not found"))
return
}
a, _ := fetchAlert(r.Context(), db, id)
respond(w, http.StatusOK, a)
}
}
func handleUnarchive(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
respond(w, http.StatusBadRequest, errResp("invalid alert id"))
return
}
res, err := db.ExecContext(r.Context(),
"UPDATE alerts SET archived_at = NULL WHERE id = ?", id)
if err != nil {
respond(w, http.StatusInternalServerError, errResp("internal error"))
return
}
if n, _ := res.RowsAffected(); n == 0 {
respond(w, http.StatusNotFound, errResp("alert not found"))
return
}
w.WriteHeader(http.StatusNoContent)
}
}