Browse Source

Make React UI the default, keep old UI under /classic (#8142)

The React app's assets are now served under /assets, while all old
custom web assets (including the ones for console templates) are now
served from /classic/static.

I tested different combinations of --web.external-url and
--web.route-prefix with proxies in front, and I couldn't find a problem
yet with the routing. Console templates also still work.

While migrating old endpoints to /classic, I noticed that /version was
being treated like a lot of the old UI pages, with readiness check
handler in front of it, etc. I kept it in /version and removed that
readiness wrapper, since it doesn't seem to be needed for that endpoint.

Signed-off-by: Julius Volz <julius.volz@gmail.com>
pull/8148/head
Julius Volz 4 years ago committed by GitHub
parent
commit
3470ee1fbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      Makefile
  2. 22
      console_libraries/prom.lib
  3. 2
      docs/configuration/template_reference.md
  4. 2
      web/ui/react-app/src/Navbar.tsx
  5. 2
      web/ui/react-app/src/constants/constants.tsx
  6. 6
      web/ui/react-app/src/pages/targets/ScrapePoolList.test.tsx
  7. 2
      web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.test.tsx
  8. 4
      web/ui/static/js/graph/graph_template.handlebar
  9. 2
      web/ui/static/js/graph/index.js
  10. 6
      web/ui/static/js/prom_console.js
  11. 35
      web/ui/templates/_base.html
  12. 6
      web/ui/templates/alerts.html
  13. 32
      web/ui/templates/graph.html
  14. 4
      web/ui/templates/rules.html
  15. 4
      web/ui/templates/service-discovery.html
  16. 4
      web/ui/templates/targets.html
  17. 112
      web/web.go
  18. 40
      web/web_test.go

11
Makefile

@ -15,10 +15,11 @@
DOCKER_ARCHS ?= amd64 armv7 arm64 ppc64le s390x
REACT_APP_PATH = web/ui/react-app
REACT_APP_SOURCE_FILES = $(wildcard $(REACT_APP_PATH)/public/* $(REACT_APP_PATH)/src/* $(REACT_APP_PATH)/tsconfig.json)
REACT_APP_SOURCE_FILES = $(shell find $(REACT_APP_PATH)/public/ $(REACT_APP_PATH)/src/ $(REACT_APP_PATH)/tsconfig.json)
REACT_APP_OUTPUT_DIR = web/ui/static/react
REACT_APP_NODE_MODULES_PATH = $(REACT_APP_PATH)/node_modules
REACT_APP_NPM_LICENSES_TARBALL = "npm_licenses.tar.bz2"
REACT_APP_BUILD_SCRIPT = ./scripts/build_react_app.sh
PROMTOOL = ./promtool
TSDB_BENCHMARK_NUM_METRICS ?= 1000
@ -32,9 +33,9 @@ DOCKER_IMAGE_NAME ?= prometheus
$(REACT_APP_NODE_MODULES_PATH): $(REACT_APP_PATH)/package.json $(REACT_APP_PATH)/yarn.lock
cd $(REACT_APP_PATH) && yarn --frozen-lockfile
$(REACT_APP_OUTPUT_DIR): $(REACT_APP_NODE_MODULES_PATH) $(REACT_APP_SOURCE_FILES)
$(REACT_APP_OUTPUT_DIR): $(REACT_APP_NODE_MODULES_PATH) $(REACT_APP_SOURCE_FILES) $(REACT_APP_BUILD_SCRIPT)
@echo ">> building React app"
@./scripts/build_react_app.sh
@$(REACT_APP_BUILD_SCRIPT)
.PHONY: assets
assets: $(REACT_APP_OUTPUT_DIR)
@ -46,12 +47,12 @@ assets: $(REACT_APP_OUTPUT_DIR)
@$(GOFMT) -w ./web/ui
.PHONY: react-app-lint
react-app-lint:
react-app-lint:
@echo ">> running React app linting"
cd $(REACT_APP_PATH) && yarn lint:ci
.PHONY: react-app-lint-fix
react-app-lint-fix:
react-app-lint-fix:
@echo ">> running React app linting and fixing errors where possible"
cd $(REACT_APP_PATH) && yarn lint

22
console_libraries/prom.lib

@ -1,21 +1,21 @@
{{/* vim: set ft=html: */}}
{{/* Load Prometheus console library JS/CSS. Should go in <head> */}}
{{ define "prom_console_head" }}
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/bootstrap-4.5.2/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/prom_console.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/bootstrap4-glyphicons/css/bootstrap-glyphicons.min.css">
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.v3.js"></script>
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.layout.min.js"></script>
<script src="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.js"></script>
<script src="{{ pathPrefix }}/static/vendor/js/jquery-3.5.1.min.js"></script>
<script src="{{ pathPrefix }}/static/vendor/js/popper.min.js"></script>
<script src="{{ pathPrefix }}/static/vendor/bootstrap-4.5.2/js/bootstrap.min.js"></script>
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/vendor/rickshaw/rickshaw.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/vendor/bootstrap-4.5.2/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/css/prom_console.css">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/vendor/bootstrap4-glyphicons/css/bootstrap-glyphicons.min.css">
<script src="{{ pathPrefix }}/classic/static/vendor/rickshaw/vendor/d3.v3.js"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/rickshaw/vendor/d3.layout.min.js"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/rickshaw/rickshaw.min.js"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/js/jquery-3.5.1.min.js"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/js/popper.min.js"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/bootstrap-4.5.2/js/bootstrap.min.js"></script>
<script>
var PATH_PREFIX = "{{ pathPrefix }}";
</script>
<script src="{{ pathPrefix }}/static/js/prom_console.js"></script>
<script src="{{ pathPrefix }}/classic/static/js/prom_console.js"></script>
{{ end }}
{{/* Top of all pages. */}}

