mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
The build version added to: - victoria-metrics - vmagent - vmalert Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10249 Co-authored-by: Hui Wang <[email protected]> Signed-off-by: Zhu Jiekun <[email protected]>
682 lines
30 KiB
Plaintext
682 lines
30 KiB
Plaintext
{% package main %}
|
|
|
|
{% import (
|
|
"time"
|
|
"sort"
|
|
"net/http"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/vmalertutil"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/rule"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
|
|
) %}
|
|
|
|
{% func Controls(prefix, currentIcon, currentText string, icons, filters map[string]string, search bool) %}
|
|
<div class="btn-toolbar mb-3" role="toolbar">
|
|
<div class="d-flex gap-2 justify-content-between w-100">
|
|
<div class="d-flex gap-2 align-items-center">
|
|
<a class="btn btn-outline-dark d-flex align-items-center" role="button" onclick="actionAll(true)">
|
|
<span class="d-none d-md-block">Collapse All</span>
|
|
<svg class="d-md-none" height="20" width="20">
|
|
<use href="{%s prefix %}static/icons/icons.svg#collapse"/>
|
|
</svg>
|
|
</a>
|
|
<a class="btn btn-outline-dark d-flex align-items-center" role="button" onclick="actionAll(false)">
|
|
<span class="d-none d-md-block">Expand All</span>
|
|
<svg class="d-md-none" width="20" height="20">
|
|
<use href="{%s prefix %}static/icons/icons.svg#expand"/>
|
|
</svg>
|
|
</a>
|
|
{% if len(filters) > 0 %}
|
|
<span class="d-none d-md-inline-block">Filter by status:</span>
|
|
<svg class="d-md-none" width="20" height="20">
|
|
<use href="{%s prefix %}static/icons/icons.svg#filter">
|
|
</svg>
|
|
<div class="dropdown">
|
|
<button
|
|
class="btn btn-outline-dark dropdown-toggle d-flex justify-content-between align-items-center"
|
|
type="button"
|
|
data-bs-toggle="dropdown"
|
|
aria-expanded="false"
|
|
>
|
|
<span class="d-none d-md-inline-block">{%s currentText %}</span>
|
|
<svg class="d-md-none" width="22" height="22">
|
|
<use href="{%s prefix %}static/icons/icons.svg#{%s currentIcon %}"/>
|
|
</svg>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
{% for key, title := range filters %}
|
|
{% if title != currentText %}
|
|
<li>
|
|
<a class="dropdown-item" onclick="groupFilter('{%s key %}')">
|
|
<span class="d-none d-md-inline-block">{%s title %}</span>
|
|
<svg class="d-md-none" width="22" height="22">
|
|
<use href="{%s prefix %}static/icons/icons.svg#{%s icons[key] %}"/>
|
|
</svg>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% if search %}
|
|
<div class="input-group flex-grow-1 justify-content-end">
|
|
<span class="input-group-text">
|
|
<svg height="25" width="20">
|
|
<use href="{%s prefix %}static/icons/icons.svg#search">
|
|
</svg>
|
|
</span>
|
|
<input id="search" placeholder="Filter by group, rule or labels" type="text" class="form-control"/>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfunc %}
|
|
|
|
{% func Welcome(r *http.Request) %}
|
|
{%= tpl.Header(r, navItems, "vmalert", getLastConfigError()) %}
|
|
<p>
|
|
Version {%s buildinfo.Version %} <br>
|
|
|
|
API:<br>
|
|
{% for _, p := range apiLinks %}
|
|
{%code p, doc := p[0], p[1] %}
|
|
<a href="{%s p %}">{%s p %}</a> - {%s doc %}<br/>
|
|
{% endfor %}
|
|
{% if r.Header.Get("X-Forwarded-For") == "" %}
|
|
System:<br>
|
|
{% for _, p := range systemLinks %}
|
|
{%code p, doc := p[0], p[1] %}
|
|
<a href="{%s p %}">{%s p %}</a> - {%s doc %}<br/>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</p>
|
|
{%= tpl.Footer(r) %}
|
|
{% endfunc %}
|
|
|
|
{% func ListGroups(r *http.Request, groups []*rule.ApiGroup, filter string) %}
|
|
{%code
|
|
prefix := vmalertutil.Prefix(r.URL.Path)
|
|
filters := map[string]string{
|
|
"": "All",
|
|
"unhealthy": "Unhealthy",
|
|
"nomatch": "No Match",
|
|
}
|
|
icons := map[string]string{
|
|
"": "all",
|
|
"unhealthy": "unhealthy",
|
|
"nomatch": "nomatch",
|
|
}
|
|
currentText := filters[filter]
|
|
currentIcon := icons[filter]
|
|
%}
|
|
{%= tpl.Header(r, navItems, "Groups", getLastConfigError()) %}
|
|
{%= Controls(prefix, currentIcon, currentText, icons, filters, true) %}
|
|
{% if len(groups) > 0 %}
|
|
{% for _, g := range groups %}
|
|
<div id="group-{%s g.ID %}" class="w-100 border-0 flex-column vm-group{% if g.Unhealthy > 0 %} alert-danger{% endif %}">
|
|
<span class="d-flex justify-content-between">
|
|
<a
|
|
class="vm-group-search"
|
|
href="#group-{%s g.ID %}"
|
|
>{%s g.Name %}{% if g.Type != "prometheus" %} ({%s g.Type %}){% endif %} (every {%f.0 g.Interval %}s) #</a>
|
|
<span
|
|
class="flex-grow-1 d-flex justify-content-end"
|
|
role="button"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#item-{%s g.ID %}"
|
|
>
|
|
<span class="d-flex gap-2">
|
|
{% if g.Unhealthy > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d g.Unhealthy %}</span> {% endif %}
|
|
{% if g.NoMatch > 0 %}<span class="badge bg-warning" title="Number of rules with status NoMatch">{%d g.NoMatch %}</span> {% endif %}
|
|
<span class="badge bg-success" title="Number of rules with status Ok">{%d g.Healthy %}</span>
|
|
</span>
|
|
</span>
|
|
</span>
|
|
<span
|
|
class="d-flex flex-column row-gap-2 mb-2"
|
|
role="button"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#item-{%s g.ID %}"
|
|
>
|
|
<span class="fs-6 text-start vm-group-search w-100 fw-lighter">{%s g.File %}</span>
|
|
{% if len(g.Params) > 0 %}
|
|
<span class="fs-6 text-start w-100 d-flex justify-content-between fw-lighter">
|
|
<span>Extra params</span>
|
|
<span class="d-flex align-items-center gap-2">
|
|
{% for _, param := range g.Params %}
|
|
<span class="badge bg-primary">{%s param %}</span>
|
|
{% endfor %}
|
|
</span>
|
|
</span>
|
|
{% endif %}
|
|
{% if len(g.Headers) > 0 %}
|
|
<span class="fs-6 text-start w-100 d-flex justify-content-between fw-lighter">
|
|
<span>Extra headers</span>
|
|
<span class="d-flex align-items-center gap-2">
|
|
{% for _, header := range g.Headers %}
|
|
<span class="badge bg-primary label">{%s header %}</span>
|
|
{% endfor %}
|
|
</span>
|
|
</span>
|
|
{% endif %}
|
|
</span>
|
|
<div class="collapse" id="item-{%s g.ID %}">
|
|
<table class="table table-striped table-hover table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" class="w-60">Rule</th>
|
|
<th scope="col" class="w-20" class="text-center" title="How many series were produced by the rule">Series</th>
|
|
<th scope="col" class="w-20" class="text-center" title="How many seconds ago rule was executed">Updated</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for _, r := range g.Rules %}
|
|
<tr class="vm-item{% if r.LastError != "" %} alert-danger{% endif %}">
|
|
<td>
|
|
<div class="row">
|
|
<div class="col-12 mb-2">
|
|
{% if r.Type == "alerting" %}
|
|
{% if r.KeepFiringFor > 0 %}
|
|
<b>alert:</b> {%s r.Name %} (for: {%v r.Duration %} seconds, keep_firing_for: {%v r.KeepFiringFor %} seconds)
|
|
{% else %}
|
|
<b>alert:</b> {%s r.Name %} (for: {%v r.Duration %} seconds)
|
|
{% endif %}
|
|
{% else %}
|
|
<b>record:</b> {%s r.Name %}
|
|
{% endif %}
|
|
|
|
|
{%= seriesFetchedWarn(prefix, r) %}
|
|
<span><a target="_blank" href="{%s prefix+r.WebLink() %}">Details</a></span>
|
|
</div>
|
|
<div class="col-12">
|
|
<code><pre>{%s r.Query %}</pre></code>
|
|
</div>
|
|
<div class="col-12 mb-2">
|
|
{% if len(r.Labels) > 0 %} <b>Labels:</b>{% endif %}
|
|
{% for k, v := range r.Labels %}
|
|
<span class="ms-1 badge bg-primary label">{%s k %}={%s v %}</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% if r.LastError != "" %}
|
|
<div class="col-12">
|
|
<b>Error:</b>
|
|
<div class="error-cell">
|
|
{%s r.LastError %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td class="text-center">{%d r.LastSamples %}</td>
|
|
<td class="text-center">{% if r.LastEvaluation.IsZero() %}
|
|
Never
|
|
{% else %}
|
|
{%f.3 time.Since(r.LastEvaluation).Seconds() %}s ago
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div>
|
|
<p>No groups...</p>
|
|
</div>
|
|
{% endif %}
|
|
{%= tpl.Footer(r) %}
|
|
{% endfunc %}
|
|
|
|
|
|
{% func ListAlerts(r *http.Request, groupAlerts []rule.GroupAlerts) %}
|
|
{%code prefix := vmalertutil.Prefix(r.URL.Path) %}
|
|
{%= tpl.Header(r, navItems, "Alerts", getLastConfigError()) %}
|
|
{%= Controls(prefix, "", "", nil, nil, true) %}
|
|
{% if len(groupAlerts) > 0 %}
|
|
{% for _, ga := range groupAlerts %}
|
|
{%code
|
|
g := ga.Group
|
|
var keys []string
|
|
alertsByRule := make(map[string][]*rule.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="w-100 flex-column vm-group alert-danger">
|
|
<span id="group-{%s g.ID %}" class="d-flex justify-content-between">
|
|
<a href="#group-{%s g.ID %}">{%s g.Name %}{% if g.Type != "prometheus" %} ({%s g.Type %}){% endif %}</a>
|
|
<span
|
|
class="flex-grow-1 d-flex justify-content-end"
|
|
role="button"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#item-{%s g.ID %}"
|
|
>
|
|
<span class="badge bg-danger" title="Number of active alerts">{%d len(ga.Alerts) %}</span>
|
|
</span>
|
|
</span>
|
|
<span>
|
|
<span
|
|
class="fs-6 text-start w-100 fw-lighter"
|
|
role="button"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#item-{%s g.ID %}"
|
|
>{%s g.File %}</span>
|
|
</span>
|
|
<div class="collapse" id="item-{%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>
|
|
<div class="vm-item">
|
|
<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 label">{%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 %}
|
|
{% if ar.Stabilizing %}{%= badgeStabilizing() %}{% endif %}
|
|
</td>
|
|
<td>{%s ar.Value %}</td>
|
|
<td><a href="{%s prefix+ar.WebLink() %}">Details</a></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div>
|
|
<p>No active alerts...</p>
|
|
</div>
|
|
{% endif %}
|
|
{%= tpl.Footer(r) %}
|
|
{% endfunc %}
|
|
|
|
{% func ListTargets(r *http.Request, targets map[notifier.TargetType][]notifier.Target) %}
|
|
{%code prefix := vmalertutil.Prefix(r.URL.Path) %}
|
|
{%= tpl.Header(r, navItems, "Notifiers", getLastConfigError()) %}
|
|
{%= Controls(prefix, "", "", nil, nil, false) %}
|
|
{% if len(targets) > 0 %}
|
|
{%code
|
|
var keys []string
|
|
for key := range targets {
|
|
keys = append(keys, string(key))
|
|
}
|
|
sort.Strings(keys)
|
|
%}
|
|
{% for i := range keys %}
|
|
{%code
|
|
typeK, ns := keys[i], targets[notifier.TargetType(keys[i])]
|
|
count := len(ns)
|
|
%}
|
|
<div class="w-100 flex-column vm-group">
|
|
<span class="d-flex justify-content-between" id="group-{%s typeK %}">
|
|
<a href="#group-{%s typeK %}">{%s typeK %} ({%d count %})</a>
|
|
<span
|
|
class="flex-grow-1"
|
|
role="button"
|
|
data-bs-toggle="collapse"
|
|
data-bs-target="#item-{%s typeK %}"
|
|
></span>
|
|
</span>
|
|
<div id="item-{%s typeK %}" class="collapse show">
|
|
<table class="table table-striped table-hover table-sm">
|
|
<thead>
|
|
<tr class="vm-item">
|
|
<th scope="col">Labels</th>
|
|
<th scope="col">Address</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for _, n := range ns %}
|
|
<tr>
|
|
<td>
|
|
{% for _, l := range n.Labels.GetLabels() %}
|
|
<span class="ms-1 badge bg-primary">{%s l.Name %}={%s l.Value %}</span>
|
|
{% endfor %}
|
|
</td>
|
|
<td>{%s n.Notifier.Addr() %}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div>
|
|
<p>No targets...</p>
|
|
</div>
|
|
{% endif %}
|
|
{%= tpl.Footer(r) %}
|
|
{% endfunc %}
|
|
|
|
{% func Alert(r *http.Request, alert *rule.ApiAlert) %}
|
|
{%code prefix := vmalertutil.Prefix(r.URL.Path) %}
|
|
{%= tpl.Header(r, navItems, "", getLastConfigError()) %}
|
|
{%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">Alert: {%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 class="annotations">{%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="{%s prefix %}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(r) %}
|
|
|
|
{% endfunc %}
|
|
|
|
|
|
{% func RuleDetails(r *http.Request, rule rule.ApiRule) %}
|
|
{%code prefix := vmalertutil.Prefix(r.URL.Path) %}
|
|
{%= tpl.Header(r, navItems, "", getLastConfigError()) %}
|
|
{%code
|
|
var labelKeys []string
|
|
for k := range rule.Labels {
|
|
labelKeys = append(labelKeys, k)
|
|
}
|
|
sort.Strings(labelKeys)
|
|
|
|
var annotationKeys []string
|
|
for k := range rule.Annotations {
|
|
annotationKeys = append(annotationKeys, k)
|
|
}
|
|
sort.Strings(annotationKeys)
|
|
|
|
var seriesFetchedEnabled bool
|
|
var seriesFetchedWarning bool
|
|
for _, u := range rule.Updates {
|
|
if u.SeriesFetched != nil {
|
|
seriesFetchedEnabled = true
|
|
if *u.SeriesFetched == 0 && u.Samples == 0{
|
|
seriesFetchedWarning = true
|
|
}
|
|
}
|
|
}
|
|
|
|
%}
|
|
<div class="display-6 pb-3 mb-3">Rule: {%s rule.Name %}<span class="ms-2 badge {% if rule.Health!="ok" %}bg-danger{% else %} bg-success text-dark{% endif %}">{%s rule.Health %}</span></div>
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Expr
|
|
</div>
|
|
<div class="col">
|
|
<code><pre>{%s rule.Query %}</pre></code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% if rule.Type == "alerting" %}
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
For
|
|
</div>
|
|
<div class="col">
|
|
{%v rule.Duration %} seconds
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% if rule.KeepFiringFor > 0 %}
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Keep firing for
|
|
</div>
|
|
<div class="col">
|
|
{%v rule.KeepFiringFor %} seconds
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
<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 rule.Labels[k] %}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% if rule.Type == "alerting" %}
|
|
<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 class="annotations">{%s rule.Annotations[k] %}</p>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Debug
|
|
</div>
|
|
<div class="col">
|
|
{%v rule.Debug %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
<div class="container border-bottom p-2">
|
|
<div class="row">
|
|
<div class="col-2">
|
|
Group
|
|
</div>
|
|
<div class="col">
|
|
<a target="_blank" href="{%s prefix %}groups#group-{%s rule.GroupID %}">{%s rule.GroupID %}</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<br>
|
|
{% if seriesFetchedWarning %}
|
|
<div class="alert alert-warning" role="alert">
|
|
<strong>Warning:</strong> some of updates have "Series fetched" equal to 0.<br>
|
|
It might be that either this data is missing in the datasource or there is a typo in rule's expression.
|
|
For example, <strong>foo{label="bar"} > 0</strong> could never trigger because <strong>foo{label="bar"}</strong>
|
|
metric doesn't exist.
|
|
<br>
|
|
Rule's expressions without time series selector, like <strong>expr: 42</strong> or <strong>expr: time()</strong>
|
|
aren't fetching time series from datasource, so they could have "Series fetched" equal to 0 and this won't be a problem.
|
|
<br>
|
|
See more details about this detection <a target="_blank" href="https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4039">here</a>.
|
|
</div>
|
|
{% endif %}
|
|
<div class="display-6 pb-3">Last {%d len(rule.Updates) %}/{%d rule.MaxUpdates %} updates</span>:</div>
|
|
<table class="table table-striped table-hover table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col" title="The time when the rule was executed">Updated at</th>
|
|
<th scope="col" class="w-10 text-center" title="How many series expression returns. Each series will represent an alert.">Series returned</th>
|
|
{% if seriesFetchedEnabled %}<th scope="col" class="w-10 text-center" title="How many series were scanned by datasource during the evaluation">Series fetched</th>{% endif %}
|
|
<th scope="col" class="w-10 text-center" title="How many seconds request took">Duration</th>
|
|
<th scope="col" class="text-center" title="The time used in execution query request">Execution timestamp</th>
|
|
<th scope="col" class="text-center" title="cURL command with request example">cURL</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
|
|
{% for _, u := range rule.Updates %}
|
|
<tr{% if u.Err != nil %} class="alert-danger"{% endif %}>
|
|
<td>
|
|
<span class="badge bg-primary rounded-pill me-3" title="Updated at">{%s u.Time.Format(time.RFC3339) %}</span>
|
|
</td>
|
|
<td class="text-center">{%d u.Samples %}</td>
|
|
{% if seriesFetchedEnabled %}<td class="text-center">{% if u.SeriesFetched != nil %}{%d *u.SeriesFetched %}{% endif %}</td>{% endif %}
|
|
<td class="text-center">{%f.3 u.Duration.Seconds() %}s</td>
|
|
<td class="text-center">{%s u.At.Format(time.RFC3339) %}</td>
|
|
<td>
|
|
<textarea class="curl-area" rows="1" onclick="this.focus();this.select()">{%s u.Curl %}</textarea>
|
|
</td>
|
|
</tr>
|
|
</li>
|
|
{% if u.Err != nil %}
|
|
<tr{% if u.Err != nil %} class="alert-danger"{% endif %}>
|
|
<td colspan="{% if seriesFetchedEnabled %}6{%else%}5{%endif%}">
|
|
<span class="alert-danger">{%v u.Err %}</span>
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{%= tpl.Footer(r) %}
|
|
{% 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 %}
|
|
|
|
{% func badgeStabilizing() %}
|
|
<span class="badge bg-warning text-dark" title="This firing state is kept because of `keep_firing_for`">stabilizing</span>
|
|
{% endfunc %}
|
|
|
|
{% func seriesFetchedWarn(prefix string, r rule.ApiRule) %}
|
|
{% if isNoMatch(r) %}
|
|
<svg
|
|
data-bs-toggle="tooltip"
|
|
title="No match! This rule's last evaluation hasn't selected any time series from the datasource.
|
|
It might be that either this data is missing in the datasource or there is a typo in rule's expression.
|
|
See more in Details."
|
|
width="18" height="18" fill="currentColor" class="bi bi-exclamation-triangle-fill flex-shrink-0 me-2" role="img" aria-label="Warning:">
|
|
<use href="{%s prefix %}static/icons/icons.svg#exclamation"/>
|
|
</svg>
|
|
{% endif %}
|
|
{% endfunc %}
|
|
|
|
{%code
|
|
func isNoMatch (r rule.ApiRule) bool {
|
|
return r.LastSamples == 0 && r.LastSeriesFetched != nil && *r.LastSeriesFetched == 0
|
|
}
|
|
%}
|