diff --git a/base/lasuite/integration-deployment.yaml b/base/lasuite/integration-deployment.yaml index 23807b4..2d367c5 100644 --- a/base/lasuite/integration-deployment.yaml +++ b/base/lasuite/integration-deployment.yaml @@ -1,7 +1,14 @@ -# La Gaufre integration service — O Estúdio app launcher. -# Serves a custom gaufre.js widget and services.json for our app list. -# Apps set GAUFREJS_URL=https://integration.DOMAIN_SUFFIX/api/v1/gaufre.js -# which loads this widget; clicking it fetches /api/v1/services.json. +# La Gaufre integration service — O Estúdio app launcher (La Gaufre v2). +# Serves the lagaufre.js v2 widget, SVG logos, and the v2 services API. +# Apps load gaufre.js (via people-frontend sub_filter) which in turn initialises +# the v2 widget with the button already rendered by @gouvfr-lasuite/ui-kit. +# +# Image: src.DOMAIN_SUFFIX/studio/integration:latest +# Built from sunbeam/integration-service/ (context: sunbeam/ root) +# Baked in: lagaufre.js v2, official La Suite logos, custom logos, gaufre.js, nginx.conf +# +# ConfigMap: only services.json (v2 format) — the one thing that varies per env +# DOMAIN_SUFFIX substituted at deploy time. --- apiVersion: v1 @@ -11,259 +18,26 @@ metadata: namespace: lasuite data: services.json: | - [ - { - "id": "documentos", - "name": "Documentos", - "url": "https://docs.DOMAIN_SUFFIX" - }, - { - "id": "ficheiros", - "name": "Ficheiros", - "url": "https://drive.DOMAIN_SUFFIX" - }, - { - "id": "reunioes", - "name": "Reuniões", - "url": "https://meet.DOMAIN_SUFFIX" - }, - { - "id": "conversas", - "name": "Conversas", - "url": "https://chat.DOMAIN_SUFFIX" - }, - { - "id": "correio", - "name": "Correio", - "url": "https://mail.DOMAIN_SUFFIX" - }, - { - "id": "pessoas", - "name": "Pessoas", - "url": "https://people.DOMAIN_SUFFIX" - }, - { - "id": "descobrir", - "name": "Descobrir", - "url": "https://find.DOMAIN_SUFFIX" - }, - { - "id": "administrar", - "name": "Administrar", - "url": "https://admin.DOMAIN_SUFFIX" - } - ] - - gaufre.js: | - /** - * O Estúdio — La Gaufre widget (self-hosted). - * Drop-in replacement for the official integration.lasuite.numerique.gouv.fr widget. - * Fetches services.json from its own origin and renders a DSFR-style popup. - */ - (function () { - 'use strict'; - - const origin = (function () { - const s = document.currentScript; - if (s && s.src) { - const u = new URL(s.src); - return u.origin; - } - return window.location.origin; - })(); - - const SERVICES_URL = origin + '/api/v1/services.json'; - const SUITE_NAME = 'O Estúdio'; - - const STYLE = ` - #estudio-gaufre-overlay { - display: none; - position: fixed; - inset: 0; - z-index: 9999; - } - #estudio-gaufre-overlay.open { - display: block; - } - #estudio-gaufre-backdrop { - position: absolute; - inset: 0; - } - #estudio-gaufre-panel { - position: absolute; - top: 56px; - left: 16px; - background: #fff; - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 4px 24px rgba(0,0,0,.15); - padding: 16px; - width: 320px; - z-index: 1; - } - #estudio-gaufre-panel h2 { - font-size: 14px; - font-weight: 700; - color: #666; - margin: 0 0 12px; - text-transform: uppercase; - letter-spacing: .05em; - } - #estudio-gaufre-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 8px; - } - .estudio-gaufre-app { - display: flex; - flex-direction: column; - align-items: center; - gap: 6px; - padding: 12px 8px; - border-radius: 6px; - text-decoration: none; - color: #333; - font-size: 12px; - font-weight: 500; - transition: background .15s; - } - .estudio-gaufre-app:hover { - background: #f5f5f5; - } - .estudio-gaufre-icon { - width: 40px; - height: 40px; - border-radius: 8px; - background: #e8eaf6; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - } - `; - - const ICONS = { - documentos: '📄', - ficheiros: '📁', - reunioes: '🎥', - conversas: '💬', - correio: '✉️', - pessoas: '👥', - descobrir: '🔍', - administrar: '⚙️', - }; - - function injectStyles() { - const el = document.createElement('style'); - el.textContent = STYLE; - document.head.appendChild(el); - } - - function buildPanel(services) { - const overlay = document.createElement('div'); - overlay.id = 'estudio-gaufre-overlay'; - - const backdrop = document.createElement('div'); - backdrop.id = 'estudio-gaufre-backdrop'; - backdrop.addEventListener('click', close); - - const panel = document.createElement('div'); - panel.id = 'estudio-gaufre-panel'; - - const title = document.createElement('h2'); - title.textContent = SUITE_NAME; - panel.appendChild(title); - - const grid = document.createElement('div'); - grid.id = 'estudio-gaufre-grid'; - - services.forEach(function (svc) { - const a = document.createElement('a'); - a.className = 'estudio-gaufre-app'; - a.href = svc.url; - - const icon = document.createElement('div'); - icon.className = 'estudio-gaufre-icon'; - icon.textContent = ICONS[svc.id] || '🔲'; - - const label = document.createElement('span'); - label.textContent = svc.name; - - a.appendChild(icon); - a.appendChild(label); - grid.appendChild(a); - }); - - panel.appendChild(grid); - overlay.appendChild(backdrop); - overlay.appendChild(panel); - document.body.appendChild(overlay); - return overlay; - } - - let overlay = null; - let services = null; - - function open() { - if (!services) { - fetch(SERVICES_URL) - .then(function (r) { return r.json(); }) - .then(function (data) { - services = data; - overlay = buildPanel(services); - overlay.classList.add('open'); - }); - } else { - if (!overlay) overlay = buildPanel(services); - overlay.classList.add('open'); - } - } - - function close() { - if (overlay) overlay.classList.remove('open'); - } - - function init() { - injectStyles(); - document.querySelectorAll('.js-lasuite-gaufre-btn').forEach(function (btn) { - btn.addEventListener('click', function (e) { - e.stopPropagation(); - overlay && overlay.classList.contains('open') ? close() : open(); - }); - }); - document.addEventListener('keydown', function (e) { - if (e.key === 'Escape') close(); - }); - } - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); - } else { - init(); - } - })(); - - nginx.conf: | - server { - listen 80; - server_name _; - - location = /api/v1/services.json { - alias /etc/integration/services.json; - add_header Content-Type "application/json; charset=utf-8"; - add_header Access-Control-Allow-Origin "*"; - } - - location = /api/v1/gaufre.js { - alias /etc/integration/gaufre.js; - add_header Content-Type "application/javascript; charset=utf-8"; - add_header Access-Control-Allow-Origin "*"; - } - - location / { - return 404; + { + "services": [ + { + "name": "Documentos", + "url": "https://docs.DOMAIN_SUFFIX", + "logo": "https://integration.DOMAIN_SUFFIX/logos/docs.svg" + }, + { + "name": "Reuniões", + "url": "https://meet.DOMAIN_SUFFIX", + "logo": "https://integration.DOMAIN_SUFFIX/logos/visio.svg" + }, + { + "name": "Humans", + "url": "https://people.DOMAIN_SUFFIX", + "logo": "https://integration.DOMAIN_SUFFIX/logos/people.svg" } + ] } + --- apiVersion: apps/v1 kind: Deployment @@ -282,16 +56,14 @@ spec: spec: containers: - name: integration - image: nginx:alpine + image: integration ports: - name: http containerPort: 80 volumeMounts: - name: config - mountPath: /etc/integration - - name: nginx-conf - mountPath: /etc/nginx/conf.d/default.conf - subPath: nginx.conf + mountPath: /etc/integration/services.json + subPath: services.json resources: limits: memory: 32Mi @@ -302,17 +74,6 @@ spec: - name: config configMap: name: integration-config - items: - - key: services.json - path: services.json - - key: gaufre.js - path: gaufre.js - - name: nginx-conf - configMap: - name: integration-config - items: - - key: nginx.conf - path: nginx.conf --- apiVersion: v1 kind: Service diff --git a/overlays/local/kustomization.yaml b/overlays/local/kustomization.yaml index 14343a1..63743f5 100644 --- a/overlays/local/kustomization.yaml +++ b/overlays/local/kustomization.yaml @@ -12,6 +12,7 @@ kind: Kustomization # replace DOMAIN_SUFFIX with .sslip.io before kubectl apply. resources: + - people-frontend-nginx-configmap.yaml - ../../base/ingress - ../../base/ory - ../../base/data @@ -22,12 +23,17 @@ resources: - ../../base/vso images: - # Pulled from our Gitea registry. Built and pushed by: sunbeam.py --build + # Pulled from our Gitea registry. Built and pushed by: sunbeam build # imagePullPolicy: Always in values-pingora.yaml ensures each rollout pulls fresh. - name: sunbeam-proxy newName: src.DOMAIN_SUFFIX/studio/sunbeam-proxy newTag: latest + # La Gaufre v2 integration service — lagaufre.js widget + SVG logos + nginx + - name: integration + newName: src.DOMAIN_SUFFIX/studio/integration + newTag: latest + # amd64-only La Suite images — mirrored to our Gitea registry with a patched # OCI index that adds an arm64 alias so Rosetta can run them on the Lima VM. # DOMAIN_SUFFIX is substituted by local-up.py at deploy time (sed replacement). @@ -63,5 +69,8 @@ patches: kind: Service name: livekit-server-turn + # Rewrite hardcoded production integration URL in people-frontend static build + - path: patch-people-frontend-nginx.yaml + # Apply §10.7 memory limits to all Deployments - path: values-resources.yaml diff --git a/overlays/local/patch-people-frontend-nginx.yaml b/overlays/local/patch-people-frontend-nginx.yaml new file mode 100644 index 0000000..02737a4 --- /dev/null +++ b/overlays/local/patch-people-frontend-nginx.yaml @@ -0,0 +1,20 @@ +# Patch: mount the nginx ConfigMap into people-frontend to rewrite the +# hardcoded production integration URL at serve time. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: people-frontend + namespace: lasuite +spec: + template: + spec: + containers: + - name: desk + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + volumes: + - name: nginx-conf + configMap: + name: people-frontend-nginx-conf diff --git a/overlays/local/people-frontend-nginx-configmap.yaml b/overlays/local/people-frontend-nginx-configmap.yaml new file mode 100644 index 0000000..531dede --- /dev/null +++ b/overlays/local/people-frontend-nginx-configmap.yaml @@ -0,0 +1,46 @@ +# nginx config for people-frontend that rewrites the hardcoded production +# integration URL baked into the desk static Next.js build. +# +# The people-frontend image has integration.lasuite.numerique.gouv.fr compiled +# in. sub_filter rewrites it to our local instance so the gaufre.js and +# services.json come from integration.DOMAIN_SUFFIX instead of the official +# government service. +# +# gzip must be off for sub_filter to operate on JS responses. +apiVersion: v1 +kind: ConfigMap +metadata: + name: people-frontend-nginx-conf + namespace: lasuite +data: + default.conf: | + server { + listen 3000; + listen 8080; + server_name localhost; + server_tokens off; + + root /usr/share/nginx/html; + + gzip off; + sub_filter 'integration.lasuite.numerique.gouv.fr' 'integration.DOMAIN_SUFFIX'; + sub_filter_once off; + sub_filter_types text/html application/javascript; + + location / { + try_files $uri index.html $uri/ =404; + } + + location /teams/ { + error_page 404 /teams/[id]/; + } + + location /mail-domains/ { + error_page 404 /mail-domains/[slug]/; + } + + error_page 404 /404.html; + location = /404.html { + internal; + } + }