Compare commits
50 Commits
889af27766
...
1c9a9eff8f
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c9a9eff8f | |||
| 2d92846f04 | |||
| 428e614c87 | |||
| 69b5ab7462 | |||
| 9b75964c29 | |||
| 6dd9a09a58 | |||
| 0572c4dff6 | |||
|
|
9289011b3c | ||
|
|
9867adbcc2 | ||
|
|
4a2be26a10 | ||
|
|
5c3d644ab8 | ||
|
|
c693da6273 | ||
|
|
c0944cfb13 | ||
|
|
06fad063a4 | ||
|
|
a37e29e42a | ||
|
|
1f1d525ea1 | ||
|
|
3da5fd104c | ||
|
|
2991c7f068 | ||
|
|
37f699fcc2 | ||
|
|
3eae610c5e | ||
|
|
57ef1785aa | ||
|
|
cbc8eee818 | ||
|
|
666e0be2a2 | ||
|
|
35206fb14b | ||
|
|
d553cc4278 | ||
|
|
5105123ec4 | ||
|
|
87cc629d81 | ||
|
|
d8b24f8968 | ||
|
|
f692be04d2 | ||
|
|
7270e1a89a | ||
|
|
7defcabdc3 | ||
|
|
b006caa3df | ||
|
|
64f3233e47 | ||
|
|
bf032cc26a | ||
|
|
392782a0d2 | ||
|
|
b44cdd5f11 | ||
|
|
e168f86578 | ||
|
|
5e8d4796d4 | ||
|
|
9500ca2d70 | ||
| e451892860 | |||
|
|
5776b15bb5 | ||
|
|
f412839e6e | ||
|
|
6f9ee7fc5c | ||
|
|
fe6ab4a2ff | ||
|
|
03a6d55571 | ||
|
|
7a7b9fde47 | ||
|
|
120567a6e5 | ||
|
|
9d1db4d035 | ||
|
|
eb07a99e5e | ||
|
|
85bd5209c7 |
@@ -22,4 +22,5 @@ jobs:
|
||||
working-directory: src/frontend
|
||||
run: |
|
||||
npm ci
|
||||
npm test
|
||||
npm run build
|
||||
|
||||
2
app.json
2
app.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "manus-dashboard",
|
||||
"name": "Dashboard Production",
|
||||
"name": "Dashboard",
|
||||
"category": "INFRA",
|
||||
"urls": {
|
||||
"recette": "https://dashboard.recette.santinova-soft.org",
|
||||
|
||||
@@ -1,47 +1,58 @@
|
||||
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
|
||||
# Redémarrage automatique après un arrêt anormal ; le processus reste borné.
|
||||
# Le dashboard doit repartir seul après un arrêt anormal du processus.
|
||||
restart: always
|
||||
mem_limit: 512m
|
||||
privileged: true
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=${NODE_ENV:-production}
|
||||
- DASHBOARD_ENV=prod
|
||||
- DASHBOARD_ENV=${DASHBOARD_ENV:-recette}
|
||||
- PORT=${PORT:-3001}
|
||||
- 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}
|
||||
- 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
|
||||
# Contrôles bornés et dédoublonnés par healthcheck.js.
|
||||
- HEALTH_CHECK_INTERVAL=${HEALTH_CHECK_INTERVAL:-60000}
|
||||
- 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"
|
||||
- "traefik.http.routers.manus-dashboard.rule=Host(`dashboard.santinova-soft.org`)"
|
||||
# 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.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"
|
||||
|
||||
|
||||
@@ -51,9 +51,13 @@ function normalizeManifest(manifest, directory, environment) {
|
||||
throw new Error('ci doit être un objet avec une propriété required booléenne');
|
||||
}
|
||||
|
||||
const displayName = manifest.id === 'manus-dashboard'
|
||||
? `Dashboard ${environment === 'prod' ? 'Production' : 'Recette'}`
|
||||
: manifest.name.trim();
|
||||
|
||||
return {
|
||||
...manifest,
|
||||
name: manifest.name.trim(),
|
||||
name: displayName,
|
||||
directory,
|
||||
containerName: manifest.containerName || manifest.id,
|
||||
healthCheckUrl: manifest.urls[environment],
|
||||
|
||||
@@ -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 || process.env.GITEA_TOKEN || null,
|
||||
productionToken: process.env.GITEA_TOKEN_PROD || null,
|
||||
},
|
||||
|
||||
dsiAlerts: {
|
||||
|
||||
@@ -30,6 +30,16 @@ try {
|
||||
},
|
||||
});
|
||||
|
||||
writeManifest(sandbox, 'dashboard', {
|
||||
id: 'manus-dashboard',
|
||||
name: 'Dashboard',
|
||||
category: 'INFRA',
|
||||
urls: {
|
||||
recette: 'https://dashboard.recette.santinova-soft.org',
|
||||
prod: 'https://dashboard.santinova-soft.org',
|
||||
},
|
||||
});
|
||||
|
||||
writeManifest(sandbox, 'invalide', {
|
||||
id: 'app-invalide',
|
||||
name: 'Application invalide',
|
||||
@@ -38,22 +48,32 @@ try {
|
||||
});
|
||||
|
||||
const discovery = discoverApps({ appsBasePath: sandbox, environment: 'recette' });
|
||||
assert.equal(discovery.apps.length, 1, 'un manifeste invalide doit être exclu');
|
||||
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');
|
||||
|
||||
const app = discovery.apps[0];
|
||||
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');
|
||||
|
||||
const dashboardRecette = discovery.apps.find((candidate) => candidate.id === 'manus-dashboard');
|
||||
assert.equal(dashboardRecette.name, 'Dashboard Recette');
|
||||
const productionDiscovery = discoverApps({ appsBasePath: sandbox, environment: 'prod' });
|
||||
const dashboardProduction = productionDiscovery.apps.find((candidate) => candidate.id === 'manus-dashboard');
|
||||
assert.equal(dashboardProduction.name, 'Dashboard Production');
|
||||
|
||||
const config = {
|
||||
appsBasePath: sandbox,
|
||||
environment: 'recette',
|
||||
apps: [{ id: 'itinova-contacts', name: 'ancienne valeur statique' }],
|
||||
};
|
||||
const refresh = refreshApps(config);
|
||||
assert.equal(refresh.apps.length, 1, 'les applications non déployées ne doivent pas rester en cache');
|
||||
assert.equal(config.apps[0].name, 'Itinova Contacts', 'app.json doit prévaloir sur toute définition statique');
|
||||
assert.equal(refresh.apps.length, 2, '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',
|
||||
'app.json doit prévaloir sur toute définition statique'
|
||||
);
|
||||
|
||||
console.log('OK app-registry');
|
||||
} finally {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Dashboard Production - Gestion des Applications</title>
|
||||
<title>Dashboard - Gestion des Applications</title>
|
||||
</head>
|
||||
<body class="bg-dark-900 text-white">
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"test": "node --test test/*.test.mjs",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ChevronDown,
|
||||
} from 'lucide-react';
|
||||
import { getAvailableStacks, createApp, getGiteaRepos } from '../utils/api';
|
||||
import { APP_DOMAIN_SUFFIX, ENVIRONMENT_LABEL_LOWER } from '../utils/environment';
|
||||
|
||||
const STEPS = [
|
||||
{ id: 1, title: 'Informations', icon: FileText },
|
||||
@@ -180,7 +181,7 @@ export default function AddAppModal({ isOpen, onClose, onSuccess }) {
|
||||
Ajouter une application
|
||||
</h2>
|
||||
<p className="text-sm text-gray-400 mt-0.5">
|
||||
Déployez une nouvelle application sur le serveur de recette
|
||||
Déployez une nouvelle application sur le serveur de {ENVIRONMENT_LABEL_LOWER}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -271,7 +272,7 @@ export default function AddAppModal({ isOpen, onClose, onSuccess }) {
|
||||
}`}
|
||||
/>
|
||||
<span className="px-3 py-2.5 bg-dark-600 border border-dark-500 border-l-0 rounded-r-lg text-gray-400 text-sm whitespace-nowrap">
|
||||
.recette.santinova-soft.org
|
||||
.{APP_DOMAIN_SUFFIX}
|
||||
</span>
|
||||
</div>
|
||||
{errors.subdomain && (
|
||||
@@ -280,7 +281,7 @@ export default function AddAppModal({ isOpen, onClose, onSuccess }) {
|
||||
{formData.subdomain && !errors.subdomain && (
|
||||
<p className="text-gray-500 text-xs mt-1 flex items-center gap-1">
|
||||
<Globe className="w-3 h-3" />
|
||||
URL: https://{formData.subdomain}.recette.santinova-soft.org
|
||||
URL: https://{formData.subdomain}.{APP_DOMAIN_SUFFIX}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -545,7 +546,7 @@ export default function AddAppModal({ isOpen, onClose, onSuccess }) {
|
||||
L'application est en cours de déploiement. Suivez la progression dans l'onglet Déploiements.
|
||||
</p>
|
||||
<p className="text-gray-500 mt-2 text-xs">
|
||||
URL: https://{formData.subdomain}.recette.santinova-soft.org
|
||||
URL: https://{formData.subdomain}.{APP_DOMAIN_SUFFIX}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -573,7 +574,7 @@ export default function AddAppModal({ isOpen, onClose, onSuccess }) {
|
||||
<div className="flex justify-between items-center px-4 py-3">
|
||||
<span className="text-gray-400 text-sm">URL</span>
|
||||
<span className="text-primary-400 text-sm">
|
||||
https://{formData.subdomain}.recette.santinova-soft.org
|
||||
https://{formData.subdomain}.{APP_DOMAIN_SUFFIX}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center px-4 py-3">
|
||||
|
||||
@@ -12,6 +12,7 @@ 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 },
|
||||
@@ -40,7 +41,7 @@ export default function Sidebar({
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-lg font-bold text-white">Dashboard</h1>
|
||||
<p className="text-xs text-gray-500">Production</p>
|
||||
<p className="text-xs text-gray-500">{ENVIRONMENT_LABEL}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ 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 (
|
||||
@@ -101,7 +102,7 @@ export default function DashboardPage({ apps }) {
|
||||
<div className="flex items-center justify-between py-2 border-b border-dark-700">
|
||||
<span className="text-sm text-gray-400">Serveur</span>
|
||||
<span className="text-sm text-gray-200">
|
||||
180.149.196.138 (Production)
|
||||
{SERVER_PUBLIC_IP} ({ENVIRONMENT_LABEL})
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-2 border-b border-dark-700">
|
||||
@@ -114,12 +115,12 @@ export default function DashboardPage({ apps }) {
|
||||
<div className="flex items-center justify-between py-2 border-b border-dark-700">
|
||||
<span className="text-sm text-gray-400">Gitea</span>
|
||||
<a
|
||||
href="https://git.santinova-soft.org"
|
||||
href={GITEA_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-primary-400 hover:text-primary-300"
|
||||
>
|
||||
git.santinova-soft.org
|
||||
{GITEA_HOST}
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-2 border-b border-dark-700">
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
ShieldCheck,
|
||||
} from 'lucide-react';
|
||||
import { approveGiteaTokenRotation, getGiteaRepos, getGiteaCommits, getGiteaTokenRotation, getGiteaWorkflowRuns, getGiteaWorkflowSummary, prepareGiteaTokenRotation } from '../utils/api';
|
||||
import { GITEA_HOST, GITEA_URL } from '../utils/environment';
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return 'N/A';
|
||||
@@ -137,12 +138,12 @@ export default function GiteaPage() {
|
||||
<p className="text-gray-400 mt-1">
|
||||
Explorez les dépôts sur{' '}
|
||||
<a
|
||||
href="https://git.recette.santinova-soft.org"
|
||||
href={GITEA_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary-400 hover:text-primary-300"
|
||||
>
|
||||
git.recette.santinova-soft.org
|
||||
{GITEA_HOST}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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 = [
|
||||
{
|
||||
@@ -89,7 +90,7 @@ const APPS_CONFIG = [
|
||||
},
|
||||
{
|
||||
id: 'manus-dashboard',
|
||||
name: 'Dashboard Production',
|
||||
name: `Dashboard ${ENVIRONMENT_LABEL}`,
|
||||
repoName: 'manus-dashboard',
|
||||
urlRecette: 'https://dashboard.recette.santinova-soft.org',
|
||||
urlProd: 'https://dashboard.santinova-soft.org',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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('');
|
||||
@@ -34,7 +35,7 @@ export default function LoginPage({ onLogin }) {
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-600 mb-4">
|
||||
<Server className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-white">Dashboard Production</h1>
|
||||
<h1 className="text-2xl font-bold text-white">Dashboard {ENVIRONMENT_LABEL}</h1>
|
||||
<p className="text-gray-400 mt-2">Gestion des applications</p>
|
||||
</div>
|
||||
|
||||
@@ -130,7 +131,7 @@ export default function LoginPage({ onLogin }) {
|
||||
</div>
|
||||
|
||||
<p className="text-center text-gray-600 text-sm mt-6">
|
||||
Santinova Soft — Serveur de production
|
||||
Santinova Soft — Serveur de {ENVIRONMENT_LABEL_LOWER}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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 = [
|
||||
@@ -30,8 +31,8 @@ export default function TerminalPage() {
|
||||
|
||||
// Formulaire de connexion
|
||||
const [form, setForm] = useState({
|
||||
preset: 'recette',
|
||||
host: '78.138.58.109',
|
||||
preset: DEFAULT_SSH_PRESET,
|
||||
host: SERVER_PUBLIC_IP,
|
||||
port: 22,
|
||||
username: 'root',
|
||||
password: '',
|
||||
@@ -116,7 +117,7 @@ export default function TerminalPage() {
|
||||
fitAddonRef.current = fitAddon;
|
||||
|
||||
term.writeln('\x1b[1;34m╔══════════════════════════════════════════════════╗\x1b[0m');
|
||||
term.writeln('\x1b[1;34m║ Terminal SSH — Dashboard Production Santinova ║\x1b[0m');
|
||||
term.writeln(`\x1b[1;34m║ Terminal SSH — Dashboard ${ENVIRONMENT_LABEL} 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');
|
||||
|
||||
38
src/frontend/src/utils/environment.js
Normal file
38
src/frontend/src/utils/environment.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Identité de l'environnement courant, déterminée par le domaine public.
|
||||
* Le même commit peut ainsi être construit et exécuté en recette comme en
|
||||
* production, les secrets et domaines restant fournis par chaque serveur.
|
||||
*/
|
||||
export function getEnvironmentConfig(hostname) {
|
||||
const normalizedHostname = hostname.toLowerCase();
|
||||
const isRecette =
|
||||
normalizedHostname.includes('recette') ||
|
||||
normalizedHostname === 'localhost' ||
|
||||
normalizedHostname === '127.0.0.1';
|
||||
|
||||
return {
|
||||
isRecette,
|
||||
label: isRecette ? 'Recette' : 'Production',
|
||||
labelLower: isRecette ? 'recette' : 'production',
|
||||
serverPublicIp: isRecette ? '78.138.58.109' : '180.149.196.138',
|
||||
giteaUrl: isRecette
|
||||
? 'https://git.recette.santinova-soft.org'
|
||||
: 'https://git.santinova-soft.org',
|
||||
appDomainSuffix: isRecette
|
||||
? 'recette.santinova-soft.org'
|
||||
: 'santinova-soft.org',
|
||||
defaultSshPreset: isRecette ? 'recette' : 'production',
|
||||
};
|
||||
}
|
||||
|
||||
const currentHostname = typeof window === 'undefined' ? 'localhost' : window.location.hostname;
|
||||
const environment = getEnvironmentConfig(currentHostname);
|
||||
|
||||
export const IS_RECETTE = environment.isRecette;
|
||||
export const ENVIRONMENT_LABEL = environment.label;
|
||||
export const ENVIRONMENT_LABEL_LOWER = environment.labelLower;
|
||||
export const SERVER_PUBLIC_IP = environment.serverPublicIp;
|
||||
export const GITEA_URL = environment.giteaUrl;
|
||||
export const GITEA_HOST = new URL(GITEA_URL).hostname;
|
||||
export const APP_DOMAIN_SUFFIX = environment.appDomainSuffix;
|
||||
export const DEFAULT_SSH_PRESET = environment.defaultSshPreset;
|
||||
27
src/frontend/test/environment.test.mjs
Normal file
27
src/frontend/test/environment.test.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
import assert from 'node:assert/strict';
|
||||
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');
|
||||
assert.equal(config.isRecette, true);
|
||||
assert.equal(config.label, 'Recette');
|
||||
assert.equal(config.serverPublicIp, '78.138.58.109');
|
||||
assert.equal(config.giteaUrl, 'https://git.recette.santinova-soft.org');
|
||||
assert.equal(config.appDomainSuffix, 'recette.santinova-soft.org');
|
||||
});
|
||||
|
||||
test('détecte le dashboard de production depuis son domaine public', () => {
|
||||
const config = getEnvironmentConfig('dashboard.santinova-soft.org');
|
||||
assert.equal(config.isRecette, false);
|
||||
assert.equal(config.label, 'Production');
|
||||
assert.equal(config.serverPublicIp, '180.149.196.138');
|
||||
assert.equal(config.giteaUrl, 'https://git.santinova-soft.org');
|
||||
assert.equal(config.appDomainSuffix, 'santinova-soft.org');
|
||||
});
|
||||
|
||||
test('utilise la recette pour le développement local', () => {
|
||||
assert.equal(getEnvironmentConfig('localhost').isRecette, true);
|
||||
assert.equal(getEnvironmentConfig('127.0.0.1').isRecette, true);
|
||||
});
|
||||
Reference in New Issue
Block a user