diff --git a/docker-compose.yml b/docker-compose.yml index 8feb276..70a8294 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,7 +29,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.recette.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/src/backend/src/config.js b/src/backend/src/config.js index 3a60082..afd7218 100644 --- a/src/backend/src/config.js +++ b/src/backend/src/config.js @@ -110,5 +110,20 @@ module.exports = { healthCheckUrl: 'https://facturation.recette.santinova-soft.org', port: 3001, }, + { + id: 'demat-facturation-dsi', + name: 'Démat Facturation DSI', + description: 'Dématérialisation des factures DSI - Import email/dossier, analyse IA, export SharePoint/PDF, validation BAP', + directory: 'demat-facturation-dsi', + giteaRepo: 'demat-facturation-dsi', + giteaOwner: 'manus-admin', + urls: { + recette: 'https://demat-facturation.recette.santinova-soft.org', + prod: null, + }, + containerName: 'demat-facturation-app', + healthCheckUrl: 'https://demat-facturation.recette.santinova-soft.org', + port: 3000, + }, ], }; diff --git a/src/backend/src/routes.js b/src/backend/src/routes.js index e83a978..fbd26db 100644 --- a/src/backend/src/routes.js +++ b/src/backend/src/routes.js @@ -475,3 +475,76 @@ router.get('/system/metrics', authMiddleware, async (req, res) => { }); module.exports = router; + +// ===== INVENTAIRE DES APPLICATIONS ===== +const { exec } = require('child_process'); +const GITEA_RECETTE_INTERNAL = process.env.GITEA_RECETTE_URL || 'http://gitea:3000'; +const GITEA_PROD_EXTERNAL = 'https://git.santinova-soft.org'; +const GITEA_USER_INV = process.env.GITEA_USERNAME || 'manus-admin'; +const GITEA_PASS_REC = process.env.GITEA_PASSWORD || 'Itinova69!'; +const GITEA_PASS_PRD = process.env.GITEA_PASSWORD_PROD || 'ManusGitea2026!'; + +const INVENTORY_APPS = [ + { id: 'itinova-contacts', name: 'Itinova Contacts', repoName: 'itinova-contacts' }, + { id: 'itinova-podcasts', name: 'Itinova Podcasts', repoName: 'itinova-podcasts' }, + { id: 'veille-reglementaire', name: 'Veille Réglementaire', repoName: 'veille-reglementaire' }, + { id: 'itinova-vehicle-exchange', name: 'Itinova Gestion de Flotte', repoName: 'itinova-vehicle-exchange' }, + { id: 'sonum', name: 'SONUM', repoName: 'sonum' }, + { id: 'demat-facturation-dsi', name: 'Démat. Facturation DSI', repoName: 'demat-facturation-dsi' }, + { id: 'facturation-santinova', name: 'Facturation Santinova', repoName: 'facturation-santinova' }, + { id: 'formation-manager-itinova', name: 'Formation Manager', repoName: 'formation-manager-itinova' }, + { id: 'portail-santinova', name: 'Portail Applicatif', repoName: 'portail-santinova' }, + { id: 'manus-dashboard', name: 'Dashboard Manus', repoName: 'manus-dashboard' }, +]; + +function curlGitea(baseUrl, owner, repo, pass) { + return new Promise((resolve) => { + const auth = `${GITEA_USER_INV}:${pass}`; + const cmd = `curl -sk --max-time 6 -u "${auth}" "${baseUrl}/api/v1/repos/${owner}/${repo}"`; + exec(cmd, { timeout: 7000 }, (err, stdout) => { + if (err || !stdout) return resolve({ present: false, url: null, version: null }); + try { + const data = JSON.parse(stdout); + if (!data.id) return resolve({ present: false, url: null, version: null }); + // Récupérer le dernier commit + const cmd2 = `curl -sk --max-time 6 -u "${auth}" "${baseUrl}/api/v1/repos/${owner}/${repo}/commits?limit=1"`; + exec(cmd2, { timeout: 7000 }, (err2, stdout2) => { + let version = null; + try { + const commits = JSON.parse(stdout2 || '[]'); + if (commits.length > 0) { + const c = commits[0]; + const sha = c.sha ? c.sha.substring(0, 7) : null; + const date = c.commit && c.commit.author && c.commit.author.date + ? new Date(c.commit.author.date).toLocaleDateString('fr-FR') : null; + version = sha && date ? `${sha} (${date})` : sha; + } + } catch(e) {} + resolve({ present: true, url: `${baseUrl}/${owner}/${repo}`, version }); + }); + } catch(e) { + resolve({ present: false, url: null, version: null }); + } + }); + }); +} + +router.get('/inventory', authMiddleware, async (req, res) => { + try { + const owner = GITEA_USER_INV; + const results = await Promise.all( + INVENTORY_APPS.map(async (app) => { + const [repoRecette, repoProd] = await Promise.all([ + curlGitea(GITEA_RECETTE_INTERNAL, owner, app.repoName, GITEA_PASS_REC), + curlGitea(GITEA_PROD_EXTERNAL, owner, app.repoName, GITEA_PASS_PRD), + ]); + return { ...app, repoRecette, repoProd }; + }) + ); + res.json(results); + } catch (err) { + console.error('Erreur /inventory:', err.message); + res.status(500).json({ error: err.message }); + } +}); +; diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 8134de9..1c9a8f0 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -11,21 +11,25 @@ services: - JWT_SECRET=manus-dashboard-jwt-secret-2026-recette - ADMIN_USERNAME=adminItinova - ADMIN_PASSWORD=Itinova69! - - GITEA_URL=https://git.santinova-soft.org + - GITEA_URL=https://git.recette.santinova-soft.org - GITEA_USERNAME=manus-admin - - GITEA_PASSWORD=ManusGitea2026! + - GITEA_PASSWORD=Itinova69! + - GITEA_RECETTE_URL=http://gitea:3000 + - GITEA_PASSWORD_PROD=ManusGitea2026! - APPS_BASE_PATH=/opt/manus-deploy/apps - INFRA_BASE_PATH=/opt/manus-deploy/infrastructure - HEALTH_CHECK_INTERVAL=30000 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" networks: - web labels: - "traefik.enable=true" - # Route HTTPS via dashboard.santinova-soft.org - - "traefik.http.routers.manus-dashboard.rule=Host(`dashboard.santinova-soft.org`)" + # Route HTTPS via dashboard.recette.santinova-soft.org + - "traefik.http.routers.manus-dashboard.rule=Host(`dashboard.recette.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/src/frontend/public/infra_schema_v2.png b/src/frontend/public/infra_schema_v2.png new file mode 100644 index 0000000..43fb240 Binary files /dev/null and b/src/frontend/public/infra_schema_v2.png differ diff --git a/src/frontend/src/App.jsx b/src/frontend/src/App.jsx index 9c415b8..6642b20 100644 --- a/src/frontend/src/App.jsx +++ b/src/frontend/src/App.jsx @@ -6,16 +6,16 @@ import MonitoringPage from './pages/MonitoringPage'; import DeploymentsPage from './pages/DeploymentsPage'; import GiteaPage from './pages/GiteaPage'; import DockerPage from './pages/DockerPage'; +import DocumentationPage from './pages/DocumentationPage'; +import InventairePage from './pages/InventairePage'; import Sidebar from './components/Sidebar'; import useWebSocket from './hooks/useWebSocket'; import { getMe, getApps, logout as apiLogout } from './utils/api'; - export default function App() { const [authenticated, setAuthenticated] = useState(false); const [loading, setLoading] = useState(true); const [currentPage, setCurrentPage] = useState('dashboard'); const [apps, setApps] = useState([]); - // Vérifier l'authentification au chargement useEffect(() => { const token = localStorage.getItem('dashboard_token'); @@ -34,7 +34,6 @@ export default function App() { setLoading(false); } }, []); - const fetchApps = async () => { try { const res = await getApps(); @@ -43,23 +42,19 @@ export default function App() { console.error('Erreur chargement apps:', err); } }; - // WebSocket handler const handleWsMessage = useCallback((data) => { if (data.type === 'health_update') { setApps(data.data); } }, []); - const { connected: wsConnected } = useWebSocket( authenticated ? handleWsMessage : null ); - const handleLogin = (data) => { setAuthenticated(true); fetchApps(); }; - const handleLogout = async () => { try { await apiLogout(); @@ -70,7 +65,6 @@ export default function App() { setAuthenticated(false); setApps([]); }; - if (loading) { return (
@@ -96,11 +90,9 @@ export default function App() {
); } - if (!authenticated) { return ; } - const renderPage = () => { switch (currentPage) { case 'dashboard': @@ -115,11 +107,14 @@ export default function App() { return ; case 'monitoring': return ; + case 'documentation': + return ; + case 'inventaire': + return ; default: return ; } }; - return (
- {/* Navigation */} -