@swiss-ai-hub/web 0.290.11 → 0.291.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,43 +109,45 @@ you get cryptic errors like _"Vue instance ... was created in a different applic
|
|
|
109
109
|
requirement applies to PrimeVue, whose theme system relies on a global singleton (two instances → unstyled components).
|
|
110
110
|
|
|
111
111
|
The fix is to force the whole dependency tree onto one Vue version using your package manager's override mechanism. Add
|
|
112
|
-
|
|
112
|
+
the **`vue`** override to your project's `package.json` — this is the one that actually breaks if omitted. Note the key
|
|
113
|
+
differs per package manager (npm/pnpm use `overrides`, Yarn uses `resolutions`):
|
|
113
114
|
|
|
114
115
|
```jsonc
|
|
115
|
-
// npm
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
116
|
+
// npm
|
|
117
|
+
{ "overrides": { "vue": "3.5.17" } }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```jsonc
|
|
121
|
+
// Yarn
|
|
122
|
+
{ "resolutions": { "vue": "3.5.17" } }
|
|
122
123
|
```
|
|
123
124
|
|
|
124
125
|
```jsonc
|
|
125
126
|
// pnpm
|
|
126
|
-
{
|
|
127
|
-
"pnpm": {
|
|
128
|
-
"overrides": {
|
|
129
|
-
"vue": "3.5.17",
|
|
130
|
-
"@vueuse/router>vue-router": "4.6.4"
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
127
|
+
{ "pnpm": { "overrides": { "vue": "3.5.17" } } }
|
|
134
128
|
```
|
|
135
129
|
|
|
136
130
|
Then reinstall from a clean state so the lockfile is regenerated, and confirm a single Vue instance:
|
|
137
131
|
|
|
138
132
|
```bash
|
|
139
|
-
rm -rf node_modules package-lock.json # or pnpm-lock.yaml
|
|
133
|
+
rm -rf node_modules package-lock.json # or yarn.lock / pnpm-lock.yaml
|
|
140
134
|
npm install
|
|
141
135
|
npm ls @vue/runtime-core # must print exactly one version: 3.5.17
|
|
142
136
|
```
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
>
|
|
148
|
-
>
|
|
138
|
+
The same single-instance requirement applies to **PrimeVue** (its theme system is a global singleton); pinning the
|
|
139
|
+
`primevue` peer to one version is enough — it does not need an override.
|
|
140
|
+
|
|
141
|
+
> **Optional — silence a `vue-router` peer warning.** `@vueuse/router` declares a `vue-router@^4` peer, but some
|
|
142
|
+
> transitive dependencies may pull in `vue-router` 5.x, which can surface a peer-range warning on install. It is
|
|
143
|
+
> harmless (the layer does not depend on that resolution), but if you want it gone, pin `vue-router` to `4.6.4` **scoped
|
|
144
|
+
> to `@vueuse/router`** so it never affects the router Nuxt itself uses:
|
|
145
|
+
>
|
|
146
|
+
> ```jsonc
|
|
147
|
+
> // npm: "overrides": { "@vueuse/router": { "vue-router": "4.6.4" } }
|
|
148
|
+
> // Yarn: "resolutions": { "@vueuse/router/vue-router": "4.6.4" }
|
|
149
|
+
> // pnpm: "pnpm": { "overrides": { "@vueuse/router>vue-router": "4.6.4" } }
|
|
150
|
+
> ```
|
|
149
151
|
|
|
150
152
|
## Quick start
|
|
151
153
|
|
|
@@ -170,8 +172,9 @@ Replace the generated `nuxt.config.ts` with:
|
|
|
170
172
|
export default defineNuxtConfig({
|
|
171
173
|
extends: ['@swiss-ai-hub/web'],
|
|
172
174
|
|
|
173
|
-
// These defaults match infra/docker-compose.dev.yml + `make run-api`.
|
|
174
|
-
//
|
|
175
|
+
// These dev defaults match infra/docker-compose.dev.yml + `make run-api`.
|
|
176
|
+
// For production, leave these declared (the keys must exist) but blank, and
|
|
177
|
+
// inject values at runtime via /config.js -- see Runtime configuration below.
|
|
175
178
|
runtimeConfig: {
|
|
176
179
|
public: {
|
|
177
180
|
env: 'dev',
|
|
@@ -405,12 +408,19 @@ ______________________________________________________________________
|
|
|
405
408
|
npx nuxi generate
|
|
406
409
|
```
|
|
407
410
|
|
|
408
|
-
This produces a fully static
|
|
409
|
-
|
|
411
|
+
This produces a fully static SPA in `.output/public/` (the layer is client-only -- `ssr: false`). Because the output is
|
|
412
|
+
static, there is **no Node server at runtime to read environment variables**. Instead, the layer ships a small
|
|
413
|
+
runtime-config mechanism (see [Runtime configuration](#runtime-configuration)) so you build **one** image and configure
|
|
414
|
+
it per environment at container start -- including which backend API it talks to.
|
|
410
415
|
|
|
411
416
|
### Dockerfile example
|
|
412
417
|
|
|
418
|
+
This mirrors how Swiss AI Hub ships the admin UI: build the static site, serve it with nginx, and generate `/config.js`
|
|
419
|
+
from a template at startup so the same image works in any environment.
|
|
420
|
+
|
|
413
421
|
```dockerfile
|
|
422
|
+
# 1. Build the static SPA. ENV must be unset (or anything other than 'dev') so
|
|
423
|
+
# the layer emits the <script src="/config.js"> tag that loads runtime config.
|
|
414
424
|
FROM node:22-alpine AS build
|
|
415
425
|
WORKDIR /app
|
|
416
426
|
COPY package.json package-lock.json ./
|
|
@@ -418,24 +428,100 @@ RUN npm ci
|
|
|
418
428
|
COPY . .
|
|
419
429
|
RUN npx nuxi generate
|
|
420
430
|
|
|
431
|
+
# 2. Serve with nginx; render /config.js from env vars on container start.
|
|
421
432
|
FROM nginx:alpine
|
|
433
|
+
RUN apk add --no-cache gettext # provides envsubst
|
|
422
434
|
COPY --from=build /app/.output/public /usr/share/nginx/html
|
|
435
|
+
COPY config.template.js /usr/share/nginx/html/config.template.js
|
|
436
|
+
CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/config.template.js > /usr/share/nginx/html/config.js && nginx -g 'daemon off;'"]
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
nginx must serve `/config.js` uncached and never fall back to `index.html` for it -- declare it **before** the SPA
|
|
440
|
+
catch-all:
|
|
441
|
+
|
|
442
|
+
```nginx
|
|
443
|
+
location = /config.js {
|
|
444
|
+
add_header Cache-Control "no-store" always;
|
|
445
|
+
default_type application/javascript;
|
|
446
|
+
try_files $uri =404;
|
|
447
|
+
}
|
|
448
|
+
location / { try_files $uri /index.html; }
|
|
423
449
|
```
|
|
424
450
|
|
|
425
451
|
______________________________________________________________________
|
|
426
452
|
|
|
427
453
|
## Runtime configuration
|
|
428
454
|
|
|
429
|
-
|
|
430
|
-
|
|
455
|
+
The layer reads all runtime configuration from `runtimeConfig.public`, populated in **two phases**:
|
|
456
|
+
|
|
457
|
+
1. **Build-time defaults** -- whatever you put in `runtimeConfig.public` in your `nuxt.config.ts`. Used directly in dev,
|
|
458
|
+
baked into the static build.
|
|
459
|
+
2. **Runtime overrides (production)** -- a `window.__AIHUB_CONFIG__` object loaded synchronously from `/config.js`
|
|
460
|
+
**before** the app boots. A plugin shipped in this layer (`plugins/0.runtime-config.client.ts`) maps it into
|
|
461
|
+
`runtimeConfig.public`. This is how a single static build is configured per environment without rebuilding.
|
|
462
|
+
|
|
463
|
+
`/config.js` is rendered by `envsubst` from your `config.template.js` at container start (see the Dockerfile above). The
|
|
464
|
+
layer injects the `<script src="/config.js">` tag automatically for any non-dev build, and the mapping plugin runs in
|
|
465
|
+
every app that extends the layer -- so you only supply the template:
|
|
466
|
+
|
|
467
|
+
```js
|
|
468
|
+
// config.template.js -- envsubst replaces ${...} at container start.
|
|
469
|
+
// Include only the keys your deployment needs; unset ones resolve to defaults.
|
|
470
|
+
window.__AIHUB_CONFIG__ = {
|
|
471
|
+
API_BASE_URL: '${API_BASE_URL}',
|
|
472
|
+
OAUTH_CLIENT_ID: '${OAUTH_CLIENT_ID}',
|
|
473
|
+
OAUTH_AUTHORITY_URL: '${OAUTH_AUTHORITY_URL}',
|
|
474
|
+
WEBUI_URL: '${WEBUI_URL}',
|
|
475
|
+
WS_ENDPOINT: '${WS_ENDPOINT}',
|
|
476
|
+
}
|
|
477
|
+
```
|
|
431
478
|
|
|
432
|
-
|
|
|
433
|
-
|
|
|
434
|
-
| `
|
|
435
|
-
| `oidc.clientId`
|
|
436
|
-
| `oidc.authorityUrl`
|
|
437
|
-
| `webui.url`
|
|
438
|
-
| `ws.endpoint`
|
|
479
|
+
| `runtimeConfig.public` key | `window.__AIHUB_CONFIG__` key | Default | Description |
|
|
480
|
+
| -------------------------- | ----------------------------- | ----------------------- | ------------------------------------------------------------------ |
|
|
481
|
+
| `apiBaseUrl` | `API_BASE_URL` | `/api/v1` (same origin) | Backend API base URL -- see [below](#pointing-at-your-backend-api) |
|
|
482
|
+
| `oidc.clientId` | `OAUTH_CLIENT_ID` | -- | OIDC client ID (Keycloak) |
|
|
483
|
+
| `oidc.authorityUrl` | `OAUTH_AUTHORITY_URL` | -- | OIDC authority / realm URL |
|
|
484
|
+
| `webui.url` | `WEBUI_URL` | -- | Open-WebUI URL (chat link) |
|
|
485
|
+
| `ws.endpoint` | `WS_ENDPOINT` | -- | WebSocket endpoint for real-time agent events |
|
|
486
|
+
| `env` | -- | -- | Set to `dev` locally (uses build-time values, skips `/config.js`) |
|
|
487
|
+
|
|
488
|
+
> **Declare the groups you want populated.** The mapping plugin only writes into config groups your app already declared
|
|
489
|
+
> in `runtimeConfig.public` (e.g. `oidc: {}`, `webui: {}`, `ws: {}`); in production set their build-time values to empty
|
|
490
|
+
> strings and let `/config.js` fill them. `apiBaseUrl` is the exception -- it is always applied when `API_BASE_URL` is
|
|
491
|
+
> present.
|
|
492
|
+
|
|
493
|
+
### Pointing at your backend API
|
|
494
|
+
|
|
495
|
+
The admin UI talks to the Swiss AI Hub backend through a single base URL, `runtimeConfig.public.apiBaseUrl`. It defaults
|
|
496
|
+
to the **same origin** as the UI (`/api/v1`) -- the simplest setup: put the UI and the API behind one reverse proxy
|
|
497
|
+
(what the platform's Traefik does) and no API configuration is needed at all.
|
|
498
|
+
|
|
499
|
+
If your UI is served from a **different origin** than the API, set the base URL explicitly:
|
|
500
|
+
|
|
501
|
+
- **Dev / build-time** -- set it in `nuxt.config.ts`, or (recommended for local dev) keep `/api/v1` and proxy it with
|
|
502
|
+
Nitro:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
export default defineNuxtConfig({
|
|
506
|
+
extends: ['@swiss-ai-hub/web'],
|
|
507
|
+
// Option A: point straight at the API origin
|
|
508
|
+
runtimeConfig: { public: { apiBaseUrl: 'https://aihub.example.com/api/v1' } },
|
|
509
|
+
// Option B (same-origin dev): keep /api/v1 and proxy it
|
|
510
|
+
nitro: { devProxy: { '/api/v1': { target: 'http://localhost:8000/api/v1', changeOrigin: true, ws: true } } },
|
|
511
|
+
})
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
- **Production (static image)** -- inject `API_BASE_URL` at container start via `config.template.js`. One image, any
|
|
515
|
+
backend:
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
docker run -e API_BASE_URL=https://aihub.example.com/api/v1 my-aihub-frontend
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
> **Cross-origin caveats.** A different API origin must allow your UI's origin via CORS, and your Keycloak client must
|
|
522
|
+
> list it as an allowed web/redirect origin. Same-origin (`/api/v1` behind one proxy) avoids both. Do **not** call
|
|
523
|
+
> `client.setConfig({ baseURL: ... })` in your own `app.vue` unless you deliberately want to hard-override this
|
|
524
|
+
> mechanism.
|
|
439
525
|
|
|
440
526
|
## Peer dependencies
|
|
441
527
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"license": "AGPL-3.0-or-later",
|
|
4
4
|
"author": "bbv Software Services AG (https://www.bbv.ch)",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.291.0",
|
|
7
7
|
"description": "Swiss AI Hub - Admin & Management UI (Nuxt 3 layer)",
|
|
8
8
|
"main": "./nuxt.config.ts",
|
|
9
9
|
"repository": {
|