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.
This commit is contained in:
Roman Khavronenko 2021-10-13 15:25:11 +03:00 committed by Aliaksandr Valialkin
parent 9568961054
commit 14995eece6
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
5 changed files with 164 additions and 128 deletions

View file

@ -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 (

View file

@ -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,

View file

@ -155,7 +155,9 @@
sort.Strings(labelKeys)
%}
<br>
<b>alert:</b> {%s defaultAR.Name %} ({%d len(alertsByRule[ruleID]) %})<br>
<b>alert:</b> {%s defaultAR.Name %} ({%d len(alertsByRule[ruleID]) %})
| <span><a target="_blank" href="{%s defaultAR.SourceLink %}">Source</a></span>
<br>
<b>expr:</b><code><pre>{%s defaultAR.Expression %}</pre></code>
<table class="table table-striped table-hover table-sm">
<thead>
@ -270,6 +272,16 @@
<a target="_blank" href="/groups#group-{%s alert.GroupID %}">{%s alert.GroupID %}</a>
</div>
</div>
</div>
<div class="container border-bottom p-2">
<div class="row">
<div class="col-2">
Source link
</div>
<div class="col">
<a target="_blank" href="{%s alert.SourceLink %}">Link</a>
</div>
</div>
</div>
{%= tpl.Footer() %}

View file

@ -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(`)<br>
qw422016.N().S(`)
| <span><a target="_blank" href="`)
//line app/vmalert/web.qtpl:159
qw422016.E().S(defaultAR.SourceLink)
//line app/vmalert/web.qtpl:159
qw422016.N().S(`">Source</a></span>
<br>
<b>expr:</b><code><pre>`)
//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(`</pre></code>
<table class="table table-striped table-hover table-sm">
<thead>
@ -591,152 +597,152 @@ func StreamListAlerts(qw422016 *qt422016.Writer, groupAlerts []GroupAlerts) {
</thead>
<tbody>
`)
//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(`
<tr>
<td>
`)
//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(`
<span class="ms-1 badge bg-primary">`)
//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(`</span>
`)
//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(`
</td>
<td><span class="badge `)
//line app/vmalert/web.qtpl:178
//line app/vmalert/web.qtpl:180
if ar.State == "firing" {
//line app/vmalert/web.qtpl:178
//line app/vmalert/web.qtpl:180
qw422016.N().S(`bg-danger`)
//line app/vmalert/web.qtpl:178
//line app/vmalert/web.qtpl:180
} else {
//line app/vmalert/web.qtpl:178
//line app/vmalert/web.qtpl:180
qw422016.N().S(` bg-warning text-dark`)
//line app/vmalert/web.qtpl:178
//line app/vmalert/web.qtpl:180
}
//line app/vmalert/web.qtpl:178
//line app/vmalert/web.qtpl:180
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(`</span></td>
<td>`)
//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(`</td>
<td>`)
//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(`</td>
<td>
<a href="/`)
//line app/vmalert/web.qtpl:182
//line app/vmalert/web.qtpl:184
qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:182
//line app/vmalert/web.qtpl:184
qw422016.N().S(`/`)
//line app/vmalert/web.qtpl:182
//line app/vmalert/web.qtpl:184
qw422016.E().S(ar.ID)
//line app/vmalert/web.qtpl:182
//line app/vmalert/web.qtpl:184
qw422016.N().S(`/status">Details</a>
</td>
</tr>
`)
//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(`
</tbody>
</table>
`)
//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(`
</div>
<br>
`)
//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(`
<div>
<p>No items...</p>
</div>
`)
//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(`
<div class="display-6 pb-3 mb-3">`)
//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(`<span class="ms-2 badge `)
//line app/vmalert/web.qtpl:218
//line app/vmalert/web.qtpl:220
if alert.State == "firing" {
//line app/vmalert/web.qtpl:218
//line app/vmalert/web.qtpl:220
qw422016.N().S(`bg-danger`)
//line app/vmalert/web.qtpl:218
//line app/vmalert/web.qtpl:220
} else {
//line app/vmalert/web.qtpl:218
//line app/vmalert/web.qtpl:220
qw422016.N().S(` bg-warning text-dark`)
//line app/vmalert/web.qtpl:218
//line app/vmalert/web.qtpl:220
}
//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(`</span></div>
<div class="container border-bottom p-2">
<div class="row">
@ -779,9 +785,9 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
</div>
<div class="col">
`)
//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(`
</div>
</div>
@ -793,9 +799,9 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
</div>
<div class="col">
<code><pre>`)
//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(`</pre></code>
</div>
</div>
@ -807,23 +813,23 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
</div>
<div class="col">
`)
//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(`
<span class="m-1 badge bg-primary">`)
//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(`</span>
`)
//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(`
</div>
</div>
@ -835,24 +841,24 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
</div>
<div class="col">
`)
//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(`
<b>`)
//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(`:</b><br>
<p>`)
//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(`</p>
`)
//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(`
</div>
</div>
@ -864,49 +870,63 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) {
</div>
<div class="col">
<a target="_blank" href="/groups#group-`)
//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(`">`)
//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(`</a>
</div>
</div>
</div>
<div class="container border-bottom p-2">
<div class="row">
<div class="col-2">
Source link
</div>
<div class="col">
<a target="_blank" href="`)
//line app/vmalert/web.qtpl:282
qw422016.E().S(alert.SourceLink)
//line app/vmalert/web.qtpl:282
qw422016.N().S(`">Link</a>
</div>
</div>
</div>
`)
//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
}

View file

@ -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