Skip to content

Add feature to build and enable UI V2#4974

Closed
sysadmind wants to merge 9 commits intoprometheus:mainfrom
sysadmind:ui-v2-ff
Closed

Add feature to build and enable UI V2#4974
sysadmind wants to merge 9 commits intoprometheus:mainfrom
sysadmind:ui-v2-ff

Conversation

@sysadmind
Copy link
Copy Markdown
Contributor

Adds the Makefile and UI embedding components to serve the new UI based on Mantine. This can be enabled when built with the tag builtinassets. This is a close match to what Prometheus does to build and embed the UI.

The UI is still very early, so the utility is very limited, but this will allow testing the features more easily as they become available.

Adds the Makefile and UI embedding components to serve the new UI based on Mantine. This can be enabled when built with the tag `builtinassets`. This is a close match to what Prometheus does to build and embed the UI.

The UI is still very early, so the utility is very limited, but this will allow testing the features more easily as they become available.

Signed-off-by: Joe Adams <github@joeadams.io>
Signed-off-by: Joe Adams <github@joeadams.io>
Signed-off-by: Joe Adams <github@joeadams.io>
Signed-off-by: Joe Adams <github@joeadams.io>
Signed-off-by: Joe Adams <github@joeadams.io>
@siavashs
Copy link
Copy Markdown
Contributor

CI is failing even after a re-run.
It is not clear why the build is failing for elm, it does not seem that there are any changes to that:

cd /__w/alertmanager/alertmanager/ui/app && make script.js
make[1]: Entering directory '/__w/alertmanager/alertmanager/ui/app'
>> building elm-env docker image
/bin/sh: 3: docker: not found
make[1]: *** [Makefile:17: elm-env] Error 127
make[1]: Leaving directory '/__w/alertmanager/alertmanager/ui/app'
make: *** [Makefile:67: ui/app/script.js] Error 2

@sysadmind
Copy link
Copy Markdown
Contributor Author

The problem is that this is now happening in the build process where it was not before and it's already inside docker so it doesn't have access to run docker. I think the logical option is to remove the elm build from the regular build process. The other option would be to enable the docker in docker build.

Signed-off-by: Joe Adams <github@joeadams.io>
Signed-off-by: Joe Adams <github@joeadams.io>
Signed-off-by: Joe Adams <github@joeadams.io>
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We removed this file in #4617 do we need to add it back now again?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in prometheus for the bundling of static assets into a go file. I think we should keep it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should add compress_assets.sh.

  • It is very complex, having to both generate .go files and running extra shell scripts as part of the build. It would be better to just directly use Vite for building the gzip files in a dist folder. This would allow us to easily add brotli compression later.

  • Everytime the ui is opened, alertmanager will decompress the files before sending them over the network. This can be seen from the curl output:

curl -s -D - -H "Accept-Encoding: gzip" http://localhost:9093/assets/index-D3YozeYE.js -o /dev/null
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 530907
Content-Type: application/javascript
Date: Sat, 07 Mar 2026 19:22:22 GMT

So, we are missing out on the main benefit of compression: Reducing the number of TCP round trips.

  • The benefits of compression are negligible:
❯ du -hs  assets/index-D3YozeYE.js.gz  assets/index-pOPVlMO9.css.gz index.html.gz
164K	assets/index-D3YozeYE.js.gz
32K	assets/index-pOPVlMO9.css.gz
4.0K	index.html.gz
❯ du -hs  assets/index-D3YozeYE.js  assets/index-pOPVlMO9.css index.html
520K	assets/index-D3YozeYE.js
216K	assets/index-pOPVlMO9.css
4.0K	index.html

Thus, your change will reduce the binary size by 500K .

TLDR; We should keep it simple and not do any compression. If we want to do compression, we should do it properly, and support streaming the files compressed over the network.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The binary file saving are small right now because of tree shaking. There is very little in the UI if you compile it from this PR. As more features and functionality get added, this will grow. I don't have hard data around what that will be, but if there's consensus around embedding the uncompressed files, I'll pull this out.