2
docs/configuration/template_reference.md

@ -73,7 +73,7 @@ versions.
| match | pattern, text | boolean | [regexp.MatchString](https://golang.org/pkg/regexp/#MatchString) Tests for a unanchored regexp match. |
| reReplaceAll | pattern, replacement, text | string | [Regexp.ReplaceAllString](https://golang.org/pkg/regexp/#Regexp.ReplaceAllString) Regexp substitution, unanchored. |
| graphLink | expr | string | Returns path to graph view in the [expression browser](https://prometheus.io/docs/visualization/browser/) for the expression. |
| tableLink | expr | string | Returns path to tabular ("Console") view in the [expression browser](https://prometheus.io/docs/visualization/browser/) for the expression. |
| tableLink | expr | string | Returns path to tabular ("Table") view in the [expression browser](https://prometheus.io/docs/visualization/browser/) for the expression. |
### Others

2
web/ui/react-app/src/Navbar.tsx

@ -77,7 +77,7 @@ const Navigation: FC<NavbarProps> = ({ consolesLink }) => {
<NavLink href="https://prometheus.io/docs/prometheus/latest/getting_started/">Help</NavLink>
</NavItem>
<NavItem>
<NavLink href={`${pathPrefix}/../graph${window.location.search}`}>Classic UI</NavLink>
<NavLink href={`${pathPrefix}/classic/graph${window.location.search}`}>Classic UI</NavLink>
</NavItem>
</Nav>
</Collapse>

2
web/ui/react-app/src/constants/constants.tsx

@ -1 +1 @@
export const API_PATH = '../api/v1';
export const API_PATH = 'api/v1';

6
web/ui/react-app/src/pages/targets/ScrapePoolList.test.tsx

@ -43,7 +43,7 @@ describe('ScrapePoolList', () => {
);
});
scrapePoolList.update();
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/targets?state=active', {
expect(mock).toHaveBeenCalledWith('/path/prefix/api/v1/targets?state=active', {
cache: 'no-store',
credentials: 'same-origin',
});
@ -69,7 +69,7 @@ describe('ScrapePoolList', () => {
);
});
scrapePoolList.update();
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/targets?state=active', {
expect(mock).toHaveBeenCalledWith('/path/prefix/api/v1/targets?state=active', {
cache: 'no-store',
credentials: 'same-origin',
});
@ -92,7 +92,7 @@ describe('ScrapePoolList', () => {
});
scrapePoolList.update();
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/targets?state=active', {
expect(mock).toHaveBeenCalledWith('/path/prefix/api/v1/targets?state=active', {
cache: 'no-store',
credentials: 'same-origin',
});

2
web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.test.tsx

@ -75,7 +75,7 @@ describe('TSDB Stats', () => {
});
page.update();
expect(mock).toHaveBeenCalledWith('/path/prefix/../api/v1/status/tsdb', {
expect(mock).toHaveBeenCalledWith('/path/prefix/api/v1/status/tsdb', {
cache: 'no-store',
credentials: 'same-origin',
});

4
web/ui/static/js/graph/graph_template.handlebar

@ -6,10 +6,10 @@
</div>
<div class="col-lg-2">
<div class="eval_stats float-right"></div>
<img src="{{ pathPrefix }}/static/img/ajax-loader.gif?v={{ buildVersion }}" class="spinner" alt="ajax_spinner">
<img src="{{ pathPrefix }}/classic/static/img/ajax-loader.gif?v={{ buildVersion }}" class="spinner" alt="ajax_spinner">
</div>
</div>
<div class="form-inline">
<div class="form-inline">
<input class="btn btn-primary execute_btn" type="submit" value="Execute" name="submit">
<select class="custom-select form-control expression_select" name="insert_metric">
<option value="">- insert metric at cursor -</option>

2
web/ui/static/js/graph/index.js

@ -1199,7 +1199,7 @@ function init() {
});
$.ajax({
url: PATH_PREFIX + "/static/js/graph/graph_template.handlebar?v=" + BUILD_VERSION,
url: PATH_PREFIX + "/classic/static/js/graph/graph_template.handlebar?v=" + BUILD_VERSION,
success: function(data) {
graphTemplate = data;

6
web/ui/static/js/prom_console.js

@ -612,7 +612,7 @@ PromConsole.Graph.prototype.dispatch = function() {
}
var loadingImg = document.createElement("img");
loadingImg.src = PATH_PREFIX + '/static/img/ajax-loader.gif';
loadingImg.src = PATH_PREFIX + '/classic/static/img/ajax-loader.gif';
loadingImg.alt = 'Loading...';
loadingImg.className = 'prom_graph_loading';
this.graphTd.appendChild(loadingImg);
@ -645,7 +645,7 @@ PromConsole._chooseNameFunction = function(data) {
}
return name + "}";
};
// If only one label varies, use that value.
var labelValues = {};
for (var e = 0; e < data.length; e++) {
@ -658,7 +658,7 @@ PromConsole._chooseNameFunction = function(data) {
}
}
}
var multiValueLabels = [];
for (var label in labelValues) {
if (Object.keys(labelValues[label]).length > 1) {

35
web/ui/templates/_base.html

@ -4,14 +4,14 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="robots" content="noindex,nofollow">
<title>{{ pageTitle }}</title>
<link rel="shortcut icon" href="{{ pathPrefix }}/static/img/favicon.ico?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/static/vendor/js/jquery-3.5.1.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/js/popper.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/bootstrap-4.5.2/js/bootstrap.min.js?v={{ buildVersion }}"></script>
<link rel="shortcut icon" href="{{ pathPrefix }}/classic/static/img/favicon.ico?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/classic/static/vendor/js/jquery-3.5.1.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/js/popper.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/bootstrap-4.5.2/js/bootstrap.min.js?v={{ buildVersion }}"></script>
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/bootstrap-4.5.2/css/bootstrap.min.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/prometheus.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/bootstrap4-glyphicons/css/bootstrap-glyphicons.min.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/vendor/bootstrap-4.5.2/css/bootstrap.min.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/css/prometheus.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/vendor/bootstrap4-glyphicons/css/bootstrap-glyphicons.min.css?v={{ buildVersion }}">
<script>
var PATH_PREFIX = "{{ pathPrefix }}";
@ -26,7 +26,7 @@
<body>
<nav class="navbar fixed-top navbar-expand-sm navbar-dark bg-dark">
<div class="container-fluid">
<div class="container-fluid">
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#nav-content" aria-expanded="false" aria-controls="nav-content" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
@ -45,22 +45,23 @@
{{if $consoles}}
<li class="nav-item"><a class="nav-link" href="{{$consoles}}">Consoles</a></li>
{{ end }}
<li class="nav-item"><a class="nav-link" href="{{ pathPrefix }}/alerts">Alerts</a></li>
<li class="nav-item"><a class="nav-link" href="{{ pathPrefix }}/graph">Graph</a></li>
<li class="nav-item"><a class="nav-link" href="{{ pathPrefix }}/classic/alerts">Alerts</a></li>
<li class="nav-item"><a class="nav-link" href="{{ pathPrefix }}/classic/graph">Graph</a></li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Status <span class="caret"></span></a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{{ pathPrefix }}/status">Runtime &amp; Build Information</a>
<a class="dropdown-item" href="{{ pathPrefix }}/flags">Command-Line Flags</a>
<a class="dropdown-item" href="{{ pathPrefix }}/config">Configuration</a>
<a class="dropdown-item" href="{{ pathPrefix }}/rules">Rules</a>
<a class="dropdown-item" href="{{ pathPrefix }}/targets">Targets</a>
<a class="dropdown-item" href="{{ pathPrefix }}/service-discovery">Service Discovery</a>
<a class="dropdown-item" href="{{ pathPrefix }}/classic/status">Runtime &amp; Build Information</a>
<a class="dropdown-item" href="{{ pathPrefix }}/classic/flags">Command-Line Flags</a>
<a class="dropdown-item" href="{{ pathPrefix }}/classic/config">Configuration</a>
<a class="dropdown-item" href="{{ pathPrefix }}/classic/rules">Rules</a>
<a class="dropdown-item" href="{{ pathPrefix }}/classic/targets">Targets</a>
<a class="dropdown-item" href="{{ pathPrefix }}/classic/service-discovery">Service Discovery</a>
</div>
</li>
<li class= "nav-item" >
<li class= "nav-item">
<a class ="nav-link" href="https://prometheus.io/docs/prometheus/latest/getting_started/" target="_blank">Help</a>
</li>
<li class="nav-item"><a class="nav-link" href="{{ pathPrefix }}/graph">New UI</a></li>
</ul>
</div>
</div>

6
web/ui/templates/alerts.html

@ -1,6 +1,6 @@
{{define "head"}}
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/alerts.css?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/static/js/alerts.js?v={{ buildVersion }}"></script>
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/css/alerts.css?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/classic/static/js/alerts.js?v={{ buildVersion }}"></script>
{{end}}
{{define "content"}}
@ -39,7 +39,7 @@
<tr class="alert_details">
<td>
<div>
<pre style="display:block; padding:9.5px; font-size:13px; color:#333; word-break:break-all; background-color:#f5f5f5; border:1px solid #ccc; border-radius:4px;" ><code>{{.HTMLSnippet pathPrefix}}</code></pre>
<pre style="display:block; padding:9.5px; font-size:13px; color:#333; word-break:break-all; background-color:#f5f5f5; border:1px solid #ccc; border-radius:4px;" ><code>{{.HTMLSnippet (print pathPrefix "/classic")}}</code></pre>
</div>
{{if $activeAlerts}}
<table class="table table-bordered table-hover table-sm alert_elements_table">

32
web/ui/templates/graph.html

@ -1,25 +1,25 @@
{{define "head"}}
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/vendor/rickshaw/rickshaw.min.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.css?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.v3.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/rickshaw/vendor/d3.layout.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/rickshaw/rickshaw.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/moment/moment.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/moment/moment-timezone-with-data.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/fuzzy/fuzzy.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/rickshaw/vendor/d3.v3.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/rickshaw/vendor/d3.layout.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/rickshaw/rickshaw.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/moment/moment.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/moment/moment-timezone-with-data.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/fuzzy/fuzzy.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/mustache/mustache.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/static/vendor/js/jquery.selection.js?v={{ buildVersion }}"></script>
<!-- <script src="{{ pathPrefix }}/static/vendor/js/jquery.hotkeys.js?v={{ buildVersion }}"></script> -->
<script src="{{ pathPrefix }}/classic/static/vendor/mustache/mustache.min.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/vendor/js/jquery.selection.js?v={{ buildVersion }}"></script>
<!-- <script src="{{ pathPrefix }}/classic/static/vendor/js/jquery.hotkeys.js?v={{ buildVersion }}"></script> -->
<script src="{{ pathPrefix }}/static/js/graph/index.js?v={{ buildVersion }}"></script>
<script src="{{ pathPrefix }}/classic/static/js/graph/index.js?v={{ buildVersion }}"></script>
<script id="graph_template" type="text/x-handlebars-template"></script>
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/graph.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/css/graph.css?v={{ buildVersion }}">
{{end}}
{{define "content"}}
@ -29,7 +29,7 @@
<i class="glyphicon glyphicon-unchecked"></i>
<button type="button" class="search-history" title="search previous queries">Enable query history</button>
</div>
<button type="button" class="btn btn-link btn-sm new_ui_button" onclick="window.location.pathname='{{ pathPrefix }}/new/graph'">Try experimental React UI</button>
<button type="button" class="btn btn-link btn-sm new_ui_button" onclick="window.location.pathname='{{ pathPrefix }}/graph'">Back to the new UI</button>
</div>
</div>

4
web/ui/templates/rules.html

@ -1,5 +1,5 @@
{{define "head"}}
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/rules.css?v={{ buildVersion }}">
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/css/rules.css?v={{ buildVersion }}">
{{end}}
{{define "content"}}
@ -24,7 +24,7 @@
</tr>
{{range .Rules}}
<tr>
<td class="rule_cell">{{.HTMLSnippet pathPrefix}}</td>
<td class="rule_cell">{{.HTMLSnippet (print pathPrefix (print pathPrefix "/classic"))}}</td>
<td class="state">
<span class="alert alert-{{ .Health | ruleHealthToClass }} state_indicator text-uppercase">
{{.Health}}

4
web/ui/templates/service-discovery.html

@ -1,6 +1,6 @@
{{define "head"}}
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/targets.css?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/static/js/targets.js?v={{ buildVersion }}"></script>
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/css/targets.css?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/classic/static/js/targets.js?v={{ buildVersion }}"></script>
<style>
*[id]:before {

4
web/ui/templates/targets.html

@ -1,6 +1,6 @@
{{define "head"}}
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/static/css/targets.css?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/static/js/targets.js?v={{ buildVersion }}"></script>
<link type="text/css" rel="stylesheet" href="{{ pathPrefix }}/classic/static/css/targets.css?v={{ buildVersion }}">
<script src="{{ pathPrefix }}/classic/static/js/targets.js?v={{ buildVersion }}"></script>
{{end}}

112
web/web.go

@ -69,7 +69,6 @@ import (
// Paths that are handled by the React / Reach router that should all be served the main React app's index.html.
var reactRouterPaths = []string{
"/",
"/alerts",
"/config",
"/flags",
@ -336,70 +335,85 @@ func New(logger log.Logger, o *Options) *Handler {
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/graph"), http.StatusFound)
})
router.Get("/classic/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/classic/graph"), http.StatusFound)
})
router.Get("/alerts", readyf(h.alerts))
router.Get("/graph", readyf(h.graph))
router.Get("/status", readyf(h.status))
router.Get("/flags", readyf(h.flags))
router.Get("/config", readyf(h.serveConfig))
router.Get("/rules", readyf(h.rules))
router.Get("/targets", readyf(h.targets))
router.Get("/version", readyf(h.version))
router.Get("/service-discovery", readyf(h.serviceDiscovery))
router.Get("/metrics", promhttp.Handler().ServeHTTP)
router.Get("/federate", readyf(httputil.CompressionHandler{
Handler: http.HandlerFunc(h.federation),
}.ServeHTTP))
router.Get("/consoles/*filepath", readyf(h.consoles))
// Redirect the original React UI's path (under "/new") to its new path at the root.
router.Get("/new/*path", func(w http.ResponseWriter, r *http.Request) {
p := route.Param(r.Context(), "path")
http.Redirect(w, r, path.Join(o.ExternalURL.Path, strings.TrimPrefix(p, "/new"))+"?"+r.URL.RawQuery, http.StatusFound)
})
router.Get("/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
router.Get("/classic/alerts", readyf(h.alerts))
router.Get("/classic/graph", readyf(h.graph))
router.Get("/classic/status", readyf(h.status))
router.Get("/classic/flags", readyf(h.flags))
router.Get("/classic/config", readyf(h.serveConfig))
router.Get("/classic/rules", readyf(h.rules))
router.Get("/classic/targets", readyf(h.targets))
router.Get("/classic/service-discovery", readyf(h.serviceDiscovery))
router.Get("/classic/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = path.Join("/static", route.Param(r.Context(), "filepath"))
fs := server.StaticFileServer(ui.Assets)
fs.ServeHTTP(w, r)
})
// Make sure that "<path-prefix>/new" is redirected to "<path-prefix>/new/" and
// not just the naked "/new/", which would be the default behavior of the router
// Make sure that "<path-prefix>/classic" is redirected to "<path-prefix>/classic/" and
// not just the naked "/classic/", which would be the default behavior of the router
// with the "RedirectTrailingSlash" option (https://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash),
// and which breaks users with a --web.route-prefix that deviates from the path derived
// from the external URL.
router.Get("/new", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, path.Join(o.ExternalURL.Path, "new")+"/", http.StatusFound)
// See https://github.com/prometheus/prometheus/issues/6163#issuecomment-553855129.
router.Get("/classic", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, path.Join(o.ExternalURL.Path, "classic")+"/", http.StatusFound)
})
router.Get("/new/*filepath", func(w http.ResponseWriter, r *http.Request) {
p := route.Param(r.Context(), "filepath")
router.Get("/version", h.version)
router.Get("/metrics", promhttp.Handler().ServeHTTP)
// For paths that the React/Reach router handles, we want to serve the
// index.html, but with replaced path prefix placeholder.
for _, rp := range reactRouterPaths {
if p != rp {
continue
}
router.Get("/federate", readyf(httputil.CompressionHandler{
Handler: http.HandlerFunc(h.federation),
}.ServeHTTP))
f, err := ui.Assets.Open("/static/react/index.html")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Error opening React index.html: %v", err)
return
}
idx, err := ioutil.ReadAll(f)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Error reading React index.html: %v", err)
return
}
replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
w.Write(replacedIdx)
router.Get("/consoles/*filepath", readyf(h.consoles))
serveReactApp := func(w http.ResponseWriter, r *http.Request) {
f, err := ui.Assets.Open("/static/react/index.html")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Error opening React index.html: %v", err)
return
}
idx, err := ioutil.ReadAll(f)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Error reading React index.html: %v", err)
return
}
replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
w.Write(replacedIdx)
}
// Serve the React app.
for _, p := range reactRouterPaths {
router.Get(p, serveReactApp)
}
// For all other paths, serve auxiliary assets.
r.URL.Path = path.Join("/static/react/", p)
// The favicon and manifest are bundled as part of the React app, but we want to serve
// them on the root.
for _, p := range []string{"/favicon.ico", "/manifest.json"} {
assetPath := "/static/react" + p
router.Get(p, func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = assetPath
fs := server.StaticFileServer(ui.Assets)
fs.ServeHTTP(w, r)
})
}
// Static files required by the React app.
router.Get("/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = path.Join("/static/react/static", route.Param(r.Context(), "filepath"))
fs := server.StaticFileServer(ui.Assets)
fs.ServeHTTP(w, r)
})

40
web/web_test.go

@ -163,14 +163,13 @@ func TestReadyAndHealthy(t *testing.T) {
for _, u := range []string{
"http://localhost:9090/-/ready",
"http://localhost:9090/version",
"http://localhost:9090/graph",
"http://localhost:9090/flags",
"http://localhost:9090/rules",
"http://localhost:9090/service-discovery",
"http://localhost:9090/targets",
"http://localhost:9090/status",
"http://localhost:9090/config",
"http://localhost:9090/classic/graph",
"http://localhost:9090/classic/flags",
"http://localhost:9090/classic/rules",
"http://localhost:9090/classic/service-discovery",
"http://localhost:9090/classic/targets",
"http://localhost:9090/classic/status",
"http://localhost:9090/classic/config",
} {
resp, err = http.Get(u)
require.NoError(t, err)
@ -194,14 +193,13 @@ func TestReadyAndHealthy(t *testing.T) {
for _, u := range []string{
"http://localhost:9090/-/healthy",
"http://localhost:9090/-/ready",
"http://localhost:9090/version",
"http://localhost:9090/graph",
"http://localhost:9090/flags",
"http://localhost:9090/rules",
"http://localhost:9090/service-discovery",
"http://localhost:9090/targets",
"http://localhost:9090/status",
"http://localhost:9090/config",
"http://localhost:9090/classic/graph",
"http://localhost:9090/classic/flags",
"http://localhost:9090/classic/rules",
"http://localhost:9090/classic/service-discovery",
"http://localhost:9090/classic/targets",
"http://localhost:9090/classic/status",
"http://localhost:9090/classic/config",
} {
resp, err = http.Get(u)
require.NoError(t, err)
@ -276,11 +274,6 @@ func TestRoutePrefix(t *testing.T) {
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
cleanupTestResponse(t, resp)
resp, err = http.Get("http://localhost:9091" + opts.RoutePrefix + "/version")
require.NoError(t, err)
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
cleanupTestResponse(t, resp)
resp, err = http.Post("http://localhost:9091"+opts.RoutePrefix+"/api/v1/admin/tsdb/snapshot", "", strings.NewReader(""))
require.NoError(t, err)
require.Equal(t, http.StatusServiceUnavailable, resp.StatusCode)
@ -304,11 +297,6 @@ func TestRoutePrefix(t *testing.T) {
require.Equal(t, http.StatusOK, resp.StatusCode)
cleanupTestResponse(t, resp)
resp, err = http.Get("http://localhost:9091" + opts.RoutePrefix + "/version")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
cleanupTestResponse(t, resp)
resp, err = http.Post("http://localhost:9091"+opts.RoutePrefix+"/api/v1/admin/tsdb/snapshot", "", strings.NewReader(""))
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)

Loading…
Cancel
Save