From 8df3c569c7c632db6966ff7f9715070b0011a5bc Mon Sep 17 00:00:00 2001 From: Roman Khavronenko Date: Wed, 13 Oct 2021 15:25:11 +0300 Subject: [PATCH] vmalert: add Source link to alerts UI (#1701) The source link is controlled by `external.url` and `external.alert.source` flags, in the same way as for alertmanager notifications. The source link is added to Alerts list view, and specific Alert view. --- app/vmalert/alerting.go | 6 +- app/vmalert/main.go | 29 +++-- app/vmalert/web.qtpl | 14 ++- app/vmalert/web.qtpl.go | 242 +++++++++++++++++++++------------------ app/vmalert/web_types.go | 1 + 5 files changed, 164 insertions(+), 128 deletions(-) diff --git a/app/vmalert/alerting.go b/app/vmalert/alerting.go index 4aa77adea..671750ecd 100644 --- a/app/vmalert/alerting.go +++ b/app/vmalert/alerting.go @@ -415,7 +415,7 @@ func (ar *AlertingRule) AlertsAPI() []*APIAlert { } func (ar *AlertingRule) newAlertAPI(a notifier.Alert) *APIAlert { - return &APIAlert{ + aa := &APIAlert{ // encode as strings to avoid rounding ID: fmt.Sprintf("%d", a.ID), GroupID: fmt.Sprintf("%d", a.GroupID), @@ -429,6 +429,10 @@ func (ar *AlertingRule) newAlertAPI(a notifier.Alert) *APIAlert { ActiveAt: a.Start, Value: strconv.FormatFloat(a.Value, 'f', -1, 32), } + if alertURLGeneratorFn != nil { + aa.SourceLink = alertURLGeneratorFn(a) + } + return aa } const ( diff --git a/app/vmalert/main.go b/app/vmalert/main.go index fa8ed3753..7e9c109f3 100644 --- a/app/vmalert/main.go +++ b/app/vmalert/main.go @@ -59,6 +59,8 @@ eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{ dryRun = flag.Bool("dryRun", false, "Whether to check only config files without running vmalert. The rules file are validated. The `-rule` flag must be specified.") ) +var alertURLGeneratorFn notifier.AlertURLGenerator + func main() { // Write flags and help message to stdout, since it is easier to grep or pipe. flag.CommandLine.SetOutput(os.Stdout) @@ -79,15 +81,22 @@ func main() { } return } + + eu, err := getExternalURL(*externalURL, *httpListenAddr, httpserver.IsTLS()) + if err != nil { + logger.Fatalf("failed to init `external.url`: %s", err) + } + notifier.InitTemplateFunc(eu) + alertURLGeneratorFn, err = getAlertURLGenerator(eu, *externalAlertSource, *validateTemplates) + if err != nil { + logger.Fatalf("failed to init `external.alert.source`: %s", err) + } + if *replayFrom != "" || *replayTo != "" { rw, err := remotewrite.Init(context.Background()) if err != nil { logger.Fatalf("failed to init remoteWrite: %s", err) } - eu, err := getExternalURL(*externalURL, *httpListenAddr, httpserver.IsTLS()) - if err != nil { - logger.Fatalf("failed to init `external.url`: %s", err) - } notifier.InitTemplateFunc(eu) groupsCfg, err := config.Parse(*rulePath, *validateTemplates, *validateExpressions) if err != nil { @@ -148,20 +157,10 @@ func newManager(ctx context.Context) (*manager, error) { if err != nil { return nil, fmt.Errorf("failed to init datasource: %w", err) } - eu, err := getExternalURL(*externalURL, *httpListenAddr, httpserver.IsTLS()) - if err != nil { - return nil, fmt.Errorf("failed to init `external.url`: %w", err) - } - notifier.InitTemplateFunc(eu) - aug, err := getAlertURLGenerator(eu, *externalAlertSource, *validateTemplates) - if err != nil { - return nil, fmt.Errorf("failed to init `external.alert.source`: %w", err) - } - nts, err := notifier.Init(aug) + nts, err := notifier.Init(alertURLGeneratorFn) if err != nil { return nil, fmt.Errorf("failed to init notifier: %w", err) } - manager := &manager{ groups: make(map[uint64]*Group), querierBuilder: q, diff --git a/app/vmalert/web.qtpl b/app/vmalert/web.qtpl index 7c5c6b2b4..b64640a45 100644 --- a/app/vmalert/web.qtpl +++ b/app/vmalert/web.qtpl @@ -155,7 +155,9 @@ sort.Strings(labelKeys) %}
- alert: {%s defaultAR.Name %} ({%d len(alertsByRule[ruleID]) %})
+ alert: {%s defaultAR.Name %} ({%d len(alertsByRule[ruleID]) %}) + | Source +
expr:
{%s defaultAR.Expression %}
@@ -270,6 +272,16 @@ {%s alert.GroupID %} + +
+
+
+ Source link +
+
+ Link +
+
{%= tpl.Footer() %} diff --git a/app/vmalert/web.qtpl.go b/app/vmalert/web.qtpl.go index a15c6523f..5b7a3bbb4 100644 --- a/app/vmalert/web.qtpl.go +++ b/app/vmalert/web.qtpl.go @@ -573,11 +573,17 @@ func StreamListAlerts(qw422016 *qt422016.Writer, groupAlerts []GroupAlerts) { //line app/vmalert/web.qtpl:158 qw422016.N().D(len(alertsByRule[ruleID])) //line app/vmalert/web.qtpl:158 - qw422016.N().S(`)
+ qw422016.N().S(`) + | Source +
expr:
`)
-//line app/vmalert/web.qtpl:159
+//line app/vmalert/web.qtpl:161
 				qw422016.E().S(defaultAR.Expression)
-//line app/vmalert/web.qtpl:159
+//line app/vmalert/web.qtpl:161
 				qw422016.N().S(`
@@ -591,152 +597,152 @@ func StreamListAlerts(qw422016 *qt422016.Writer, groupAlerts []GroupAlerts) { `) -//line app/vmalert/web.qtpl:171 +//line app/vmalert/web.qtpl:173 for _, ar := range alertsByRule[ruleID] { -//line app/vmalert/web.qtpl:171 +//line app/vmalert/web.qtpl:173 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:185 +//line app/vmalert/web.qtpl:187 } -//line app/vmalert/web.qtpl:185 +//line app/vmalert/web.qtpl:187 qw422016.N().S(`
`) -//line app/vmalert/web.qtpl:174 +//line app/vmalert/web.qtpl:176 for _, k := range labelKeys { -//line app/vmalert/web.qtpl:174 +//line app/vmalert/web.qtpl:176 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:175 +//line app/vmalert/web.qtpl:177 qw422016.E().S(k) -//line app/vmalert/web.qtpl:175 +//line app/vmalert/web.qtpl:177 qw422016.N().S(`=`) -//line app/vmalert/web.qtpl:175 +//line app/vmalert/web.qtpl:177 qw422016.E().S(ar.Labels[k]) -//line app/vmalert/web.qtpl:175 +//line app/vmalert/web.qtpl:177 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:176 +//line app/vmalert/web.qtpl:178 } -//line app/vmalert/web.qtpl:176 +//line app/vmalert/web.qtpl:178 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:178 +//line app/vmalert/web.qtpl:180 qw422016.E().S(ar.State) -//line app/vmalert/web.qtpl:178 +//line app/vmalert/web.qtpl:180 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:179 +//line app/vmalert/web.qtpl:181 qw422016.E().S(ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00")) -//line app/vmalert/web.qtpl:179 +//line app/vmalert/web.qtpl:181 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:180 +//line app/vmalert/web.qtpl:182 qw422016.E().S(ar.Value) -//line app/vmalert/web.qtpl:180 +//line app/vmalert/web.qtpl:182 qw422016.N().S(` Details
`) -//line app/vmalert/web.qtpl:188 +//line app/vmalert/web.qtpl:190 } -//line app/vmalert/web.qtpl:188 +//line app/vmalert/web.qtpl:190 qw422016.N().S(`
`) -//line app/vmalert/web.qtpl:191 +//line app/vmalert/web.qtpl:193 } -//line app/vmalert/web.qtpl:191 +//line app/vmalert/web.qtpl:193 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:193 +//line app/vmalert/web.qtpl:195 } else { -//line app/vmalert/web.qtpl:193 +//line app/vmalert/web.qtpl:195 qw422016.N().S(`

No items...

`) -//line app/vmalert/web.qtpl:197 +//line app/vmalert/web.qtpl:199 } -//line app/vmalert/web.qtpl:197 +//line app/vmalert/web.qtpl:199 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:199 +//line app/vmalert/web.qtpl:201 tpl.StreamFooter(qw422016) -//line app/vmalert/web.qtpl:199 +//line app/vmalert/web.qtpl:201 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 } -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 func WriteListAlerts(qq422016 qtio422016.Writer, groupAlerts []GroupAlerts) { -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 StreamListAlerts(qw422016, groupAlerts) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 qt422016.ReleaseWriter(qw422016) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 } -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 func ListAlerts(groupAlerts []GroupAlerts) string { -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 qb422016 := qt422016.AcquireByteBuffer() -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 WriteListAlerts(qb422016, groupAlerts) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 qs422016 := string(qb422016.B) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 qt422016.ReleaseByteBuffer(qb422016) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 return qs422016 -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:203 } -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:205 func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { -//line app/vmalert/web.qtpl:203 - qw422016.N().S(` - `) -//line app/vmalert/web.qtpl:204 - tpl.StreamHeader(qw422016, "", navItems) -//line app/vmalert/web.qtpl:204 +//line app/vmalert/web.qtpl:205 qw422016.N().S(` `) //line app/vmalert/web.qtpl:206 + tpl.StreamHeader(qw422016, "", navItems) +//line app/vmalert/web.qtpl:206 + qw422016.N().S(` + `) +//line app/vmalert/web.qtpl:208 var labelKeys []string for k := range alert.Labels { labelKeys = append(labelKeys, k) @@ -749,28 +755,28 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { } sort.Strings(annotationKeys) -//line app/vmalert/web.qtpl:217 +//line app/vmalert/web.qtpl:219 qw422016.N().S(`
`) -//line app/vmalert/web.qtpl:218 +//line app/vmalert/web.qtpl:220 qw422016.E().S(alert.Name) -//line app/vmalert/web.qtpl:218 +//line app/vmalert/web.qtpl:220 qw422016.N().S(``) -//line app/vmalert/web.qtpl:218 +//line app/vmalert/web.qtpl:220 qw422016.E().S(alert.State) -//line app/vmalert/web.qtpl:218 +//line app/vmalert/web.qtpl:220 qw422016.N().S(`
@@ -779,9 +785,9 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
`) -//line app/vmalert/web.qtpl:225 +//line app/vmalert/web.qtpl:227 qw422016.E().S(alert.ActiveAt.Format("2006-01-02T15:04:05Z07:00")) -//line app/vmalert/web.qtpl:225 +//line app/vmalert/web.qtpl:227 qw422016.N().S(`
@@ -793,9 +799,9 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
`)
-//line app/vmalert/web.qtpl:235
+//line app/vmalert/web.qtpl:237
 	qw422016.E().S(alert.Expression)
-//line app/vmalert/web.qtpl:235
+//line app/vmalert/web.qtpl:237
 	qw422016.N().S(`
@@ -807,23 +813,23 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
`) -//line app/vmalert/web.qtpl:245 +//line app/vmalert/web.qtpl:247 for _, k := range labelKeys { -//line app/vmalert/web.qtpl:245 +//line app/vmalert/web.qtpl:247 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:246 +//line app/vmalert/web.qtpl:248 qw422016.E().S(k) -//line app/vmalert/web.qtpl:246 +//line app/vmalert/web.qtpl:248 qw422016.N().S(`=`) -//line app/vmalert/web.qtpl:246 +//line app/vmalert/web.qtpl:248 qw422016.E().S(alert.Labels[k]) -//line app/vmalert/web.qtpl:246 +//line app/vmalert/web.qtpl:248 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:247 +//line app/vmalert/web.qtpl:249 } -//line app/vmalert/web.qtpl:247 +//line app/vmalert/web.qtpl:249 qw422016.N().S(`
@@ -835,24 +841,24 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
`) -//line app/vmalert/web.qtpl:257 +//line app/vmalert/web.qtpl:259 for _, k := range annotationKeys { -//line app/vmalert/web.qtpl:257 +//line app/vmalert/web.qtpl:259 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:258 +//line app/vmalert/web.qtpl:260 qw422016.E().S(k) -//line app/vmalert/web.qtpl:258 +//line app/vmalert/web.qtpl:260 qw422016.N().S(`:

`) -//line app/vmalert/web.qtpl:259 +//line app/vmalert/web.qtpl:261 qw422016.E().S(alert.Annotations[k]) -//line app/vmalert/web.qtpl:259 +//line app/vmalert/web.qtpl:261 qw422016.N().S(`

`) -//line app/vmalert/web.qtpl:260 +//line app/vmalert/web.qtpl:262 } -//line app/vmalert/web.qtpl:260 +//line app/vmalert/web.qtpl:262 qw422016.N().S(`
@@ -864,49 +870,63 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
`) -//line app/vmalert/web.qtpl:270 +//line app/vmalert/web.qtpl:272 qw422016.E().S(alert.GroupID) -//line app/vmalert/web.qtpl:270 +//line app/vmalert/web.qtpl:272 qw422016.N().S(`
+ +
+
+
+ Source link +
+
+ Link +
+
`) -//line app/vmalert/web.qtpl:274 +//line app/vmalert/web.qtpl:286 tpl.StreamFooter(qw422016) -//line app/vmalert/web.qtpl:274 +//line app/vmalert/web.qtpl:286 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 } -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 func WriteAlert(qq422016 qtio422016.Writer, alert *APIAlert) { -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 StreamAlert(qw422016, alert) -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 qt422016.ReleaseWriter(qw422016) -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 } -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 func Alert(alert *APIAlert) string { -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 qb422016 := qt422016.AcquireByteBuffer() -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 WriteAlert(qb422016, alert) -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 qs422016 := string(qb422016.B) -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 qt422016.ReleaseByteBuffer(qb422016) -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 return qs422016 -//line app/vmalert/web.qtpl:276 +//line app/vmalert/web.qtpl:288 } diff --git a/app/vmalert/web_types.go b/app/vmalert/web_types.go index 5a4f25362..fa14d3cc1 100644 --- a/app/vmalert/web_types.go +++ b/app/vmalert/web_types.go @@ -17,6 +17,7 @@ type APIAlert struct { Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations"` ActiveAt time.Time `json:"activeAt"` + SourceLink string `json:"source"` } // APIGroup represents Group for WEB view