diff --git a/APP_MANIFEST.md b/APP_MANIFEST.md index ce78df2..e0c824a 100644 --- a/APP_MANIFEST.md +++ b/APP_MANIFEST.md @@ -19,6 +19,15 @@ Le dashboard et le portail applicatif utilisent exclusivement le manifeste plac "giteaRepo": "itinova-contacts", "giteaOwner": "manus-admin", "image": "images/itinova-contacts.png", + "ai": { + "engines": [ + { + "provider": "Google Gemini", + "model": "gemini-2.5-flash", + "role": "Assistant conversationnel" + } + ] + }, "urls": { "recette": "https://contacts.recette.santinova-soft.org", "prod": "https://contacts.santinova-soft.org" @@ -35,11 +44,12 @@ Le dashboard et le portail applicatif utilisent exclusivement le manifeste plac | `giteaRepo` | Dépôt source | Sert au webhook CI/CD et à l’inventaire. | | `giteaOwner` | Organisation/propriétaire Gitea | Facultatif si la valeur par défaut suffit. | | `image` | Image locale du portail | Chemin relatif à `portail-santinova/images/`. | +| `ai.engines` | Moteurs IA déclarés | Tableau optionnel de métadonnées publiques : `provider`, `model` et `role` optionnel. Ne jamais y mettre une clé, un jeton ni un endpoint privé. | | `urls.recette` | URL de recette | Obligatoire et HTTPS pour un dashboard recette. | | `urls.prod` | URL de production | Obligatoire et HTTPS pour un dashboard de production. | ## Règles de déploiement -Chaque premier déploiement doit créer ou mettre à jour le manifeste dans le même commit que le `docker-compose.yml` et les éléments nécessaires à l’application. Lorsqu’une application est promue, son manifeste de production doit contenir l’URL de production avant le démarrage du dashboard. +Chaque premier déploiement doit créer ou mettre à jour le manifeste dans le même commit que le `docker-compose.yml` et les éléments nécessaires à l’application. Lorsqu’une application est promue, son manifeste de production doit contenir l’URL de production avant le démarrage du dashboard. Lorsque l’application utilise un moteur IA, le même commit renseigne `ai.engines` avec des informations descriptives vérifiées, jamais avec des secrets. Le dashboard ne maintient plus de liste statique d’applications, et le portail ne maintient plus de tuiles codées en dur. Cette règle élimine les écarts de version, d’URL, de catégorie et de statut entre les deux interfaces. diff --git a/app.json b/app.json index cfcb864..316342b 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "id": "manus-dashboard", - "name": "Dashboard", + "name": "Dashboard Production", "category": "INFRA", "urls": { "recette": "https://dashboard.recette.santinova-soft.org", diff --git a/docker-compose.yml b/docker-compose.yml index a454dd0..d58d158 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,58 +1,47 @@ services: dashboard: build: - # Les sources actives sont versionnées sous src/. Le contexte racine - # pouvait réutiliser d’anciens dossiers backend/frontend restés sur l’hôte. context: ./src dockerfile: Dockerfile container_name: manus-dashboard - # Le dashboard doit repartir seul après un arrêt anormal du processus. + # Redémarrage automatique après un arrêt anormal ; le processus reste borné. restart: always mem_limit: 512m + privileged: true env_file: - .env environment: - NODE_ENV=${NODE_ENV:-production} - - DASHBOARD_ENV=${DASHBOARD_ENV:-recette} + - DASHBOARD_ENV=prod - PORT=${PORT:-3001} - - JWT_SECRET=${JWT_SECRET:?JWT_SECRET requis dans le fichier .env serveur} - - ADMIN_USERNAME=${ADMIN_USERNAME:?ADMIN_USERNAME requis dans le fichier .env serveur} - - ADMIN_PASSWORD=${ADMIN_PASSWORD:?ADMIN_PASSWORD requis dans le fichier .env serveur} - - GITEA_URL=${GITEA_URL:?GITEA_URL requis dans le fichier .env serveur} - - GITEA_USERNAME=${GITEA_USERNAME:-manus-admin} - - GITEA_TOKEN=${GITEA_TOKEN:?GITEA_TOKEN requis dans le fichier .env serveur} - - GITEA_RECETTE_URL=${GITEA_RECETTE_URL:-http://gitea:3000} - - GITEA_RECETTE_PUBLIC_URL=${GITEA_RECETTE_PUBLIC_URL:-https://git.recette.santinova-soft.org} - - GITEA_RECETTE_TOKEN=${GITEA_RECETTE_TOKEN:-} - - GITEA_PROD_URL=${GITEA_PROD_URL:-https://git.santinova-soft.org} - - 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_TOKEN=${IA_REFERENTIEL_TOKEN:-} - - DSI_ALERT_EMAIL=${DSI_ALERT_EMAIL:-} - - CI_ALERT_EMAIL_QUEUE=/opt/manus-deploy/secrets/ci-alerts/outbox - - APPS_BASE_PATH=/opt/manus-deploy/apps - - INFRA_BASE_PATH=/opt/manus-deploy/infrastructure + - JWT_SECRET=${JWT_SECRET} + - ADMIN_USERNAME=${ADMIN_USERNAME} + - ADMIN_PASSWORD=${ADMIN_PASSWORD} + - GITEA_URL=${GITEA_URL} + - GITEA_USERNAME=${GITEA_USERNAME} + - GITEA_PASSWORD=${GITEA_PASSWORD} + - GITEA_TOKEN=${GITEA_TOKEN:?GITEA_TOKEN requis} + - WEBHOOK_SECRET=${WEBHOOK_SECRET:?WEBHOOK_SECRET requis} + - GITEA_RECETTE_URL=${GITEA_RECETTE_URL} + - GITEA_RECETTE_TOKEN=${GITEA_RECETTE_TOKEN} + - GITEA_PASSWORD_PROD=${GITEA_PASSWORD} + - APPS_BASE_PATH=${APPS_BASE_PATH:-/opt/manus-deploy/apps} + - INFRA_BASE_PATH=${INFRA_BASE_PATH:-/opt/manus-deploy/infrastructure} # Contrôles bornés et dédoublonnés par healthcheck.js. - - HEALTH_CHECK_INTERVAL=60000 + - HEALTH_CHECK_INTERVAL=${HEALTH_CHECK_INTERVAL:-60000} volumes: - /var/run/docker.sock:/var/run/docker.sock - /opt/manus-deploy:/opt/manus-deploy - extra_hosts: - - "git.santinova-soft.org:180.149.196.138" - - "git.recette.santinova-soft.org:78.138.58.109" networks: - web labels: - "traefik.enable=true" - # Le domaine public est spécifique au serveur, mais le commit reste commun. - - "traefik.http.routers.manus-dashboard.rule=Host(`${DASHBOARD_DOMAIN:?DASHBOARD_DOMAIN requis dans le fichier .env serveur}`)" + - "traefik.http.routers.manus-dashboard.rule=Host(`dashboard.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" - "traefik.http.routers.manus-dashboard.service=manus-dashboard-svc" - "traefik.http.routers.manus-dashboard.priority=100" - # Service - "traefik.http.services.manus-dashboard-svc.loadbalancer.server.port=3001" - "traefik.docker.network=web" diff --git a/src/backend/package.json b/src/backend/package.json index 05268d9..0e20165 100644 --- a/src/backend/package.json +++ b/src/backend/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "node src/index.js", "dev": "nodemon src/index.js", - "test": "node test/app-registry.test.js && node test/healthcheck.test.js && node test/webhook.test.js && node test/gitea.test.js && node test/gitea-auth.test.js && node test/token-rotation.test.js && node test/ci-alerts.test.js && node test/ci-email-alert.test.js && node test/webhook-setup.test.js && node test/dashboard-alert-token.test.js" + "test": "node test/app-registry.test.js && node test/healthcheck.test.js && node test/healthcheck-category.test.js && node test/applications-ui-contract.test.js && node test/webhook.test.js && node test/gitea.test.js && node test/gitea-auth.test.js && node test/token-rotation.test.js && node test/ci-alerts.test.js && node test/ci-email-alert.test.js && node test/webhook-setup.test.js && node test/dashboard-alert-token.test.js" }, "dependencies": { "express": "^4.21.0", diff --git a/src/backend/src/ai-catalog.js b/src/backend/src/ai-catalog.js new file mode 100644 index 0000000..0586f58 --- /dev/null +++ b/src/backend/src/ai-catalog.js @@ -0,0 +1,70 @@ +/** + * Moteurs IA vérifiés pour les applications historiques dont le manifeste ne + * contient pas encore ai.engines. Un champ ai.engines dans app.json reste + * prioritaire et permet à chaque application de remplacer cette valeur. + * + * Ce catalogue contient uniquement des informations descriptives publiques : + * aucun secret, endpoint privé ou paramètre d'exécution n'y figure. + */ +const AI_CATALOG = Object.freeze({ + 'demat-facturation-dsi': [ + { provider: 'Mistral AI', model: 'mistral-large-latest', role: 'Extraction de factures' }, + { provider: 'Google Gemini', model: 'gemini-2.0-flash', role: 'Relève de traitement' }, + ], + 'facturation-santinova': [ + { provider: 'OpenAI', model: 'gpt-4.1-mini', role: 'Traitement documentaire' }, + ], + 'falc-generator': [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Transformation FALC' }, + { provider: 'Google Gemini', model: 'gemini-3.1-pro-preview', role: 'Récits de vie' }, + ], + 'formation-manager-itinova': [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }, + ], + 'gestion-at-v2': [ + { provider: 'Manus Forge', model: 'gemini-3-flash-preview', role: 'Analyse CERFA' }, + { provider: 'OpenAI', model: 'gpt-image-2', role: 'Génération d’images' }, + ], + 'gestion-llm-locaux': [ + { provider: 'Ollama local', model: 'llama3.2:1b', role: 'Moteur privé approuvé' }, + ], + 'itinova-budget-si': [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }, + ], + 'itinova-contacts': [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }, + { provider: 'Anthropic Claude', model: 'claude-sonnet-4-6', role: 'Connexion configurée' }, + ], + 'itinova-podcasts': [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }, + ], + 'itinova-vehicle-exchange': [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }, + ], + 'pilotage-masse-salariale': [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }, + ], + portee: [ + { provider: 'OpenAI', model: 'gpt-image-2', role: 'Génération d’images' }, + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }, + ], + 'referentiel-ia': [ + { provider: 'Google Gemini', model: 'gemini-2.5-flash', role: 'Moteur référencé' }, + { provider: 'OpenAI', model: 'gpt-5.4-mini', role: 'Moteur référencé' }, + { provider: 'Anthropic Claude', model: 'claude-sonnet-4-6', role: 'Moteur référencé' }, + { provider: 'Mistral AI', model: 'mistral-large', role: 'Moteur référencé' }, + { provider: 'Ollama local', model: 'llama3.2:1b', role: 'Moteur référencé' }, + ], + sonum: [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }, + ], + 'veille-reglementaire': [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Classification réglementaire' }, + ], +}); + +function getAiCatalogEntry(appId) { + return AI_CATALOG[appId] || []; +} + +module.exports = { AI_CATALOG, getAiCatalogEntry }; diff --git a/src/backend/src/app-registry.js b/src/backend/src/app-registry.js index eb452be..3d74ce4 100644 --- a/src/backend/src/app-registry.js +++ b/src/backend/src/app-registry.js @@ -7,6 +7,7 @@ */ const fs = require('fs'); const path = require('path'); +const { getAiCatalogEntry } = require('./ai-catalog'); const CATEGORIES = new Set(['ITINOVA', 'SANTINOVA', 'INFRA']); const ENVIRONMENTS = new Set(['recette', 'prod']); @@ -20,6 +21,40 @@ function isHttpsUrl(value) { } } +/** + * Normalise les moteurs IA déclarés dans app.json. Seules des métadonnées + * descriptives (fournisseur, modèle, rôle) sont acceptées : aucune clé ni URL + * technique ne doit être publiée par le dashboard. + */ +function normalizeAi(ai, appId) { + if (ai === undefined) return { engines: getAiCatalogEntry(appId) }; + if (!ai || typeof ai !== 'object' || Array.isArray(ai) || !Array.isArray(ai.engines)) { + throw new Error('ai doit être un objet avec un tableau engines'); + } + + return { + engines: ai.engines.map((engine, index) => { + if (!engine || typeof engine !== 'object' || Array.isArray(engine)) { + throw new Error(`ai.engines[${index}] doit être un objet`); + } + if (typeof engine.provider !== 'string' || engine.provider.trim().length === 0) { + throw new Error(`ai.engines[${index}].provider absent ou invalide`); + } + if (typeof engine.model !== 'string' || engine.model.trim().length === 0) { + throw new Error(`ai.engines[${index}].model absent ou invalide`); + } + if (engine.role !== undefined && (typeof engine.role !== 'string' || engine.role.trim().length === 0)) { + throw new Error(`ai.engines[${index}].role invalide`); + } + return { + provider: engine.provider.trim(), + model: engine.model.trim(), + ...(engine.role ? { role: engine.role.trim() } : {}), + }; + }), + }; +} + /** * Valide puis normalise un manifeste app.json. * Les champs dérivés (directory et healthCheckUrl) ne sont pas écrits dans le @@ -54,10 +89,12 @@ function normalizeManifest(manifest, directory, environment) { const displayName = manifest.id === 'manus-dashboard' ? `Dashboard ${environment === 'prod' ? 'Production' : 'Recette'}` : manifest.name.trim(); + const ai = normalizeAi(manifest.ai, manifest.id); return { ...manifest, name: displayName, + ai, directory, containerName: manifest.containerName || manifest.id, healthCheckUrl: manifest.urls[environment], diff --git a/src/backend/src/config.js b/src/backend/src/config.js index c3c1d2e..649f277 100644 --- a/src/backend/src/config.js +++ b/src/backend/src/config.js @@ -27,7 +27,7 @@ module.exports = { username: process.env.GITEA_USERNAME || 'manus-admin', token: process.env.GITEA_TOKEN || null, productionUrl: process.env.GITEA_PROD_URL || 'https://git.santinova-soft.org', - productionToken: process.env.GITEA_TOKEN_PROD || null, + productionToken: process.env.GITEA_TOKEN_PROD || process.env.GITEA_TOKEN || null, }, dsiAlerts: { diff --git a/src/backend/src/healthcheck.js b/src/backend/src/healthcheck.js index cdd09bc..ac63c91 100644 --- a/src/backend/src/healthcheck.js +++ b/src/backend/src/healthcheck.js @@ -63,8 +63,9 @@ async function checkApp(appConfig) { id: appConfig.id, name: appConfig.name, description: appConfig.description || '', - // Préserve la catégorie du manifeste pour le rendu différencié des services INFRA. + // Préserve la catégorie et les métadonnées IA du manifeste pour l’interface. category: appConfig.category || 'SANTINOVA', + ai: appConfig.ai || { engines: [] }, urls: appConfig.urls, containerName: appConfig.containerName, lastCheck: new Date().toISOString(), diff --git a/src/backend/test/app-registry.test.js b/src/backend/test/app-registry.test.js index 2fb1134..bfd1692 100644 --- a/src/backend/test/app-registry.test.js +++ b/src/backend/test/app-registry.test.js @@ -24,6 +24,9 @@ try { category: 'ITINOVA', containerName: 'itinova-contacts', image: 'images/itinova-contacts.png', + ai: { + engines: [{ provider: 'Google Gemini', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }], + }, urls: { recette: 'https://contacts.recette.santinova-soft.org', prod: 'https://contacts.santinova-soft.org', @@ -40,21 +43,46 @@ try { }, }); + writeManifest(sandbox, 'falc', { + id: 'falc-generator', + name: 'FALC Generator', + category: 'ITINOVA', + urls: { recette: 'https://falc.recette.santinova-soft.org' }, + }); + writeManifest(sandbox, 'invalide', { id: 'app-invalide', name: 'Application invalide', category: 'INCONNUE', urls: { recette: 'https://invalide.recette.santinova-soft.org' }, }); + writeManifest(sandbox, 'ia-invalide', { + id: 'app-ia-invalide', + name: 'Application IA invalide', + category: 'ITINOVA', + ai: { engines: [{ provider: 'Google Gemini' }] }, + urls: { recette: 'https://ia-invalide.recette.santinova-soft.org' }, + }); const discovery = discoverApps({ appsBasePath: sandbox, environment: 'recette' }); - assert.equal(discovery.apps.length, 2, 'un manifeste invalide doit être exclu'); - assert.equal(discovery.errors.length, 1, 'une erreur explicite doit être retournée'); + assert.equal(discovery.apps.length, 3, 'un manifeste invalide doit être exclu'); + assert.equal(discovery.errors.length, 2, 'une erreur explicite doit être retournée pour chaque manifeste invalide'); const app = discovery.apps.find((candidate) => candidate.id === 'itinova-contacts'); assert.equal(app.directory, 'contacts', 'le dossier doit être déduit du chemin du manifeste'); assert.equal(app.healthCheckUrl, app.urls.recette, 'la vérification doit cibler l’environnement courant'); assert.equal(app.containerName, 'itinova-contacts'); + assert.deepEqual(app.ai, { + engines: [{ provider: 'Google Gemini', model: 'gemini-2.5-flash', role: 'Assistant conversationnel' }], + }, 'la métadonnée IA descriptive doit être conservée'); + + const falc = discovery.apps.find((candidate) => candidate.id === 'falc-generator'); + assert.deepEqual(falc.ai, { + engines: [ + { provider: 'Manus Forge', model: 'gemini-2.5-flash', role: 'Transformation FALC' }, + { provider: 'Google Gemini', model: 'gemini-3.1-pro-preview', role: 'Récits de vie' }, + ], + }, 'le catalogue IA doit compléter un manifeste historique sans moteur déclaré'); const dashboardRecette = discovery.apps.find((candidate) => candidate.id === 'manus-dashboard'); assert.equal(dashboardRecette.name, 'Dashboard Recette'); @@ -68,7 +96,7 @@ try { apps: [{ id: 'itinova-contacts', name: 'ancienne valeur statique' }], }; const refresh = refreshApps(config); - assert.equal(refresh.apps.length, 2, 'les applications non déployées ne doivent pas rester en cache'); + assert.equal(refresh.apps.length, 3, 'les applications non déployées ne doivent pas rester en cache'); assert.equal( config.apps.find((candidate) => candidate.id === 'itinova-contacts').name, 'Itinova Contacts', diff --git a/src/backend/test/applications-ui-contract.test.js b/src/backend/test/applications-ui-contract.test.js new file mode 100644 index 0000000..816834a --- /dev/null +++ b/src/backend/test/applications-ui-contract.test.js @@ -0,0 +1,21 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const appCard = fs.readFileSync( + path.resolve(__dirname, '../../frontend/src/components/AppCard.jsx'), + 'utf8' +); +const appSorting = fs.readFileSync( + path.resolve(__dirname, '../../frontend/src/utils/appSorting.js'), + 'utf8' +); + +assert.match(appCard, /const isInfra = app\.category === 'INFRA';/); +assert.doesNotMatch(appCard, /\['portail-santinova', 'manus-dashboard'\]\.includes\(app\.id\)/); +assert.match(appCard, /const aiEngines = Array\.isArray\(app\.ai\?\.engines\) \? app\.ai\.engines : \[\];/); +assert.match(appCard, />\s*Moteur IA\s* { console.error(error); diff --git a/src/frontend/index.html b/src/frontend/index.html index 2cbebf5..a6e7555 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -4,7 +4,7 @@ - Dashboard - Gestion des Applications + Dashboard Production - Gestion des Applications
diff --git a/src/frontend/src/components/AppCard.jsx b/src/frontend/src/components/AppCard.jsx index efaf3b3..419f454 100644 --- a/src/frontend/src/components/AppCard.jsx +++ b/src/frontend/src/components/AppCard.jsx @@ -118,8 +118,9 @@ export default function AppCard({ app, commits, onRefresh }) { const latestCommit = commits && commits.length > 0 ? commits[0] : null; - // Apps d'infrastructure : portail et dashboard - const isInfra = ['portail-santinova', 'manus-dashboard'].includes(app.id); + // Le manifeste est la seule source de vérité pour le type et les moteurs IA. + const isInfra = app.category === 'INFRA'; + const aiEngines = Array.isArray(app.ai?.engines) ? app.ai.engines : []; return (
@@ -217,7 +218,26 @@ export default function AppCard({ app, commits, onRefresh }) { )} {/* Info Grid */} -
+
+ {/* Moteurs IA déclarés dans app.json */} +
+ + Moteur IA + +
+ {aiEngines.length > 0 ? aiEngines.map((engine, index) => ( +
+

{engine.provider}

+

+ {engine.model}{engine.role ? ` — ${engine.role}` : ''} +

+
+ )) : ( +

Non déclaré

+ )} +
+
+ {/* URLs */}
diff --git a/src/frontend/src/components/Sidebar.jsx b/src/frontend/src/components/Sidebar.jsx index 956a7db..9ee3ddd 100644 --- a/src/frontend/src/components/Sidebar.jsx +++ b/src/frontend/src/components/Sidebar.jsx @@ -12,7 +12,6 @@ import { LayoutList, TerminalSquare, ShieldCheck, Activity, BookOpen, } from 'lucide-react'; -import { ENVIRONMENT_LABEL } from '../utils/environment'; const navItems = [ { id: 'dashboard', label: 'Tableau de bord', icon: LayoutDashboard }, { id: 'apps', label: 'Applications', icon: Box }, @@ -41,7 +40,7 @@ export default function Sidebar({

Dashboard

-

{ENVIRONMENT_LABEL}

+

Production

diff --git a/src/frontend/src/pages/DashboardPage.jsx b/src/frontend/src/pages/DashboardPage.jsx index 3073dbb..1e3f1e1 100644 --- a/src/frontend/src/pages/DashboardPage.jsx +++ b/src/frontend/src/pages/DashboardPage.jsx @@ -13,7 +13,6 @@ import { import { getApps, getSystemInfo } from '../utils/api'; import StatusBadge from '../components/StatusBadge'; import { sortAppsWithInfraFirst } from '../utils/appSorting'; -import { ENVIRONMENT_LABEL, GITEA_HOST, GITEA_URL, SERVER_PUBLIC_IP } from '../utils/environment'; function StatCard({ icon: Icon, label, value, color, subtext }) { return ( @@ -102,7 +101,7 @@ export default function DashboardPage({ apps }) {
Serveur - {SERVER_PUBLIC_IP} ({ENVIRONMENT_LABEL}) + 180.149.196.138 (Production)
@@ -115,12 +114,12 @@ export default function DashboardPage({ apps }) {
Gitea - {GITEA_HOST} + git.santinova-soft.org
diff --git a/src/frontend/src/pages/InventairePage.jsx b/src/frontend/src/pages/InventairePage.jsx index 6878390..be463b1 100644 --- a/src/frontend/src/pages/InventairePage.jsx +++ b/src/frontend/src/pages/InventairePage.jsx @@ -1,7 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Table, RefreshCw, GitBranch, CheckCircle, XCircle, ExternalLink, Tag, AlertCircle, ArrowUp, ArrowDown, Minus, AlertTriangle } from 'lucide-react'; import api from '../utils/api'; -import { ENVIRONMENT_LABEL } from '../utils/environment'; const APPS_CONFIG = [ { @@ -90,7 +89,7 @@ const APPS_CONFIG = [ }, { id: 'manus-dashboard', - name: `Dashboard ${ENVIRONMENT_LABEL}`, + name: 'Dashboard Production', repoName: 'manus-dashboard', urlRecette: 'https://dashboard.recette.santinova-soft.org', urlProd: 'https://dashboard.santinova-soft.org', diff --git a/src/frontend/src/pages/LoginPage.jsx b/src/frontend/src/pages/LoginPage.jsx index 6f61e52..98d1c0c 100644 --- a/src/frontend/src/pages/LoginPage.jsx +++ b/src/frontend/src/pages/LoginPage.jsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { Lock, User, AlertCircle, Server } from 'lucide-react'; import { login } from '../utils/api'; -import { ENVIRONMENT_LABEL, ENVIRONMENT_LABEL_LOWER } from '../utils/environment'; export default function LoginPage({ onLogin }) { const [username, setUsername] = useState(''); @@ -35,7 +34,7 @@ export default function LoginPage({ onLogin }) {
-

Dashboard {ENVIRONMENT_LABEL}

+

Dashboard Production

Gestion des applications

@@ -131,7 +130,7 @@ export default function LoginPage({ onLogin }) {

- Santinova Soft — Serveur de {ENVIRONMENT_LABEL_LOWER} + Santinova Soft — Serveur de production

diff --git a/src/frontend/src/pages/TerminalPage.jsx b/src/frontend/src/pages/TerminalPage.jsx index d1a9e40..94e267f 100644 --- a/src/frontend/src/pages/TerminalPage.jsx +++ b/src/frontend/src/pages/TerminalPage.jsx @@ -1,6 +1,5 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Terminal, Wifi, WifiOff, X, Plus, ChevronDown } from 'lucide-react'; -import { DEFAULT_SSH_PRESET, ENVIRONMENT_LABEL, SERVER_PUBLIC_IP } from '../utils/environment'; // Serveurs SSH prédéfinis const PRESET_SERVERS = [ @@ -31,8 +30,8 @@ export default function TerminalPage() { // Formulaire de connexion const [form, setForm] = useState({ - preset: DEFAULT_SSH_PRESET, - host: SERVER_PUBLIC_IP, + preset: 'recette', + host: '78.138.58.109', port: 22, username: 'root', password: '', @@ -117,7 +116,7 @@ export default function TerminalPage() { fitAddonRef.current = fitAddon; term.writeln('\x1b[1;34m╔══════════════════════════════════════════════════╗\x1b[0m'); - term.writeln(`\x1b[1;34m║ Terminal SSH — Dashboard ${ENVIRONMENT_LABEL} Santinova ║\x1b[0m`); + term.writeln('\x1b[1;34m║ Terminal SSH — Dashboard Production Santinova ║\x1b[0m'); term.writeln('\x1b[1;34m╚══════════════════════════════════════════════════╝\x1b[0m'); term.writeln(''); term.writeln('\x1b[90mSélectionnez un serveur et connectez-vous pour démarrer.\x1b[0m');