diff --git a/emails/templates/ng_alert_notification.html b/emails/templates/ng_alert_notification.html
index 7eb44968824..d68c13e9b02 100644
--- a/emails/templates/ng_alert_notification.html
+++ b/emails/templates/ng_alert_notification.html
@@ -1,150 +1,218 @@
[[Subject .Subject "[[.Title]]"]]
+[[ define "alert" ]]
+ [[ if gt (len .Annotations.SortedPairs) 0 ]]
+
+ |
+ [[ range .Annotations.SortedPairs ]]
+ [[ .Name ]]: [[ .Value ]]
+ [[ end ]]
+ |
+
+ [[ end ]]
+
+ | Labels: |
+
+
+ [[ range .Labels.SortedPairs ]]- [[ .Name ]]: [[ .Value ]]
[[ end ]]
+
+ |
+
+
+
+ [[ if .SilenceURL ]]
+
+
+ Silence
+
+ [[ end ]]
+ [[ if .Annotations.runbook_url ]]
+
+
+ View Runbook
+
+ [[ end ]]
+ [[ if .DashboardURL]]
+
+
+ Go to Dashboard
+
+ [[ end ]]
+ [[ if .PanelURL]]
+
+
+ Go to Panel
+
+ [[ end ]]
+ [[ if gt (len .GeneratorURL) 0 ]]Source[[ end ]]
+ |
+
+
+ |
+
+
+
+ |
+
+[[ end ]]
+
+[[ if gt (len .Message) 0 ]]
+ [[ .Message ]]
+[[ else ]]
+
-
-
+
+
+ [[ if gt (len .Alerts.Firing) 0 ]]
-
- [[ if gt (len .Alerts.Firing) 0 ]]
- [[.Title]]
- [[ else ]]
- [[.Title]]
- [[ end ]]
+ |
+ Firing: [[ .Alerts.Firing | len ]] alert[[ if gt (len .Alerts.Firing) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
+ [[ range .GroupLabels.SortedPairs ]]
+ [[ .Name ]]=[[ .Value ]]
+ [[ end ]][[ end ]]
|
-
- |
-
-
-
-
-
- [[ if gt (len .Alerts.Firing) 0 ]]
- |
- [[ .Alerts | len ]] alert[[ if gt (len .Alerts) 1 ]]s[[ end ]] for
- [[ range .GroupLabels.SortedPairs ]]
- [[ .Name ]]=[[ .Value ]]
- [[ end ]]
- |
- [[ else ]]
-
- [[ .Alerts | len ]] alert[[ if gt (len .Alerts) 1 ]]s[[ end ]] for
- [[ range .GroupLabels.SortedPairs ]]
- [[ .Name ]]=[[ .Value ]]
- [[ end ]]
- |
- [[ end ]]
-
-
-
-
-
- [[ if gt (len .Alerts.Firing) 0 ]]
-
-
- ([[ .Alerts.Firing | len ]]) Firing
- |
-
- [[ end ]]
[[ range .Alerts.Firing ]]
-
-
- Labels
- |
-
- [[ if gt (len .Annotations) 0 ]]Annotations[[ end ]]
- |
-
-
-
- [[ range .Labels.SortedPairs ]][[ .Name ]] = [[ .Value ]] [[ end ]]
- Source
- |
-
- [[ range .Annotations.SortedPairs ]][[ .Name ]] = [[ .Value ]] [[ end ]]
- |
-
+
+ |
+ Firing
+ |
+
+ [[ .Labels.alertname ]]
+ |
+
+ [[ template "alert" . ]]
[[ end ]]
-
- [[ if gt (len .Alerts.Resolved) 0 ]]
- [[ if gt (len .Alerts.Firing) 0 ]]
+ [[ end ]]
+ [[ if gt (len .Alerts.Resolved) 0 ]]
-
-
-
-
+ |
+ Resolved: [[ .Alerts.Resolved | len ]] alert[[ if gt (len .Alerts.Resolved) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
+ [[ range .GroupLabels.SortedPairs ]]
+ [[ .Name ]]=[[ .Value ]]
+ [[ end ]][[ end ]]
|
- [[ end ]]
-
-
- ([[ .Alerts.Resolved | len ]]) Resolved
- |
-
- [[ end ]]
[[ range .Alerts.Resolved ]]
-
-
- Labels
- |
-
- [[ if gt (len .Annotations) 0 ]]Annotations[[ end ]]
- |
-
-
-
- [[ range .Labels.SortedPairs ]][[ .Name ]] = [[ .Value ]] [[ end ]]
- Source
- |
-
- [[ range .Annotations.SortedPairs ]][[ .Name ]] = [[ .Value ]] [[ end ]]
- |
-
+
+ |
+ Resolved
+ |
+
+ [[ .Labels.alertname ]]
+ |
+
+ [[ template "alert" . ]]
[[ end ]]
+ [[ end ]]
+
+ |
+ Go to alerts page
+ |
+
+
|
|
-
+
-
-
-
-
-
-
+[[ end ]]
\ No newline at end of file
diff --git a/pkg/services/ngalert/notifier/channels/email.go b/pkg/services/ngalert/notifier/channels/email.go
index 344c6da405b..24ec0de23ac 100644
--- a/pkg/services/ngalert/notifier/channels/email.go
+++ b/pkg/services/ngalert/notifier/channels/email.go
@@ -3,6 +3,7 @@ package channels
import (
"context"
"fmt"
+ "net/url"
"path"
gokit_log "github.com/go-kit/kit/log"
@@ -66,12 +67,25 @@ func NewEmailNotifier(model *NotificationChannelConfig, t *template.Template) (*
// Notify sends the alert notification.
func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
// We only need ExternalURL from this template object. This hack should go away with https://github.com/prometheus/alertmanager/pull/2508.
- data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: en.tmpl.ExternalURL}, as, gokit_log.NewLogfmtLogger(logging.NewWrapper(en.log)))
+ data, err := ExtendData(notify.GetTemplateData(ctx, &template.Template{ExternalURL: en.tmpl.ExternalURL}, as, gokit_log.NewLogfmtLogger(logging.NewWrapper(en.log))))
+ if err != nil {
+ return false, err
+ }
var tmplErr error
- tmpl := notify.TmplText(en.tmpl, data, &tmplErr)
+ tmpl := TmplText(en.tmpl, data, &tmplErr)
title := tmpl(`{{ template "default.title" . }}`)
+ u, err := url.Parse(en.tmpl.ExternalURL.String())
+ if err != nil {
+ return false, fmt.Errorf("failed to parse external URL: %w", err)
+ }
+ basePath := u.Path
+ u.Path = path.Join(basePath, "/alerting/list")
+ ruleURL := u.String()
+ u.RawQuery = "alertState=firing&view=state"
+ alertPageURL := u.String()
+
cmd := &models.SendEmailCommandSync{
SendEmailCommand: models.SendEmailCommand{
Subject: title,
@@ -84,8 +98,8 @@ func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
"CommonLabels": data.CommonLabels,
"CommonAnnotations": data.CommonAnnotations,
"ExternalURL": data.ExternalURL,
- "RuleUrl": path.Join(en.tmpl.ExternalURL.String(), "/alerting/list"),
- "AlertPageUrl": path.Join(en.tmpl.ExternalURL.String(), "/alerting/list?alertState=firing&view=state"),
+ "RuleUrl": ruleURL,
+ "AlertPageUrl": alertPageURL,
},
To: en.Addresses,
SingleEmail: en.SingleEmail,
diff --git a/pkg/services/ngalert/notifier/channels/email_test.go b/pkg/services/ngalert/notifier/channels/email_test.go
index 184af54e18b..907a52283e9 100644
--- a/pkg/services/ngalert/notifier/channels/email_test.go
+++ b/pkg/services/ngalert/notifier/channels/email_test.go
@@ -18,7 +18,7 @@ import (
func TestEmailNotifier(t *testing.T) {
tmpl := templateForTests(t)
- externalURL, err := url.Parse("http://localhost")
+ externalURL, err := url.Parse("http://localhost/base")
require.NoError(t, err)
tmpl.ExternalURL = externalURL
@@ -67,7 +67,7 @@ func TestEmailNotifier(t *testing.T) {
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "AlwaysFiring", "severity": "warning"},
- Annotations: model.LabelSet{"runbook_url": "http://fix.me"},
+ Annotations: model.LabelSet{"runbook_url": "http://fix.me", "__dashboardUid__": "abc", "__panelId__": "5"},
},
},
}
@@ -85,20 +85,23 @@ func TestEmailNotifier(t *testing.T) {
"Title": "[FIRING:1] (AlwaysFiring warning)",
"Message": "[FIRING:1] (AlwaysFiring warning)",
"Status": "firing",
- "Alerts": template.Alerts{
- template.Alert{
- Status: "firing",
- Labels: template.KV{"alertname": "AlwaysFiring", "severity": "warning"},
- Annotations: template.KV{"runbook_url": "http://fix.me"},
- Fingerprint: "15a37193dce72bab",
+ "Alerts": ExtendedAlerts{
+ ExtendedAlert{
+ Status: "firing",
+ Labels: template.KV{"alertname": "AlwaysFiring", "severity": "warning"},
+ Annotations: template.KV{"runbook_url": "http://fix.me"},
+ Fingerprint: "15a37193dce72bab",
+ SilenceURL: "http://localhost/base/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DAlwaysFiring%2Cseverity%3Dwarning",
+ DashboardURL: "http://localhost/base/d/abc",
+ PanelURL: "http://localhost/base/d/abc?viewPanel=5",
},
},
"GroupLabels": template.KV{},
"CommonLabels": template.KV{"alertname": "AlwaysFiring", "severity": "warning"},
"CommonAnnotations": template.KV{"runbook_url": "http://fix.me"},
- "ExternalURL": "http://localhost",
- "RuleUrl": "http:/localhost/alerting/list",
- "AlertPageUrl": "http:/localhost/alerting/list?alertState=firing&view=state",
+ "ExternalURL": "http://localhost/base",
+ "RuleUrl": "http://localhost/base/alerting/list",
+ "AlertPageUrl": "http://localhost/base/alerting/list?alertState=firing&view=state",
},
}, expected)
})
diff --git a/pkg/services/ngalert/notifier/channels/template_data.go b/pkg/services/ngalert/notifier/channels/template_data.go
new file mode 100644
index 00000000000..bb3ace8d64f
--- /dev/null
+++ b/pkg/services/ngalert/notifier/channels/template_data.go
@@ -0,0 +1,153 @@
+package channels
+
+import (
+ "fmt"
+ "net/url"
+ "path"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/prometheus/alertmanager/template"
+ "github.com/prometheus/common/model"
+)
+
+type ExtendedAlert struct {
+ Status string `json:"status"`
+ Labels template.KV `json:"labels"`
+ Annotations template.KV `json:"annotations"`
+ StartsAt time.Time `json:"startsAt"`
+ EndsAt time.Time `json:"endsAt"`
+ GeneratorURL string `json:"generatorURL"`
+ Fingerprint string `json:"fingerprint"`
+ SilenceURL string `json:"silenceURL"`
+ DashboardURL string `json:"dashboardURL"`
+ PanelURL string `json:"panelURL"`
+}
+
+type ExtendedAlerts []ExtendedAlert
+
+type ExtendedData struct {
+ Receiver string `json:"receiver"`
+ Status string `json:"status"`
+ Alerts ExtendedAlerts `json:"alerts"`
+
+ GroupLabels template.KV `json:"groupLabels"`
+ CommonLabels template.KV `json:"commonLabels"`
+ CommonAnnotations template.KV `json:"commonAnnotations"`
+
+ ExternalURL string `json:"externalURL"`
+}
+
+func removePrivateItems(kv template.KV) template.KV {
+ for key := range kv {
+ if strings.HasPrefix(key, "__") && strings.HasSuffix(key, "__") {
+ kv = kv.Remove([]string{key})
+ }
+ }
+ return kv
+}
+
+func extendAlert(alert template.Alert, externalURL string) (*ExtendedAlert, error) {
+ extended := ExtendedAlert{
+ Status: alert.Status,
+ Labels: alert.Labels,
+ Annotations: alert.Annotations,
+ StartsAt: alert.StartsAt,
+ EndsAt: alert.EndsAt,
+ GeneratorURL: alert.GeneratorURL,
+ Fingerprint: alert.Fingerprint,
+ }
+
+ // fill in some grafana-specific urls
+ if len(externalURL) > 0 {
+ u, err := url.Parse(externalURL)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse external URL: %w", err)
+ }
+ externalPath := u.Path
+ dashboardUid := alert.Annotations["__dashboardUid__"]
+ if len(dashboardUid) > 0 {
+ u.Path = path.Join(externalPath, "/d/", dashboardUid)
+ extended.DashboardURL = u.String()
+ panelId := alert.Annotations["__panelId__"]
+ if len(panelId) > 0 {
+ u.RawQuery = "viewPanel=" + panelId
+ extended.PanelURL = u.String()
+ }
+ }
+
+ matchers := make([]string, 0)
+ for key, value := range alert.Labels {
+ if !(strings.HasPrefix(key, "__") && strings.HasSuffix(key, "__")) {
+ matchers = append(matchers, key+"="+value)
+ }
+ }
+ sort.Strings(matchers)
+ u.Path = path.Join(externalPath, "/alerting/silence/new")
+ u.RawQuery = "alertmanager=grafana&matchers=" + url.QueryEscape(strings.Join(matchers, ","))
+ extended.SilenceURL = u.String()
+ }
+
+ // remove "private" annotations & labels so they don't show up in the template
+ extended.Annotations = removePrivateItems(extended.Annotations)
+ extended.Labels = removePrivateItems(extended.Labels)
+
+ return &extended, nil
+}
+
+func ExtendData(data *template.Data) (*ExtendedData, error) {
+ alerts := []ExtendedAlert{}
+
+ for _, alert := range data.Alerts {
+ extendedAlert, err := extendAlert(alert, data.ExternalURL)
+ if err != nil {
+ return nil, err
+ }
+ alerts = append(alerts, *extendedAlert)
+ }
+
+ extended := &ExtendedData{
+ Receiver: data.Receiver,
+ Status: data.Status,
+ Alerts: alerts,
+ GroupLabels: data.GroupLabels,
+ CommonLabels: removePrivateItems(data.CommonLabels),
+ CommonAnnotations: removePrivateItems(data.CommonAnnotations),
+
+ ExternalURL: data.ExternalURL,
+ }
+ return extended, nil
+}
+
+func TmplText(tmpl *template.Template, data *ExtendedData, err *error) func(string) string {
+ return func(name string) (s string) {
+ if *err != nil {
+ return
+ }
+ s, *err = tmpl.ExecuteTextString(name, data)
+ return s
+ }
+}
+
+// Firing returns the subset of alerts that are firing.
+func (as ExtendedAlerts) Firing() []ExtendedAlert {
+ res := []ExtendedAlert{}
+ for _, a := range as {
+ if a.Status == string(model.AlertFiring) {
+ res = append(res, a)
+ }
+ }
+ return res
+}
+
+// Resolved returns the subset of alerts that are resolved.
+func (as ExtendedAlerts) Resolved() []ExtendedAlert {
+ res := []ExtendedAlert{}
+ for _, a := range as {
+ if a.Status == string(model.AlertResolved) {
+ res = append(res, a)
+ }
+ }
+ return res
+}
diff --git a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
index 4101304ce0d..ea1b1f51a83 100644
--- a/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
+++ b/public/app/features/alerting/unified/components/silences/SilencesEditor.tsx
@@ -1,5 +1,5 @@
import { Silence, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types';
-import React, { FC, useState } from 'react';
+import React, { FC, useMemo, useState } from 'react';
import { Button, Field, FieldSet, Input, LinkButton, TextArea, useStyles } from '@grafana/ui';
import {
DefaultTimeZone,
@@ -9,6 +9,7 @@ import {
addDurationToDate,
dateTime,
isValidDate,
+ UrlQueryMap,
} from '@grafana/data';
import { useDebounce } from 'react-use';
import { config } from '@grafana/runtime';
@@ -23,13 +24,34 @@ import { css, cx } from '@emotion/css';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import { makeAMLink } from '../../utils/misc';
import { useCleanup } from 'app/core/hooks/useCleanup';
+import { useQueryParams } from 'app/core/hooks/useQueryParams';
+import { parseQueryParamMatchers } from '../../utils/matchers';
interface Props {
silence?: Silence;
alertManagerSourceName: string;
}
-const getDefaultFormValues = (silence?: Silence): SilenceFormFields => {
+const defaultsFromQuery = (queryParams: UrlQueryMap): Partial => {
+ const defaults: Partial = {};
+
+ const { matchers, comment } = queryParams;
+
+ if (typeof matchers === 'string') {
+ const formMatchers = parseQueryParamMatchers(matchers);
+ if (formMatchers.length) {
+ defaults.matchers = formMatchers;
+ }
+ }
+
+ if (typeof comment === 'string') {
+ defaults.comment = comment;
+ }
+
+ return defaults;
+};
+
+const getDefaultFormValues = (queryParams: UrlQueryMap, silence?: Silence): SilenceFormFields => {
const now = new Date();
if (silence) {
const isExpired = Date.parse(silence.endsAt) < Date.now();
@@ -66,12 +88,15 @@ const getDefaultFormValues = (silence?: Silence): SilenceFormFields => {
matcherName: '',
matcherValue: '',
timeZone: DefaultTimeZone,
+ ...defaultsFromQuery(queryParams),
};
}
};
export const SilencesEditor: FC = ({ silence, alertManagerSourceName }) => {
- const formAPI = useForm({ defaultValues: getDefaultFormValues(silence) });
+ const [queryParams] = useQueryParams();
+ const defaultValues = useMemo(() => getDefaultFormValues(queryParams, silence), [silence, queryParams]);
+ const formAPI = useForm({ defaultValues });
const dispatch = useDispatch();
const styles = useStyles(getStyles);
diff --git a/public/app/features/alerting/unified/utils/matchers.ts b/public/app/features/alerting/unified/utils/matchers.ts
new file mode 100644
index 00000000000..d740569620e
--- /dev/null
+++ b/public/app/features/alerting/unified/utils/matchers.ts
@@ -0,0 +1,10 @@
+import { Matcher } from 'app/plugins/datasource/alertmanager/types';
+import { parseMatcher } from './alertmanager';
+
+// parses comma separated matchers like "foo=bar,baz=~bad*" into SilenceMatcher[]
+export function parseQueryParamMatchers(paramValue: string): Matcher[] {
+ return paramValue
+ .split(',')
+ .filter((x) => !!x.trim())
+ .map((x) => parseMatcher(x.trim()));
+}
diff --git a/public/emails/ng_alert_notification.html b/public/emails/ng_alert_notification.html
index 06082c71b02..1eaae4fef27 100644
--- a/public/emails/ng_alert_notification.html
+++ b/public/emails/ng_alert_notification.html
@@ -92,21 +92,12 @@ text-decoration: underline;
table[class="body"] .columns {
table-layout: fixed !important; float: none !important; width: 100% !important; padding-right: 0px !important; padding-left: 0px !important; display: block !important;
}
- table[class="body"] .column {
- table-layout: fixed !important; float: none !important; width: 100% !important; padding-right: 0px !important; padding-left: 0px !important; display: block !important;
- }
table[class="body"] table.columns td {
width: 100% !important;
}
- table[class="body"] .columns td.four {
- width: 33.333333% !important;
- }
table[class="body"] .columns td.six {
width: 50% !important;
}
- table[class="body"] .columns td.eight {
- width: 66.666666% !important;
- }
table[class="body"] .columns td.twelve {
width: 100% !important;
}
@@ -215,150 +206,127 @@ text-decoration: underline;
{{Subject .Subject "{{.Title}}"}}
+{{ define "alert" }}
+ {{ if gt (len .Annotations.SortedPairs) 0 }}
+ |
+ |
+ {{ range .Annotations.SortedPairs }}
+ {{ .Name }}: {{ .Value }}
+ {{ end }}
+ |
+
+ {{ end }}
+
+ | Labels: |
+
+
+ {{ range .Labels.SortedPairs }}- {{ .Name }}: {{ .Value }}
{{ end }}
+
+ |
+
+
+
+ {{ if .SilenceURL }}
+
+
+ Silence
+
+ {{ end }}
+ {{ if .Annotations.runbook_url }}
+
+
+ View Runbook
+
+ {{ end }}
+ {{ if .DashboardURL}}
+
+
+ Go to Dashboard
+
+ {{ end }}
+ {{ if .PanelURL}}
+
+
+ Go to Panel
+
+ {{ end }}
+ {{ if gt (len .GeneratorURL) 0 }}Source{{ end }}
+ |
+
+
+ |
+
+
+
+ |
+
+{{ end }}
+
+{{ if gt (len .Message) 0 }}
+ {{ .Message }}
+{{ else }}
+
-
-
+
+
+ {{ if gt (len .Alerts.Firing) 0 }}
-
- {{ if gt (len .Alerts.Firing) 0 }}
- {{.Title}}
- {{ else }}
- {{.Title}}
- {{ end }}
+ |
+ Firing: {{ .Alerts.Firing | len }} alert{{ if gt (len .Alerts.Firing) 1 }}s{{ end }}{{ if gt (len .GroupLabels.SortedPairs) 1 }} for
+ {{ range .GroupLabels.SortedPairs }}
+ {{ .Name }}={{ .Value }}
+ {{ end }}{{ end }}
|
-
- |
-
-
-
-
-
- {{ if gt (len .Alerts.Firing) 0 }}
- |
- {{ .Alerts | len }} alert{{ if gt (len .Alerts) 1 }}s{{ end }} for
- {{ range .GroupLabels.SortedPairs }}
- {{ .Name }}={{ .Value }}
- {{ end }}
- |
- {{ else }}
-
- {{ .Alerts | len }} alert{{ if gt (len .Alerts) 1 }}s{{ end }} for
- {{ range .GroupLabels.SortedPairs }}
- {{ .Name }}={{ .Value }}
- {{ end }}
- |
- {{ end }}
-
-
-
-
-
- {{ if gt (len .Alerts.Firing) 0 }}
-
-
- ({{ .Alerts.Firing | len }}) Firing
- |
-
- {{ end }}
{{ range .Alerts.Firing }}
-
-
- Labels
- |
-
- {{ if gt (len .Annotations) 0 }}Annotations{{ end }}
- |
-
-
-
- {{ range .Labels.SortedPairs }}{{ .Name }} = {{ .Value }} {{ end }}
- Source
- |
-
- {{ range .Annotations.SortedPairs }}{{ .Name }} = {{ .Value }} {{ end }}
- |
-
+
+ |
+ Firing
+ |
+
+ {{ .Labels.alertname }}
+ |
+
+ {{ template "alert" . }}
{{ end }}
-
- {{ if gt (len .Alerts.Resolved) 0 }}
- {{ if gt (len .Alerts.Firing) 0 }}
+ {{ end }}
+ {{ if gt (len .Alerts.Resolved) 0 }}
-
-
-
-
+ |
+ Resolved: {{ .Alerts.Resolved | len }} alert{{ if gt (len .Alerts.Resolved) 1 }}s{{ end }}{{ if gt (len .GroupLabels.SortedPairs) 1 }} for
+ {{ range .GroupLabels.SortedPairs }}
+ {{ .Name }}={{ .Value }}
+ {{ end }}{{ end }}
|
- {{ end }}
-
-
- ({{ .Alerts.Resolved | len }}) Resolved
- |
-
- {{ end }}
{{ range .Alerts.Resolved }}
-
-
- Labels
- |
-
- {{ if gt (len .Annotations) 0 }}Annotations{{ end }}
- |
-
-
-
- {{ range .Labels.SortedPairs }}{{ .Name }} = {{ .Value }} {{ end }}
- Source
- |
-
- {{ range .Annotations.SortedPairs }}{{ .Name }} = {{ .Value }} {{ end }}
- |
-
+
+ |
+ Resolved
+ |
+
+ {{ .Labels.alertname }}
+ |
+
+ {{ template "alert" . }}
{{ end }}
+ {{ end }}
+
+ |
+ Go to alerts page
+ |
+
- |
-
+
+
+
|
-
-
-
-
-
-
-
+{{ end }}
-
+
@@ -367,7 +335,7 @@ text-decoration: underline;
-
- |
-
-
+
+
+
+