From 9fd5dbb0dfce623e80c93236fd6db4edfb011c86 Mon Sep 17 00:00:00 2001 From: Santinova Deployment Date: Sat, 24 Oct 2026 17:06:49 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20normaliser=20les=20URL=20production=20p?= =?UTF-8?q?ar=20entit=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- APP_MANIFEST.md | 4 +- Dockerfile | 31 +++++--------- README.md | 2 +- app.json | 4 +- docker-compose.yml | 2 +- ops/provision-dashboard-alert-token.sh | 2 +- ops/setup-gitea-webhooks.py | 2 +- src/README.md | 2 +- src/backend/src/config.js | 2 +- .../test/active-ai-configuration.test.js | 4 +- src/backend/test/app-registry.test.js | 10 ++--- .../test/dockerfile-source-path.test.js | 14 +++++++ src/docker-compose.yml | 2 +- src/frontend/src/pages/DocumentationPage.jsx | 36 ++++++++--------- src/frontend/src/pages/InventairePage.jsx | 40 +++++++++---------- src/frontend/test/documentation-ai.test.mjs | 2 +- src/frontend/test/environment.test.mjs | 4 +- 17 files changed, 84 insertions(+), 79 deletions(-) create mode 100644 src/backend/test/dockerfile-source-path.test.js diff --git a/APP_MANIFEST.md b/APP_MANIFEST.md index a094c75..a1cf0cb 100644 --- a/APP_MANIFEST.md +++ b/APP_MANIFEST.md @@ -32,8 +32,8 @@ Le dashboard et le portail applicatif utilisent exclusivement le manifeste plac "activeConfigurationPath": "/api/ai/active-configuration" }, "urls": { - "recette": "https://contacts.recette.santinova-soft.org", - "prod": "https://contacts.santinova-soft.org" + "recette": "https://contacts-itinova.recette.santinova-soft.org", + "prod": "https://contacts-itinova.santinova-soft.org" } } ``` diff --git a/Dockerfile b/Dockerfile index 82c8d4f..e653dca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,43 +1,34 @@ -# Stage 1: Build frontend +# Stage 1: Build frontend from Git-tracked sources FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend -# Copy frontend package files -COPY frontend/package.json frontend/pnpm-lock.yaml* ./ +# Copy frontend package files and source from the versioned src tree. +COPY src/frontend/package.json src/frontend/pnpm-lock.yaml* ./ RUN npm install - -# Copy frontend source -COPY frontend/ ./ - -# Build frontend +COPY src/frontend/ ./ RUN npm run build -# Stage 2: Production +# Stage 2: Production runtime FROM node:20-alpine WORKDIR /app -# Install docker CLI for docker compose commands +# Docker controls are required by the dashboard runtime. RUN apk add --no-cache docker-cli docker-cli-compose git curl unzip bash openssh-client - -# Configure git global identity to avoid "Author identity unknown" errors RUN git config --global user.email "admin@santinova-soft.org" && \ git config --global user.name "Manus Dashboard" -# Copy backend -COPY backend/package.json ./backend/ +# Copy backend dependencies and source from the versioned src tree. +COPY src/backend/package.json src/backend/package-lock.json ./backend/ WORKDIR /app/backend -RUN npm install --production +RUN npm ci --omit=dev +COPY src/backend/ ./ -COPY backend/ ./ - -# Copy built frontend +# Copy the frontend built from the same Git revision. WORKDIR /app COPY --from=frontend-builder /app/frontend/dist ./frontend/dist WORKDIR /app/backend - EXPOSE 3001 - CMD ["node", "src/index.js"] diff --git a/README.md b/README.md index 6cf68ec..9c3920e 100644 --- a/README.md +++ b/README.md @@ -28,5 +28,5 @@ docker compose build --no-cache && docker compose up -d ## Accès -- URL : https://dashboard.santinova-soft.org +- URL : https://dashboard-santinova.santinova-soft.org - Login : adminItinova / Itinova69! diff --git a/app.json b/app.json index 316342b..1303ba0 100644 --- a/app.json +++ b/app.json @@ -3,8 +3,8 @@ "name": "Dashboard Production", "category": "INFRA", "urls": { - "recette": "https://dashboard.recette.santinova-soft.org", - "prod": "https://dashboard.santinova-soft.org" + "recette": "https://dashboard-santinova.recette.santinova-soft.org", + "prod": "https://dashboard-santinova.santinova-soft.org" }, "containerName": "manus-dashboard", "image": "images/dashboard.png", diff --git a/docker-compose.yml b/docker-compose.yml index d58d158..9e79ab1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: - web labels: - "traefik.enable=true" - - "traefik.http.routers.manus-dashboard.rule=Host(`dashboard.santinova-soft.org`)" + - "traefik.http.routers.manus-dashboard.rule=Host(`dashboard.santinova-soft.org`) || Host(`dashboard-santinova.santinova-soft.org`)" - "traefik.http.routers.manus-dashboard.entrypoints=websecure" - "traefik.http.routers.manus-dashboard.tls=true" - "traefik.http.routers.manus-dashboard.tls.certresolver=letsencrypt" diff --git a/ops/provision-dashboard-alert-token.sh b/ops/provision-dashboard-alert-token.sh index 0bbee60..ac56b08 100644 --- a/ops/provision-dashboard-alert-token.sh +++ b/ops/provision-dashboard-alert-token.sh @@ -4,7 +4,7 @@ set -Eeuo pipefail readonly DASHBOARD_APP_DIR="${DASHBOARD_APP_DIR:-/opt/manus-deploy/apps/manus-dashboard}" readonly DASHBOARD_ENV_FILE="${DASHBOARD_ENV_FILE:-${DASHBOARD_APP_DIR}/.env}" readonly REFERENTIEL_CONTAINER="${REFERENTIEL_CONTAINER:-referentiel-ia}" -readonly REFERENTIEL_URL="${IA_REFERENTIEL_URL:-https://referentiel-ia.recette.santinova-soft.org}" +readonly REFERENTIEL_URL="${IA_REFERENTIEL_URL:-https://referentiel-ia-santinova.recette.santinova-soft.org}" backup="$(mktemp /tmp/manus-dashboard-env-backup.XXXXXX)" temporary="$(mktemp /tmp/manus-dashboard-env-next.XXXXXX)" diff --git a/ops/setup-gitea-webhooks.py b/ops/setup-gitea-webhooks.py index b6ddbe1..c2569c7 100644 --- a/ops/setup-gitea-webhooks.py +++ b/ops/setup-gitea-webhooks.py @@ -12,7 +12,7 @@ from pathlib import Path GITEA_URL = os.environ.get("GITEA_URL", "https://git.recette.santinova-soft.org").rstrip("/") GITEA_USER = os.environ.get("GITEA_USERNAME", "manus-admin") -DASHBOARD_URL = os.environ.get("DASHBOARD_URL", "https://dashboard.recette.santinova-soft.org").rstrip("/") +DASHBOARD_URL = os.environ.get("DASHBOARD_URL", "https://dashboard-santinova.recette.santinova-soft.org").rstrip("/") DASHBOARD_APP_DIR = Path(os.environ.get("DASHBOARD_APP_DIR", "/opt/manus-deploy/apps/manus-dashboard")) ENV_FILE = DASHBOARD_APP_DIR / ".env" WEBHOOK_ENDPOINT = f"{DASHBOARD_URL}/api/webhook/gitea" diff --git a/src/README.md b/src/README.md index 6cf68ec..9c3920e 100644 --- a/src/README.md +++ b/src/README.md @@ -28,5 +28,5 @@ docker compose build --no-cache && docker compose up -d ## Accès -- URL : https://dashboard.santinova-soft.org +- URL : https://dashboard-santinova.santinova-soft.org - Login : adminItinova / Itinova69! diff --git a/src/backend/src/config.js b/src/backend/src/config.js index 649f277..f2c539a 100644 --- a/src/backend/src/config.js +++ b/src/backend/src/config.js @@ -31,7 +31,7 @@ module.exports = { }, dsiAlerts: { - url: process.env.IA_REFERENTIEL_URL || 'https://referentiel-ia.recette.santinova-soft.org', + url: process.env.IA_REFERENTIEL_URL || 'https://referentiel-ia-santinova.recette.santinova-soft.org', token: process.env.IA_REFERENTIEL_TOKEN || null, email: process.env.DSI_ALERT_EMAIL || null, emailQueue: process.env.CI_ALERT_EMAIL_QUEUE || '/opt/manus-deploy/secrets/ci-alerts/outbox', diff --git a/src/backend/test/active-ai-configuration.test.js b/src/backend/test/active-ai-configuration.test.js index 6a469db..69a9b13 100644 --- a/src/backend/test/active-ai-configuration.test.js +++ b/src/backend/test/active-ai-configuration.test.js @@ -25,9 +25,9 @@ const { getActiveAiConfiguration } = require('../src/healthcheck'); const result = await getActiveAiConfiguration({ ai: { activeConfigurationPath: '/api/ai/active-configuration' }, - }, 'https://contacts.recette.santinova-soft.org'); + }, 'https://contacts-itinova.recette.santinova-soft.org'); - assert.equal(requestedUrl, 'https://contacts.recette.santinova-soft.org/api/ai/active-configuration'); + assert.equal(requestedUrl, 'https://contacts-itinova.recette.santinova-soft.org/api/ai/active-configuration'); assert.deepEqual(result, { active: true, runtimeStatus: 'active', diff --git a/src/backend/test/app-registry.test.js b/src/backend/test/app-registry.test.js index 9a6e286..40cd027 100644 --- a/src/backend/test/app-registry.test.js +++ b/src/backend/test/app-registry.test.js @@ -29,8 +29,8 @@ try { activeConfigurationPath: '/api/ai/active-configuration', }, urls: { - recette: 'https://contacts.recette.santinova-soft.org', - prod: 'https://contacts.santinova-soft.org', + recette: 'https://contacts-itinova.recette.santinova-soft.org', + prod: 'https://contacts-itinova.santinova-soft.org', }, }); @@ -39,8 +39,8 @@ try { name: 'Dashboard', category: 'INFRA', urls: { - recette: 'https://dashboard.recette.santinova-soft.org', - prod: 'https://dashboard.santinova-soft.org', + recette: 'https://dashboard-santinova.recette.santinova-soft.org', + prod: 'https://dashboard-santinova.santinova-soft.org', }, }); @@ -48,7 +48,7 @@ try { id: 'falc-generator', name: 'FALC Generator', category: 'ITINOVA', - urls: { recette: 'https://falc.recette.santinova-soft.org' }, + urls: { recette: 'https://falc-itinova.recette.santinova-soft.org' }, }); writeManifest(sandbox, 'invalide', { diff --git a/src/backend/test/dockerfile-source-path.test.js b/src/backend/test/dockerfile-source-path.test.js new file mode 100644 index 0000000..e4210c1 --- /dev/null +++ b/src/backend/test/dockerfile-source-path.test.js @@ -0,0 +1,14 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const dockerfile = fs.readFileSync(path.resolve(__dirname, '../../../Dockerfile'), 'utf8'); + +assert.match(dockerfile, /COPY src\/frontend\/package\.json src\/frontend\/pnpm-lock\.yaml\* \.\//); +assert.match(dockerfile, /COPY src\/frontend\/ \.\//); +assert.match(dockerfile, /COPY src\/backend\/package\.json src\/backend\/package-lock\.json \.\/backend\//); +assert.match(dockerfile, /COPY src\/backend\/ \.\//); +assert.doesNotMatch(dockerfile, /COPY frontend\//); +assert.doesNotMatch(dockerfile, /COPY backend\//); + +console.log('OK dockerfile source paths'); diff --git a/src/docker-compose.yml b/src/docker-compose.yml index c8c31ed..c05d640 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -17,7 +17,7 @@ services: - GITEA_RECETTE_URL=http://gitea:3000 - GITEA_TOKEN_PROD=${GITEA_TOKEN_PROD:-} - WEBHOOK_SECRET=${WEBHOOK_SECRET:?WEBHOOK_SECRET requis dans le fichier .env serveur} - - IA_REFERENTIEL_URL=${IA_REFERENTIEL_URL:-https://referentiel-ia.recette.santinova-soft.org} + - IA_REFERENTIEL_URL=https://referentiel-ia-santinova.santinova-soft.org - IA_REFERENTIEL_TOKEN=${IA_REFERENTIEL_TOKEN:-} - DSI_ALERT_EMAIL=${DSI_ALERT_EMAIL:-} - CI_ALERT_EMAIL_QUEUE=/opt/manus-deploy/secrets/ci-alerts/outbox diff --git a/src/frontend/src/pages/DocumentationPage.jsx b/src/frontend/src/pages/DocumentationPage.jsx index b32a39a..bbb4f45 100644 --- a/src/frontend/src/pages/DocumentationPage.jsx +++ b/src/frontend/src/pages/DocumentationPage.jsx @@ -55,9 +55,9 @@ const RECETTE = { icon: LayoutDashboard, color: 'blue', name: 'Dashboard', - url: 'dashboard.recette.santinova-soft.org', + url: 'dashboard-santinova.recette.santinova-soft.org', desc: 'Monitoring CPU / RAM / Disque', - href: 'https://dashboard.recette.santinova-soft.org', + href: 'https://dashboard-santinova.recette.santinova-soft.org', }, { icon: Globe, @@ -80,38 +80,38 @@ const RECETTE = { icon: '🧾', color: 'green', name: 'Démat. Facturation DSI', - url: 'demat-facturation.recette.santinova-soft.org', - href: 'https://demat-facturation.recette.santinova-soft.org', + url: 'demat-facturation-santinova.recette.santinova-soft.org', + href: 'https://demat-facturation-santinova.recette.santinova-soft.org', }, { icon: '👁️', color: 'indigo', name: 'Veille Réglementaire', - url: 'veille.recette.santinova-soft.org', - href: 'https://veille.recette.santinova-soft.org', + url: 'veille-itinova.recette.santinova-soft.org', + href: 'https://veille-itinova.recette.santinova-soft.org', }, { icon: '🗺️', color: 'cyan', name: 'SONUM', - url: 'sonum.recette.santinova-soft.org', + url: 'sonum-santinova.recette.santinova-soft.org', desc: 'Cartographie solutions numériques FEHAP', - href: 'https://sonum.recette.santinova-soft.org', + href: 'https://sonum-santinova.recette.santinova-soft.org', }, { icon: '👤', color: 'pink', name: 'Itinova Contacts', - url: 'contacts.recette.santinova-soft.org', - href: 'https://contacts.recette.santinova-soft.org', + url: 'contacts-itinova.recette.santinova-soft.org', + href: 'https://contacts-itinova.recette.santinova-soft.org', }, { icon: BrainCircuit, color: 'purple', name: 'Référentiel IA Santinova', - url: 'referentiel-ia.recette.santinova-soft.org', + url: 'referentiel-ia-santinova.recette.santinova-soft.org', desc: 'Décisions, contrats sécurisés et budgets IA', - href: 'https://referentiel-ia.recette.santinova-soft.org', + href: 'https://referentiel-ia-santinova.recette.santinova-soft.org', }, { icon: ServerCog, @@ -125,15 +125,15 @@ const RECETTE = { icon: '🚗', color: 'yellow', name: 'Flotte Véhicules', - url: 'flotte.recette.santinova-soft.org', - href: 'https://flotte.recette.santinova-soft.org', + url: 'flotte-itinova.recette.santinova-soft.org', + href: 'https://flotte-itinova.recette.santinova-soft.org', }, { icon: '🎧', color: 'orange', name: 'Podcasts Itinova', - url: 'podcasts.recette.santinova-soft.org', - href: 'https://podcasts.recette.santinova-soft.org', + url: 'podcasts-itinova.recette.santinova-soft.org', + href: 'https://podcasts-itinova.recette.santinova-soft.org', }, ], }; @@ -159,9 +159,9 @@ const PRODUCTION = { icon: LayoutDashboard, color: 'blue', name: 'Dashboard', - url: 'dashboard.santinova-soft.org', + url: 'dashboard-santinova.santinova-soft.org', desc: 'Monitoring temps réel', - href: 'https://dashboard.santinova-soft.org', + href: 'https://dashboard-santinova.santinova-soft.org', }, { icon: Globe, diff --git a/src/frontend/src/pages/InventairePage.jsx b/src/frontend/src/pages/InventairePage.jsx index b0e51a1..2e21edb 100644 --- a/src/frontend/src/pages/InventairePage.jsx +++ b/src/frontend/src/pages/InventairePage.jsx @@ -7,49 +7,49 @@ const APPS_CONFIG = [ id: 'itinova-contacts', name: 'Itinova Contacts', repoName: 'itinova-contacts', - urlRecette: 'https://contacts.recette.santinova-soft.org', - urlProd: 'https://contacts.santinova-soft.org', + urlRecette: 'https://contacts-itinova.recette.santinova-soft.org', + urlProd: 'https://contacts-itinova.santinova-soft.org', }, { id: 'itinova-podcasts', name: 'Itinova Podcasts', repoName: 'itinova-podcasts', - urlRecette: 'https://podcasts.recette.santinova-soft.org', - urlProd: 'https://podcasts.santinova-soft.org', + urlRecette: 'https://podcasts-itinova.recette.santinova-soft.org', + urlProd: 'https://podcasts-itinova.santinova-soft.org', }, { id: 'veille-reglementaire', name: 'Veille Réglementaire', repoName: 'veille-reglementaire', - urlRecette: 'https://veille.recette.santinova-soft.org', - urlProd: 'https://veille.santinova-soft.org', + urlRecette: 'https://veille-itinova.recette.santinova-soft.org', + urlProd: 'https://veille-itinova.santinova-soft.org', }, { id: 'itinova-vehicle-exchange', name: 'Itinova Gestion de Flotte', repoName: 'itinova-vehicle-exchange', - urlRecette: 'https://flotte.recette.santinova-soft.org', - urlProd: 'https://flotte.santinova-soft.org', + urlRecette: 'https://flotte-itinova.recette.santinova-soft.org', + urlProd: 'https://flotte-itinova.santinova-soft.org', }, { id: 'sonum', name: 'SONUM', repoName: 'sonum', - urlRecette: 'https://sonum.recette.santinova-soft.org', - urlProd: 'https://sonum.santinova-soft.org', + urlRecette: 'https://sonum-santinova.recette.santinova-soft.org', + urlProd: 'https://sonum-santinova.santinova-soft.org', }, { id: 'demat-facturation-dsi', name: 'Démat. Facturation DSI', repoName: 'demat-facturation-dsi', - urlRecette: 'https://demat-facturation.recette.santinova-soft.org', - urlProd: 'https://demat-facturation.santinova-soft.org', + urlRecette: 'https://demat-facturation-santinova.recette.santinova-soft.org', + urlProd: 'https://demat-facturation-santinova.santinova-soft.org', }, { id: 'facturation-santinova', name: 'Facturation Santinova', repoName: 'facturation-santinova', - urlRecette: 'https://facturation.recette.santinova-soft.org', + urlRecette: 'https://facturation-santinova.recette.santinova-soft.org', urlProd: null, }, { @@ -57,28 +57,28 @@ const APPS_CONFIG = [ name: 'Formation Manager', repoName: 'formation-manager-itinova', urlRecette: 'https://formation.recette.santinova-soft.org', - urlProd: 'https://formations.itinova.org', + urlProd: 'https://formations-itinova.santinova-soft.org', }, { id: 'pilotage-masse-salariale', name: 'Pilotage Masse Salariale', repoName: 'pilotage-masse-salariale', - urlRecette: 'https://pilotage-ms.recette.santinova-soft.org', + urlRecette: 'https://pilotage-ms-itinova.recette.santinova-soft.org', urlProd: null, }, { id: 'itinova-budget-si', name: 'Gestion Budget Informatique', repoName: 'itinova-budget-si', - urlRecette: 'https://budget-si.recette.santinova-soft.org', + urlRecette: 'https://budget-si-santinova.recette.santinova-soft.org', urlProd: null, }, { id: 'falc-generator', name: 'FALC Generator', repoName: 'falc-generator', - urlRecette: 'https://falc.recette.santinova-soft.org', - urlProd: 'https://falc.santinova-soft.org', + urlRecette: 'https://falc-itinova.recette.santinova-soft.org', + urlProd: 'https://falc-itinova.santinova-soft.org', }, { id: 'portail-santinova', @@ -91,8 +91,8 @@ const APPS_CONFIG = [ id: 'manus-dashboard', name: 'Dashboard Production', repoName: 'manus-dashboard', - urlRecette: 'https://dashboard.recette.santinova-soft.org', - urlProd: 'https://dashboard.santinova-soft.org', + urlRecette: 'https://dashboard-santinova.recette.santinova-soft.org', + urlProd: 'https://dashboard-santinova.santinova-soft.org', }, ]; diff --git a/src/frontend/test/documentation-ai.test.mjs b/src/frontend/test/documentation-ai.test.mjs index f3048b5..501e5d5 100644 --- a/src/frontend/test/documentation-ai.test.mjs +++ b/src/frontend/test/documentation-ai.test.mjs @@ -22,7 +22,7 @@ test('le workflow rappelle la séparation des budgets et des accès internes', a assert.match(source, /Aucune clé fournisseur ni URL Ollama privée/); assert.match(source, /aria-labelledby="ia-workflow-title"/); assert.match(source, /aria-label="Modes de décision du moteur IA"/); - assert.match(source, /referentiel-ia\.recette\.santinova-soft\.org/); + assert.match(source, /referentiel-ia-santinova\.recette\.santinova-soft\.org/); assert.match(source, /llm-local\.recette\.santinova-soft\.org/); assert.doesNotMatch(source, /http:\/\/ollama:11434/); }); diff --git a/src/frontend/test/environment.test.mjs b/src/frontend/test/environment.test.mjs index 4e149ec..2838cef 100644 --- a/src/frontend/test/environment.test.mjs +++ b/src/frontend/test/environment.test.mjs @@ -4,7 +4,7 @@ import test from 'node:test'; import { getEnvironmentConfig } from '../src/utils/environment.js'; test('détecte le dashboard de recette depuis son domaine public', () => { - const config = getEnvironmentConfig('dashboard.recette.santinova-soft.org'); + const config = getEnvironmentConfig('dashboard-santinova.recette.santinova-soft.org'); assert.equal(config.isRecette, true); assert.equal(config.label, 'Recette'); assert.equal(config.serverPublicIp, '78.138.58.109'); @@ -13,7 +13,7 @@ test('détecte le dashboard de recette depuis son domaine public', () => { }); test('détecte le dashboard de production depuis son domaine public', () => { - const config = getEnvironmentConfig('dashboard.santinova-soft.org'); + const config = getEnvironmentConfig('dashboard-santinova.santinova-soft.org'); assert.equal(config.isRecette, false); assert.equal(config.label, 'Production'); assert.equal(config.serverPublicIp, '180.149.196.138');