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:
Julius Volz 2024-04-18 15:00:27 +02:00
parent a057601771
commit e8bbe191d4
13 changed files with 24675 additions and 149 deletions

3
.gitignore vendored
View file

@ -22,7 +22,8 @@ benchmark.txt
/documentation/examples/remote_storage/example_write_adapter/example_write_adapter
npm_licenses.tar.bz2
/web/ui/static/react
/web/ui/static/react-app
/web/ui/static/mantine-ui
/vendor
/.build

View file

@ -48,6 +48,10 @@ ui-bump-version:
.PHONY: ui-install
ui-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
ui-build:
@ -64,6 +68,10 @@ ui-test:
.PHONY: ui-lint
ui-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
assets: ui-install ui-build

View file

@ -230,6 +230,9 @@ func (c *flagConfig) setFeatureListOptions(logger log.Logger) error {
continue
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)
case "new-ui":
c.web.UseNewUI = true
level.Info(logger).Log("msg", "Serving experimental new web UI.")
default:
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.").
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)
promlogflag.AddFlags(a, &cfg.promlogConfig)

View file

@ -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.
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`

View file

@ -30,8 +30,8 @@ function publish() {
cmd+=" --dry-run"
fi
for workspace in ${workspaces}; do
# package "app" is private so we shouldn't try to publish it.
if [[ "${workspace}" != "react-app" ]]; then
# package "mantine-ui" is private so we shouldn't try to publish it.
if [[ "${workspace}" != "mantine-ui" ]]; then
cd "${workspace}"
eval "${cmd}"
cd "${root_ui_folder}"

View file

@ -30,10 +30,19 @@ function buildModule() {
}
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"
npm run build -w @prometheus-io/mantine-ui
rm -rf ./static/react
mv ./mantine-ui/dist ./static/react
rm -rf ./static/mantine-ui
mv ./mantine-ui/dist ./static/mantine-ui
}
for i in "$@"; do
@ -41,6 +50,7 @@ for i in "$@"; do
--all)
buildModule
buildReactApp
buildMantineUI
shift
;;
--build-module)

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<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" />
<!--

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -41,6 +41,7 @@
"@lezer/common": "^1.1.1",
"@lezer/highlight": "^1.2.0",
"@lezer/lr": "^1.3.14",
"eslint-plugin-prettier": "^5.1.3",
"isomorphic-fetch": "^3.0.0",
"nock": "^13.4.0"
},

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

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{
"name": "@prometheus-io/app",
"name": "@prometheus-io/react-app",
"version": "0.49.1",
"private": true,
"dependencies": {
@ -68,7 +68,9 @@
"@testing-library/react-hooks": "^7.0.2",
"@types/enzyme": "^3.10.18",
"@types/flot": "0.0.36",
"@types/jest": "^29.5.11",
"@types/jquery": "^3.5.29",
"@types/node": "^20.10.4",
"@types/react": "^17.0.71",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^17.0.25",
@ -78,8 +80,16 @@
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
"enzyme": "^3.11.0",
"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",
"sinon": "^14.0.2"
"prettier": "^2.8.8",
"react-scripts": "^5.0.1",
"sinon": "^14.0.2",
"ts-jest": "^29.1.1"
},
"jest": {
"snapshotSerializers": [

View file

@ -81,6 +81,7 @@ var reactRouterAgentPaths = []string{
// Paths that are handled by the React router when the Agent mode is not set.
var reactRouterServerPaths = []string{
"/alerts",
"/graph",
"/query",
"/rules",
"/tsdb-status",
@ -251,6 +252,7 @@ type Options struct {
UserAssetsPath string
ConsoleTemplatesPath string
ConsoleLibrariesPath string
UseNewUI bool
EnableLifecycle bool
EnableAdminAPI bool
PageTitle string
@ -361,10 +363,13 @@ func New(logger log.Logger, o *Options) *Handler {
router = router.WithPrefix(o.RoutePrefix)
}
homePage := "/query"
homePage := "/graph"
if o.IsAgent {
homePage = "/agent"
}
if o.UseNewUI {
homePage = "/query"
}
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)
})
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`.
router.Get("/classic/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
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))
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 {
w.WriteHeader(http.StatusInternalServerError)
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
// them on the root.
for _, p := range []string{"/favicon.ico", "/manifest.json"} {
assetPath := "/static/react" + p
for _, p := range []string{"/favicon.svg", "/favicon.ico", "/manifest.json"} {
assetPath := reactAssetsRoot + p
router.Get(p, func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = assetPath
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.
router.Get("/assets/*filepath", func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = path.Join("/static/react/assets", route.Param(r.Context(), "filepath"))
router.Get(reactStaticAssetsDir+"/*filepath", func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = path.Join(reactAssetsRoot+reactStaticAssetsDir, route.Param(r.Context(), "filepath"))
fs := server.StaticFileServer(ui.Assets)
fs.ServeHTTP(w, r)
})