mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Build both old & new UI into Prometheus, allow choosing via feature flag
This keeps the old "react-app" directory in its existing location (to make it easier to merge changes from the main branch), but separates it from the npm workspaces setup. Thus it now needs to be npm-installed/built/linted separately. This is a bit hacky, but should only be needed temporarily, until the old UI can be removed. Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
parent
a057601771
commit
e8bbe191d4
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -22,7 +22,8 @@ benchmark.txt
|
||||||
/documentation/examples/remote_storage/example_write_adapter/example_write_adapter
|
/documentation/examples/remote_storage/example_write_adapter/example_write_adapter
|
||||||
|
|
||||||
npm_licenses.tar.bz2
|
npm_licenses.tar.bz2
|
||||||
/web/ui/static/react
|
/web/ui/static/react-app
|
||||||
|
/web/ui/static/mantine-ui
|
||||||
|
|
||||||
/vendor
|
/vendor
|
||||||
/.build
|
/.build
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -48,6 +48,10 @@ ui-bump-version:
|
||||||
.PHONY: ui-install
|
.PHONY: ui-install
|
||||||
ui-install:
|
ui-install:
|
||||||
cd $(UI_PATH) && npm install
|
cd $(UI_PATH) && npm install
|
||||||
|
# The old React app has been separated from the npm workspaces setup to avoid
|
||||||
|
# issues with conflicting dependencies. This is a temporary solution until the
|
||||||
|
# new Mantine-based UI is fully integrated and the old app can be removed.
|
||||||
|
cd $(UI_PATH)/react-app && npm install
|
||||||
|
|
||||||
.PHONY: ui-build
|
.PHONY: ui-build
|
||||||
ui-build:
|
ui-build:
|
||||||
|
@ -64,6 +68,10 @@ ui-test:
|
||||||
.PHONY: ui-lint
|
.PHONY: ui-lint
|
||||||
ui-lint:
|
ui-lint:
|
||||||
cd $(UI_PATH) && npm run lint
|
cd $(UI_PATH) && npm run lint
|
||||||
|
# The old React app has been separated from the npm workspaces setup to avoid
|
||||||
|
# issues with conflicting dependencies. This is a temporary solution until the
|
||||||
|
# new Mantine-based UI is fully integrated and the old app can be removed.
|
||||||
|
cd $(UI_PATH)/react-app && npm run lint
|
||||||
|
|
||||||
.PHONY: assets
|
.PHONY: assets
|
||||||
assets: ui-install ui-build
|
assets: ui-install ui-build
|
||||||
|
|
|
@ -230,6 +230,9 @@ func (c *flagConfig) setFeatureListOptions(logger log.Logger) error {
|
||||||
continue
|
continue
|
||||||
case "promql-at-modifier", "promql-negative-offset":
|
case "promql-at-modifier", "promql-negative-offset":
|
||||||
level.Warn(logger).Log("msg", "This option for --enable-feature is now permanently enabled and therefore a no-op.", "option", o)
|
level.Warn(logger).Log("msg", "This option for --enable-feature is now permanently enabled and therefore a no-op.", "option", o)
|
||||||
|
case "new-ui":
|
||||||
|
c.web.UseNewUI = true
|
||||||
|
level.Info(logger).Log("msg", "Serving experimental new web UI.")
|
||||||
default:
|
default:
|
||||||
level.Warn(logger).Log("msg", "Unknown option for --enable-feature", "option", o)
|
level.Warn(logger).Log("msg", "Unknown option for --enable-feature", "option", o)
|
||||||
}
|
}
|
||||||
|
@ -446,7 +449,7 @@ func main() {
|
||||||
a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates.").
|
a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates.").
|
||||||
Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval)
|
Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval)
|
||||||
|
|
||||||
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, promql-experimental-functions, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
|
a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, auto-gomemlimit, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, promql-experimental-functions, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver, new-ui. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
|
||||||
Default("").StringsVar(&cfg.featureList)
|
Default("").StringsVar(&cfg.featureList)
|
||||||
|
|
||||||
promlogflag.AddFlags(a, &cfg.promlogConfig)
|
promlogflag.AddFlags(a, &cfg.promlogConfig)
|
||||||
|
|
|
@ -224,3 +224,9 @@ When the `concurrent-rule-eval` feature flag is enabled, rules without any depen
|
||||||
This has the potential to improve rule group evaluation latency and resource utilization at the expense of adding more concurrent query load.
|
This has the potential to improve rule group evaluation latency and resource utilization at the expense of adding more concurrent query load.
|
||||||
|
|
||||||
The number of concurrent rule evaluations can be configured with `--rules.max-concurrent-rule-evals`, which is set to `4` by default.
|
The number of concurrent rule evaluations can be configured with `--rules.max-concurrent-rule-evals`, which is set to `4` by default.
|
||||||
|
|
||||||
|
## Experimental new web UI
|
||||||
|
|
||||||
|
Enables the new experimental web UI instead of the old and stable web UI. The new UI is a complete rewrite and aims to be cleaner, less cluttered, and more modern under the hood. It is not feature complete yet and is still under active development.
|
||||||
|
|
||||||
|
`--enable-feature=new-ui`
|
||||||
|
|
|
@ -30,8 +30,8 @@ function publish() {
|
||||||
cmd+=" --dry-run"
|
cmd+=" --dry-run"
|
||||||
fi
|
fi
|
||||||
for workspace in ${workspaces}; do
|
for workspace in ${workspaces}; do
|
||||||
# package "app" is private so we shouldn't try to publish it.
|
# package "mantine-ui" is private so we shouldn't try to publish it.
|
||||||
if [[ "${workspace}" != "react-app" ]]; then
|
if [[ "${workspace}" != "mantine-ui" ]]; then
|
||||||
cd "${workspace}"
|
cd "${workspace}"
|
||||||
eval "${cmd}"
|
eval "${cmd}"
|
||||||
cd "${root_ui_folder}"
|
cd "${root_ui_folder}"
|
||||||
|
|
|
@ -30,10 +30,19 @@ function buildModule() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildReactApp() {
|
function buildReactApp() {
|
||||||
|
echo "build react-app"
|
||||||
|
cd react-app
|
||||||
|
npm run build
|
||||||
|
cd ..
|
||||||
|
rm -rf ./static/react-app
|
||||||
|
mv ./react-app/build ./static/react-app
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMantineUI() {
|
||||||
echo "build mantine-ui"
|
echo "build mantine-ui"
|
||||||
npm run build -w @prometheus-io/mantine-ui
|
npm run build -w @prometheus-io/mantine-ui
|
||||||
rm -rf ./static/react
|
rm -rf ./static/mantine-ui
|
||||||
mv ./mantine-ui/dist ./static/react
|
mv ./mantine-ui/dist ./static/mantine-ui
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in "$@"; do
|
for i in "$@"; do
|
||||||
|
@ -41,6 +50,7 @@ for i in "$@"; do
|
||||||
--all)
|
--all)
|
||||||
buildModule
|
buildModule
|
||||||
buildReactApp
|
buildReactApp
|
||||||
|
buildMantineUI
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--build-module)
|
--build-module)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/prometheus-logo.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
@ -41,6 +41,7 @@
|
||||||
"@lezer/common": "^1.1.1",
|
"@lezer/common": "^1.1.1",
|
||||||
"@lezer/highlight": "^1.2.0",
|
"@lezer/highlight": "^1.2.0",
|
||||||
"@lezer/lr": "^1.3.14",
|
"@lezer/lr": "^1.3.14",
|
||||||
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
"nock": "^13.4.0"
|
"nock": "^13.4.0"
|
||||||
},
|
},
|
||||||
|
|
688
web/ui/package-lock.json
generated
688
web/ui/package-lock.json
generated
File diff suppressed because it is too large
Load diff
24050
web/ui/react-app/package-lock.json
generated
Normal file
24050
web/ui/react-app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@prometheus-io/app",
|
"name": "@prometheus-io/react-app",
|
||||||
"version": "0.49.1",
|
"version": "0.49.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -68,7 +68,9 @@
|
||||||
"@testing-library/react-hooks": "^7.0.2",
|
"@testing-library/react-hooks": "^7.0.2",
|
||||||
"@types/enzyme": "^3.10.18",
|
"@types/enzyme": "^3.10.18",
|
||||||
"@types/flot": "0.0.36",
|
"@types/flot": "0.0.36",
|
||||||
|
"@types/jest": "^29.5.11",
|
||||||
"@types/jquery": "^3.5.29",
|
"@types/jquery": "^3.5.29",
|
||||||
|
"@types/node": "^20.10.4",
|
||||||
"@types/react": "^17.0.71",
|
"@types/react": "^17.0.71",
|
||||||
"@types/react-copy-to-clipboard": "^5.0.7",
|
"@types/react-copy-to-clipboard": "^5.0.7",
|
||||||
"@types/react-dom": "^17.0.25",
|
"@types/react-dom": "^17.0.25",
|
||||||
|
@ -78,8 +80,16 @@
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-to-json": "^3.6.2",
|
"enzyme-to-json": "^3.6.2",
|
||||||
|
"eslint-config-prettier": "^8.10.0",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"jest-canvas-mock": "^2.5.2",
|
||||||
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"mutationobserver-shim": "^0.3.7",
|
"mutationobserver-shim": "^0.3.7",
|
||||||
"sinon": "^14.0.2"
|
"prettier": "^2.8.8",
|
||||||
|
"react-scripts": "^5.0.1",
|
||||||
|
"sinon": "^14.0.2",
|
||||||
|
"ts-jest": "^29.1.1"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"snapshotSerializers": [
|
"snapshotSerializers": [
|
||||||
|
|
27
web/web.go
27
web/web.go
|
@ -81,6 +81,7 @@ var reactRouterAgentPaths = []string{
|
||||||
// Paths that are handled by the React router when the Agent mode is not set.
|
// Paths that are handled by the React router when the Agent mode is not set.
|
||||||
var reactRouterServerPaths = []string{
|
var reactRouterServerPaths = []string{
|
||||||
"/alerts",
|
"/alerts",
|
||||||
|
"/graph",
|
||||||
"/query",
|
"/query",
|
||||||
"/rules",
|
"/rules",
|
||||||
"/tsdb-status",
|
"/tsdb-status",
|
||||||
|
@ -251,6 +252,7 @@ type Options struct {
|
||||||
UserAssetsPath string
|
UserAssetsPath string
|
||||||
ConsoleTemplatesPath string
|
ConsoleTemplatesPath string
|
||||||
ConsoleLibrariesPath string
|
ConsoleLibrariesPath string
|
||||||
|
UseNewUI bool
|
||||||
EnableLifecycle bool
|
EnableLifecycle bool
|
||||||
EnableAdminAPI bool
|
EnableAdminAPI bool
|
||||||
PageTitle string
|
PageTitle string
|
||||||
|
@ -361,10 +363,13 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
router = router.WithPrefix(o.RoutePrefix)
|
router = router.WithPrefix(o.RoutePrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
homePage := "/query"
|
homePage := "/graph"
|
||||||
if o.IsAgent {
|
if o.IsAgent {
|
||||||
homePage = "/agent"
|
homePage = "/agent"
|
||||||
}
|
}
|
||||||
|
if o.UseNewUI {
|
||||||
|
homePage = "/query"
|
||||||
|
}
|
||||||
|
|
||||||
readyf := h.testReady
|
readyf := h.testReady
|
||||||
|
|
||||||
|
@ -372,6 +377,11 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
http.Redirect(w, r, path.Join(o.ExternalURL.Path, homePage), http.StatusFound)
|
http.Redirect(w, r, path.Join(o.ExternalURL.Path, homePage), http.StatusFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
reactAssetsRoot := "/static/react-app"
|
||||||
|
if h.options.UseNewUI {
|
||||||
|
reactAssetsRoot = "/static/mantine-ui"
|
||||||
|
}
|
||||||
|
|
||||||
// The console library examples at 'console_libraries/prom.lib' still depend on old asset files being served under `classic`.
|
// The console library examples at 'console_libraries/prom.lib' still depend on old asset files being served under `classic`.
|
||||||
router.Get("/classic/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
|
router.Get("/classic/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.URL.Path = path.Join("/static", route.Param(r.Context(), "filepath"))
|
r.URL.Path = path.Join("/static", route.Param(r.Context(), "filepath"))
|
||||||
|
@ -389,7 +399,8 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
router.Get("/consoles/*filepath", readyf(h.consoles))
|
router.Get("/consoles/*filepath", readyf(h.consoles))
|
||||||
|
|
||||||
serveReactApp := func(w http.ResponseWriter, r *http.Request) {
|
serveReactApp := func(w http.ResponseWriter, r *http.Request) {
|
||||||
f, err := ui.Assets.Open("/static/react/index.html")
|
indexPath := reactAssetsRoot + "/index.html"
|
||||||
|
f, err := ui.Assets.Open(indexPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprintf(w, "Error opening React index.html: %v", err)
|
fmt.Fprintf(w, "Error opening React index.html: %v", err)
|
||||||
|
@ -426,8 +437,8 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
|
|
||||||
// The favicon and manifest are bundled as part of the React app, but we want to serve
|
// The favicon and manifest are bundled as part of the React app, but we want to serve
|
||||||
// them on the root.
|
// them on the root.
|
||||||
for _, p := range []string{"/favicon.ico", "/manifest.json"} {
|
for _, p := range []string{"/favicon.svg", "/favicon.ico", "/manifest.json"} {
|
||||||
assetPath := "/static/react" + p
|
assetPath := reactAssetsRoot + p
|
||||||
router.Get(p, func(w http.ResponseWriter, r *http.Request) {
|
router.Get(p, func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.URL.Path = assetPath
|
r.URL.Path = assetPath
|
||||||
fs := server.StaticFileServer(ui.Assets)
|
fs := server.StaticFileServer(ui.Assets)
|
||||||
|
@ -435,9 +446,13 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reactStaticAssetsDir := "/static"
|
||||||
|
if h.options.UseNewUI {
|
||||||
|
reactStaticAssetsDir = "/assets"
|
||||||
|
}
|
||||||
// Static files required by the React app.
|
// Static files required by the React app.
|
||||||
router.Get("/assets/*filepath", func(w http.ResponseWriter, r *http.Request) {
|
router.Get(reactStaticAssetsDir+"/*filepath", func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.URL.Path = path.Join("/static/react/assets", route.Param(r.Context(), "filepath"))
|
r.URL.Path = path.Join(reactAssetsRoot+reactStaticAssetsDir, route.Param(r.Context(), "filepath"))
|
||||||
fs := server.StaticFileServer(ui.Assets)
|
fs := server.StaticFileServer(ui.Assets)
|
||||||
fs.ServeHTTP(w, r)
|
fs.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue