vmalert: print example of curl command for rule's state (#3112)

The change adds an example of `curl` command to the Rule's page.
The command is generated for each recorded state. It is supposed
user can just copy&execute the command to see what was returned
to vmalert.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
Roman Khavronenko 2022-09-15 12:40:22 +02:00 committed by GitHub
parent 455002922e
commit 9c95c81534
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 324 additions and 158 deletions

View File

@ -264,13 +264,14 @@ const resolvedRetention = 15 * time.Minute
// Based on the Querier results AlertingRule maintains notifier.Alerts
func (ar *AlertingRule) Exec(ctx context.Context, ts time.Time, limit int) ([]prompbmarshal.TimeSeries, error) {
start := time.Now()
qMetrics, err := ar.q.Query(ctx, ar.Expr, ts)
qMetrics, req, err := ar.q.Query(ctx, ar.Expr, ts)
curState := ruleStateEntry{
time: start,
at: ts,
duration: time.Since(start),
samples: len(qMetrics),
err: err,
req: req,
}
defer func() {
@ -294,7 +295,10 @@ func (ar *AlertingRule) Exec(ctx context.Context, ts time.Time, limit int) ([]pr
}
}
qFn := func(query string) ([]datasource.Metric, error) { return ar.q.Query(ctx, query, ts) }
qFn := func(query string) ([]datasource.Metric, error) {
res, _, err := ar.q.Query(ctx, query, ts)
return res, err
}
updated := make(map[uint64]struct{})
// update list of active alerts
for _, m := range qMetrics {
@ -595,7 +599,10 @@ func (ar *AlertingRule) Restore(ctx context.Context, q datasource.Querier, lookb
}
ts := time.Now()
qFn := func(query string) ([]datasource.Metric, error) { return ar.q.Query(ctx, query, ts) }
qFn := func(query string) ([]datasource.Metric, error) {
res, _, err := ar.q.Query(ctx, query, ts)
return res, err
}
// account for external labels in filter
var labelsFilter string
@ -605,7 +612,7 @@ func (ar *AlertingRule) Restore(ctx context.Context, q datasource.Querier, lookb
expr := fmt.Sprintf("last_over_time(%s{alertname=%q%s}[%ds])",
alertForStateMetricName, ar.Name, labelsFilter, int(lookback.Seconds()))
qMetrics, err := q.Query(ctx, expr, ts)
qMetrics, _, err := q.Query(ctx, expr, ts)
if err != nil {
return err
}

View File

@ -2,18 +2,27 @@ package datasource
import (
"context"
"net/http"
"net/url"
"time"
)
// Querier interface wraps Query and QueryRange methods
type Querier interface {
Query(ctx context.Context, query string, ts time.Time) ([]Metric, error)
// Query executes instant request with the given query at the given ts.
// It returns list of Metric in response, the http.Request used for sending query
// and error if any. Returned http.Request can't be reused and its body is already read.
// Query should stop once ctx is cancelled.
Query(ctx context.Context, query string, ts time.Time) ([]Metric, *http.Request, error)
// QueryRange executes range request with the given query on the given time range.
// It returns list of Metric in response and error if any.
// QueryRange should stop once ctx is cancelled.
QueryRange(ctx context.Context, query string, from, to time.Time) ([]Metric, error)
}
// QuerierBuilder builds Querier with given params.
type QuerierBuilder interface {
// BuildWithParams creates a new Querier object with the given params
BuildWithParams(params QuerierParams) Querier
}

View File

@ -98,10 +98,10 @@ func NewVMStorage(baseURL string, authCfg *promauth.Config, lookBack time.Durati
}
// Query executes the given query and returns parsed response
func (s *VMStorage) Query(ctx context.Context, query string, ts time.Time) ([]Metric, error) {
func (s *VMStorage) Query(ctx context.Context, query string, ts time.Time) ([]Metric, *http.Request, error) {
req, err := s.newRequestPOST()
if err != nil {
return nil, err
return nil, nil, err
}
switch s.dataSourceType {
@ -110,12 +110,12 @@ func (s *VMStorage) Query(ctx context.Context, query string, ts time.Time) ([]Me
case datasourceGraphite:
s.setGraphiteReqParams(req, query, ts)
default:
return nil, fmt.Errorf("engine not found: %q", s.dataSourceType)
return nil, nil, fmt.Errorf("engine not found: %q", s.dataSourceType)
}
resp, err := s.do(ctx, req)
if err != nil {
return nil, err
return nil, req, err
}
defer func() {
_ = resp.Body.Close()
@ -125,7 +125,8 @@ func (s *VMStorage) Query(ctx context.Context, query string, ts time.Time) ([]Me
if s.dataSourceType != datasourcePrometheus {
parseFn = parseGraphiteResponse
}
return parseFn(req, resp)
result, err := parseFn(req, resp)
return result, req, err
}
// QueryRange executes the given query on the given time range.

View File

@ -94,7 +94,7 @@ func TestVMInstantQuery(t *testing.T) {
ts := time.Now()
expErr := func(err string) {
if _, err := pq.Query(ctx, query, ts); err == nil {
if _, _, err := pq.Query(ctx, query, ts); err == nil {
t.Fatalf("expected %q got nil", err)
}
}
@ -106,7 +106,7 @@ func TestVMInstantQuery(t *testing.T) {
expErr("unknown status") // 4
expErr("non-vector resultType error") // 5
m, err := pq.Query(ctx, query, ts) // 6 - vector
m, _, err := pq.Query(ctx, query, ts) // 6 - vector
if err != nil {
t.Fatalf("unexpected %s", err)
}
@ -129,10 +129,13 @@ func TestVMInstantQuery(t *testing.T) {
t.Fatalf("unexpected metric %+v want %+v", m, expected)
}
m, err = pq.Query(ctx, query, ts) // 7 - scalar
m, req, err := pq.Query(ctx, query, ts) // 7 - scalar
if err != nil {
t.Fatalf("unexpected %s", err)
}
if req == nil {
t.Fatalf("expected request to be non-nil")
}
if len(m) != 1 {
t.Fatalf("expected 1 metrics got %d in %+v", len(m), m)
}
@ -148,7 +151,7 @@ func TestVMInstantQuery(t *testing.T) {
gq := s.BuildWithParams(QuerierParams{DataSourceType: string(datasourceGraphite)})
m, err = gq.Query(ctx, queryRender, ts) // 8 - graphite
m, _, err = gq.Query(ctx, queryRender, ts) // 8 - graphite
if err != nil {
t.Fatalf("unexpected %s", err)
}

View File

@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"net/http"
"reflect"
"sort"
"sync"
@ -44,18 +45,20 @@ func (fq *fakeQuerier) BuildWithParams(_ datasource.QuerierParams) datasource.Qu
}
func (fq *fakeQuerier) QueryRange(ctx context.Context, q string, _, _ time.Time) ([]datasource.Metric, error) {
return fq.Query(ctx, q, time.Now())
req, _, err := fq.Query(ctx, q, time.Now())
return req, err
}
func (fq *fakeQuerier) Query(_ context.Context, _ string, _ time.Time) ([]datasource.Metric, error) {
func (fq *fakeQuerier) Query(_ context.Context, _ string, _ time.Time) ([]datasource.Metric, *http.Request, error) {
fq.Lock()
defer fq.Unlock()
if fq.err != nil {
return nil, fq.err
return nil, nil, fq.err
}
cp := make([]datasource.Metric, len(fq.metrics))
copy(cp, fq.metrics)
return cp, nil
req, _ := http.NewRequest(http.MethodPost, "foo.com", nil)
return cp, req, nil
}
type fakeNotifier struct {

View File

@ -115,12 +115,13 @@ func (rr *RecordingRule) ExecRange(ctx context.Context, start, end time.Time) ([
// Exec executes RecordingRule expression via the given Querier.
func (rr *RecordingRule) Exec(ctx context.Context, ts time.Time, limit int) ([]prompbmarshal.TimeSeries, error) {
start := time.Now()
qMetrics, err := rr.q.Query(ctx, rr.Expr, ts)
qMetrics, req, err := rr.q.Query(ctx, rr.Expr, ts)
curState := ruleStateEntry{
time: start,
at: ts,
duration: time.Since(start),
samples: len(qMetrics),
req: req,
}
defer func() {

View File

@ -3,6 +3,7 @@ package main
import (
"context"
"errors"
"net/http"
"sync"
"time"
@ -53,6 +54,8 @@ type ruleStateEntry struct {
// stores the number of samples returned during
// the last evaluation
samples int
// stores the HTTP request used by datasource during rule.Exec
req *http.Request
}
const defaultStateEntriesLimit = 20

View File

@ -59,6 +59,15 @@
background-color: rgba(0,0,0,.5);
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
}
textarea.curl-area{
width: 100%;
line-height: 1;
font-size: 12px;
border: none;
margin: 0;
padding: 0;
overflow: scroll;
}
</style>
</head>
<body>

View File

@ -101,143 +101,152 @@ func StreamHeader(qw422016 *qt422016.Writer, r *http.Request, navItems []NavItem
background-color: rgba(0,0,0,.5);
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
}
textarea.curl-area{
width: 100%;
line-height: 1;
font-size: 12px;
border: none;
margin: 0;
padding: 0;
overflow: scroll;
}
</style>
</head>
<body>
`)
//line app/vmalert/tpl/header.qtpl:65
//line app/vmalert/tpl/header.qtpl:74
streamprintNavItems(qw422016, r, title, navItems)
//line app/vmalert/tpl/header.qtpl:65
//line app/vmalert/tpl/header.qtpl:74
qw422016.N().S(`
<main class="px-2">
`)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
}
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
func WriteHeader(qq422016 qtio422016.Writer, r *http.Request, navItems []NavItem, title string) {
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
StreamHeader(qw422016, r, navItems, title)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
}
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
func Header(r *http.Request, navItems []NavItem, title string) string {
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
WriteHeader(qb422016, r, navItems, title)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
qs422016 := string(qb422016.B)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
return qs422016
//line app/vmalert/tpl/header.qtpl:67
//line app/vmalert/tpl/header.qtpl:76
}
//line app/vmalert/tpl/header.qtpl:71
//line app/vmalert/tpl/header.qtpl:80
type NavItem struct {
Name string
Url string
}
//line app/vmalert/tpl/header.qtpl:77
//line app/vmalert/tpl/header.qtpl:86
func streamprintNavItems(qw422016 *qt422016.Writer, r *http.Request, current string, items []NavItem) {
//line app/vmalert/tpl/header.qtpl:77
//line app/vmalert/tpl/header.qtpl:86
qw422016.N().S(`
`)
//line app/vmalert/tpl/header.qtpl:79
//line app/vmalert/tpl/header.qtpl:88
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
//line app/vmalert/tpl/header.qtpl:83
//line app/vmalert/tpl/header.qtpl:92
qw422016.N().S(`
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
`)
//line app/vmalert/tpl/header.qtpl:88
//line app/vmalert/tpl/header.qtpl:97
for _, item := range items {
//line app/vmalert/tpl/header.qtpl:88
//line app/vmalert/tpl/header.qtpl:97
qw422016.N().S(`
<li class="nav-item">
`)
//line app/vmalert/tpl/header.qtpl:91
//line app/vmalert/tpl/header.qtpl:100
u, _ := url.Parse(item.Url)
//line app/vmalert/tpl/header.qtpl:92
//line app/vmalert/tpl/header.qtpl:101
qw422016.N().S(`
<a class="nav-link`)
//line app/vmalert/tpl/header.qtpl:93
//line app/vmalert/tpl/header.qtpl:102
if current == item.Name {
//line app/vmalert/tpl/header.qtpl:93
//line app/vmalert/tpl/header.qtpl:102
qw422016.N().S(` active`)
//line app/vmalert/tpl/header.qtpl:93
//line app/vmalert/tpl/header.qtpl:102
}
//line app/vmalert/tpl/header.qtpl:93
//line app/vmalert/tpl/header.qtpl:102
qw422016.N().S(`"
href="`)
//line app/vmalert/tpl/header.qtpl:94
//line app/vmalert/tpl/header.qtpl:103
if u.IsAbs() {
//line app/vmalert/tpl/header.qtpl:94
//line app/vmalert/tpl/header.qtpl:103
qw422016.E().S(item.Url)
//line app/vmalert/tpl/header.qtpl:94
//line app/vmalert/tpl/header.qtpl:103
} else {
//line app/vmalert/tpl/header.qtpl:94
//line app/vmalert/tpl/header.qtpl:103
qw422016.E().S(path.Join(prefix, item.Url))
//line app/vmalert/tpl/header.qtpl:94
//line app/vmalert/tpl/header.qtpl:103
}
//line app/vmalert/tpl/header.qtpl:94
//line app/vmalert/tpl/header.qtpl:103
qw422016.N().S(`">
`)
//line app/vmalert/tpl/header.qtpl:95
//line app/vmalert/tpl/header.qtpl:104
qw422016.E().S(item.Name)
//line app/vmalert/tpl/header.qtpl:95
//line app/vmalert/tpl/header.qtpl:104
qw422016.N().S(`
</a>
</li>
`)
//line app/vmalert/tpl/header.qtpl:98
//line app/vmalert/tpl/header.qtpl:107
}
//line app/vmalert/tpl/header.qtpl:98
//line app/vmalert/tpl/header.qtpl:107
qw422016.N().S(`
</ul>
</div>
</nav>
`)
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
}
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
func writeprintNavItems(qq422016 qtio422016.Writer, r *http.Request, current string, items []NavItem) {
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
streamprintNavItems(qw422016, r, current, items)
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
}
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
func printNavItems(r *http.Request, current string, items []NavItem) string {
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
writeprintNavItems(qb422016, r, current, items)
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
qs422016 := string(qb422016.B)
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
return qs422016
//line app/vmalert/tpl/header.qtpl:102
//line app/vmalert/tpl/header.qtpl:111
}

View File

@ -1,7 +1,10 @@
package main
import (
"fmt"
"net/http"
"sort"
"strings"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
@ -47,3 +50,62 @@ func newTimeSeriesPB(values []float64, timestamps []int64, labels []prompbmarsha
ts.Labels = labels
return ts
}
type curlWriter struct {
b strings.Builder
}
func (cw *curlWriter) string() string {
res := "curl " + cw.b.String()
cw.b.Reset()
return strings.TrimSpace(res)
}
func (cw *curlWriter) addWithEsc(str string) {
escStr := `'` + strings.Replace(str, `'`, `'\''`, -1) + `'`
cw.add(escStr)
}
func (cw *curlWriter) add(str string) {
cw.b.WriteString(str)
cw.b.WriteString(" ")
}
func requestToCurl(req *http.Request) string {
if req.URL == nil {
return ""
}
cw := &curlWriter{}
schema := req.URL.Scheme
requestURL := req.URL.String()
if schema == "" {
schema = "http"
if req.TLS != nil {
schema = "https"
}
requestURL = schema + "://" + req.Host + requestURL
}
if schema == "https" {
cw.add("-k")
}
cw.add("-X")
cw.add(req.Method)
var keys []string
for k := range req.Header {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
cw.add("-H")
cw.addWithEsc(fmt.Sprintf("%s: %s", k, strings.Join(req.Header[k], " ")))
}
cw.addWithEsc(requestURL)
return cw.string()
}

47
app/vmalert/utils_test.go Normal file
View File

@ -0,0 +1,47 @@
package main
import (
"net/http"
"testing"
)
func TestRequestToCurl(t *testing.T) {
f := func(req *http.Request, exp string) {
got := requestToCurl(req)
if got != exp {
t.Fatalf("expected to have %q; got %q instead", exp, got)
}
}
req, _ := http.NewRequest(http.MethodPost, "foo.com", nil)
f(req, "curl -X POST 'http://foo.com'")
req, _ = http.NewRequest(http.MethodGet, "https://foo.com", nil)
f(req, "curl -k -X GET 'https://foo.com'")
req, _ = http.NewRequest(http.MethodPost, "foo.com", nil)
req.Header.Set("foo", "bar")
req.Header.Set("baz", "qux")
f(req, "curl -X POST -H 'Baz: qux' -H 'Foo: bar' 'http://foo.com'")
req, _ = http.NewRequest(http.MethodPost, "foo.com", nil)
params := req.URL.Query()
params.Add("query", "up")
params.Add("step", "10")
req.URL.RawQuery = params.Encode()
f(req, "curl -X POST 'http://foo.com?query=up&step=10'")
req, _ = http.NewRequest(http.MethodPost, "http://foo.com", nil)
params = req.URL.Query()
params.Add("query", "up")
params.Add("step", "10")
req.URL.RawQuery = params.Encode()
f(req, "curl -X POST 'http://foo.com?query=up&step=10'")
req, _ = http.NewRequest(http.MethodPost, "https://foo.com", nil)
params = req.URL.Query()
params.Add("query", "up")
params.Add("step", "10")
req.URL.RawQuery = params.Encode()
f(req, "curl -k -X POST 'https://foo.com?query=up&step=10'")
}

View File

@ -435,10 +435,11 @@
<table class="table table-striped table-hover table-sm">
<thead>
<tr>
<th scope="col" style="width: 20%" title="The time when event was created">Updated at</th>
<th scope="col" style="width: 20%" class="text-center" title="How many samples were returned">Samples</th>
<th scope="col" style="width: 20%" class="text-center" title="How many seconds request took">Duration</th>
<th scope="col" style="width: 20%" class="text-center" title="Time used for rule execution">Executed at</th>
<th scope="col" title="The time when event was created">Updated at</th>
<th scope="col" style="width: 10%" class="text-center" title="How many samples were returned">Samples</th>
<th scope="col" style="width: 10%" class="text-center" title="How many seconds request took">Duration</th>
<th scope="col" class="text-center" title="Time used for rule execution">Executed at</th>
<th scope="col" class="text-center" title="cURL command with request example">cURL</th>
</tr>
</thead>
<tbody>
@ -448,14 +449,17 @@
<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>
<td class="text-center" wi>{%d u.samples %}</td>
<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 requestToCurl(u.req) %}</textarea>
</td>
</tr>
</li>
{% if u.err != nil %}
<tr{% if u.err != nil %} class="alert-danger"{% endif %}>
<td colspan="4">
<td colspan="5">
<span class="alert-danger">{%v u.err %}</span>
</td>
</tr>

View File

@ -1290,203 +1290,211 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
<table class="table table-striped table-hover table-sm">
<thead>
<tr>
<th scope="col" style="width: 20%" title="The time when event was created">Updated at</th>
<th scope="col" style="width: 20%" class="text-center" title="How many samples were returned">Samples</th>
<th scope="col" style="width: 20%" class="text-center" title="How many seconds request took">Duration</th>
<th scope="col" style="width: 20%" class="text-center" title="Time used for rule execution">Executed at</th>
<th scope="col" title="The time when event was created">Updated at</th>
<th scope="col" style="width: 10%" class="text-center" title="How many samples were returned">Samples</th>
<th scope="col" style="width: 10%" class="text-center" title="How many seconds request took">Duration</th>
<th scope="col" class="text-center" title="Time used for rule execution">Executed at</th>
<th scope="col" class="text-center" title="cURL command with request example">cURL</th>
</tr>
</thead>
<tbody>
`)
//line app/vmalert/web.qtpl:446
//line app/vmalert/web.qtpl:447
for _, u := range rule.Updates {
//line app/vmalert/web.qtpl:446
//line app/vmalert/web.qtpl:447
qw422016.N().S(`
<tr`)
//line app/vmalert/web.qtpl:447
//line app/vmalert/web.qtpl:448
if u.err != nil {
//line app/vmalert/web.qtpl:447
//line app/vmalert/web.qtpl:448
qw422016.N().S(` class="alert-danger"`)
//line app/vmalert/web.qtpl:447
//line app/vmalert/web.qtpl:448
}
//line app/vmalert/web.qtpl:447
//line app/vmalert/web.qtpl:448
qw422016.N().S(`>
<td>
<span class="badge bg-primary rounded-pill me-3" title="Updated at">`)
//line app/vmalert/web.qtpl:449
//line app/vmalert/web.qtpl:450
qw422016.E().S(u.time.Format(time.RFC3339))
//line app/vmalert/web.qtpl:449
//line app/vmalert/web.qtpl:450
qw422016.N().S(`</span>
</td>
<td class="text-center">`)
//line app/vmalert/web.qtpl:451
<td class="text-center" wi>`)
//line app/vmalert/web.qtpl:452
qw422016.N().D(u.samples)
//line app/vmalert/web.qtpl:451
//line app/vmalert/web.qtpl:452
qw422016.N().S(`</td>
<td class="text-center">`)
//line app/vmalert/web.qtpl:452
//line app/vmalert/web.qtpl:453
qw422016.N().FPrec(u.duration.Seconds(), 3)
//line app/vmalert/web.qtpl:452
//line app/vmalert/web.qtpl:453
qw422016.N().S(`s</td>
<td class="text-center">`)
//line app/vmalert/web.qtpl:453
//line app/vmalert/web.qtpl:454
qw422016.E().S(u.at.Format(time.RFC3339))
//line app/vmalert/web.qtpl:453
//line app/vmalert/web.qtpl:454
qw422016.N().S(`</td>
<td>
<textarea class="curl-area" rows="1" onclick="this.focus();this.select()">`)
//line app/vmalert/web.qtpl:456
qw422016.E().S(requestToCurl(u.req))
//line app/vmalert/web.qtpl:456
qw422016.N().S(`</textarea>
</td>
</tr>
</li>
`)
//line app/vmalert/web.qtpl:456
//line app/vmalert/web.qtpl:460
if u.err != nil {
//line app/vmalert/web.qtpl:456
//line app/vmalert/web.qtpl:460
qw422016.N().S(`
<tr`)
//line app/vmalert/web.qtpl:457
//line app/vmalert/web.qtpl:461
if u.err != nil {
//line app/vmalert/web.qtpl:457
//line app/vmalert/web.qtpl:461
qw422016.N().S(` class="alert-danger"`)
//line app/vmalert/web.qtpl:457
//line app/vmalert/web.qtpl:461
}
//line app/vmalert/web.qtpl:457
//line app/vmalert/web.qtpl:461
qw422016.N().S(`>
<td colspan="4">
<span class="alert-danger">`)
//line app/vmalert/web.qtpl:459
//line app/vmalert/web.qtpl:463
qw422016.E().V(u.err)
//line app/vmalert/web.qtpl:459
//line app/vmalert/web.qtpl:463
qw422016.N().S(`</span>
</td>
</tr>
`)
//line app/vmalert/web.qtpl:462
//line app/vmalert/web.qtpl:466
}
//line app/vmalert/web.qtpl:462
//line app/vmalert/web.qtpl:466
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:463
//line app/vmalert/web.qtpl:467
}
//line app/vmalert/web.qtpl:463
//line app/vmalert/web.qtpl:467
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:465
//line app/vmalert/web.qtpl:469
tpl.StreamFooter(qw422016, r)
//line app/vmalert/web.qtpl:465
//line app/vmalert/web.qtpl:469
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
}
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
func WriteRuleDetails(qq422016 qtio422016.Writer, r *http.Request, rule APIRule) {
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
StreamRuleDetails(qw422016, r, rule)
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
}
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
func RuleDetails(r *http.Request, rule APIRule) string {
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
WriteRuleDetails(qb422016, r, rule)
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
return qs422016
//line app/vmalert/web.qtpl:466
//line app/vmalert/web.qtpl:470
}
//line app/vmalert/web.qtpl:470
//line app/vmalert/web.qtpl:474
func streambadgeState(qw422016 *qt422016.Writer, state string) {
//line app/vmalert/web.qtpl:470
//line app/vmalert/web.qtpl:474
qw422016.N().S(`
`)
//line app/vmalert/web.qtpl:472
//line app/vmalert/web.qtpl:476
badgeClass := "bg-warning text-dark"
if state == "firing" {
badgeClass = "bg-danger"
}
//line app/vmalert/web.qtpl:476
//line app/vmalert/web.qtpl:480
qw422016.N().S(`
<span class="badge `)
//line app/vmalert/web.qtpl:477
//line app/vmalert/web.qtpl:481
qw422016.E().S(badgeClass)
//line app/vmalert/web.qtpl:477
//line app/vmalert/web.qtpl:481
qw422016.N().S(`">`)
//line app/vmalert/web.qtpl:477
//line app/vmalert/web.qtpl:481
qw422016.E().S(state)
//line app/vmalert/web.qtpl:477
//line app/vmalert/web.qtpl:481
qw422016.N().S(`</span>
`)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
}
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
func writebadgeState(qq422016 qtio422016.Writer, state string) {
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
streambadgeState(qw422016, state)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
}
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
func badgeState(state string) string {
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
writebadgeState(qb422016, state)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
return qs422016
//line app/vmalert/web.qtpl:478
//line app/vmalert/web.qtpl:482
}
//line app/vmalert/web.qtpl:480
//line app/vmalert/web.qtpl:484
func streambadgeRestored(qw422016 *qt422016.Writer) {
//line app/vmalert/web.qtpl:480
//line app/vmalert/web.qtpl:484
qw422016.N().S(`
<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:482
//line app/vmalert/web.qtpl:486
}
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
func writebadgeRestored(qq422016 qtio422016.Writer) {
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
streambadgeRestored(qw422016)
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
}
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
func badgeRestored() string {
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
writebadgeRestored(qb422016)
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
qs422016 := string(qb422016.B)
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
return qs422016
//line app/vmalert/web.qtpl:482
//line app/vmalert/web.qtpl:486
}