backup: préserver les améliorations robustes de recette
This commit is contained in:
13
app.json
Normal file
13
app.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"id": "manus-dashboard",
|
||||||
|
"name": "Dashboard Recette",
|
||||||
|
"category": "INFRA",
|
||||||
|
"urls": {
|
||||||
|
"recette": "https://dashboard.recette.santinova-soft.org",
|
||||||
|
"prod": "https://dashboard.santinova-soft.org"
|
||||||
|
},
|
||||||
|
"containerName": "manus-dashboard",
|
||||||
|
"image": "images/dashboard.png",
|
||||||
|
"giteaRepo": "manus-dashboard",
|
||||||
|
"giteaOwner": "manus-admin"
|
||||||
|
}
|
||||||
@@ -434,7 +434,7 @@ function addDynamicApp(appDef) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initDynamicApps() {
|
function initDynamicApps() {
|
||||||
// 1. Charger les apps depuis .dashboard-apps.json (créées via l'interface)
|
// 1. Charger les apps depuis .dashboard-apps.json (legacy)
|
||||||
const dynamicApps = loadDynamicApps();
|
const dynamicApps = loadDynamicApps();
|
||||||
for (const app of dynamicApps) {
|
for (const app of dynamicApps) {
|
||||||
const existingInConfig = config.apps.findIndex((a) => a.id === app.id);
|
const existingInConfig = config.apps.findIndex((a) => a.id === app.id);
|
||||||
@@ -442,39 +442,32 @@ function initDynamicApps() {
|
|||||||
config.apps.push(app);
|
config.apps.push(app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 2. Scanner les dossiers app.json (découverte automatique)
|
||||||
// 2. Découverte automatique : scanner les dossiers apps/*.json
|
|
||||||
let discovered = 0;
|
|
||||||
try {
|
try {
|
||||||
const appsBasePath = config.appsBasePath;
|
const appsBasePath = config.appsBasePath || process.env.APPS_BASE_PATH || '/opt/manus-deploy/apps';
|
||||||
if (fs.existsSync(appsBasePath)) {
|
const entries = fs.readdirSync(appsBasePath, { withFileTypes: true });
|
||||||
const entries = fs.readdirSync(appsBasePath, { withFileTypes: true });
|
let scanned = 0;
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (!entry.isDirectory()) continue;
|
if (!entry.isDirectory()) continue;
|
||||||
const appJsonPath = path.join(appsBasePath, entry.name, 'app.json');
|
const appJsonPath = path.join(appsBasePath, entry.name, 'app.json');
|
||||||
if (!fs.existsSync(appJsonPath)) continue;
|
if (!fs.existsSync(appJsonPath)) continue;
|
||||||
try {
|
try {
|
||||||
const appDef = JSON.parse(fs.readFileSync(appJsonPath, 'utf-8'));
|
const appDef = JSON.parse(fs.readFileSync(appJsonPath, 'utf-8'));
|
||||||
if (!appDef.id) continue;
|
if (!appDef.id) continue;
|
||||||
// Ne pas écraser une app déjà en config (statique ou dynamique)
|
const existingInConfig = config.apps.findIndex((a) => a.id === appDef.id);
|
||||||
const existingIdx = config.apps.findIndex((a) => a.id === appDef.id);
|
if (existingInConfig < 0) {
|
||||||
if (existingIdx >= 0) {
|
config.apps.push(appDef);
|
||||||
// Mettre à jour avec les données du app.json (source de vérité)
|
scanned++;
|
||||||
config.apps[existingIdx] = { ...config.apps[existingIdx], ...appDef };
|
|
||||||
} else {
|
|
||||||
config.apps.push(appDef);
|
|
||||||
discovered++;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`[discovery] Erreur lecture ${appJsonPath}:`, e.message);
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Erreur lecture app.json dans ${entry.name}:`, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(`Apps découvertes via app.json: ${scanned}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[discovery] Erreur scan apps:', err.message);
|
console.error('Erreur scan app.json:', err.message);
|
||||||
}
|
}
|
||||||
|
console.log(`Total apps chargées: ${config.apps.length}`);
|
||||||
console.log(`Apps chargées: ${dynamicApps.length} dynamiques + ${discovered} découvertes (total: ${config.apps.length})`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ CREATION D'APPLICATION ============
|
// ============ CREATION D'APPLICATION ============
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ module.exports = {
|
|||||||
healthCheckUrl: 'https://pilotage-ms.recette.santinova-soft.org',
|
healthCheckUrl: 'https://pilotage-ms.recette.santinova-soft.org',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
category: 'ITINOVA',
|
category: 'ITINOVA',
|
||||||
|
image: 'images/pilotage-ms.png',
|
||||||
status: 'recette',
|
status: 'recette',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -155,6 +156,7 @@ module.exports = {
|
|||||||
healthCheckUrl: 'https://demat-facturation.recette.santinova-soft.org',
|
healthCheckUrl: 'https://demat-facturation.recette.santinova-soft.org',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
category: 'SANTINOVA',
|
category: 'SANTINOVA',
|
||||||
|
image: 'images/demat-facturation-dsi.jpg',
|
||||||
status: 'production',
|
status: 'production',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -205,7 +207,7 @@ module.exports = {
|
|||||||
containerName: 'portail-santinova',
|
containerName: 'portail-santinova',
|
||||||
healthCheckUrl: 'https://portail.recette.santinova-soft.org',
|
healthCheckUrl: 'https://portail.recette.santinova-soft.org',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
category: 'SANTINOVA',
|
category: 'INFRA',
|
||||||
status: 'production',
|
status: 'production',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -139,11 +139,38 @@ function gitPull(appConfig) {
|
|||||||
/**
|
/**
|
||||||
* Démarrer un conteneur Docker
|
* Démarrer un conteneur Docker
|
||||||
*/
|
*/
|
||||||
async function startContainer(containerName) {
|
async function startContainer(containerName, appId) {
|
||||||
try {
|
try {
|
||||||
|
// Tenter d'abord un docker start (conteneur existant mais arrêté)
|
||||||
const container = docker.getContainer(containerName);
|
const container = docker.getContainer(containerName);
|
||||||
await container.start();
|
const info = await container.inspect().catch(() => null);
|
||||||
return { success: true, message: `Conteneur ${containerName} démarré` };
|
if (info) {
|
||||||
|
// Le conteneur existe, on le démarre
|
||||||
|
if (!info.State.Running) {
|
||||||
|
await container.start();
|
||||||
|
}
|
||||||
|
return { success: true, message: `Conteneur ${containerName} démarré` };
|
||||||
|
}
|
||||||
|
// Le conteneur n'existe pas : docker compose up dans le répertoire de l'app
|
||||||
|
const id = appId || containerName;
|
||||||
|
const appsBasePath = config.appsBasePath || process.env.APPS_BASE_PATH || '/opt/manus-deploy/apps';
|
||||||
|
const appDir = path.join(appsBasePath, id);
|
||||||
|
const fs = require('fs');
|
||||||
|
const composeFile = fs.existsSync(path.join(appDir, 'docker-compose.yml'))
|
||||||
|
? path.join(appDir, 'docker-compose.yml')
|
||||||
|
: fs.existsSync(path.join(appDir, 'docker-compose.prod.yml'))
|
||||||
|
? path.join(appDir, 'docker-compose.prod.yml')
|
||||||
|
: null;
|
||||||
|
if (!composeFile) {
|
||||||
|
return { success: false, message: `Aucun docker-compose.yml trouvé dans ${appDir}` };
|
||||||
|
}
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
exec(`docker compose -f ${composeFile} up -d`, { cwd: appDir }, (err, stdout, stderr) => {
|
||||||
|
if (err) reject(new Error(stderr || err.message));
|
||||||
|
else resolve(stdout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return { success: true, message: `Conteneur ${containerName} créé et démarré via docker compose` };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { success: false, message: err.message };
|
return { success: false, message: err.message };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ router.post('/apps/:id/start', authMiddleware, async (req, res) => {
|
|||||||
const app = config.apps.find((a) => a.id === req.params.id);
|
const app = config.apps.find((a) => a.id === req.params.id);
|
||||||
if (!app) return res.status(404).json({ error: 'Application non trouvée' });
|
if (!app) return res.status(404).json({ error: 'Application non trouvée' });
|
||||||
const containerName = app.containerName || app.id;
|
const containerName = app.containerName || app.id;
|
||||||
const result = await startContainer(containerName);
|
const result = await startContainer(containerName, app.id);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
|
|||||||
Reference in New Issue
Block a user