vmalert: make UI and assets links relative (#2831)

* make all links in vmalert relative, so links continue to work even if vmalert sits behind the proxy;
* update vmalert's routing to always have component-unique path prefix, e.g. /vmalert;

See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2825

Signed-off-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
Roman Khavronenko 2022-07-06 10:46:01 +02:00 committed by GitHub
parent edc76286ac
commit 84e7c517d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 716 additions and 690 deletions

View file

@ -1,12 +1,19 @@
{% import ( {% import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver" "net/http"
"strings"
) %} ) %}
{% func Footer() %}
{% code pathPrefix := httpserver.GetPathPrefix() %} {% func Footer(r *http.Request) %}
{%code
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
%}
</main> </main>
<script src="{%s pathPrefix %}/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 pathPrefix %}/static/js/bootstrap.bundle.min.js" type="text/javascript"></script> <script src="{%s prefix %}static/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<script type="text/javascript"> <script type="text/javascript">
function expandAll() { function expandAll() {
$('.collapse').addClass('show'); $('.collapse').addClass('show');

View file

@ -6,43 +6,47 @@ package tpl
//line app/vmalert/tpl/footer.qtpl:1 //line app/vmalert/tpl/footer.qtpl:1
import ( import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver" "net/http"
"strings"
) )
//line app/vmalert/tpl/footer.qtpl:5 //line app/vmalert/tpl/footer.qtpl:7
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line app/vmalert/tpl/footer.qtpl:5 //line app/vmalert/tpl/footer.qtpl:7
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line app/vmalert/tpl/footer.qtpl:5 //line app/vmalert/tpl/footer.qtpl:7
func StreamFooter(qw422016 *qt422016.Writer) { func StreamFooter(qw422016 *qt422016.Writer, r *http.Request) {
//line app/vmalert/tpl/footer.qtpl:5 //line app/vmalert/tpl/footer.qtpl:7
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/tpl/footer.qtpl:6 //line app/vmalert/tpl/footer.qtpl:9
pathPrefix := httpserver.GetPathPrefix() prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
//line app/vmalert/tpl/footer.qtpl:6 //line app/vmalert/tpl/footer.qtpl:13
qw422016.N().S(` qw422016.N().S(`
</main> </main>
<script src="`) <script src="`)
//line app/vmalert/tpl/footer.qtpl:8 //line app/vmalert/tpl/footer.qtpl:15
qw422016.E().S(pathPrefix) qw422016.E().S(prefix)
//line app/vmalert/tpl/footer.qtpl:8 //line app/vmalert/tpl/footer.qtpl:15
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:9 //line app/vmalert/tpl/footer.qtpl:16
qw422016.E().S(pathPrefix) qw422016.E().S(prefix)
//line app/vmalert/tpl/footer.qtpl:9 //line app/vmalert/tpl/footer.qtpl:16
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() {
$('.collapse').addClass('show'); $('.collapse').addClass('show');
@ -75,31 +79,31 @@ func StreamFooter(qw422016 *qt422016.Writer) {
</body> </body>
</html> </html>
`) `)
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
} }
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
func WriteFooter(qq422016 qtio422016.Writer) { func WriteFooter(qq422016 qtio422016.Writer, r *http.Request) {
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
StreamFooter(qw422016) StreamFooter(qw422016, r)
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
} }
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
func Footer() string { func Footer(r *http.Request) string {
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
WriteFooter(qb422016) WriteFooter(qb422016, r)
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
return qs422016 return qs422016
//line app/vmalert/tpl/footer.qtpl:41 //line app/vmalert/tpl/footer.qtpl:48
} }

View file

@ -1,14 +1,21 @@
{% import ( {% import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver" "strings"
"net/http"
"path"
) %} ) %}
{% func Header(title string, pages []NavItem) %} {% func Header(r *http.Request, navItems []NavItem, title string) %}
{% code pathPrefix := httpserver.GetPathPrefix() %} {%code
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
%}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>vmalert{% if title != "" %} - {%s title %}{% endif %}</title> <title>vmalert{% if title != "" %} - {%s title %}{% endif %}</title>
<link href="{%s pathPrefix %}/static/css/bootstrap.min.css" rel="stylesheet" /> <link href="{%s prefix %}static/css/bootstrap.min.css" rel="stylesheet" />
<style> <style>
body{ body{
min-height: 75rem; min-height: 75rem;
@ -57,6 +64,37 @@
</style> </style>
</head> </head>
<body> <body>
{%= PrintNavItems(title, pages) %} {%= printNavItems(r, title, navItems) %}
<main class="px-2"> <main class="px-2">
{% endfunc %} {% endfunc %}
{% code
type NavItem struct {
Name string
Url string
}
%}
{% func printNavItems(r *http.Request, current string, items []NavItem) %}
{%code
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
%}
<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">
{% for _, item := range items %}
<li class="nav-item">
<a class="nav-link{% if current == item.Name %} active{% endif %}" href="{%s path.Join(prefix,item.Url) %}">
{%s item.Name %}
</a>
</li>
{% endfor %}
</ul>
</div>
</nav>
{% endfunc %}

View file

@ -6,51 +6,56 @@ package tpl
//line app/vmalert/tpl/header.qtpl:1 //line app/vmalert/tpl/header.qtpl:1
import ( import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver" "net/http"
"path"
"strings"
) )
//line app/vmalert/tpl/header.qtpl:5 //line app/vmalert/tpl/header.qtpl:7
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line app/vmalert/tpl/header.qtpl:5 //line app/vmalert/tpl/header.qtpl:7
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line app/vmalert/tpl/header.qtpl:5 //line app/vmalert/tpl/header.qtpl:7
func StreamHeader(qw422016 *qt422016.Writer, title string, pages []NavItem) { func StreamHeader(qw422016 *qt422016.Writer, r *http.Request, navItems []NavItem, title string) {
//line app/vmalert/tpl/header.qtpl:5 //line app/vmalert/tpl/header.qtpl:7
qw422016.N().S(` qw422016.N().S(`
`) `)
//line app/vmalert/tpl/header.qtpl:6 //line app/vmalert/tpl/header.qtpl:9
pathPrefix := httpserver.GetPathPrefix() prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
//line app/vmalert/tpl/header.qtpl:6 //line app/vmalert/tpl/header.qtpl:13
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:10 //line app/vmalert/tpl/header.qtpl:17
if title != "" { if title != "" {
//line app/vmalert/tpl/header.qtpl:10 //line app/vmalert/tpl/header.qtpl:17
qw422016.N().S(` - `) qw422016.N().S(` - `)
//line app/vmalert/tpl/header.qtpl:10 //line app/vmalert/tpl/header.qtpl:17
qw422016.E().S(title) qw422016.E().S(title)
//line app/vmalert/tpl/header.qtpl:10 //line app/vmalert/tpl/header.qtpl:17
} }
//line app/vmalert/tpl/header.qtpl:10 //line app/vmalert/tpl/header.qtpl:17
qw422016.N().S(`</title> qw422016.N().S(`</title>
<link href="`) <link href="`)
//line app/vmalert/tpl/header.qtpl:11 //line app/vmalert/tpl/header.qtpl:18
qw422016.E().S(pathPrefix) qw422016.E().S(prefix)
//line app/vmalert/tpl/header.qtpl:11 //line app/vmalert/tpl/header.qtpl:18
qw422016.N().S(`/static/css/bootstrap.min.css" rel="stylesheet" /> qw422016.N().S(`static/css/bootstrap.min.css" rel="stylesheet" />
<style> <style>
body{ body{
min-height: 75rem; min-height: 75rem;
@ -100,37 +105,124 @@ func StreamHeader(qw422016 *qt422016.Writer, title string, pages []NavItem) {
</head> </head>
<body> <body>
`) `)
//line app/vmalert/tpl/header.qtpl:60 //line app/vmalert/tpl/header.qtpl:67
StreamPrintNavItems(qw422016, title, pages) streamprintNavItems(qw422016, r, title, navItems)
//line app/vmalert/tpl/header.qtpl:60 //line app/vmalert/tpl/header.qtpl:67
qw422016.N().S(` qw422016.N().S(`
<main class="px-2"> <main class="px-2">
`) `)
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
} }
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
func WriteHeader(qq422016 qtio422016.Writer, title string, pages []NavItem) { func WriteHeader(qq422016 qtio422016.Writer, r *http.Request, navItems []NavItem, title string) {
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
StreamHeader(qw422016, title, pages) StreamHeader(qw422016, r, navItems, title)
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
} }
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
func Header(title string, pages []NavItem) string { func Header(r *http.Request, navItems []NavItem, title string) string {
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
WriteHeader(qb422016, title, pages) WriteHeader(qb422016, r, navItems, title)
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
return qs422016 return qs422016
//line app/vmalert/tpl/header.qtpl:62 //line app/vmalert/tpl/header.qtpl:69
}
//line app/vmalert/tpl/header.qtpl:73
type NavItem struct {
Name string
Url string
}
//line app/vmalert/tpl/header.qtpl:79
func streamprintNavItems(qw422016 *qt422016.Writer, r *http.Request, current string, items []NavItem) {
//line app/vmalert/tpl/header.qtpl:79
qw422016.N().S(`
`)
//line app/vmalert/tpl/header.qtpl:81
prefix := "/vmalert/"
if strings.HasPrefix(r.URL.Path, prefix) {
prefix = ""
}
//line app/vmalert/tpl/header.qtpl:85
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:90
for _, item := range items {
//line app/vmalert/tpl/header.qtpl:90
qw422016.N().S(`
<li class="nav-item">
<a class="nav-link`)
//line app/vmalert/tpl/header.qtpl:92
if current == item.Name {
//line app/vmalert/tpl/header.qtpl:92
qw422016.N().S(` active`)
//line app/vmalert/tpl/header.qtpl:92
}
//line app/vmalert/tpl/header.qtpl:92
qw422016.N().S(`" href="`)
//line app/vmalert/tpl/header.qtpl:92
qw422016.E().S(path.Join(prefix, item.Url))
//line app/vmalert/tpl/header.qtpl:92
qw422016.N().S(`">
`)
//line app/vmalert/tpl/header.qtpl:93
qw422016.E().S(item.Name)
//line app/vmalert/tpl/header.qtpl:93
qw422016.N().S(`
</a>
</li>
`)
//line app/vmalert/tpl/header.qtpl:96
}
//line app/vmalert/tpl/header.qtpl:96
qw422016.N().S(`
</ul>
</div>
</nav>
`)
//line app/vmalert/tpl/header.qtpl:100
}
//line app/vmalert/tpl/header.qtpl:100
func writeprintNavItems(qq422016 qtio422016.Writer, r *http.Request, current string, items []NavItem) {
//line app/vmalert/tpl/header.qtpl:100
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/header.qtpl:100
streamprintNavItems(qw422016, r, current, items)
//line app/vmalert/tpl/header.qtpl:100
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/header.qtpl:100
}
//line app/vmalert/tpl/header.qtpl:100
func printNavItems(r *http.Request, current string, items []NavItem) string {
//line app/vmalert/tpl/header.qtpl:100
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/header.qtpl:100
writeprintNavItems(qb422016, r, current, items)
//line app/vmalert/tpl/header.qtpl:100
qs422016 := string(qb422016.B)
//line app/vmalert/tpl/header.qtpl:100
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/header.qtpl:100
return qs422016
//line app/vmalert/tpl/header.qtpl:100
} }

View file

@ -1,25 +0,0 @@
{% code
type NavItem struct {
Name string
Url string
}
%}
{% func PrintNavItems(current string, items []NavItem) %}
<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">
{% for _, item := range items %}
<li class="nav-item">
<a class="nav-link{% if current == item.Name %} active{% endif %}" href="{%s item.Url %}">
{%s item.Name %}
</a>
</li>
{% endfor %}
</ul>
</div>
</nav>
{% endfunc %}

View file

@ -1,96 +0,0 @@
// Code generated by qtc from "nav.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line app/vmalert/tpl/nav.qtpl:1
package tpl
//line app/vmalert/tpl/nav.qtpl:1
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line app/vmalert/tpl/nav.qtpl:1
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line app/vmalert/tpl/nav.qtpl:2
type NavItem struct {
Name string
Url string
}
//line app/vmalert/tpl/nav.qtpl:8
func StreamPrintNavItems(qw422016 *qt422016.Writer, current string, items []NavItem) {
//line app/vmalert/tpl/nav.qtpl:8
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/nav.qtpl:13
for _, item := range items {
//line app/vmalert/tpl/nav.qtpl:13
qw422016.N().S(`
<li class="nav-item">
<a class="nav-link`)
//line app/vmalert/tpl/nav.qtpl:15
if current == item.Name {
//line app/vmalert/tpl/nav.qtpl:15
qw422016.N().S(` active`)
//line app/vmalert/tpl/nav.qtpl:15
}
//line app/vmalert/tpl/nav.qtpl:15
qw422016.N().S(`" href="`)
//line app/vmalert/tpl/nav.qtpl:15
qw422016.E().S(item.Url)
//line app/vmalert/tpl/nav.qtpl:15
qw422016.N().S(`">
`)
//line app/vmalert/tpl/nav.qtpl:16
qw422016.E().S(item.Name)
//line app/vmalert/tpl/nav.qtpl:16
qw422016.N().S(`
</a>
</li>
`)
//line app/vmalert/tpl/nav.qtpl:19
}
//line app/vmalert/tpl/nav.qtpl:19
qw422016.N().S(`
</ul>
</div>
</nav>
`)
//line app/vmalert/tpl/nav.qtpl:23
}
//line app/vmalert/tpl/nav.qtpl:23
func WritePrintNavItems(qq422016 qtio422016.Writer, current string, items []NavItem) {
//line app/vmalert/tpl/nav.qtpl:23
qw422016 := qt422016.AcquireWriter(qq422016)
//line app/vmalert/tpl/nav.qtpl:23
StreamPrintNavItems(qw422016, current, items)
//line app/vmalert/tpl/nav.qtpl:23
qt422016.ReleaseWriter(qw422016)
//line app/vmalert/tpl/nav.qtpl:23
}
//line app/vmalert/tpl/nav.qtpl:23
func PrintNavItems(current string, items []NavItem) string {
//line app/vmalert/tpl/nav.qtpl:23
qb422016 := qt422016.AcquireByteBuffer()
//line app/vmalert/tpl/nav.qtpl:23
WritePrintNavItems(qb422016, current, items)
//line app/vmalert/tpl/nav.qtpl:23
qs422016 := string(qb422016.B)
//line app/vmalert/tpl/nav.qtpl:23
qt422016.ReleaseByteBuffer(qb422016)
//line app/vmalert/tpl/nav.qtpl:23
return qs422016
//line app/vmalert/tpl/nav.qtpl:23
}

View file

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"path"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -24,30 +23,24 @@ var (
navItems []tpl.NavItem navItems []tpl.NavItem
) )
var (
//go:embed static
staticFiles embed.FS
staticServer = http.FileServer(http.FS(staticFiles))
)
func initLinks() { func initLinks() {
pathPrefix := httpserver.GetPathPrefix()
if pathPrefix == "" {
pathPrefix = "/"
}
apiLinks = [][2]string{ apiLinks = [][2]string{
{path.Join(pathPrefix, "api/v1/rules"), "list all loaded groups and rules"}, // api links are relative since they can be used by external clients
{path.Join(pathPrefix, "api/v1/alerts"), "list all active alerts"}, // such as Grafana and proxied via vmselect.
{path.Join(pathPrefix, "api/v1/groupID/alertID/status"), "get alert status by ID"}, {"api/v1/rules", "list all loaded groups and rules"},
{path.Join(pathPrefix, "flags"), "command-line flags"}, {"api/v1/alerts", "list all active alerts"},
{path.Join(pathPrefix, "metrics"), "list of application metrics"}, {"api/v1/groupID/alertID/status", "get alert status by ID"},
{path.Join(pathPrefix, "-/reload"), "reload configuration"},
// system links
{"/flags", "command-line flags"},
{"/metrics", "list of application metrics"},
{"/-/reload", "reload configuration"},
} }
navItems = []tpl.NavItem{ navItems = []tpl.NavItem{
{Name: "vmalert", Url: path.Join(pathPrefix, "/")}, {Name: "vmalert", Url: "home"},
{Name: "Groups", Url: path.Join(pathPrefix, "groups")}, {Name: "Groups", Url: "groups"},
{Name: "Alerts", Url: path.Join(pathPrefix, "alerts")}, {Name: "Alerts", Url: "alerts"},
{Name: "Notifiers", Url: path.Join(pathPrefix, "notifiers")}, {Name: "Notifiers", Url: "notifiers"},
{Name: "Docs", Url: "https://docs.victoriametrics.com/vmalert.html"}, {Name: "Docs", Url: "https://docs.victoriametrics.com/vmalert.html"},
} }
} }
@ -56,33 +49,50 @@ type requestHandler struct {
m *manager m *manager
} }
var (
//go:embed static
staticFiles embed.FS
staticHandler = http.FileServer(http.FS(staticFiles))
staticServer = http.StripPrefix("/vmalert", staticHandler)
)
func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool { func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
once.Do(func() { once.Do(func() {
initLinks() initLinks()
}) })
pathPrefix := httpserver.GetPathPrefix() if strings.HasPrefix(r.URL.Path, "/vmalert/static") {
if pathPrefix == "" { staticServer.ServeHTTP(w, r)
pathPrefix = "/" return true
} }
switch r.URL.Path { switch r.URL.Path {
case "/": case "/", "/vmalert", "/vmalert/home":
if r.Method != "GET" { if r.Method != "GET" {
return false return false
} }
WriteWelcome(w) WriteWelcome(w, r)
return true return true
case "/alerts": case "/vmalert/alerts":
WriteListAlerts(w, pathPrefix, rh.groupAlerts()) WriteListAlerts(w, r, rh.groupAlerts())
return true return true
case "/groups", "/rules": case "/vmalert/groups":
WriteListGroups(w, rh.groups()) WriteListGroups(w, r, rh.groups())
return true return true
case "/notifiers": case "/vmalert/notifiers":
WriteListTargets(w, notifier.GetTargets()) WriteListTargets(w, r, notifier.GetTargets())
return true return true
case "/api/v1/rules":
// special cases for Grafana requests,
// served without `vmalert` prefix:
case "/rules":
// Grafana makes an extra request to `/rules`
// handler in addition to `/api/v1/rules` calls in alerts UI,
WriteListGroups(w, r, rh.groups())
return true
case "/vmalert/api/v1/rules", "/api/v1/rules":
// path used by Grafana for ng alerting
data, err := rh.listGroups() data, err := rh.listGroups()
if err != nil { if err != nil {
httpserver.Errorf(w, r, "%s", err) httpserver.Errorf(w, r, "%s", err)
@ -91,7 +101,8 @@ 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 "/api/v1/alerts": case "/vmalert/api/v1/alerts", "/api/v1/alerts":
// path used by Grafana for ng alerting
data, err := rh.listAlerts() data, err := rh.listAlerts()
if err != nil { if err != nil {
httpserver.Errorf(w, r, "%s", err) httpserver.Errorf(w, r, "%s", err)
@ -100,17 +111,14 @@ 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 "/-/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()
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
return true return true
default:
if strings.HasPrefix(r.URL.Path, "/static") {
staticServer.ServeHTTP(w, r)
return true
}
default:
if !strings.HasSuffix(r.URL.Path, "/status") { if !strings.HasSuffix(r.URL.Path, "/status") {
return false return false
} }
@ -133,7 +141,7 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool {
} }
// <groupID>/<alertID>/status // <groupID>/<alertID>/status
WriteAlert(w, pathPrefix, alert) WriteAlert(w, r, alert)
return true return true
} }
} }

View file

@ -4,14 +4,15 @@
"time" "time"
"sort" "sort"
"path" "path"
"net/http"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/tpl"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
) %} ) %}
{% func Welcome() %} {% func Welcome(r *http.Request) %}
{%= tpl.Header("vmalert", navItems) %} {%= tpl.Header(r, navItems, "vmalert") %}
<p> <p>
API:<br> API:<br>
{% for _, p := range apiLinks %} {% for _, p := range apiLinks %}
@ -21,11 +22,11 @@
<a href="{%s p %}">{%s p %}</a> - {%s doc %}<br/> <a href="{%s p %}">{%s p %}</a> - {%s doc %}<br/>
{% endfor %} {% endfor %}
</p> </p>
{%= tpl.Footer() %} {%= tpl.Footer(r) %}
{% endfunc %} {% endfunc %}
{% func ListGroups(groups []APIGroup) %} {% func ListGroups(r *http.Request, groups []APIGroup) %}
{%= tpl.Header("Groups", navItems) %} {%= tpl.Header(r, navItems, "Groups") %}
{% if len(groups) > 0 %} {% if len(groups) > 0 %}
{%code {%code
rOk := make(map[string]int) rOk := make(map[string]int)
@ -112,13 +113,13 @@
</div> </div>
{% endif %} {% endif %}
{%= tpl.Footer() %} {%= tpl.Footer(r) %}
{% endfunc %} {% endfunc %}
{% func ListAlerts(pathPrefix string, groupAlerts []GroupAlerts) %} {% func ListAlerts(r *http.Request, groupAlerts []GroupAlerts) %}
{%= tpl.Header("Alerts", navItems) %} {%= 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>
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a> <a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
@ -182,7 +183,7 @@
</td> </td>
<td>{%s ar.Value %}</td> <td>{%s ar.Value %}</td>
<td> <td>
<a href="{%s path.Join(pathPrefix, g.ID, ar.ID, "status") %}">Details</a> <a href="{%s path.Join(g.ID, ar.ID, "status") %}">Details</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -199,12 +200,12 @@
</div> </div>
{% endif %} {% endif %}
{%= tpl.Footer() %} {%= tpl.Footer(r) %}
{% endfunc %} {% endfunc %}
{% func ListTargets(targets map[notifier.TargetType][]notifier.Target) %} {% func ListTargets(r *http.Request, targets map[notifier.TargetType][]notifier.Target) %}
{%= tpl.Header("Notifiers", navItems) %} {%= tpl.Header(r, navItems, "Notifiers") %}
{% if len(targets) > 0 %} {% if len(targets) > 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>
<a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a> <a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
@ -255,12 +256,12 @@
</div> </div>
{% endif %} {% endif %}
{%= tpl.Footer() %} {%= tpl.Footer(r) %}
{% endfunc %} {% endfunc %}
{% func Alert(pathPrefix string, alert *APIAlert) %} {% func Alert(r *http.Request, alert *APIAlert) %}
{%= tpl.Header("", navItems) %} {%= tpl.Header(r, navItems, "") %}
{%code {%code
var labelKeys []string var labelKeys []string
for k := range alert.Labels { for k := range alert.Labels {
@ -326,7 +327,7 @@
Group Group
</div> </div>
<div class="col"> <div class="col">
<a target="_blank" href="{%s path.Join(pathPrefix,"groups") %}#group-{%s alert.GroupID %}">{%s alert.GroupID %}</a> <a target="_blank" href="/groups#group-{%s alert.GroupID %}">{%s alert.GroupID %}</a>
</div> </div>
</div> </div>
</div> </div>
@ -340,7 +341,7 @@
</div> </div>
</div> </div>
</div> </div>
{%= tpl.Footer() %} {%= tpl.Footer(r) %}
{% endfunc %} {% endfunc %}

File diff suppressed because it is too large Load diff