@swiss-ai-hub/web 0.290.11 → 0.291.1

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.
Files changed (2) hide show
  1. package/README.md +121 -35
  2. 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
- this to your project's `package.json`:
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 or yarn
116
- {
117
- "overrides": {
118
- "vue": "3.5.17",
119
- "@vueuse/router": { "vue-router": "4.6.4" }
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
- > **Why these entries?** `vue` pins the framework runtime to a single instance shared by your app, the layer, and every
145
- > Nuxt module -- this is the one that actually breaks if omitted. The scoped `@vueuse/router > vue-router` entry
146
- > resolves a harmless peer-range mismatch (`@vueuse/router` expects vue-router 4, Nuxt's router is 5); it is scoped so
147
- > it only affects that one nested dependency and never the router Nuxt itself uses. The versions match the
148
- > [peer dependencies](#peer-dependencies) the layer is built and tested against.
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
- // In production, override via NUXT_PUBLIC_* environment variables.
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 site in `.output/public/`. Runtime configuration values are baked in during build, but you
409
- can override them at runtime using `NUXT_PUBLIC_*` environment variables (Nuxt rewrites them on the client at startup).
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
- All configuration is provided through `runtimeConfig.public` in `nuxt.config.ts`. In production, override them via
430
- environment variables -- Nuxt automatically maps `NUXT_PUBLIC_*` variables to the corresponding config keys:
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
- | Config key | Environment variable | Default (dev) | Description |
433
- | ------------------- | -------------------------------- | --------------------------------------------- | ------------------------------------ |
434
- | `env` | `NUXT_PUBLIC_ENV` | `dev` | Environment identifier |
435
- | `oidc.clientId` | `NUXT_PUBLIC_OIDC_CLIENT_ID` | `aihub-frontend` | OIDC client ID for Keycloak |
436
- | `oidc.authorityUrl` | `NUXT_PUBLIC_OIDC_AUTHORITY_URL` | `http://localhost:8180/realms/aihub` | Keycloak realm URL |
437
- | `webui.url` | `NUXT_PUBLIC_WEBUI_URL` | `http://localhost:8080` | Open-WebUI URL (chat link) |
438
- | `ws.endpoint` | `NUXT_PUBLIC_WS_ENDPOINT` | `ws://localhost:8000/api/v1/active/events/ws` | WebSocket for real-time agent events |
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.290.11",
6
+ "version": "0.291.1",
7
7
  "description": "Swiss AI Hub - Admin & Management UI (Nuxt 3 layer)",
8
8
  "main": "./nuxt.config.ts",
9
9
  "repository": {