mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-15 16:30:55 +01:00
e0f21d6000
Previously, ID for alert entity was generated without alertname or groupname. This led to collision, when multiple alerting rules within the same group producing same labelsets. E.g. expr: `sum(metric1) by (job) > 0` and expr: `sum(metric2) by (job) > 0` could result into same labelset `job: "job"`. The issue affects only UI and Web API parts of vmalert, because alert ID is used only for displaying and finding active alerts. It does not affect state restore procedure, since this label was added right before pushing to remote storage. The change now adds all extra labels right after receiving response from the datasource. And removes adding extra labels before pushing to remote storage. Additionally, change introduces a new flag `Restored` which will be displayed in UI for alerts which have been restored from remote storage on restart.
305 lines
12 KiB
Plaintext
305 lines
12 KiB
Plaintext
{% package main %}
|
|
|
|
{% import (
|
|
"time"
|
|
"sort"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl"
|
|
) %}
|
|
|
|
|
|
{% func Welcome() %}
|
|
{%= tpl.Header("vmalert", navItems) %}
|
|
<p>
|
|
API:<br>
|
|
{% for _, p := range apiLinks %}
|
|
{%code
|
|
p, doc := p[0], p[1]
|
|
%}
|
|
<a href="{%s p %}">{%s p %}</a> - {%s doc %}<br/>
|
|
{% endfor %}
|
|
</p>
|
|
{%= tpl.Footer() %}
|
|
{% endfunc %}
|
|
|
|
{% func ListGroups(groups []APIGroup) %}
|
|
{%= tpl.Header("Groups", navItems) %}
|
|
{% if len(groups) > 0 %}
|
|
{%code
|
|
rOk := make(map[string]int)
|
|
rNotOk := make(map[string]int)
|
|
for _, g := range groups {
|
|
for _, r := range g.AlertingRules{
|
|
if r.LastError != "" {
|
|
rNotOk[g.Name]++
|
|
} else {
|
|
rOk[g.Name]++
|
|
}
|
|
}
|
|
for _, r := range g.RecordingRules{
|
|
if r.LastError != "" {
|
|
rNotOk[g.Name]++
|
|
} else {
|
|
rOk[g.Name]++
|
|
}
|
|
}
|
|
}
|
|
%}
|
|
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
|
|
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
|
|
{% for _, g := range groups %}
|
|
<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 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 %}
|
|
<div class="fs-6 fw-lighter">Extra filter labels
|
|
{% for k, v := range g.ExtraFilterLabels %}
|
|
<span class="float-left badge bg-primary">{%s k %}={%s v %}</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="collapse" id="rules-{%s g.ID %}">
|
|
<table class="table table-striped table-hover table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">Rule</th>
|
|
<th scope="col" title="Shows if rule's execution ended with error">Error</th>
|
|
<th scope="col" title="How many samples were produced by the rule">Samples</th>
|
|
<th scope="col" title="How many seconds ago rule was executed">Updated</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for _, ar := range g.AlertingRules %}
|
|
<tr{% if ar.LastError != "" %} class="alert-danger"{% endif %}>
|
|
<td>
|
|
<b>alert:</b> {%s ar.Name %} (for: {%v ar.For %})<br>
|
|
<code><pre>{%s ar.Expression %}</pre></code><br>
|
|
{% if len(ar.Labels) > 0 %} <b>Labels:</b>{% endif %}
|
|
{% for k, v := range ar.Labels %}
|
|
<span class="ms-1 badge bg-primary">{%s k %}={%s v %}</span>
|
|
{% endfor %}
|
|
</td>
|
|
<td><div class="error-cell">{%s ar.LastError %}</div></td>
|
|
<td>{%d ar.LastSamples %}</td>
|
|
<td>{%f.3 time.Since(ar.LastExec).Seconds() %}s ago</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% for _, rr := range g.RecordingRules %}
|
|
<tr>
|
|
<td>
|
|
<b>record:</b> {%s rr.Name %}<br>
|
|
<code><pre>{%s rr.Expression %}</pre></code>
|
|
{% if len(rr.Labels) > 0 %} <b>Labels:</b>{% endif %}
|
|
{% for k, v := range rr.Labels %}
|
|
<span class="ms-1 badge bg-primary">{%s k %}={%s v %}</span>
|
|
{% endfor %}
|
|
</td>
|
|
<td><div class="error-cell">{%s rr.LastError %}</div></td>
|
|
<td>{%d rr.LastSamples %}</td>
|
|
<td>{%f.3 time.Since(rr.LastExec).Seconds() %}s ago</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% else %}
|
|
<div>
|
|
<p>No items...</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{%= tpl.Footer() %}
|
|
|
|
{% endfunc %}
|
|
|
|
|
|
{% func ListAlerts(groupAlerts []GroupAlerts) %}
|
|
{%= tpl.Header("Alerts", navItems) %}
|
|
{% if len(groupAlerts) > 0 %}
|
|
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
|
|
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
|
|
{% for _, ga := range groupAlerts %}
|
|
{%code g := ga.Group %}
|
|
<div class="group-heading alert-danger" 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 %}</a>
|
|
<span class="badge bg-danger" title="Number of active alerts">{%d len(ga.Alerts) %}</span>
|
|
<br>
|
|
<p class="fs-6 fw-lighter">{%s g.File %}</p>
|
|
</div>
|
|
{%code
|
|
var keys []string
|
|
alertsByRule := make(map[string][]*APIAlert)
|
|
for _, alert := range ga.Alerts {
|
|
if len(alertsByRule[alert.RuleID]) < 1 {
|
|
keys = append(keys, alert.RuleID)
|
|
}
|
|
alertsByRule[alert.RuleID] = append(alertsByRule[alert.RuleID], alert)
|
|
}
|
|
sort.Strings(keys)
|
|
%}
|
|
<div class="collapse" id="rules-{%s g.ID %}">
|
|
{% for _, ruleID := range keys %}
|
|
{%code
|
|
defaultAR := alertsByRule[ruleID][0]
|
|
var labelKeys []string
|
|
for k := range defaultAR.Labels {
|
|
labelKeys = append(labelKeys, k)
|
|
}
|
|
sort.Strings(labelKeys)
|
|
%}
|
|
<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>
|
|
<tr>
|
|
<th scope="col">Labels</th>
|
|
<th scope="col">State</th>
|
|
<th scope="col">Active at</th>
|
|
<th scope="col">Value</th>
|
|
<th scope="col">Link</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for _, ar := range alertsByRule[ruleID] %}
|
|
<tr>
|
|
<td>
|
|
{% for _, k := range labelKeys %}
|
|
<span class="ms-1 badge bg-primary">{%s k %}={%s ar.Labels[k] %}</span>
|
|
{% endfor %}
|
|
</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>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% endfor %}
|
|
</div>
|
|
<br>
|
|
{% endfor %}
|
|
|
|
{% else %}
|
|
<div>
|
|
<p>No items...</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{%= tpl.Footer() %}
|
|
|
|
{% endfunc %}
|
|
|
|
{% func Alert(alert *APIAlert) %}
|
|
{%= tpl.Header("", navItems) %}
|
|
{%code
|
|
var labelKeys []string
|
|
for k := range alert.Labels {
|
|
labelKeys = append(labelKeys, k)
|
|
}
|
|
sort.Strings(labelKeys)
|
|
|
|
var annotationKeys []string
|
|
for k := range alert.Annotations {
|
|
annotationKeys = append(annotationKeys, k)
|
|
}
|
|
sort.Strings(annotationKeys)
|
|
%}
|
|
<div class="display-6 pb-3 mb-3">{%s alert.Name %}<span class="ms-2 badge {% if alert.State=="firing" %}bg-danger{% else %} bg-warning text-dark{% endif %}">{%s alert.State %}</span></div>
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Active at
|
|
</div>
|
|
<div class="col">
|
|
{%s alert.ActiveAt.Format("2006-01-02T15:04:05Z07:00") %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Expr
|
|
</div>
|
|
<div class="col">
|
|
<code><pre>{%s alert.Expression %}</pre></code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Labels
|
|
</div>
|
|
<div class="col">
|
|
{% for _, k := range labelKeys %}
|
|
<span class="m-1 badge bg-primary">{%s k %}={%s alert.Labels[k] %}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Annotations
|
|
</div>
|
|
<div class="col">
|
|
{% for _, k := range annotationKeys %}
|
|
<b>{%s k %}:</b><br>
|
|
<p>{%s alert.Annotations[k] %}</p>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Group
|
|
</div>
|
|
<div class="col">
|
|
<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() %}
|
|
|
|
{% 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 %} |