vmalert: deprecate alert's status link (#2840)

* vmalert: deprecate alert's status link

Deprecate alert's status link `/api/v1/<groupID>/<alertID>/status` in favour of
`api/v1/alerts?group_id=<group_id>&alert_id=<alert_id>"`.

The change was needed for simplifying logic in vmselect for proxying vmalert's requests.

The old alert's status link will be still supported for a few versions but will be removed in the future.

https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2825
Signed-off-by: hagen1778 <roman@victoriametrics.com>

* vmalert: fix review comments

Signed-off-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
Roman Khavronenko 2022-07-08 10:26:13 +02:00 committed by GitHub
parent 0713da9e7a
commit e9b977859b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 511 additions and 381 deletions

View file

@ -481,7 +481,7 @@ or time series modification via [relabeling](https://docs.victoriametrics.com/vm
* `http://<vmalert-addr>` - UI; * `http://<vmalert-addr>` - UI;
* `http://<vmalert-addr>/api/v1/rules` - list of all loaded groups and rules; * `http://<vmalert-addr>/api/v1/rules` - list of all loaded groups and rules;
* `http://<vmalert-addr>/api/v1/alerts` - list of all active alerts; * `http://<vmalert-addr>/api/v1/alerts` - list of all active alerts;
* `http://<vmalert-addr>/api/v1/<groupID>/<alertID>/status"` - get alert status by ID. * `http://<vmalert-addr>/vmalert/api/v1/alert?group_id=<group_id>&alert_id=<alert_id>"` - get alert status by ID.
Used as alert source in AlertManager. Used as alert source in AlertManager.
* `http://<vmalert-addr>/metrics` - application metrics. * `http://<vmalert-addr>/metrics` - application metrics.
* `http://<vmalert-addr>/-/reload` - hot configuration reload. * `http://<vmalert-addr>/-/reload` - hot configuration reload.
@ -681,7 +681,7 @@ The shortlist of configuration flags is the following:
How often to evaluate the rules (default 1m0s) How often to evaluate the rules (default 1m0s)
-external.alert.source string -external.alert.source string
External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service. External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service.
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/api/v1/:groupID/alertID/status' is used eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/vmalert/api/v1/alert?group_id=&alert_id=' is used
-external.label array -external.label array
Optional label in the form 'Name=value' to add to all generated recording rules and alerts. Pass multiple -label flags in order to add multiple label sets. Optional label in the form 'Name=value' to add to all generated recording rules and alerts. Pass multiple -label flags in order to add multiple label sets.
Supports an array of values separated by comma or specified via multiple flags. Supports an array of values separated by comma or specified via multiple flags.

View file

@ -59,7 +59,7 @@ absolute path to all .tpl files in root.`)
externalURL = flag.String("external.url", "", "External URL is used as alert's source for sent alerts to the notifier") externalURL = flag.String("external.url", "", "External URL is used as alert's source for sent alerts to the notifier")
externalAlertSource = flag.String("external.alert.source", "", `External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service. externalAlertSource = flag.String("external.alert.source", "", `External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service.
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/api/v1/:groupID/alertID/status' is used`) eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/vmalert/api/v1/alert?group_id=&alert_id=' is used`)
externalLabels = flagutil.NewArray("external.label", "Optional label in the form 'Name=value' to add to all generated recording rules and alerts. "+ externalLabels = flagutil.NewArray("external.label", "Optional label in the form 'Name=value' to add to all generated recording rules and alerts. "+
"Pass multiple -label flags in order to add multiple label sets.") "Pass multiple -label flags in order to add multiple label sets.")
@ -236,8 +236,9 @@ func getExternalURL(externalURL, httpListenAddr string, isSecure bool) (*url.URL
func getAlertURLGenerator(externalURL *url.URL, externalAlertSource string, validateTemplate bool) (notifier.AlertURLGenerator, error) { func getAlertURLGenerator(externalURL *url.URL, externalAlertSource string, validateTemplate bool) (notifier.AlertURLGenerator, error) {
if externalAlertSource == "" { if externalAlertSource == "" {
return func(alert notifier.Alert) string { return func(a notifier.Alert) string {
return fmt.Sprintf("%s/api/v1/%s/%s/status", externalURL, strconv.FormatUint(alert.GroupID, 10), strconv.FormatUint(alert.ID, 10)) gID, aID := strconv.FormatUint(a.GroupID, 10), strconv.FormatUint(a.ID, 10)
return fmt.Sprintf("%s/vmalert/api/v1/alert?%s=%s&%s=%s", externalURL, paramGroupID, gID, paramAlertID, aID)
}, nil }, nil
} }
if validateTemplate { if validateTemplate {

View file

@ -41,7 +41,8 @@ func TestGetAlertURLGenerator(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error %s", err) t.Errorf("unexpected error %s", err)
} }
if exp := "https://victoriametrics.com/path/api/v1/42/2/status"; exp != fn(testAlert) { exp := fmt.Sprintf("https://victoriametrics.com/path/vmalert/api/v1/alert?%s=42&%s=2", paramGroupID, paramAlertID)
if exp != fn(testAlert) {
t.Errorf("unexpected url want %s, got %s", exp, fn(testAlert)) t.Errorf("unexpected url want %s, got %s", exp, fn(testAlert))
} }
_, err = getAlertURLGenerator(nil, "foo?{{invalid}}", true) _, err = getAlertURLGenerator(nil, "foo?{{invalid}}", true)

View file

@ -1,16 +1,12 @@
{% import ( {% import (
"net/http" "net/http"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
) %} ) %}
{% func Footer(r *http.Request) %} {% func Footer(r *http.Request) %}
{%code {%code prefix := utils.Prefix(r.URL.Path) %}
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
%}
</main> </main>
<script src="{%s prefix %}static/js/jquery-3.6.0.min.js" type="text/javascript"></script> <script src="{%s prefix %}static/js/jquery-3.6.0.min.js" type="text/javascript"></script>
<script src="{%s prefix %}static/js/bootstrap.bundle.min.js" type="text/javascript"></script> <script src="{%s prefix %}static/js/bootstrap.bundle.min.js" type="text/javascript"></script>

View file

@ -7,45 +7,43 @@ package tpl
//line app/vmalert/tpl/footer.qtpl:1 //line app/vmalert/tpl/footer.qtpl:1
import ( import (
"net/http" "net/http"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
) )
//line app/vmalert/tpl/footer.qtpl:7 //line app/vmalert/tpl/footer.qtpl:8
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line app/vmalert/tpl/footer.qtpl:7 //line app/vmalert/tpl/footer.qtpl:8
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line app/vmalert/tpl/footer.qtpl:7 //line app/vmalert/tpl/footer.qtpl:8
func StreamFooter(qw422016 *qt422016.Writer, r *http.Request) { func StreamFooter(qw422016 *qt422016.Writer, r *http.Request) {
//line app/vmalert/tpl/footer.qtpl:7 //line app/vmalert/tpl/footer.qtpl:8
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/tpl/footer.qtpl:9 //line app/vmalert/tpl/footer.qtpl:9
prefix := "/vmalert/" prefix := utils.Prefix(r.URL.Path)
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
//line app/vmalert/tpl/footer.qtpl:13 //line app/vmalert/tpl/footer.qtpl:9
qw422016.N().S(` qw422016.N().S(`
</main> </main>
<script src="`) <script src="`)
//line app/vmalert/tpl/footer.qtpl:15 //line app/vmalert/tpl/footer.qtpl:11
qw422016.E().S(prefix) qw422016.E().S(prefix)
//line app/vmalert/tpl/footer.qtpl:15 //line app/vmalert/tpl/footer.qtpl:11
qw422016.N().S(`static/js/jquery-3.6.0.min.js" type="text/javascript"></script> qw422016.N().S(`static/js/jquery-3.6.0.min.js" type="text/javascript"></script>
<script src="`) <script src="`)
//line app/vmalert/tpl/footer.qtpl:16 //line app/vmalert/tpl/footer.qtpl:12
qw422016.E().S(prefix) qw422016.E().S(prefix)
//line app/vmalert/tpl/footer.qtpl:16 //line app/vmalert/tpl/footer.qtpl:12
qw422016.N().S(`static/js/bootstrap.bundle.min.js" type="text/javascript"></script> qw422016.N().S(`static/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<script type="text/javascript"> <script type="text/javascript">
function expandAll() { function expandAll() {
@ -79,31 +77,31 @@ func StreamFooter(qw422016 *qt422016.Writer, r *http.Request) {
</body> </body>
</html> </html>
`) `)
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
} }
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
func WriteFooter(qq422016 qtio422016.Writer, r *http.Request) { func WriteFooter(qq422016 qtio422016.Writer, r *http.Request) {
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
StreamFooter(qw422016, r) StreamFooter(qw422016, r)
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
} }
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
func Footer(r *http.Request) string { func Footer(r *http.Request) string {
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
WriteFooter(qb422016, r) WriteFooter(qb422016, r)
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
return qs422016 return qs422016
//line app/vmalert/tpl/footer.qtpl:48 //line app/vmalert/tpl/footer.qtpl:44
} }

View file

@ -2,15 +2,12 @@
"strings" "strings"
"net/http" "net/http"
"path" "path"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
) %} ) %}
{% func Header(r *http.Request, navItems []NavItem, title string) %} {% func Header(r *http.Request, navItems []NavItem, title string) %}
{%code {%code prefix := utils.Prefix(r.URL.Path) %}
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
%}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>

View file

@ -9,52 +9,51 @@ import (
"net/http" "net/http"
"path" "path"
"strings" "strings"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
) )
//line app/vmalert/tpl/header.qtpl:7 //line app/vmalert/tpl/header.qtpl:9
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line app/vmalert/tpl/header.qtpl:7 //line app/vmalert/tpl/header.qtpl:9
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line app/vmalert/tpl/header.qtpl:7
func StreamHeader(qw422016 *qt422016.Writer, r *http.Request, navItems []NavItem, title string) {
//line app/vmalert/tpl/header.qtpl:7
qw422016.N().S(`
`)
//line app/vmalert/tpl/header.qtpl:9 //line app/vmalert/tpl/header.qtpl:9
prefix := "/vmalert/" func StreamHeader(qw422016 *qt422016.Writer, r *http.Request, navItems []NavItem, title string) {
if strings.HasPrefix(r.URL.Path, prefix) { //line app/vmalert/tpl/header.qtpl:9
prefix = "" qw422016.N().S(`
} `)
//line app/vmalert/tpl/header.qtpl:10
prefix := utils.Prefix(r.URL.Path)
//line app/vmalert/tpl/header.qtpl:13 //line app/vmalert/tpl/header.qtpl:10
qw422016.N().S(` qw422016.N().S(`
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>vmalert`) <title>vmalert`)
//line app/vmalert/tpl/header.qtpl:17 //line app/vmalert/tpl/header.qtpl:14
if title != "" { if title != "" {
//line app/vmalert/tpl/header.qtpl:17 //line app/vmalert/tpl/header.qtpl:14
qw422016.N().S(` - `) qw422016.N().S(` - `)
//line app/vmalert/tpl/header.qtpl:17 //line app/vmalert/tpl/header.qtpl:14
qw422016.E().S(title) qw422016.E().S(title)
//line app/vmalert/tpl/header.qtpl:17 //line app/vmalert/tpl/header.qtpl:14
} }
//line app/vmalert/tpl/header.qtpl:17 //line app/vmalert/tpl/header.qtpl:14
qw422016.N().S(`</title> qw422016.N().S(`</title>
<link href="`) <link href="`)
//line app/vmalert/tpl/header.qtpl:18 //line app/vmalert/tpl/header.qtpl:15
qw422016.E().S(prefix) qw422016.E().S(prefix)
//line app/vmalert/tpl/header.qtpl:18 //line app/vmalert/tpl/header.qtpl:15
qw422016.N().S(`static/css/bootstrap.min.css" rel="stylesheet" /> qw422016.N().S(`static/css/bootstrap.min.css" rel="stylesheet" />
<style> <style>
body{ body{
@ -105,124 +104,124 @@ func StreamHeader(qw422016 *qt422016.Writer, r *http.Request, navItems []NavItem
</head> </head>
<body> <body>
`) `)
//line app/vmalert/tpl/header.qtpl:67 //line app/vmalert/tpl/header.qtpl:64
streamprintNavItems(qw422016, r, title, navItems) streamprintNavItems(qw422016, r, title, navItems)
//line app/vmalert/tpl/header.qtpl:67 //line app/vmalert/tpl/header.qtpl:64
qw422016.N().S(` qw422016.N().S(`
<main class="px-2"> <main class="px-2">
`) `)
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
} }
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
func WriteHeader(qq422016 qtio422016.Writer, r *http.Request, navItems []NavItem, title string) { func WriteHeader(qq422016 qtio422016.Writer, r *http.Request, navItems []NavItem, title string) {
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
StreamHeader(qw422016, r, navItems, title) StreamHeader(qw422016, r, navItems, title)
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
} }
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
func Header(r *http.Request, navItems []NavItem, title string) string { func Header(r *http.Request, navItems []NavItem, title string) string {
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
WriteHeader(qb422016, r, navItems, title) WriteHeader(qb422016, r, navItems, title)
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
return qs422016 return qs422016
//line app/vmalert/tpl/header.qtpl:69 //line app/vmalert/tpl/header.qtpl:66
} }
//line app/vmalert/tpl/header.qtpl:73 //line app/vmalert/tpl/header.qtpl:70
type NavItem struct { type NavItem struct {
Name string Name string
Url string Url string
} }
//line app/vmalert/tpl/header.qtpl:79 //line app/vmalert/tpl/header.qtpl:76
func streamprintNavItems(qw422016 *qt422016.Writer, r *http.Request, current string, items []NavItem) { func streamprintNavItems(qw422016 *qt422016.Writer, r *http.Request, current string, items []NavItem) {
//line app/vmalert/tpl/header.qtpl:79 //line app/vmalert/tpl/header.qtpl:76
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/tpl/header.qtpl:81 //line app/vmalert/tpl/header.qtpl:78
prefix := "/vmalert/" prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) { if strings.HasPrefix(r.URL.Path, prefix) {
prefix = "" prefix = ""
} }
//line app/vmalert/tpl/header.qtpl:85 //line app/vmalert/tpl/header.qtpl:82
qw422016.N().S(` qw422016.N().S(`
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container-fluid"> <div class="container-fluid">
<div class="collapse navbar-collapse" id="navbarCollapse"> <div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0"> <ul class="navbar-nav me-auto mb-2 mb-md-0">
`) `)
//line app/vmalert/tpl/header.qtpl:90 //line app/vmalert/tpl/header.qtpl:87
for _, item := range items { for _, item := range items {
//line app/vmalert/tpl/header.qtpl:90 //line app/vmalert/tpl/header.qtpl:87
qw422016.N().S(` qw422016.N().S(`
<li class="nav-item"> <li class="nav-item">
<a class="nav-link`) <a class="nav-link`)
//line app/vmalert/tpl/header.qtpl:92 //line app/vmalert/tpl/header.qtpl:89
if current == item.Name { if current == item.Name {
//line app/vmalert/tpl/header.qtpl:92 //line app/vmalert/tpl/header.qtpl:89
qw422016.N().S(` active`) qw422016.N().S(` active`)
//line app/vmalert/tpl/header.qtpl:92 //line app/vmalert/tpl/header.qtpl:89
} }
//line app/vmalert/tpl/header.qtpl:92 //line app/vmalert/tpl/header.qtpl:89
qw422016.N().S(`" href="`) qw422016.N().S(`" href="`)
//line app/vmalert/tpl/header.qtpl:92 //line app/vmalert/tpl/header.qtpl:89
qw422016.E().S(path.Join(prefix, item.Url)) qw422016.E().S(path.Join(prefix, item.Url))
//line app/vmalert/tpl/header.qtpl:92 //line app/vmalert/tpl/header.qtpl:89
qw422016.N().S(`"> qw422016.N().S(`">
`) `)
//line app/vmalert/tpl/header.qtpl:93 //line app/vmalert/tpl/header.qtpl:90
qw422016.E().S(item.Name) qw422016.E().S(item.Name)
//line app/vmalert/tpl/header.qtpl:93 //line app/vmalert/tpl/header.qtpl:90
qw422016.N().S(` qw422016.N().S(`
</a> </a>
</li> </li>
`) `)
//line app/vmalert/tpl/header.qtpl:96 //line app/vmalert/tpl/header.qtpl:93
} }
//line app/vmalert/tpl/header.qtpl:96 //line app/vmalert/tpl/header.qtpl:93
qw422016.N().S(` qw422016.N().S(`
</ul> </ul>
</div> </div>
</nav> </nav>
`) `)
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
} }
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
func writeprintNavItems(qq422016 qtio422016.Writer, r *http.Request, current string, items []NavItem) { func writeprintNavItems(qq422016 qtio422016.Writer, r *http.Request, current string, items []NavItem) {
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
streamprintNavItems(qw422016, r, current, items) streamprintNavItems(qw422016, r, current, items)
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
} }
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
func printNavItems(r *http.Request, current string, items []NavItem) string { func printNavItems(r *http.Request, current string, items []NavItem) string {
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
writeprintNavItems(qb422016, r, current, items) writeprintNavItems(qb422016, r, current, items)
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
return qs422016 return qs422016
//line app/vmalert/tpl/header.qtpl:100 //line app/vmalert/tpl/header.qtpl:97
} }

View file

@ -0,0 +1,12 @@
package utils
import "strings"
const prefix = "/vmalert/"
func Prefix(path string) string {
if strings.HasPrefix(path, prefix) {
return ""
}
return prefix
}

View file

@ -25,11 +25,11 @@ var (
func initLinks() { func initLinks() {
apiLinks = [][2]string{ apiLinks = [][2]string{
// api links are relative since they can be used by external clients // api links are relative since they can be used by external clients,
// such as Grafana and proxied via vmselect. // such as Grafana, and proxied via vmselect.
{"api/v1/rules", "list all loaded groups and rules"}, {"api/v1/rules", "list all loaded groups and rules"},
{"api/v1/alerts", "list all active alerts"}, {"api/v1/alerts", "list all active alerts"},
{"api/v1/groupID/alertID/status", "get alert status by ID"}, {fmt.Sprintf("api/v1/alert?%s=<int>&%s=<int>", paramGroupID, paramAlertID), "get alert status by group and alert ID"},
// system links // system links
{"/flags", "command-line flags"}, {"/flags", "command-line flags"},
@ -76,6 +76,14 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
case "/vmalert/alerts": case "/vmalert/alerts":
WriteListAlerts(w, r, rh.groupAlerts()) WriteListAlerts(w, r, rh.groupAlerts())
return true return true
case "/vmalert/alert":
alert, err := rh.getAlert(r)
if err != nil {
httpserver.Errorf(w, r, "%s", err)
return true
}
WriteAlert(w, r, alert)
return true
case "/vmalert/groups": case "/vmalert/groups":
WriteListGroups(w, r, rh.groups()) WriteListGroups(w, r, rh.groups())
return true return true
@ -111,7 +119,20 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Write(data) w.Write(data)
return true return true
case "/vmalert/api/v1/alert", "/api/v1/alert":
alert, err := rh.getAlert(r)
if err != nil {
httpserver.Errorf(w, r, "%s", err)
return true
}
data, err := json.Marshal(alert)
if err != nil {
httpserver.Errorf(w, r, "failed to marshal alert: %s", err)
return true
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
return true
case "/-/reload": case "/-/reload":
logger.Infof("api config reload was called, sending sighup") logger.Infof("api config reload was called, sending sighup")
procutil.SelfSIGHUP() procutil.SelfSIGHUP()
@ -119,6 +140,11 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
return true return true
default: default:
// Support of deprecated links:
// * /api/v1/<groupID>/<alertID>/status
// * <groupID>/<alertID>/status
// TODO: to remove in next versions
if !strings.HasSuffix(r.URL.Path, "/status") { if !strings.HasSuffix(r.URL.Path, "/status") {
return false return false
} }
@ -128,24 +154,36 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
return true return true
} }
// /api/v1/<groupID>/<alertID>/status redirectURL := alert.WebLink()
if strings.HasPrefix(r.URL.Path, "/api/v1/") { if strings.HasPrefix(r.URL.Path, "/api/v1/") {
data, err := json.Marshal(alert) redirectURL = alert.APILink()
if err != nil {
httpserver.Errorf(w, r, "failed to marshal alert: %s", err)
return true
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
return true
} }
http.Redirect(w, r, "/"+redirectURL, http.StatusPermanentRedirect)
// <groupID>/<alertID>/status
WriteAlert(w, r, alert)
return true return true
} }
} }
const (
paramGroupID = "group_id"
paramAlertID = "alert_id"
)
func (rh *requestHandler) getAlert(r *http.Request) (*APIAlert, error) {
groupID, err := strconv.ParseUint(r.FormValue(paramGroupID), 10, 0)
if err != nil {
return nil, fmt.Errorf("failed to read %q param: %s", paramGroupID, err)
}
alertID, err := strconv.ParseUint(r.FormValue(paramAlertID), 10, 0)
if err != nil {
return nil, fmt.Errorf("failed to read %q param: %s", paramAlertID, err)
}
a, err := rh.m.AlertAPI(groupID, alertID)
if err != nil {
return nil, errResponse(err, http.StatusNotFound)
}
return a, nil
}
type listGroupsResponse struct { type listGroupsResponse struct {
Status string `json:"status"` Status string `json:"status"`
Data struct { Data struct {
@ -245,10 +283,10 @@ func (rh *requestHandler) listAlerts() ([]byte, error) {
} }
func (rh *requestHandler) alertByPath(path string) (*APIAlert, error) { func (rh *requestHandler) alertByPath(path string) (*APIAlert, error) {
rh.m.groupsMu.RLock() if strings.HasPrefix(path, "/vmalert") {
defer rh.m.groupsMu.RUnlock() path = strings.TrimLeft(path, "/vmalert")
}
parts := strings.SplitN(strings.TrimLeft(path, "/"), "/", 3) parts := strings.SplitN(strings.TrimLeft(path, "/"), "/", -1)
if len(parts) != 3 { if len(parts) != 3 {
return nil, &httpserver.ErrorWithStatusCode{ return nil, &httpserver.ErrorWithStatusCode{
Err: fmt.Errorf(`path %q cointains /status suffix but doesn't match pattern "/groupID/alertID/status"`, path), Err: fmt.Errorf(`path %q cointains /status suffix but doesn't match pattern "/groupID/alertID/status"`, path),

View file

@ -3,10 +3,10 @@
{% import ( {% import (
"time" "time"
"sort" "sort"
"path"
"net/http" "net/http"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
) %} ) %}
@ -119,6 +119,7 @@
{% func ListAlerts(r *http.Request, groupAlerts []GroupAlerts) %} {% func ListAlerts(r *http.Request, groupAlerts []GroupAlerts) %}
{%code prefix := utils.Prefix(r.URL.Path) %}
{%= tpl.Header(r, navItems, "Alerts") %} {%= tpl.Header(r, navItems, "Alerts") %}
{% if len(groupAlerts) > 0 %} {% if len(groupAlerts) > 0 %}
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a> <a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
@ -183,7 +184,7 @@
</td> </td>
<td>{%s ar.Value %}</td> <td>{%s ar.Value %}</td>
<td> <td>
<a href="{%s path.Join(g.ID, ar.ID, "status") %}">Details</a> <a href="{%s prefix+ar.WebLink() %}">Details</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -261,6 +262,7 @@
{% endfunc %} {% endfunc %}
{% func Alert(r *http.Request, alert *APIAlert) %} {% func Alert(r *http.Request, alert *APIAlert) %}
{%code prefix := utils.Prefix(r.URL.Path) %}
{%= tpl.Header(r, navItems, "") %} {%= tpl.Header(r, navItems, "") %}
{%code {%code
var labelKeys []string var labelKeys []string
@ -327,7 +329,7 @@
Group Group
</div> </div>
<div class="col"> <div class="col">
<a target="_blank" href="/groups#group-{%s alert.GroupID %}">{%s alert.GroupID %}</a> <a target="_blank" href="{%s prefix %}groups#group-{%s alert.GroupID %}">{%s alert.GroupID %}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -7,12 +7,12 @@ package main
//line app/vmalert/web.qtpl:3 //line app/vmalert/web.qtpl:3
import ( import (
"net/http" "net/http"
"path"
"sort" "sort"
"time" "time"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
) )
//line app/vmalert/web.qtpl:14 //line app/vmalert/web.qtpl:14
@ -434,70 +434,76 @@ func StreamListAlerts(qw422016 *qt422016.Writer, r *http.Request, groupAlerts []
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:122 //line app/vmalert/web.qtpl:122
tpl.StreamHeader(qw422016, r, navItems, "Alerts") prefix := utils.Prefix(r.URL.Path)
//line app/vmalert/web.qtpl:122 //line app/vmalert/web.qtpl:122
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:123 //line app/vmalert/web.qtpl:123
if len(groupAlerts) > 0 { tpl.StreamHeader(qw422016, r, navItems, "Alerts")
//line app/vmalert/web.qtpl:123 //line app/vmalert/web.qtpl:123
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:124
if len(groupAlerts) > 0 {
//line app/vmalert/web.qtpl:124
qw422016.N().S(` qw422016.N().S(`
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a> <a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a> <a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
`) `)
//line app/vmalert/web.qtpl:126 //line app/vmalert/web.qtpl:127
for _, ga := range groupAlerts { for _, ga := range groupAlerts {
//line app/vmalert/web.qtpl:126 //line app/vmalert/web.qtpl:127
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:127 //line app/vmalert/web.qtpl:128
g := ga.Group g := ga.Group
//line app/vmalert/web.qtpl:127 //line app/vmalert/web.qtpl:128
qw422016.N().S(` qw422016.N().S(`
<div class="group-heading alert-danger" data-bs-target="rules-`) <div class="group-heading alert-danger" data-bs-target="rules-`)
//line app/vmalert/web.qtpl:128 //line app/vmalert/web.qtpl:129
qw422016.E().S(g.ID) qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:128 //line app/vmalert/web.qtpl:129
qw422016.N().S(`"> qw422016.N().S(`">
<span class="anchor" id="group-`) <span class="anchor" id="group-`)
//line app/vmalert/web.qtpl:129 //line app/vmalert/web.qtpl:130
qw422016.E().S(g.ID) qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:129 //line app/vmalert/web.qtpl:130
qw422016.N().S(`"></span> qw422016.N().S(`"></span>
<a href="#group-`) <a href="#group-`)
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
qw422016.E().S(g.ID) qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
qw422016.E().S(g.Name) qw422016.E().S(g.Name)
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
if g.Type != "prometheus" { if g.Type != "prometheus" {
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
qw422016.N().S(` (`) qw422016.N().S(` (`)
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
qw422016.E().S(g.Type) qw422016.E().S(g.Type)
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
qw422016.N().S(`)`) qw422016.N().S(`)`)
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
} }
//line app/vmalert/web.qtpl:130 //line app/vmalert/web.qtpl:131
qw422016.N().S(`</a> qw422016.N().S(`</a>
<span class="badge bg-danger" title="Number of active alerts">`) <span class="badge bg-danger" title="Number of active alerts">`)
//line app/vmalert/web.qtpl:131 //line app/vmalert/web.qtpl:132
qw422016.N().D(len(ga.Alerts)) qw422016.N().D(len(ga.Alerts))
//line app/vmalert/web.qtpl:131 //line app/vmalert/web.qtpl:132
qw422016.N().S(`</span> qw422016.N().S(`</span>
<br> <br>
<p class="fs-6 fw-lighter">`) <p class="fs-6 fw-lighter">`)
//line app/vmalert/web.qtpl:133 //line app/vmalert/web.qtpl:134
qw422016.E().S(g.File) qw422016.E().S(g.File)
//line app/vmalert/web.qtpl:133 //line app/vmalert/web.qtpl:134
qw422016.N().S(`</p> qw422016.N().S(`</p>
</div> </div>
`) `)
//line app/vmalert/web.qtpl:136 //line app/vmalert/web.qtpl:137
var keys []string var keys []string
alertsByRule := make(map[string][]*APIAlert) alertsByRule := make(map[string][]*APIAlert)
for _, alert := range ga.Alerts { for _, alert := range ga.Alerts {
@ -508,20 +514,20 @@ func StreamListAlerts(qw422016 *qt422016.Writer, r *http.Request, groupAlerts []
} }
sort.Strings(keys) sort.Strings(keys)
//line app/vmalert/web.qtpl:145 //line app/vmalert/web.qtpl:146
qw422016.N().S(` qw422016.N().S(`
<div class="collapse" id="rules-`) <div class="collapse" id="rules-`)
//line app/vmalert/web.qtpl:146 //line app/vmalert/web.qtpl:147
qw422016.E().S(g.ID) qw422016.E().S(g.ID)
//line app/vmalert/web.qtpl:146 //line app/vmalert/web.qtpl:147
qw422016.N().S(`"> qw422016.N().S(`">
`) `)
//line app/vmalert/web.qtpl:147 //line app/vmalert/web.qtpl:148
for _, ruleID := range keys { for _, ruleID := range keys {
//line app/vmalert/web.qtpl:147 //line app/vmalert/web.qtpl:148
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:149 //line app/vmalert/web.qtpl:150
defaultAR := alertsByRule[ruleID][0] defaultAR := alertsByRule[ruleID][0]
var labelKeys []string var labelKeys []string
for k := range defaultAR.Labels { for k := range defaultAR.Labels {
@ -529,28 +535,28 @@ func StreamListAlerts(qw422016 *qt422016.Writer, r *http.Request, groupAlerts []
} }
sort.Strings(labelKeys) sort.Strings(labelKeys)
//line app/vmalert/web.qtpl:155 //line app/vmalert/web.qtpl:156
qw422016.N().S(` qw422016.N().S(`
<br> <br>
<b>alert:</b> `) <b>alert:</b> `)
//line app/vmalert/web.qtpl:157 //line app/vmalert/web.qtpl:158
qw422016.E().S(defaultAR.Name) qw422016.E().S(defaultAR.Name)
//line app/vmalert/web.qtpl:157 //line app/vmalert/web.qtpl:158
qw422016.N().S(` (`) qw422016.N().S(` (`)
//line app/vmalert/web.qtpl:157 //line app/vmalert/web.qtpl:158
qw422016.N().D(len(alertsByRule[ruleID])) qw422016.N().D(len(alertsByRule[ruleID]))
//line app/vmalert/web.qtpl:157 //line app/vmalert/web.qtpl:158
qw422016.N().S(`) qw422016.N().S(`)
| <span><a target="_blank" href="`) | <span><a target="_blank" href="`)
//line app/vmalert/web.qtpl:158 //line app/vmalert/web.qtpl:159
qw422016.E().S(defaultAR.SourceLink) qw422016.E().S(defaultAR.SourceLink)
//line app/vmalert/web.qtpl:158 //line app/vmalert/web.qtpl:159
qw422016.N().S(`">Source</a></span> qw422016.N().S(`">Source</a></span>
<br> <br>
<b>expr:</b><code><pre>`) <b>expr:</b><code><pre>`)
//line app/vmalert/web.qtpl:160 //line app/vmalert/web.qtpl:161
qw422016.E().S(defaultAR.Expression) qw422016.E().S(defaultAR.Expression)
//line app/vmalert/web.qtpl:160 //line app/vmalert/web.qtpl:161
qw422016.N().S(`</pre></code> qw422016.N().S(`</pre></code>
<table class="table table-striped table-hover table-sm"> <table class="table table-striped table-hover table-sm">
<thead> <thead>
@ -564,204 +570,204 @@ func StreamListAlerts(qw422016 *qt422016.Writer, r *http.Request, groupAlerts []
</thead> </thead>
<tbody> <tbody>
`) `)
//line app/vmalert/web.qtpl:172 //line app/vmalert/web.qtpl:173
for _, ar := range alertsByRule[ruleID] { for _, ar := range alertsByRule[ruleID] {
//line app/vmalert/web.qtpl:172 //line app/vmalert/web.qtpl:173
qw422016.N().S(` qw422016.N().S(`
<tr> <tr>
<td> <td>
`) `)
//line app/vmalert/web.qtpl:175 //line app/vmalert/web.qtpl:176
for _, k := range labelKeys { for _, k := range labelKeys {
//line app/vmalert/web.qtpl:175 //line app/vmalert/web.qtpl:176
qw422016.N().S(` qw422016.N().S(`
<span class="ms-1 badge bg-primary">`) <span class="ms-1 badge bg-primary">`)
//line app/vmalert/web.qtpl:176 //line app/vmalert/web.qtpl:177
qw422016.E().S(k) qw422016.E().S(k)
//line app/vmalert/web.qtpl:176 //line app/vmalert/web.qtpl:177
qw422016.N().S(`=`) qw422016.N().S(`=`)
//line app/vmalert/web.qtpl:176 //line app/vmalert/web.qtpl:177
qw422016.E().S(ar.Labels[k]) qw422016.E().S(ar.Labels[k])
//line app/vmalert/web.qtpl:176 //line app/vmalert/web.qtpl:177
qw422016.N().S(`</span> qw422016.N().S(`</span>
`) `)
//line app/vmalert/web.qtpl:177 //line app/vmalert/web.qtpl:178
} }
//line app/vmalert/web.qtpl:177 //line app/vmalert/web.qtpl:178
qw422016.N().S(` qw422016.N().S(`
</td> </td>
<td>`) <td>`)
//line app/vmalert/web.qtpl:179 //line app/vmalert/web.qtpl:180
streambadgeState(qw422016, ar.State) streambadgeState(qw422016, ar.State)
//line app/vmalert/web.qtpl:179 //line app/vmalert/web.qtpl:180
qw422016.N().S(`</td> qw422016.N().S(`</td>
<td> <td>
`) `)
//line app/vmalert/web.qtpl:181 //line app/vmalert/web.qtpl:182
qw422016.E().S(ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00")) qw422016.E().S(ar.ActiveAt.Format("2006-01-02T15:04:05Z07:00"))
//line app/vmalert/web.qtpl:181 //line app/vmalert/web.qtpl:182
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:182 //line app/vmalert/web.qtpl:183
if ar.Restored { if ar.Restored {
//line app/vmalert/web.qtpl:182 //line app/vmalert/web.qtpl:183
streambadgeRestored(qw422016) streambadgeRestored(qw422016)
//line app/vmalert/web.qtpl:182 //line app/vmalert/web.qtpl:183
} }
//line app/vmalert/web.qtpl:182 //line app/vmalert/web.qtpl:183
qw422016.N().S(` qw422016.N().S(`
</td> </td>
<td>`) <td>`)
//line app/vmalert/web.qtpl:184 //line app/vmalert/web.qtpl:185
qw422016.E().S(ar.Value) qw422016.E().S(ar.Value)
//line app/vmalert/web.qtpl:184 //line app/vmalert/web.qtpl:185
qw422016.N().S(`</td> qw422016.N().S(`</td>
<td> <td>
<a href="`) <a href="`)
//line app/vmalert/web.qtpl:186 //line app/vmalert/web.qtpl:187
qw422016.E().S(path.Join(g.ID, ar.ID, "status")) qw422016.E().S(prefix + ar.WebLink())
//line app/vmalert/web.qtpl:186 //line app/vmalert/web.qtpl:187
qw422016.N().S(`">Details</a> qw422016.N().S(`">Details</a>
</td> </td>
</tr> </tr>
`) `)
//line app/vmalert/web.qtpl:189 //line app/vmalert/web.qtpl:190
} }
//line app/vmalert/web.qtpl:189 //line app/vmalert/web.qtpl:190
qw422016.N().S(` qw422016.N().S(`
</tbody> </tbody>
</table> </table>
`) `)
//line app/vmalert/web.qtpl:192 //line app/vmalert/web.qtpl:193
} }
//line app/vmalert/web.qtpl:192 //line app/vmalert/web.qtpl:193
qw422016.N().S(` qw422016.N().S(`
</div> </div>
<br> <br>
`) `)
//line app/vmalert/web.qtpl:195 //line app/vmalert/web.qtpl:196
} }
//line app/vmalert/web.qtpl:195 //line app/vmalert/web.qtpl:196
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:197 //line app/vmalert/web.qtpl:198
} else { } else {
//line app/vmalert/web.qtpl:197 //line app/vmalert/web.qtpl:198
qw422016.N().S(` qw422016.N().S(`
<div> <div>
<p>No items...</p> <p>No items...</p>
</div> </div>
`) `)
//line app/vmalert/web.qtpl:201 //line app/vmalert/web.qtpl:202
} }
//line app/vmalert/web.qtpl:201 //line app/vmalert/web.qtpl:202
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:203 //line app/vmalert/web.qtpl:204
tpl.StreamFooter(qw422016, r) tpl.StreamFooter(qw422016, r)
//line app/vmalert/web.qtpl:203 //line app/vmalert/web.qtpl:204
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
} }
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
func WriteListAlerts(qq422016 qtio422016.Writer, r *http.Request, groupAlerts []GroupAlerts) { func WriteListAlerts(qq422016 qtio422016.Writer, r *http.Request, groupAlerts []GroupAlerts) {
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
StreamListAlerts(qw422016, r, groupAlerts) StreamListAlerts(qw422016, r, groupAlerts)
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
} }
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
func ListAlerts(r *http.Request, groupAlerts []GroupAlerts) string { func ListAlerts(r *http.Request, groupAlerts []GroupAlerts) string {
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
WriteListAlerts(qb422016, r, groupAlerts) WriteListAlerts(qb422016, r, groupAlerts)
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
return qs422016 return qs422016
//line app/vmalert/web.qtpl:205 //line app/vmalert/web.qtpl:206
} }
//line app/vmalert/web.qtpl:207 //line app/vmalert/web.qtpl:208
func StreamListTargets(qw422016 *qt422016.Writer, r *http.Request, targets map[notifier.TargetType][]notifier.Target) { func StreamListTargets(qw422016 *qt422016.Writer, r *http.Request, targets map[notifier.TargetType][]notifier.Target) {
//line app/vmalert/web.qtpl:207 //line app/vmalert/web.qtpl:208
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:208 //line app/vmalert/web.qtpl:209
tpl.StreamHeader(qw422016, r, navItems, "Notifiers") tpl.StreamHeader(qw422016, r, navItems, "Notifiers")
//line app/vmalert/web.qtpl:208 //line app/vmalert/web.qtpl:209
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:209 //line app/vmalert/web.qtpl:210
if len(targets) > 0 { if len(targets) > 0 {
//line app/vmalert/web.qtpl:209 //line app/vmalert/web.qtpl:210
qw422016.N().S(` qw422016.N().S(`
<a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a> <a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a> <a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
`) `)
//line app/vmalert/web.qtpl:214 //line app/vmalert/web.qtpl:215
var keys []string var keys []string
for key := range targets { for key := range targets {
keys = append(keys, string(key)) keys = append(keys, string(key))
} }
sort.Strings(keys) sort.Strings(keys)
//line app/vmalert/web.qtpl:219 //line app/vmalert/web.qtpl:220
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:221 //line app/vmalert/web.qtpl:222
for i := range keys { for i := range keys {
//line app/vmalert/web.qtpl:221 //line app/vmalert/web.qtpl:222
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:222 //line app/vmalert/web.qtpl:223
typeK, ns := keys[i], targets[notifier.TargetType(keys[i])] typeK, ns := keys[i], targets[notifier.TargetType(keys[i])]
count := len(ns) count := len(ns)
//line app/vmalert/web.qtpl:224 //line app/vmalert/web.qtpl:225
qw422016.N().S(` qw422016.N().S(`
<div class="group-heading data-bs-target="rules-`) <div class="group-heading data-bs-target="rules-`)
//line app/vmalert/web.qtpl:225 //line app/vmalert/web.qtpl:226
qw422016.E().S(typeK) qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:225 //line app/vmalert/web.qtpl:226
qw422016.N().S(`"> qw422016.N().S(`">
<span class="anchor" id="notifiers-`) <span class="anchor" id="notifiers-`)
//line app/vmalert/web.qtpl:226 //line app/vmalert/web.qtpl:227
qw422016.E().S(typeK) qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:226 //line app/vmalert/web.qtpl:227
qw422016.N().S(`"></span> qw422016.N().S(`"></span>
<a href="#notifiers-`) <a href="#notifiers-`)
//line app/vmalert/web.qtpl:227 //line app/vmalert/web.qtpl:228
qw422016.E().S(typeK) qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:227 //line app/vmalert/web.qtpl:228
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:227 //line app/vmalert/web.qtpl:228
qw422016.E().S(typeK) qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:227 //line app/vmalert/web.qtpl:228
qw422016.N().S(` (`) qw422016.N().S(` (`)
//line app/vmalert/web.qtpl:227 //line app/vmalert/web.qtpl:228
qw422016.N().D(count) qw422016.N().D(count)
//line app/vmalert/web.qtpl:227 //line app/vmalert/web.qtpl:228
qw422016.N().S(`)</a> qw422016.N().S(`)</a>
</div> </div>
<div class="collapse show" id="notifiers-`) <div class="collapse show" id="notifiers-`)
//line app/vmalert/web.qtpl:229 //line app/vmalert/web.qtpl:230
qw422016.E().S(typeK) qw422016.E().S(typeK)
//line app/vmalert/web.qtpl:229 //line app/vmalert/web.qtpl:230
qw422016.N().S(`"> qw422016.N().S(`">
<table class="table table-striped table-hover table-sm"> <table class="table table-striped table-hover table-sm">
<thead> <thead>
@ -772,113 +778,119 @@ func StreamListTargets(qw422016 *qt422016.Writer, r *http.Request, targets map[n
</thead> </thead>
<tbody> <tbody>
`) `)
//line app/vmalert/web.qtpl:238 //line app/vmalert/web.qtpl:239
for _, n := range ns { for _, n := range ns {
//line app/vmalert/web.qtpl:238 //line app/vmalert/web.qtpl:239
qw422016.N().S(` qw422016.N().S(`
<tr> <tr>
<td> <td>
`) `)
//line app/vmalert/web.qtpl:241 //line app/vmalert/web.qtpl:242
for _, l := range n.Labels { for _, l := range n.Labels {
//line app/vmalert/web.qtpl:241 //line app/vmalert/web.qtpl:242
qw422016.N().S(` qw422016.N().S(`
<span class="ms-1 badge bg-primary">`) <span class="ms-1 badge bg-primary">`)
//line app/vmalert/web.qtpl:242 //line app/vmalert/web.qtpl:243
qw422016.E().S(l.Name) qw422016.E().S(l.Name)
//line app/vmalert/web.qtpl:242 //line app/vmalert/web.qtpl:243
qw422016.N().S(`=`) qw422016.N().S(`=`)
//line app/vmalert/web.qtpl:242 //line app/vmalert/web.qtpl:243
qw422016.E().S(l.Value) qw422016.E().S(l.Value)
//line app/vmalert/web.qtpl:242 //line app/vmalert/web.qtpl:243
qw422016.N().S(`</span> qw422016.N().S(`</span>
`) `)
//line app/vmalert/web.qtpl:243 //line app/vmalert/web.qtpl:244
} }
//line app/vmalert/web.qtpl:243 //line app/vmalert/web.qtpl:244
qw422016.N().S(` qw422016.N().S(`
</td> </td>
<td>`) <td>`)
//line app/vmalert/web.qtpl:245 //line app/vmalert/web.qtpl:246
qw422016.E().S(n.Notifier.Addr()) qw422016.E().S(n.Notifier.Addr())
//line app/vmalert/web.qtpl:245 //line app/vmalert/web.qtpl:246
qw422016.N().S(`</td> qw422016.N().S(`</td>
</tr> </tr>
`) `)
//line app/vmalert/web.qtpl:247 //line app/vmalert/web.qtpl:248
} }
//line app/vmalert/web.qtpl:247 //line app/vmalert/web.qtpl:248
qw422016.N().S(` qw422016.N().S(`
</tbody> </tbody>
</table> </table>
</div> </div>
`) `)
//line app/vmalert/web.qtpl:251 //line app/vmalert/web.qtpl:252
} }
//line app/vmalert/web.qtpl:251 //line app/vmalert/web.qtpl:252
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:253 //line app/vmalert/web.qtpl:254
} else { } else {
//line app/vmalert/web.qtpl:253 //line app/vmalert/web.qtpl:254
qw422016.N().S(` qw422016.N().S(`
<div> <div>
<p>No items...</p> <p>No items...</p>
</div> </div>
`) `)
//line app/vmalert/web.qtpl:257 //line app/vmalert/web.qtpl:258
} }
//line app/vmalert/web.qtpl:257 //line app/vmalert/web.qtpl:258
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:259 //line app/vmalert/web.qtpl:260
tpl.StreamFooter(qw422016, r) tpl.StreamFooter(qw422016, r)
//line app/vmalert/web.qtpl:259 //line app/vmalert/web.qtpl:260
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
} }
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
func WriteListTargets(qq422016 qtio422016.Writer, r *http.Request, targets map[notifier.TargetType][]notifier.Target) { func WriteListTargets(qq422016 qtio422016.Writer, r *http.Request, targets map[notifier.TargetType][]notifier.Target) {
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
StreamListTargets(qw422016, r, targets) StreamListTargets(qw422016, r, targets)
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
} }
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
func ListTargets(r *http.Request, targets map[notifier.TargetType][]notifier.Target) string { func ListTargets(r *http.Request, targets map[notifier.TargetType][]notifier.Target) string {
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
WriteListTargets(qb422016, r, targets) WriteListTargets(qb422016, r, targets)
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
return qs422016 return qs422016
//line app/vmalert/web.qtpl:261 //line app/vmalert/web.qtpl:262
} }
//line app/vmalert/web.qtpl:263 //line app/vmalert/web.qtpl:264
func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) { func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
//line app/vmalert/web.qtpl:263 //line app/vmalert/web.qtpl:264
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:264 //line app/vmalert/web.qtpl:265
tpl.StreamHeader(qw422016, r, navItems, "") prefix := utils.Prefix(r.URL.Path)
//line app/vmalert/web.qtpl:264
//line app/vmalert/web.qtpl:265
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:266 //line app/vmalert/web.qtpl:266
tpl.StreamHeader(qw422016, r, navItems, "")
//line app/vmalert/web.qtpl:266
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:268
var labelKeys []string var labelKeys []string
for k := range alert.Labels { for k := range alert.Labels {
labelKeys = append(labelKeys, k) labelKeys = append(labelKeys, k)
@ -891,28 +903,28 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
} }
sort.Strings(annotationKeys) sort.Strings(annotationKeys)
//line app/vmalert/web.qtpl:277 //line app/vmalert/web.qtpl:279
qw422016.N().S(` qw422016.N().S(`
<div class="display-6 pb-3 mb-3">`) <div class="display-6 pb-3 mb-3">`)
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
qw422016.E().S(alert.Name) qw422016.E().S(alert.Name)
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
qw422016.N().S(`<span class="ms-2 badge `) qw422016.N().S(`<span class="ms-2 badge `)
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
if alert.State == "firing" { if alert.State == "firing" {
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
qw422016.N().S(`bg-danger`) qw422016.N().S(`bg-danger`)
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
} else { } else {
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
qw422016.N().S(` bg-warning text-dark`) qw422016.N().S(` bg-warning text-dark`)
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
} }
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
qw422016.E().S(alert.State) qw422016.E().S(alert.State)
//line app/vmalert/web.qtpl:278 //line app/vmalert/web.qtpl:280
qw422016.N().S(`</span></div> qw422016.N().S(`</span></div>
<div class="container border-bottom p-2"> <div class="container border-bottom p-2">
<div class="row"> <div class="row">
@ -921,9 +933,9 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div> </div>
<div class="col"> <div class="col">
`) `)
//line app/vmalert/web.qtpl:285 //line app/vmalert/web.qtpl:287
qw422016.E().S(alert.ActiveAt.Format("2006-01-02T15:04:05Z07:00")) qw422016.E().S(alert.ActiveAt.Format("2006-01-02T15:04:05Z07:00"))
//line app/vmalert/web.qtpl:285 //line app/vmalert/web.qtpl:287
qw422016.N().S(` qw422016.N().S(`
</div> </div>
</div> </div>
@ -935,9 +947,9 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div> </div>
<div class="col"> <div class="col">
<code><pre>`) <code><pre>`)
//line app/vmalert/web.qtpl:295 //line app/vmalert/web.qtpl:297
qw422016.E().S(alert.Expression) qw422016.E().S(alert.Expression)
//line app/vmalert/web.qtpl:295 //line app/vmalert/web.qtpl:297
qw422016.N().S(`</pre></code> qw422016.N().S(`</pre></code>
</div> </div>
</div> </div>
@ -949,23 +961,23 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div> </div>
<div class="col"> <div class="col">
`) `)
//line app/vmalert/web.qtpl:305 //line app/vmalert/web.qtpl:307
for _, k := range labelKeys { for _, k := range labelKeys {
//line app/vmalert/web.qtpl:305 //line app/vmalert/web.qtpl:307
qw422016.N().S(` qw422016.N().S(`
<span class="m-1 badge bg-primary">`) <span class="m-1 badge bg-primary">`)
//line app/vmalert/web.qtpl:306 //line app/vmalert/web.qtpl:308
qw422016.E().S(k) qw422016.E().S(k)
//line app/vmalert/web.qtpl:306 //line app/vmalert/web.qtpl:308
qw422016.N().S(`=`) qw422016.N().S(`=`)
//line app/vmalert/web.qtpl:306 //line app/vmalert/web.qtpl:308
qw422016.E().S(alert.Labels[k]) qw422016.E().S(alert.Labels[k])
//line app/vmalert/web.qtpl:306 //line app/vmalert/web.qtpl:308
qw422016.N().S(`</span> qw422016.N().S(`</span>
`) `)
//line app/vmalert/web.qtpl:307 //line app/vmalert/web.qtpl:309
} }
//line app/vmalert/web.qtpl:307 //line app/vmalert/web.qtpl:309
qw422016.N().S(` qw422016.N().S(`
</div> </div>
</div> </div>
@ -977,24 +989,24 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div> </div>
<div class="col"> <div class="col">
`) `)
//line app/vmalert/web.qtpl:317 //line app/vmalert/web.qtpl:319
for _, k := range annotationKeys { for _, k := range annotationKeys {
//line app/vmalert/web.qtpl:317 //line app/vmalert/web.qtpl:319
qw422016.N().S(` qw422016.N().S(`
<b>`) <b>`)
//line app/vmalert/web.qtpl:318 //line app/vmalert/web.qtpl:320
qw422016.E().S(k) qw422016.E().S(k)
//line app/vmalert/web.qtpl:318 //line app/vmalert/web.qtpl:320
qw422016.N().S(`:</b><br> qw422016.N().S(`:</b><br>
<p>`) <p>`)
//line app/vmalert/web.qtpl:319 //line app/vmalert/web.qtpl:321
qw422016.E().S(alert.Annotations[k]) qw422016.E().S(alert.Annotations[k])
//line app/vmalert/web.qtpl:319 //line app/vmalert/web.qtpl:321
qw422016.N().S(`</p> qw422016.N().S(`</p>
`) `)
//line app/vmalert/web.qtpl:320 //line app/vmalert/web.qtpl:322
} }
//line app/vmalert/web.qtpl:320 //line app/vmalert/web.qtpl:322
qw422016.N().S(` qw422016.N().S(`
</div> </div>
</div> </div>
@ -1005,14 +1017,18 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
Group Group
</div> </div>
<div class="col"> <div class="col">
<a target="_blank" href="/groups#group-`) <a target="_blank" href="`)
//line app/vmalert/web.qtpl:330 //line app/vmalert/web.qtpl:332
qw422016.E().S(prefix)
//line app/vmalert/web.qtpl:332
qw422016.N().S(`groups#group-`)
//line app/vmalert/web.qtpl:332
qw422016.E().S(alert.GroupID) qw422016.E().S(alert.GroupID)
//line app/vmalert/web.qtpl:330 //line app/vmalert/web.qtpl:332
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:330 //line app/vmalert/web.qtpl:332
qw422016.E().S(alert.GroupID) qw422016.E().S(alert.GroupID)
//line app/vmalert/web.qtpl:330 //line app/vmalert/web.qtpl:332
qw422016.N().S(`</a> qw422016.N().S(`</a>
</div> </div>
</div> </div>
@ -1024,132 +1040,132 @@ func StreamAlert(qw422016 *qt422016.Writer, r *http.Request, alert *APIAlert) {
</div> </div>
<div class="col"> <div class="col">
<a target="_blank" href="`) <a target="_blank" href="`)
//line app/vmalert/web.qtpl:340 //line app/vmalert/web.qtpl:342
qw422016.E().S(alert.SourceLink) qw422016.E().S(alert.SourceLink)
//line app/vmalert/web.qtpl:340 //line app/vmalert/web.qtpl:342
qw422016.N().S(`">Link</a> qw422016.N().S(`">Link</a>
</div> </div>
</div> </div>
</div> </div>
`) `)
//line app/vmalert/web.qtpl:344 //line app/vmalert/web.qtpl:346
tpl.StreamFooter(qw422016, r) tpl.StreamFooter(qw422016, r)
//line app/vmalert/web.qtpl:344 //line app/vmalert/web.qtpl:346
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
} }
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
func WriteAlert(qq422016 qtio422016.Writer, r *http.Request, alert *APIAlert) { func WriteAlert(qq422016 qtio422016.Writer, r *http.Request, alert *APIAlert) {
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
StreamAlert(qw422016, r, alert) StreamAlert(qw422016, r, alert)
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
} }
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
func Alert(r *http.Request, alert *APIAlert) string { func Alert(r *http.Request, alert *APIAlert) string {
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
WriteAlert(qb422016, r, alert) WriteAlert(qb422016, r, alert)
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
return qs422016 return qs422016
//line app/vmalert/web.qtpl:346 //line app/vmalert/web.qtpl:348
} }
//line app/vmalert/web.qtpl:348 //line app/vmalert/web.qtpl:350
func streambadgeState(qw422016 *qt422016.Writer, state string) { func streambadgeState(qw422016 *qt422016.Writer, state string) {
//line app/vmalert/web.qtpl:348 //line app/vmalert/web.qtpl:350
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/web.qtpl:350 //line app/vmalert/web.qtpl:352
badgeClass := "bg-warning text-dark" badgeClass := "bg-warning text-dark"
if state == "firing" { if state == "firing" {
badgeClass = "bg-danger" badgeClass = "bg-danger"
} }
//line app/vmalert/web.qtpl:354 //line app/vmalert/web.qtpl:356
qw422016.N().S(` qw422016.N().S(`
<span class="badge `) <span class="badge `)
//line app/vmalert/web.qtpl:355 //line app/vmalert/web.qtpl:357
qw422016.E().S(badgeClass) qw422016.E().S(badgeClass)
//line app/vmalert/web.qtpl:355 //line app/vmalert/web.qtpl:357
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:355 //line app/vmalert/web.qtpl:357
qw422016.E().S(state) qw422016.E().S(state)
//line app/vmalert/web.qtpl:355 //line app/vmalert/web.qtpl:357
qw422016.N().S(`</span> qw422016.N().S(`</span>
`) `)
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
} }
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
func writebadgeState(qq422016 qtio422016.Writer, state string) { func writebadgeState(qq422016 qtio422016.Writer, state string) {
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
streambadgeState(qw422016, state) streambadgeState(qw422016, state)
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
} }
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
func badgeState(state string) string { func badgeState(state string) string {
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
writebadgeState(qb422016, state) writebadgeState(qb422016, state)
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
return qs422016 return qs422016
//line app/vmalert/web.qtpl:356 //line app/vmalert/web.qtpl:358
} }
//line app/vmalert/web.qtpl:358 //line app/vmalert/web.qtpl:360
func streambadgeRestored(qw422016 *qt422016.Writer) { func streambadgeRestored(qw422016 *qt422016.Writer) {
//line app/vmalert/web.qtpl:358 //line app/vmalert/web.qtpl:360
qw422016.N().S(` qw422016.N().S(`
<span class="badge bg-warning text-dark" title="Alert state was restored after the service restart from remote storage">restored</span> <span class="badge bg-warning text-dark" title="Alert state was restored after the service restart from remote storage">restored</span>
`) `)
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
} }
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
func writebadgeRestored(qq422016 qtio422016.Writer) { func writebadgeRestored(qq422016 qtio422016.Writer) {
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
streambadgeRestored(qw422016) streambadgeRestored(qw422016)
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
} }
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
func badgeRestored() string { func badgeRestored() string {
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
writebadgeRestored(qb422016) writebadgeRestored(qb422016)
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
return qs422016 return qs422016
//line app/vmalert/web.qtpl:360 //line app/vmalert/web.qtpl:362
} }

View file

@ -2,6 +2,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
@ -29,7 +30,7 @@ func TestHandler(t *testing.T) {
t.Helper() t.Helper()
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
t.Errorf("unexpected err %s", err) t.Fatalf("unexpected err %s", err)
} }
if code != resp.StatusCode { if code != resp.StatusCode {
t.Errorf("unexpected status code %d want %d", resp.StatusCode, code) t.Errorf("unexpected status code %d want %d", resp.StatusCode, code)
@ -47,20 +48,72 @@ func TestHandler(t *testing.T) {
} }
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) })) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }))
defer ts.Close() defer ts.Close()
t.Run("/", func(t *testing.T) {
getResp(ts.URL, nil, 200)
getResp(ts.URL+"/vmalert", nil, 200)
getResp(ts.URL+"/vmalert/home", nil, 200)
})
t.Run("/api/v1/alerts", func(t *testing.T) { t.Run("/api/v1/alerts", func(t *testing.T) {
lr := listAlertsResponse{} lr := listAlertsResponse{}
getResp(ts.URL+"/api/v1/alerts", &lr, 200) getResp(ts.URL+"/api/v1/alerts", &lr, 200)
if length := len(lr.Data.Alerts); length != 1 { if length := len(lr.Data.Alerts); length != 1 {
t.Errorf("expected 1 alert got %d", length) t.Errorf("expected 1 alert got %d", length)
} }
lr = listAlertsResponse{}
getResp(ts.URL+"/vmalert/api/v1/alerts", &lr, 200)
if length := len(lr.Data.Alerts); length != 1 {
t.Errorf("expected 1 alert got %d", length)
}
}) })
t.Run("/api/v1/alert?alertID&groupID", func(t *testing.T) {
expAlert := ar.newAlertAPI(*ar.alerts[0])
alert := &APIAlert{}
getResp(ts.URL+"/"+expAlert.APILink(), alert, 200)
if !reflect.DeepEqual(alert, expAlert) {
t.Errorf("expected %v is equal to %v", alert, expAlert)
}
alert = &APIAlert{}
getResp(ts.URL+"/vmalert/"+expAlert.APILink(), alert, 200)
if !reflect.DeepEqual(alert, expAlert) {
t.Errorf("expected %v is equal to %v", alert, expAlert)
}
})
t.Run("/api/v1/alert?badParams", func(t *testing.T) {
params := fmt.Sprintf("?%s=0&%s=1", paramGroupID, paramAlertID)
getResp(ts.URL+"/api/v1/alert"+params, nil, 404)
getResp(ts.URL+"/vmalert/api/v1/alert"+params, nil, 404)
params = fmt.Sprintf("?%s=1&%s=0", paramGroupID, paramAlertID)
getResp(ts.URL+"/api/v1/alert"+params, nil, 404)
getResp(ts.URL+"/vmalert/api/v1/alert"+params, nil, 404)
// bad request, alertID is missing
params = fmt.Sprintf("?%s=1", paramGroupID)
getResp(ts.URL+"/api/v1/alert"+params, nil, 400)
getResp(ts.URL+"/vmalert/api/v1/alert"+params, nil, 400)
})
t.Run("/api/v1/rules", func(t *testing.T) { t.Run("/api/v1/rules", func(t *testing.T) {
lr := listGroupsResponse{} lr := listGroupsResponse{}
getResp(ts.URL+"/api/v1/rules", &lr, 200) getResp(ts.URL+"/api/v1/rules", &lr, 200)
if length := len(lr.Data.Groups); length != 1 { if length := len(lr.Data.Groups); length != 1 {
t.Errorf("expected 1 group got %d", length) t.Errorf("expected 1 group got %d", length)
} }
lr = listGroupsResponse{}
getResp(ts.URL+"/vmalert/api/v1/rules", &lr, 200)
if length := len(lr.Data.Groups); length != 1 {
t.Errorf("expected 1 group got %d", length)
}
}) })
// check deprecated links support
// TODO: remove as soon as deprecated links removed
t.Run("/api/v1/0/0/status", func(t *testing.T) { t.Run("/api/v1/0/0/status", func(t *testing.T) {
alert := &APIAlert{} alert := &APIAlert{}
getResp(ts.URL+"/api/v1/0/0/status", alert, 200) getResp(ts.URL+"/api/v1/0/0/status", alert, 200)
@ -75,7 +128,5 @@ func TestHandler(t *testing.T) {
t.Run("/api/v1/1/0/status", func(t *testing.T) { t.Run("/api/v1/1/0/status", func(t *testing.T) {
getResp(ts.URL+"/api/v1/1/0/status", nil, 404) getResp(ts.URL+"/api/v1/1/0/status", nil, 404)
}) })
t.Run("/", func(t *testing.T) {
getResp(ts.URL, nil, 200)
})
} }

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"time" "time"
) )
@ -33,6 +34,18 @@ type APIAlert struct {
Restored bool `json:"restored"` Restored bool `json:"restored"`
} }
// WebLink returns a link to the alert which can be used in UI.
func (aa *APIAlert) WebLink() string {
return fmt.Sprintf("alert?%s=%s&%s=%s",
paramGroupID, aa.GroupID, paramAlertID, aa.ID)
}
// APILink returns a link to the alert's JSON representation.
func (aa *APIAlert) APILink() string {
return fmt.Sprintf("api/v1/alert?%s=%s&%s=%s",
paramGroupID, aa.GroupID, paramAlertID, aa.ID)
}
// APIGroup represents Group for WEB view // APIGroup represents Group for WEB view
// https://github.com/prometheus/compliance/blob/main/alert_generator/specification.md#get-apiv1rules // https://github.com/prometheus/compliance/blob/main/alert_generator/specification.md#get-apiv1rules
type APIGroup struct { type APIGroup struct {

View file

@ -18,6 +18,12 @@ The following tip changes can be tested by building VictoriaMetrics components f
**Update notes:** this release introduces backwards-incompatible changes to `vm_partial_results_total` metric by changing its labels to be consistent with `vm_requests_total` metric. **Update notes:** this release introduces backwards-incompatible changes to `vm_partial_results_total` metric by changing its labels to be consistent with `vm_requests_total` metric.
If you use alerting rules or Grafana dashboards, which rely on this metric, then they must be updated. The official dashboards for VictoriaMetrics don't use this metric. If you use alerting rules or Grafana dashboards, which rely on this metric, then they must be updated. The official dashboards for VictoriaMetrics don't use this metric.
[vmalert](https://docs.victoriametrics.com/vmalert.html) routing was updated according to [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2825).
The change may affect users with `-http.pathPrefix` flag configured for vmalert. With the update, configuring this flag is no longer needed. Here's [why](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2799#issuecomment-1171392005).
* CHANGE: [vmalert](https://docs.victoriametrics.com/vmalert.html): deprecate alert's status link `/api/v1/<groupID>/<alertID>/status` in favour of `api/v1/alert?group_id=<group_id>&alert_id=<alert_id>"`.
The old alert's status link will be still supported for a few versions but will be removed in the future.
* FEATURE: [cluster version of VictoriaMetrics](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add support for querying lower-level `vmselect` nodes from upper-level `vmselect` nodes. This makes possible to build multi-level cluster setups for global querying view and HA purposes without the need to use [Promxy](https://github.com/jacksontj/promxy). See [these docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#multi-level-cluster-setup) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2778). * FEATURE: [cluster version of VictoriaMetrics](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add support for querying lower-level `vmselect` nodes from upper-level `vmselect` nodes. This makes possible to build multi-level cluster setups for global querying view and HA purposes without the need to use [Promxy](https://github.com/jacksontj/promxy). See [these docs](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html#multi-level-cluster-setup) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2778).
* FEATURE: add `-search.setLookbackToStep` command-line flag, which enables InfluxDB-like gap filling during querying. See [these docs](https://docs.victoriametrics.com/guides/migrate-from-influx.html) for details. * FEATURE: add `-search.setLookbackToStep` command-line flag, which enables InfluxDB-like gap filling during querying. See [these docs](https://docs.victoriametrics.com/guides/migrate-from-influx.html) for details.
* FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add an UI for [query tracing](https://docs.victoriametrics.com/#query-tracing). It can be enabled by clicking `trace query` checkbox and re-running the query. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2703). * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): add an UI for [query tracing](https://docs.victoriametrics.com/#query-tracing). It can be enabled by clicking `trace query` checkbox and re-running the query. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2703).

View file

@ -485,7 +485,7 @@ or time series modification via [relabeling](https://docs.victoriametrics.com/vm
* `http://<vmalert-addr>` - UI; * `http://<vmalert-addr>` - UI;
* `http://<vmalert-addr>/api/v1/rules` - list of all loaded groups and rules; * `http://<vmalert-addr>/api/v1/rules` - list of all loaded groups and rules;
* `http://<vmalert-addr>/api/v1/alerts` - list of all active alerts; * `http://<vmalert-addr>/api/v1/alerts` - list of all active alerts;
* `http://<vmalert-addr>/api/v1/<groupID>/<alertID>/status"` - get alert status by ID. * `http://<vmalert-addr>/vmalert/api/v1/alert?group_id=<group_id>&alert_id=<alert_id>"` - get alert status by ID.
Used as alert source in AlertManager. Used as alert source in AlertManager.
* `http://<vmalert-addr>/metrics` - application metrics. * `http://<vmalert-addr>/metrics` - application metrics.
* `http://<vmalert-addr>/-/reload` - hot configuration reload. * `http://<vmalert-addr>/-/reload` - hot configuration reload.
@ -685,7 +685,7 @@ The shortlist of configuration flags is the following:
How often to evaluate the rules (default 1m0s) How often to evaluate the rules (default 1m0s)
-external.alert.source string -external.alert.source string
External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service. External Alert Source allows to override the Source link for alerts sent to AlertManager for cases where you want to build a custom link to Grafana, Prometheus or any other service.
eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/api/v1/:groupID/alertID/status' is used eg. 'explore?orgId=1&left=[\"now-1h\",\"now\",\"VictoriaMetrics\",{\"expr\": \"{{$expr|quotesEscape|crlfEscape|queryEscape}}\"},{\"mode\":\"Metrics\"},{\"ui\":[true,true,true,\"none\"]}]'.If empty '/vmalert/api/v1/alert?group_id=&alert_id=' is used
-external.label array -external.label array
Optional label in the form 'Name=value' to add to all generated recording rules and alerts. Pass multiple -label flags in order to add multiple label sets. Optional label in the form 'Name=value' to add to all generated recording rules and alerts. Pass multiple -label flags in order to add multiple label sets.
Supports an array of values separated by comma or specified via multiple flags. Supports an array of values separated by comma or specified via multiple flags.