package main
import (
"fmt"
"net/http"
"strings"
)
// grafanaParse formats Grafana / Prometheus Alertmanager webhook payloads.
func grafanaParse(raw []byte, headers http.Header, meta Meta) (string, string, bool) {
v, err := decodeJSON(raw)
payload := asObject(v)
if err != nil || payload.Len() == 0 {
return "", "", false
}
status := strOr(payload.Get("status"), "unknown")
receiver := strOr(payload.Get("receiver"), "Unknown")
externalURL := strOr(payload.Get("externalURL"), "")
alerts, _ := payload.Get("alerts").([]any)
truncated := num(payload.Get("truncatedAlerts"))
icon, color := statusStyle(status, "🚨")
// Summary
rows := []string{
row("Status", ``+icon+" "+h(status)+``),
row("Receiver", h(receiver)),
row("Alert Count", fmt.Sprint(len(alerts))),
}
if truncated > 0 {
rows = append(rows, row("Truncated Alerts", fmt.Sprint(int(truncated))))
}
if externalURL != "" {
rows = append(rows, row("Alertmanager URL", link(externalURL)))
}
body := section("Alert Summary", table(rows...))
// Labels
groupLabels := asObject(payload.Get("groupLabels"))
commonLabels := asObject(payload.Get("commonLabels"))
if groupLabels.Len() > 0 || commonLabels.Len() > 0 {
var b strings.Builder
if groupLabels.Len() > 0 {
b.WriteString(`
Group Labels
` + kvTable(groupLabels))
}
if commonLabels.Len() > 0 {
b.WriteString(`Common Labels
` + kvTable(commonLabels))
}
body += section("Labels", b.String())
}
// Common annotations
if ca := asObject(payload.Get("commonAnnotations")); ca.Len() > 0 {
body += section("Common Annotations", kvTable(ca))
}
// Individual alerts
if len(alerts) > 0 {
var b strings.Builder
for i, a := range alerts {
al := asObject(a)
aStatus := strOr(al.Get("status"), "unknown")
aIcon, aColor := statusStyle(aStatus, "🔥")
b.WriteString(`` + aIcon + " Alert " + fmt.Sprint(i+1) +
` - ` + h(aStatus) + `
`)
if l := asObject(al.Get("labels")); l.Len() > 0 {
b.WriteString(`
Labels
` + kvTable(l))
}
if an := asObject(al.Get("annotations")); an.Len() > 0 {
b.WriteString(`
Annotations
` + kvTable(an))
}
var extra []string
if s := strOr(al.Get("startsAt"), ""); s != "" {
extra = append(extra, row("Started At", h(s)))
}
if s := strOr(al.Get("endsAt"), ""); s != "" {
extra = append(extra, row("Ended At", h(s)))
}
if s := strOr(al.Get("generatorURL"), ""); s != "" {
extra = append(extra, row("Generator URL", link(s)))
}
if s := strOr(al.Get("fingerprint"), ""); s != "" {
extra = append(extra, row("Fingerprint", h(s)))
}
if len(extra) > 0 {
b.WriteString(table(extra...))
}
b.WriteString(`
`)
}
body += section("Alerts", b.String())
}
body += section("📋 Request Information", table(
row("Receiver", h(receiver)),
row("IP Address", h(meta.IP)),
))
html := emailShell(icon+" Grafana Alert: "+h(status), h(meta.Date), body,
"This Grafana/Prometheus alert was automatically forwarded to your email address.")
count := "1 alert"
if len(alerts) != 1 {
count = fmt.Sprintf("%d alerts", len(alerts))
}
return html, fmt.Sprintf("%s Grafana Alert: %s - %s", icon, status, count), true
}
func statusStyle(status, firingIcon string) (icon, color string) {
switch status {
case "firing":
return firingIcon, "#dc3545"
case "resolved":
return "✅", "#28a745"
}
return "🔔", "#666666"
}
// kvTable renders an object of scalar values as a label/value table.
func kvTable(m *OMap) string {
rows := make([]string, 0, m.Len())
for _, k := range m.Keys {
rows = append(rows, row(h(k), esc(str(m.M[k]))))
}
return table(rows...)
}