Repository navigation
Description
#2711 makes the UI work offline by serving its frontend libraries from FlexMeasures instead of CDNs. That came up at the LF Energy summit, where the UI broke on spotty wifi. Self-hosting itself looks like the right call to me. This issue is about how we keep these libraries over time, before we settle on committing ~5 MB of them to the repo.
@TeaDrinkingProgrammer @nhoening, I'd like your views.
Where #2711 stands
#2711 commits 19 libraries under flexmeasures/ui/static/vendor/<lib>/<version>/. I checked that each of them is byte-identical to its upstream release. Bumping a library means downloading its files by hand, following a README.
Several parts of #2711 are worth keeping whichever way we go:
- the
vendor.htmlmacros, as the only place a third-party tag is written; vendor_versions.py, as the only place versions live;- versioned paths with long-lived cache headers;
- the clean-up of drifted versions (DataTables loaded twice, two Leaflet versions, unpinned D3, Litepicker and ECharts).
What I'd like to settle
- Provenance: nothing records where each file came from, so verifying or bumping one means reconstructing its source URL first.
- Updates and advisories: Dependabot only reads
uv.lock. Whether we use CDNs or committed copies, nothing tells us when one of these libraries is outdated or has a security advisory. - Licences: shipping these files in our wheel and Docker image counts as redistributing them. Several of them carry no licence notice in the file itself, so we need to ship their licences separately.
- Repo: about 5 MB of minified code that can't really be reviewed, plus each future bump, stays in git history for good.
Options
| Works offline | Files in git | Toolchain | Update and advisory tracking | |
|---|---|---|---|---|
| A. Commit the files (#2711 as is) | yes | yes | none | none |
| B. Fetch at build time from a manifest of npm name, version, files and SHA-256 | yes | no (small, unversioned files such as icons, fonts and logos could stay) | Python only | through a scheduled OSV check, or a tracking-only package.json |
C. package.json and lockfile, npm ci and a copy step at build time |
yes | no | Node in dev, CI and Docker | Dependabot updates and alerts |
C is how larger Python projects with a UI handle this. B could be a step towards C: its manifest holds the same data as package.json, so switching later is mechanical.
B's trade-offs are:
- Network at build time: releases and Docker builds would need access to the CDNs. A CDN outage would then break a build instead of a demo.
- The wheel: we'd need a hatch build hook so the PyPI wheel contains the files, and pip users stay offline-capable.
B could also fall back to the CDN for a file that is missing locally. The tag macros would link the CDN URL instead, with the manifest's hash as an integrity attribute. A fresh dev checkout would then just work, without a setup step, and anyone who needs it offline fetches the files once. The price is a second way of loading each file, and a deployment whose fetch silently failed would depend on CDNs again without noticing. A warning at startup when files are missing would catch that.
Questions
- Are we fine with network access at build time (B or C), or should a plain checkout work as is (A)?
- Is Node in our toolchain acceptable (C), or should we stay Python-only (A or B)?
- If B: do we want the CDN fallback for missing files?
- If B: do we go there directly in Serve UI assets from FlexMeasures instead of CDNs #2711, or merge Serve UI assets from FlexMeasures instead of CDNs #2711 as a first step and move to B in a follow-up?
- Whichever we choose, how do we want to track advisories and ship licences?
Activity
nhoening commentedon Oct 16, 2026
I would go for B with CDN fallback.
Merging #2711 first (like it is now) seems like the wrong way, then.
I can imagine users installing and using FlexMeasures headless, only talking to the API. Not downloading 5MB would be good for them.
Do we then really need to track licenses? I guess for hosting a FlexMeasures like this, what does factually change? We are downloading the JS files for that (if we choose that as hosts), but we did that also before, just in a slightly different manner.
Provenance and advisory could be discussed in a new issue.
Flix6x commentedon Oct 16, 2026
Agreed on B with CDN fallback, and on not merging #2711 as is.
On the headless point: that only holds if the fetched files stay out of the wheel. So I'd suggest the wheel ships without them and falls back to the CDN by default, the Docker image includes them, and pip-installed hosts who need offline run a fetch command once. That also means builds don't need network.
On licences: with this setup, we only redistribute the files in the Docker image, where MIT and BSD ask us to keep their notices. The fetch script can download each package's
LICENSEfile along with it, so that costs nearly nothing.Provenance comes with B, since the manifest records each file's source URL and hash. I opened #2725 for advisory tracking.
TeaDrinkingProgrammer commentedon Oct 19, 2026
Great discussion: I don't have a strong preference and the points you raised are all valid so please continue on that path. My PR was intended as an initial try, I should have probably opened an issue first.