Comment on lines +41 to +45
if enableUIV2 {
RegisterUIV2(r, logger)
} else {
RegisterUIV1(r, logger)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use Elm and React for naming these to be more clear?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm certainly open to renaming them. I don't know that I love the word "react" though because in some future, there may be a UI v3 that is also based on react.


.PHONY: build
build: common-build
build: assets-compress common-build
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the project ships pre-compiled assets bundle so UI does not have to be built each time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should re-evaluate that for the mantine based UI. The build output has unique filenames per build. This will cause a lot of churn in git.


reactAssetsRoot := "/static/mantine-ui"

serveReactApp := func(w http.ResponseWriter, _ *http.Request) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing cache-control headers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What outcome would you like to see with those?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed they are missing and wondered if we need them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, if we think they will provide value, I would want to see this happen towards the end of the project. That way we can test what benefits and what tradeoffs they cause.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caching headers are need such that users can seamlessly update their alertmanager binaries: If the browser caches the index.html, we will have situation that a browser will run an old index.html against a new alertmanager server. This will definitely cause failures, as far as I can see, because alertmanager will return 404s for these files:

    <script type="module" crossorigin src="/assets/index-D3YozeYE.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/index-pOPVlMO9.css">

We should add the cache-control headers now, since this cannot be fixed retroactively.

The other caches should be fine, since the index files contain the hashes.

Signed-off-by: Joe Adams <github@joeadams.io>
Copy link
Copy Markdown
Contributor

@siavashs siavashs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM at this stage since it is not the default UI.
Let's wait for other maintainers to also take a look.

@SoloJacobs SoloJacobs removed the request for review from TheMeier February 20, 2026 15:50
Copy link
Copy Markdown
Contributor

@SoloJacobs SoloJacobs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey,

I finally had a bit of time to look over these changes. Thanks for gathering all this information, I tried it out locally and it works. I do have some feedback, though: You stuck very close to Prometheus, but that implementation has drawbacks.

But have a look at the comments. I can also help with concrete implementations in this change.

Kind regards

@@ -0,0 +1,46 @@
#!/usr/bin/env bash
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should add compress_assets.sh.

  • It is very complex, having to both generate .go files and running extra shell scripts as part of the build. It would be better to just directly use Vite for building the gzip files in a dist folder. This would allow us to easily add brotli compression later.

  • Everytime the ui is opened, alertmanager will decompress the files before sending them over the network. This can be seen from the curl output:

curl -s -D - -H "Accept-Encoding: gzip" http://localhost:9093/assets/index-D3YozeYE.js -o /dev/null
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 530907
Content-Type: application/javascript
Date: Sat, 07 Mar 2026 19:22:22 GMT

So, we are missing out on the main benefit of compression: Reducing the number of TCP round trips.

  • The benefits of compression are negligible:
❯ du -hs  assets/index-D3YozeYE.js.gz  assets/index-pOPVlMO9.css.gz index.html.gz
164K	assets/index-D3YozeYE.js.gz
32K	assets/index-pOPVlMO9.css.gz
4.0K	index.html.gz
❯ du -hs  assets/index-D3YozeYE.js  assets/index-pOPVlMO9.css index.html
520K	assets/index-D3YozeYE.js
216K	assets/index-pOPVlMO9.css
4.0K	index.html

Thus, your change will reduce the binary size by 500K .

TLDR; We should keep it simple and not do any compression. If we want to do compression, we should do it properly, and support streaming the files compressed over the network.

"path/filepath"
"strings"

"github.com/shurcooL/httpfs/filter"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be using shurcool. The files can be easily be routed to the correct location with a few lines of go.


reactAssetsRoot := "/static/mantine-ui"

serveReactApp := func(w http.ResponseWriter, _ *http.Request) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caching headers are need such that users can seamlessly update their alertmanager binaries: If the browser caches the index.html, we will have situation that a browser will run an old index.html against a new alertmanager server. This will definitely cause failures, as far as I can see, because alertmanager will return 404s for these files:

    <script type="module" crossorigin src="/assets/index-D3YozeYE.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/index-pOPVlMO9.css">

We should add the cache-control headers now, since this cannot be fixed retroactively.

The other caches should be fine, since the index files contain the hashes.

return
}
defer func() { _ = f.Close() }()
idx, err := io.ReadAll(f)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Why not use io.Copy

// Static files required by the React app.
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(Assets)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should create a StaticFileServer on every request.

return f.enableAutoGOMAXPROCS
}

func (f *Flags) EnableUIV2() bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think UI is ready to be shipped with next release, so I would rather not include it in the release. Would it be possible for us to provide a separate build target instead?

@@ -0,0 +1,31 @@
import { FC } from 'react';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this possibly belong in a different PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I will pull it out.

router.Get(p, serveReactApp)
}

reactStaticAssetsDir := "/assets"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: inline

// Serve the React app.
reactRouterPaths := newUIReactRouterPaths

for _, p := range reactRouterPaths {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering about this: Aren't we building a SPA? Typically, you would serve everything, not just a fixed set of subpaths, i.e., localhost:9093/alerts/special-name should also serve the react app?

For now, serving 404 is fine I suppose, but we will definitely have to touch this part, once we build deep links. I am wondering whether it is better just have a general /ui path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that /ui is a fair option if there is general agreement on that direction.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment from the sidelines, since I don't have the capacity to get more deeply involved at the moment:

Since this routing code was taken from the Prometheus server: any UI URL parametrization in the Prometheus server happens only via HTTP parameters (since we encode a lot of different parameters in there on some of the pages, and then we just standardized on that single mechanism), so we only need to explicitly list a couple of fixed paths there. I wonder if for Alertmanager you really want to split this up and store some (prominent) parameters in the path and others (like detailed filters) in HTTP params, or whether it's nicer to just dump them all into HTTP params like in Prometheus and have one unified way of handling them all?

Aside from that: as a user I personally aesthetically prefer UI paths without a /ui/... prefix. Maybe the path routing can also be restructured to serve the UI for a set of patterns, or for everything that is not /api/..., /-/healthy, or one of the other special endpoints.

@SoloJacobs SoloJacobs requested a review from TheMeier March 8, 2026 09:21
@SoloJacobs
Copy link
Copy Markdown
Contributor

Hey, just because I saw you might be active here: I did make some progress on what I think the UI should look like.
https://github.com/SoloJacobs/alertmanager/tree/example-ui

This change is not ready, yet: I put the first three commit into review, but the final commit contains the interesting changes:

  • Zero-Overhead compression: We directly stream the compressed files from the binary, and don't decompress them unless the client requests it.
  • Optimization to the caching, using Vites file hashing.
  • Disallow go get github.com/prometheus/alertmanager/ui.

The basic idea is that we have to move to vite sooner or later, so we might as well do it now and find out how it affects users.

@sysadmind
Copy link
Copy Markdown
Contributor Author

I think based on the feedback, I want to take a different approach. I have opened a PR for the small UI change - #5106 . After the changes from @SoloJacobs above from the example-ui branch, I can rework this build in that spirit, in some smaller increments so that we can align on direction more easily.

@sysadmind sysadmind closed this Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants