diff --git a/app/vmalert/alerting.go b/app/vmalert/alerting.go index 671750ecd..d654246c8 100644 --- a/app/vmalert/alerting.go +++ b/app/vmalert/alerting.go @@ -163,7 +163,13 @@ func (ar *AlertingRule) ExecRange(ctx context.Context, start, end time.Time) ([] // so the hash key will be consistent on restore s.SetLabel(k, v) } - + // set additional labels to identify group and rule name + if ar.Name != "" { + s.SetLabel(alertNameLabel, ar.Name) + } + if !*disableAlertGroupLabel && ar.GroupName != "" { + s.SetLabel(alertGroupNameLabel, ar.GroupName) + } a, err := ar.newAlert(s, time.Time{}, qFn) // initial alert if err != nil { return nil, fmt.Errorf("failed to create alert: %s", err) @@ -178,13 +184,11 @@ func (ar *AlertingRule) ExecRange(ctx context.Context, start, end time.Time) ([] // if alert with For > 0 prevT := time.Time{} - //activeAt := time.Time{} for i := range s.Values { at := time.Unix(s.Timestamps[i], 0) if at.Sub(prevT) > ar.EvalInterval { // reset to Pending if there are gaps > EvalInterval between DPs a.State = notifier.StatePending - //activeAt = at a.Start = at } else if at.Sub(a.Start) >= ar.For { a.State = notifier.StateFiring @@ -231,6 +235,14 @@ func (ar *AlertingRule) Exec(ctx context.Context) ([]prompbmarshal.TimeSeries, e // so the hash key will be consistent on restore m.SetLabel(k, v) } + // set additional labels to identify group and rule name + // set additional labels to identify group and rule name + if ar.Name != "" { + m.SetLabel(alertNameLabel, ar.Name) + } + if !*disableAlertGroupLabel && ar.GroupName != "" { + m.SetLabel(alertGroupNameLabel, ar.GroupName) + } h := hash(m) if _, ok := updated[h]; ok { // duplicate may be caused by extra labels @@ -352,11 +364,6 @@ func (ar *AlertingRule) newAlert(m datasource.Metric, start time.Time, qFn notif Start: start, Expr: ar.Expr, } - // label defined here to make override possible by - // time series labels. - if !*disableAlertGroupLabel && ar.GroupName != "" { - a.Labels[alertGroupNameLabel] = ar.GroupName - } for _, l := range m.Labels { // drop __name__ to be consistent with Prometheus alerting if l.Name == "__name__" { @@ -427,6 +434,7 @@ func (ar *AlertingRule) newAlertAPI(a notifier.Alert) *APIAlert { Annotations: a.Annotations, State: a.State.String(), ActiveAt: a.Start, + Restored: a.Restored, Value: strconv.FormatFloat(a.Value, 'f', -1, 32), } if alertURLGeneratorFn != nil { @@ -447,43 +455,42 @@ const ( alertStateLabel = "alertstate" // alertGroupNameLabel defines the label name attached for generated time series. + // attaching this label may be disabled via `-disableAlertgroupLabel` flag. alertGroupNameLabel = "alertgroup" ) // alertToTimeSeries converts the given alert with the given timestamp to timeseries func (ar *AlertingRule) alertToTimeSeries(a *notifier.Alert, timestamp int64) []prompbmarshal.TimeSeries { var tss []prompbmarshal.TimeSeries - tss = append(tss, alertToTimeSeries(ar.Name, a, timestamp)) + tss = append(tss, alertToTimeSeries(a, timestamp)) if ar.For > 0 { - tss = append(tss, alertForToTimeSeries(ar.Name, a, timestamp)) + tss = append(tss, alertForToTimeSeries(a, timestamp)) } return tss } -func alertToTimeSeries(name string, a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries { +func alertToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries { labels := make(map[string]string) for k, v := range a.Labels { labels[k] = v } labels["__name__"] = alertMetricName - labels[alertNameLabel] = name labels[alertStateLabel] = a.State.String() return newTimeSeries([]float64{1}, []int64{timestamp}, labels) } // alertForToTimeSeries returns a timeseries that represents // state of active alerts, where value is time when alert become active -func alertForToTimeSeries(name string, a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries { +func alertForToTimeSeries(a *notifier.Alert, timestamp int64) prompbmarshal.TimeSeries { labels := make(map[string]string) for k, v := range a.Labels { labels[k] = v } labels["__name__"] = alertForStateMetricName - labels[alertNameLabel] = name return newTimeSeries([]float64{float64(a.Start.Unix())}, []int64{timestamp}, labels) } -// Restore restores the state of active alerts basing on previously written timeseries. +// Restore restores the state of active alerts basing on previously written time series. // Restore restores only Start field. Field State will be always Pending and supposed // to be updated on next Exec, as well as Value field. // Only rules with For > 0 will be restored. @@ -511,23 +518,13 @@ func (ar *AlertingRule) Restore(ctx context.Context, q datasource.Querier, lookb } for _, m := range qMetrics { - labels := m.Labels - m.Labels = make([]datasource.Label, 0) - // drop all extra labels, so hash key will - // be identical to time series received in Exec - for _, l := range labels { - if l.Name == alertNameLabel || l.Name == alertGroupNameLabel { - continue - } - m.Labels = append(m.Labels, l) - } - a, err := ar.newAlert(m, time.Unix(int64(m.Values[0]), 0), qFn) if err != nil { return fmt.Errorf("failed to create alert: %w", err) } a.ID = hash(m) a.State = notifier.StatePending + a.Restored = true ar.alerts[a.ID] = a logger.Infof("alert %q (%d) restored to state at %v", a.Name, a.ID, a.Start) } diff --git a/app/vmalert/alerting_test.go b/app/vmalert/alerting_test.go index 0be0c055a..cff88f53b 100644 --- a/app/vmalert/alerting_test.go +++ b/app/vmalert/alerting_test.go @@ -27,7 +27,6 @@ func TestAlertingRule_ToTimeSeries(t *testing.T) { newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{ "__name__": alertMetricName, alertStateLabel: notifier.StateFiring.String(), - alertNameLabel: "instant", }), }, }, @@ -41,7 +40,6 @@ func TestAlertingRule_ToTimeSeries(t *testing.T) { newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{ "__name__": alertMetricName, alertStateLabel: notifier.StateFiring.String(), - alertNameLabel: "instant extra labels", "job": "foo", "instance": "bar", }), @@ -57,7 +55,6 @@ func TestAlertingRule_ToTimeSeries(t *testing.T) { newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{ "__name__": alertMetricName, alertStateLabel: notifier.StateFiring.String(), - alertNameLabel: "instant labels override", }), }, }, @@ -68,13 +65,11 @@ func TestAlertingRule_ToTimeSeries(t *testing.T) { newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{ "__name__": alertMetricName, alertStateLabel: notifier.StateFiring.String(), - alertNameLabel: "for", }), newTimeSeries([]float64{float64(timestamp.Add(time.Second).Unix())}, []int64{timestamp.UnixNano()}, map[string]string{ - "__name__": alertForStateMetricName, - alertNameLabel: "for", + "__name__": alertForStateMetricName, }), }, }, @@ -85,13 +80,11 @@ func TestAlertingRule_ToTimeSeries(t *testing.T) { newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{ "__name__": alertMetricName, alertStateLabel: notifier.StatePending.String(), - alertNameLabel: "for pending", }), newTimeSeries([]float64{float64(timestamp.Add(time.Second).Unix())}, []int64{timestamp.UnixNano()}, map[string]string{ - "__name__": alertForStateMetricName, - alertNameLabel: "for pending", + "__name__": alertForStateMetricName, }), }, }, @@ -109,23 +102,27 @@ func TestAlertingRule_ToTimeSeries(t *testing.T) { func TestAlertingRule_Exec(t *testing.T) { const defaultStep = 5 * time.Millisecond + type testAlert struct { + labels []string + alert *notifier.Alert + } testCases := []struct { rule *AlertingRule steps [][]datasource.Metric - expAlerts map[uint64]*notifier.Alert + expAlerts []testAlert }{ { newTestAlertingRule("empty", 0), [][]datasource.Metric{}, - map[uint64]*notifier.Alert{}, + nil, }, { newTestAlertingRule("empty labels", 0), [][]datasource.Metric{ {datasource.Metric{Values: []float64{1}, Timestamps: []int64{1}}}, }, - map[uint64]*notifier.Alert{ - hash(datasource.Metric{}): {State: notifier.StateFiring}, + []testAlert{ + {alert: ¬ifier.Alert{State: notifier.StateFiring}}, }, }, { @@ -133,8 +130,8 @@ func TestAlertingRule_Exec(t *testing.T) { [][]datasource.Metric{ {metricWithLabels(t, "name", "foo")}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateFiring}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, }, }, { @@ -143,8 +140,8 @@ func TestAlertingRule_Exec(t *testing.T) { {metricWithLabels(t, "name", "foo")}, {}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateInactive}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}, }, }, { @@ -154,8 +151,8 @@ func TestAlertingRule_Exec(t *testing.T) { {}, {metricWithLabels(t, "name", "foo")}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateFiring}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, }, }, { @@ -166,8 +163,8 @@ func TestAlertingRule_Exec(t *testing.T) { {metricWithLabels(t, "name", "foo")}, {}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateInactive}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}, }, }, { @@ -179,7 +176,7 @@ func TestAlertingRule_Exec(t *testing.T) { {}, {}, }, - map[uint64]*notifier.Alert{}, + nil, }, { newTestAlertingRule("single-firing=>inactive=>firing=>inactive=>empty=>firing", 0), @@ -191,8 +188,8 @@ func TestAlertingRule_Exec(t *testing.T) { {}, {metricWithLabels(t, "name", "foo")}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateFiring}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, }, }, { @@ -204,10 +201,10 @@ func TestAlertingRule_Exec(t *testing.T) { metricWithLabels(t, "name", "foo2"), }, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateFiring}, - hash(metricWithLabels(t, "name", "foo1")): {State: notifier.StateFiring}, - hash(metricWithLabels(t, "name", "foo2")): {State: notifier.StateFiring}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, + {labels: []string{"name", "foo1"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, + {labels: []string{"name", "foo2"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, }, }, { @@ -220,9 +217,9 @@ func TestAlertingRule_Exec(t *testing.T) { // 1: fire first alert // 2: fire second alert, set first inactive // 3: fire third alert, set second inactive, delete first one - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo1")): {State: notifier.StateInactive}, - hash(metricWithLabels(t, "name", "foo2")): {State: notifier.StateFiring}, + []testAlert{ + {labels: []string{"name", "foo1"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}, + {labels: []string{"name", "foo2"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, }, }, { @@ -230,8 +227,8 @@ func TestAlertingRule_Exec(t *testing.T) { [][]datasource.Metric{ {metricWithLabels(t, "name", "foo")}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StatePending}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StatePending}}, }, }, { @@ -240,8 +237,8 @@ func TestAlertingRule_Exec(t *testing.T) { {metricWithLabels(t, "name", "foo")}, {metricWithLabels(t, "name", "foo")}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateFiring}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, }, }, { @@ -252,7 +249,7 @@ func TestAlertingRule_Exec(t *testing.T) { // empty step to reset and delete pending alerts {}, }, - map[uint64]*notifier.Alert{}, + nil, }, { newTestAlertingRule("for-pending=>firing=>inactive", defaultStep), @@ -262,8 +259,8 @@ func TestAlertingRule_Exec(t *testing.T) { // empty step to reset pending alerts {}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateInactive}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateInactive}}, }, }, { @@ -275,8 +272,8 @@ func TestAlertingRule_Exec(t *testing.T) { {}, {metricWithLabels(t, "name", "foo")}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StatePending}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StatePending}}, }, }, { @@ -289,8 +286,8 @@ func TestAlertingRule_Exec(t *testing.T) { {metricWithLabels(t, "name", "foo")}, {metricWithLabels(t, "name", "foo")}, }, - map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "name", "foo")): {State: notifier.StateFiring}, + []testAlert{ + {labels: []string{"name", "foo"}, alert: ¬ifier.Alert{State: notifier.StateFiring}}, }, }, } @@ -312,7 +309,15 @@ func TestAlertingRule_Exec(t *testing.T) { if len(tc.rule.alerts) != len(tc.expAlerts) { t.Fatalf("expected %d alerts; got %d", len(tc.expAlerts), len(tc.rule.alerts)) } - for key, exp := range tc.expAlerts { + expAlerts := make(map[uint64]*notifier.Alert) + for _, ta := range tc.expAlerts { + labels := ta.labels + labels = append(labels, alertNameLabel) + labels = append(labels, tc.rule.Name) + h := hash(metricWithLabels(t, labels...)) + expAlerts[h] = ta.alert + } + for key, exp := range expAlerts { got, ok := tc.rule.alerts[key] if !ok { t.Fatalf("expected to have key %d", key) @@ -468,6 +473,11 @@ func TestAlertingRule_ExecRange(t *testing.T) { var j int for _, series := range tc.data { for _, timestamp := range series.Timestamps { + a := tc.expAlerts[j] + if a.Labels == nil { + a.Labels = make(map[string]string) + } + a.Labels[alertNameLabel] = tc.rule.Name expTS = append(expTS, tc.rule.alertToTimeSeries(tc.expAlerts[j], timestamp)...) j++ } @@ -496,7 +506,6 @@ func TestAlertingRule_Restore(t *testing.T) { []datasource.Metric{ metricWithValueAndLabels(t, float64(time.Now().Truncate(time.Hour).Unix()), "__name__", alertForStateMetricName, - alertNameLabel, "", ), }, map[uint64]*notifier.Alert{ @@ -509,7 +518,7 @@ func TestAlertingRule_Restore(t *testing.T) { []datasource.Metric{ metricWithValueAndLabels(t, float64(time.Now().Truncate(time.Hour).Unix()), "__name__", alertForStateMetricName, - alertNameLabel, "", + alertNameLabel, "metric labels", alertGroupNameLabel, "groupID", "foo", "bar", "namespace", "baz", @@ -517,6 +526,8 @@ func TestAlertingRule_Restore(t *testing.T) { }, map[uint64]*notifier.Alert{ hash(metricWithLabels(t, + alertNameLabel, "metric labels", + alertGroupNameLabel, "groupID", "foo", "bar", "namespace", "baz", )): {State: notifier.StatePending, @@ -528,7 +539,6 @@ func TestAlertingRule_Restore(t *testing.T) { []datasource.Metric{ metricWithValueAndLabels(t, float64(time.Now().Truncate(time.Hour).Unix()), "__name__", alertForStateMetricName, - alertNameLabel, "", "foo", "bar", "namespace", "baz", // extra labels set by rule @@ -645,18 +655,20 @@ func TestAlertingRule_Template(t *testing.T) { metricWithValueAndLabels(t, 1, "instance", "bar"), }, map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "region", "east", "instance", "foo")): { + hash(metricWithLabels(t, alertNameLabel, "common", "region", "east", "instance", "foo")): { Annotations: map[string]string{}, Labels: map[string]string{ - "region": "east", - "instance": "foo", + alertNameLabel: "common", + "region": "east", + "instance": "foo", }, }, - hash(metricWithLabels(t, "region", "east", "instance", "bar")): { + hash(metricWithLabels(t, alertNameLabel, "common", "region", "east", "instance", "bar")): { Annotations: map[string]string{}, Labels: map[string]string{ - "region": "east", - "instance": "bar", + alertNameLabel: "common", + "region": "east", + "instance": "bar", }, }, }, @@ -679,20 +691,22 @@ func TestAlertingRule_Template(t *testing.T) { metricWithValueAndLabels(t, 10, "instance", "bar"), }, map[uint64]*notifier.Alert{ - hash(metricWithLabels(t, "region", "east", "instance", "foo")): { + hash(metricWithLabels(t, alertNameLabel, "override label", "region", "east", "instance", "foo")): { Labels: map[string]string{ - "instance": "foo", - "region": "east", + alertNameLabel: "override label", + "instance": "foo", + "region": "east", }, Annotations: map[string]string{ "summary": `Too high connection number for "foo" for region east`, "description": `It is 2 connections for "foo"`, }, }, - hash(metricWithLabels(t, "region", "east", "instance", "bar")): { + hash(metricWithLabels(t, alertNameLabel, "override label", "region", "east", "instance", "bar")): { Labels: map[string]string{ - "instance": "bar", - "region": "east", + alertNameLabel: "override label", + "instance": "bar", + "region": "east", }, Annotations: map[string]string{ "summary": `Too high connection number for "bar" for region east`, diff --git a/app/vmalert/group_test.go b/app/vmalert/group_test.go index 629f705d8..3910f4170 100644 --- a/app/vmalert/group_test.go +++ b/app/vmalert/group_test.go @@ -192,7 +192,14 @@ func TestGroupStart(t *testing.T) { // add rule labels - see config/testdata/rules1-good.rules alert1.Labels["label"] = "bar" alert1.Labels["host"] = inst1 - alert1.ID = hash(m1) + // add service labels + alert1.Labels[alertNameLabel] = alert1.Name + alert1.Labels[alertGroupNameLabel] = g.Name + var labels1 []string + for k, v := range alert1.Labels { + labels1 = append(labels1, k, v) + } + alert1.ID = hash(metricWithLabels(t, labels1...)) alert2, err := r.newAlert(m2, time.Now(), nil) if err != nil { @@ -204,7 +211,14 @@ func TestGroupStart(t *testing.T) { // add rule labels - see config/testdata/rules1-good.rules alert2.Labels["label"] = "bar" alert2.Labels["host"] = inst2 - alert2.ID = hash(m2) + // add service labels + alert2.Labels[alertNameLabel] = alert2.Name + alert2.Labels[alertGroupNameLabel] = g.Name + var labels2 []string + for k, v := range alert2.Labels { + labels2 = append(labels2, k, v) + } + alert2.ID = hash(metricWithLabels(t, labels2...)) finished := make(chan struct{}) fs.add(m1) diff --git a/app/vmalert/helpers_test.go b/app/vmalert/helpers_test.go index a4f99cddc..bc0ce54cb 100644 --- a/app/vmalert/helpers_test.go +++ b/app/vmalert/helpers_test.go @@ -205,7 +205,8 @@ func compareTimeSeries(t *testing.T, a, b []prompbmarshal.TimeSeries) error { }*/ } if len(expTS.Labels) != len(gotTS.Labels) { - return fmt.Errorf("expected number of labels %d; got %d", len(expTS.Labels), len(gotTS.Labels)) + return fmt.Errorf("expected number of labels %d (%v); got %d (%v)", + len(expTS.Labels), expTS.Labels, len(gotTS.Labels), gotTS.Labels) } for i, exp := range expTS.Labels { got := gotTS.Labels[i] diff --git a/app/vmalert/notifier/alert.go b/app/vmalert/notifier/alert.go index d345791e5..c2889b94f 100644 --- a/app/vmalert/notifier/alert.go +++ b/app/vmalert/notifier/alert.go @@ -34,6 +34,8 @@ type Alert struct { Value float64 // ID is the unique identifer for the Alert ID uint64 + // Restored is true if Alert was restored after restart + Restored bool } // AlertState type indicates the Alert state diff --git a/app/vmalert/web.qtpl b/app/vmalert/web.qtpl index b64640a45..c15fa9de8 100644 --- a/app/vmalert/web.qtpl +++ b/app/vmalert/web.qtpl @@ -51,7 +51,7 @@ <div class="group-heading{% if rNotOk[g.Name] > 0 %} alert-danger{% endif %}" data-bs-target="rules-{%s g.ID %}"> <span class="anchor" id="group-{%s g.ID %}"></span> <a href="#group-{%s g.ID %}">{%s g.Name %}{% if g.Type != "prometheus" %} ({%s g.Type %}){% endif %} (every {%s g.Interval %})</a> - {% if rNotOk[g.Name] > 0 %}<span class="badge bg-danger" title="Number of rules withs status Error">{%d rNotOk[g.Name] %}</span> {% endif %} + {% if rNotOk[g.Name] > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d rNotOk[g.Name] %}</span> {% endif %} <span class="badge bg-success" title="Number of rules withs status Ok">{%d rOk[g.Name] %}</span> <p class="fs-6 fw-lighter">{%s g.File %}</p> {% if len(g.ExtraFilterLabels) > 0 %} @@ -177,8 +177,11 @@ <span class="ms-1 badge bg-primary">{%s k %}={%s ar.Labels[k] %}</span> {% endfor %} </td> - <td><span class="badge {% if ar.State=="firing" %}bg-danger{% else %} bg-warning text-dark{% endif %}">{%s ar.State %}</span></td> - <td>{%s ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00") %}</td> + <td>{%= badgeState(ar.State) %}</td> + <td> + {%s ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00") %} + {% if ar.Restored %}{%= badgeRestored() %}{% endif %} + </td> <td>{%s ar.Value %}</td> <td> <a href="/{%s g.ID %}/{%s ar.ID %}/status">Details</a> @@ -285,4 +288,18 @@ </div> {%= tpl.Footer() %} +{% endfunc %} + +{% func badgeState(state string) %} +{%code + badgeClass := "bg-warning text-dark" + if state == "firing" { + badgeClass = "bg-danger" + } +%} +<span class="badge {%s badgeClass %}">{%s state %}</span> +{% endfunc %} + +{% func badgeRestored() %} +<span class="badge bg-warning text-dark" title="Alert state was restored after the service restart from remote storage">restored</span> {% endfunc %} \ No newline at end of file diff --git a/app/vmalert/web.qtpl.go b/app/vmalert/web.qtpl.go index 5b7a3bbb4..be8372249 100644 --- a/app/vmalert/web.qtpl.go +++ b/app/vmalert/web.qtpl.go @@ -190,7 +190,7 @@ func StreamListGroups(qw422016 *qt422016.Writer, groups []APIGroup) { //line app/vmalert/web.qtpl:54 if rNotOk[g.Name] > 0 { //line app/vmalert/web.qtpl:54 - qw422016.N().S(`<span class="badge bg-danger" title="Number of rules withs status Error">`) + qw422016.N().S(`<span class="badge bg-danger" title="Number of rules with status Error">`) //line app/vmalert/web.qtpl:54 qw422016.N().D(rNotOk[g.Name]) //line app/vmalert/web.qtpl:54 @@ -623,126 +623,125 @@ func StreamListAlerts(qw422016 *qt422016.Writer, groupAlerts []GroupAlerts) { //line app/vmalert/web.qtpl:178 qw422016.N().S(` </td> - <td><span class="badge `) -//line app/vmalert/web.qtpl:180 - if ar.State == "firing" { -//line app/vmalert/web.qtpl:180 - qw422016.N().S(`bg-danger`) -//line app/vmalert/web.qtpl:180 - } else { -//line app/vmalert/web.qtpl:180 - qw422016.N().S(` bg-warning text-dark`) -//line app/vmalert/web.qtpl:180 - } -//line app/vmalert/web.qtpl:180 - qw422016.N().S(`">`) -//line app/vmalert/web.qtpl:180 - qw422016.E().S(ar.State) -//line app/vmalert/web.qtpl:180 - qw422016.N().S(`</span></td> <td>`) -//line app/vmalert/web.qtpl:181 - qw422016.E().S(ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00")) -//line app/vmalert/web.qtpl:181 +//line app/vmalert/web.qtpl:180 + streambadgeState(qw422016, ar.State) +//line app/vmalert/web.qtpl:180 qw422016.N().S(`</td> + <td> + `) +//line app/vmalert/web.qtpl:182 + qw422016.E().S(ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00")) +//line app/vmalert/web.qtpl:182 + qw422016.N().S(` + `) +//line app/vmalert/web.qtpl:183 + if ar.Restored { +//line app/vmalert/web.qtpl:183 + streambadgeRestored(qw422016) +//line app/vmalert/web.qtpl:183 + } +//line app/vmalert/web.qtpl:183 + qw422016.N().S(` + </td> <td>`) -//line app/vmalert/web.qtpl:182 +//line app/vmalert/web.qtpl:185 qw422016.E().S(ar.Value) -//line app/vmalert/web.qtpl:182 +//line app/vmalert/web.qtpl:185 qw422016.N().S(`</td> <td> <a href="/`) -//line app/vmalert/web.qtpl:184 +//line app/vmalert/web.qtpl:187 qw422016.E().S(g.ID) -//line app/vmalert/web.qtpl:184 +//line app/vmalert/web.qtpl:187 qw422016.N().S(`/`) -//line app/vmalert/web.qtpl:184 +//line app/vmalert/web.qtpl:187 qw422016.E().S(ar.ID) -//line app/vmalert/web.qtpl:184 +//line app/vmalert/web.qtpl:187 qw422016.N().S(`/status">Details</a> </td> </tr> `) -//line app/vmalert/web.qtpl:187 +//line app/vmalert/web.qtpl:190 } -//line app/vmalert/web.qtpl:187 +//line app/vmalert/web.qtpl:190 qw422016.N().S(` </tbody> </table> `) -//line app/vmalert/web.qtpl:190 +//line app/vmalert/web.qtpl:193 } -//line app/vmalert/web.qtpl:190 +//line app/vmalert/web.qtpl:193 qw422016.N().S(` </div> <br> `) -//line app/vmalert/web.qtpl:193 +//line app/vmalert/web.qtpl:196 } -//line app/vmalert/web.qtpl:193 +//line app/vmalert/web.qtpl:196 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:195 +//line app/vmalert/web.qtpl:198 } else { -//line app/vmalert/web.qtpl:195 +//line app/vmalert/web.qtpl:198 qw422016.N().S(` <div> <p>No items...</p> </div> `) -//line app/vmalert/web.qtpl:199 +//line app/vmalert/web.qtpl:202 } -//line app/vmalert/web.qtpl:199 +//line app/vmalert/web.qtpl:202 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:204 tpl.StreamFooter(qw422016) -//line app/vmalert/web.qtpl:201 +//line app/vmalert/web.qtpl:204 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 } -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 func WriteListAlerts(qq422016 qtio422016.Writer, groupAlerts []GroupAlerts) { -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 StreamListAlerts(qw422016, groupAlerts) -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 qt422016.ReleaseWriter(qw422016) -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 } -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 func ListAlerts(groupAlerts []GroupAlerts) string { -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 qb422016 := qt422016.AcquireByteBuffer() -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 WriteListAlerts(qb422016, groupAlerts) -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 qs422016 := string(qb422016.B) -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 qt422016.ReleaseByteBuffer(qb422016) -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 return qs422016 -//line app/vmalert/web.qtpl:203 +//line app/vmalert/web.qtpl:206 } -//line app/vmalert/web.qtpl:205 -func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { -//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 +func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { +//line app/vmalert/web.qtpl:208 + qw422016.N().S(` + `) +//line app/vmalert/web.qtpl:209 + tpl.StreamHeader(qw422016, "", navItems) +//line app/vmalert/web.qtpl:209 + qw422016.N().S(` + `) +//line app/vmalert/web.qtpl:211 var labelKeys []string for k := range alert.Labels { labelKeys = append(labelKeys, k) @@ -755,28 +754,28 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { } sort.Strings(annotationKeys) -//line app/vmalert/web.qtpl:219 +//line app/vmalert/web.qtpl:222 qw422016.N().S(` <div class="display-6 pb-3 mb-3">`) -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 qw422016.E().S(alert.Name) -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 qw422016.N().S(`<span class="ms-2 badge `) -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 if alert.State == "firing" { -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 qw422016.N().S(`bg-danger`) -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 } else { -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 qw422016.N().S(` bg-warning text-dark`) -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 } -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 qw422016.N().S(`">`) -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 qw422016.E().S(alert.State) -//line app/vmalert/web.qtpl:220 +//line app/vmalert/web.qtpl:223 qw422016.N().S(`</span></div> <div class="container border-bottom p-2"> <div class="row"> @@ -785,9 +784,9 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { </div> <div class="col"> `) -//line app/vmalert/web.qtpl:227 +//line app/vmalert/web.qtpl:230 qw422016.E().S(alert.ActiveAt.Format("2006-01-02T15:04:05Z07:00")) -//line app/vmalert/web.qtpl:227 +//line app/vmalert/web.qtpl:230 qw422016.N().S(` </div> </div> @@ -799,9 +798,9 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { </div> <div class="col"> <code><pre>`) -//line app/vmalert/web.qtpl:237 +//line app/vmalert/web.qtpl:240 qw422016.E().S(alert.Expression) -//line app/vmalert/web.qtpl:237 +//line app/vmalert/web.qtpl:240 qw422016.N().S(`</pre></code> </div> </div> @@ -813,23 +812,23 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { </div> <div class="col"> `) -//line app/vmalert/web.qtpl:247 +//line app/vmalert/web.qtpl:250 for _, k := range labelKeys { -//line app/vmalert/web.qtpl:247 +//line app/vmalert/web.qtpl:250 qw422016.N().S(` <span class="m-1 badge bg-primary">`) -//line app/vmalert/web.qtpl:248 +//line app/vmalert/web.qtpl:251 qw422016.E().S(k) -//line app/vmalert/web.qtpl:248 +//line app/vmalert/web.qtpl:251 qw422016.N().S(`=`) -//line app/vmalert/web.qtpl:248 +//line app/vmalert/web.qtpl:251 qw422016.E().S(alert.Labels[k]) -//line app/vmalert/web.qtpl:248 +//line app/vmalert/web.qtpl:251 qw422016.N().S(`</span> `) -//line app/vmalert/web.qtpl:249 +//line app/vmalert/web.qtpl:252 } -//line app/vmalert/web.qtpl:249 +//line app/vmalert/web.qtpl:252 qw422016.N().S(` </div> </div> @@ -841,24 +840,24 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { </div> <div class="col"> `) -//line app/vmalert/web.qtpl:259 +//line app/vmalert/web.qtpl:262 for _, k := range annotationKeys { -//line app/vmalert/web.qtpl:259 +//line app/vmalert/web.qtpl:262 qw422016.N().S(` <b>`) -//line app/vmalert/web.qtpl:260 +//line app/vmalert/web.qtpl:263 qw422016.E().S(k) -//line app/vmalert/web.qtpl:260 +//line app/vmalert/web.qtpl:263 qw422016.N().S(`:</b><br> <p>`) -//line app/vmalert/web.qtpl:261 +//line app/vmalert/web.qtpl:264 qw422016.E().S(alert.Annotations[k]) -//line app/vmalert/web.qtpl:261 +//line app/vmalert/web.qtpl:264 qw422016.N().S(`</p> `) -//line app/vmalert/web.qtpl:262 +//line app/vmalert/web.qtpl:265 } -//line app/vmalert/web.qtpl:262 +//line app/vmalert/web.qtpl:265 qw422016.N().S(` </div> </div> @@ -870,13 +869,13 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { </div> <div class="col"> <a target="_blank" href="/groups#group-`) -//line app/vmalert/web.qtpl:272 +//line app/vmalert/web.qtpl:275 qw422016.E().S(alert.GroupID) -//line app/vmalert/web.qtpl:272 +//line app/vmalert/web.qtpl:275 qw422016.N().S(`">`) -//line app/vmalert/web.qtpl:272 +//line app/vmalert/web.qtpl:275 qw422016.E().S(alert.GroupID) -//line app/vmalert/web.qtpl:272 +//line app/vmalert/web.qtpl:275 qw422016.N().S(`</a> </div> </div> @@ -888,45 +887,132 @@ func StreamAlert(qw422016 *qt422016.Writer, alert *APIAlert) { </div> <div class="col"> <a target="_blank" href="`) -//line app/vmalert/web.qtpl:282 +//line app/vmalert/web.qtpl:285 qw422016.E().S(alert.SourceLink) -//line app/vmalert/web.qtpl:282 +//line app/vmalert/web.qtpl:285 qw422016.N().S(`">Link</a> </div> </div> </div> `) -//line app/vmalert/web.qtpl:286 +//line app/vmalert/web.qtpl:289 tpl.StreamFooter(qw422016) -//line app/vmalert/web.qtpl:286 +//line app/vmalert/web.qtpl:289 qw422016.N().S(` `) -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 } -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 func WriteAlert(qq422016 qtio422016.Writer, alert *APIAlert) { -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 qw422016 := qt422016.AcquireWriter(qq422016) -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 StreamAlert(qw422016, alert) -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 qt422016.ReleaseWriter(qw422016) -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 } -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 func Alert(alert *APIAlert) string { -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 qb422016 := qt422016.AcquireByteBuffer() -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 WriteAlert(qb422016, alert) -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 qs422016 := string(qb422016.B) -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 qt422016.ReleaseByteBuffer(qb422016) -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 return qs422016 -//line app/vmalert/web.qtpl:288 +//line app/vmalert/web.qtpl:291 +} + +//line app/vmalert/web.qtpl:293 +func streambadgeState(qw422016 *qt422016.Writer, state string) { +//line app/vmalert/web.qtpl:293 + qw422016.N().S(` +`) +//line app/vmalert/web.qtpl:295 + badgeClass := "bg-warning text-dark" + if state == "firing" { + badgeClass = "bg-danger" + } + +//line app/vmalert/web.qtpl:299 + qw422016.N().S(` +<span class="badge `) +//line app/vmalert/web.qtpl:300 + qw422016.E().S(badgeClass) +//line app/vmalert/web.qtpl:300 + qw422016.N().S(`">`) +//line app/vmalert/web.qtpl:300 + qw422016.E().S(state) +//line app/vmalert/web.qtpl:300 + qw422016.N().S(`</span> +`) +//line app/vmalert/web.qtpl:301 +} + +//line app/vmalert/web.qtpl:301 +func writebadgeState(qq422016 qtio422016.Writer, state string) { +//line app/vmalert/web.qtpl:301 + qw422016 := qt422016.AcquireWriter(qq422016) +//line app/vmalert/web.qtpl:301 + streambadgeState(qw422016, state) +//line app/vmalert/web.qtpl:301 + qt422016.ReleaseWriter(qw422016) +//line app/vmalert/web.qtpl:301 +} + +//line app/vmalert/web.qtpl:301 +func badgeState(state string) string { +//line app/vmalert/web.qtpl:301 + qb422016 := qt422016.AcquireByteBuffer() +//line app/vmalert/web.qtpl:301 + writebadgeState(qb422016, state) +//line app/vmalert/web.qtpl:301 + qs422016 := string(qb422016.B) +//line app/vmalert/web.qtpl:301 + qt422016.ReleaseByteBuffer(qb422016) +//line app/vmalert/web.qtpl:301 + return qs422016 +//line app/vmalert/web.qtpl:301 +} + +//line app/vmalert/web.qtpl:303 +func streambadgeRestored(qw422016 *qt422016.Writer) { +//line app/vmalert/web.qtpl:303 + qw422016.N().S(` +<span class="badge bg-warning text-dark" title="Alert state was restored after reload from remote storage">restored</span> +`) +//line app/vmalert/web.qtpl:305 +} + +//line app/vmalert/web.qtpl:305 +func writebadgeRestored(qq422016 qtio422016.Writer) { +//line app/vmalert/web.qtpl:305 + qw422016 := qt422016.AcquireWriter(qq422016) +//line app/vmalert/web.qtpl:305 + streambadgeRestored(qw422016) +//line app/vmalert/web.qtpl:305 + qt422016.ReleaseWriter(qw422016) +//line app/vmalert/web.qtpl:305 +} + +//line app/vmalert/web.qtpl:305 +func badgeRestored() string { +//line app/vmalert/web.qtpl:305 + qb422016 := qt422016.AcquireByteBuffer() +//line app/vmalert/web.qtpl:305 + writebadgeRestored(qb422016) +//line app/vmalert/web.qtpl:305 + qs422016 := string(qb422016.B) +//line app/vmalert/web.qtpl:305 + qt422016.ReleaseByteBuffer(qb422016) +//line app/vmalert/web.qtpl:305 + return qs422016 +//line app/vmalert/web.qtpl:305 } diff --git a/app/vmalert/web_types.go b/app/vmalert/web_types.go index fa14d3cc1..cb13fb23f 100644 --- a/app/vmalert/web_types.go +++ b/app/vmalert/web_types.go @@ -18,6 +18,7 @@ type APIAlert struct { Annotations map[string]string `json:"annotations"` ActiveAt time.Time `json:"activeAt"` SourceLink string `json:"source"` + Restored bool `json:"restored"` } // APIGroup represents Group for WEB view