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'; 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', }, { id: 'itinova-podcasts', name: 'Itinova Podcasts', repoName: 'itinova-podcasts', urlRecette: 'https://podcasts.recette.santinova-soft.org', urlProd: 'https://podcasts.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', }, { 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', }, { id: 'sonum', name: 'SONUM', repoName: 'sonum', urlRecette: 'https://sonum.recette.santinova-soft.org', urlProd: 'https://sonum.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', }, { id: 'facturation-santinova', name: 'Facturation Santinova', repoName: 'facturation-santinova', urlRecette: 'https://facturation.recette.santinova-soft.org', urlProd: null, }, { id: 'formation-manager-itinova', name: 'Formation Manager', repoName: 'formation-manager-itinova', urlRecette: 'https://formation.recette.santinova-soft.org', urlProd: 'https://formations.itinova.org', }, { id: 'pilotage-masse-salariale', name: 'Pilotage Masse Salariale', repoName: 'pilotage-masse-salariale', urlRecette: 'https://pilotage-ms.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', urlProd: null, }, { id: 'falc-generator', name: 'FALC Generator', repoName: 'falc-generator', urlRecette: 'https://falc.recette.santinova-soft.org', urlProd: 'https://falc.santinova-soft.org', }, { id: 'portail-santinova', name: 'Portail Applicatif', repoName: 'portail-santinova', urlRecette: 'https://portail-santinova.recette.santinova-soft.org', urlProd: 'https://portail-santinova.santinova-soft.org', }, { id: 'manus-dashboard', name: 'Dashboard Recette', repoName: 'manus-dashboard', urlRecette: 'https://dashboard.recette.santinova-soft.org', urlProd: 'https://dashboard.santinova-soft.org', }, ]; function SyncBadge({ versionRecette, versionProd }) { // Extraire le SHA (7 premiers caractères avant l'espace) const shaRec = versionRecette ? versionRecette.split(' ')[0] : null; const shaProd = versionProd ? versionProd.split(' ')[0] : null; // Extraire la date entre parenthèses pour comparer const dateRec = versionRecette ? versionRecette.match(/\((.+?)\)/)?.[1] : null; const dateProd = versionProd ? versionProd.match(/\((.+?)\)/)?.[1] : null; if (!shaRec && !shaProd) { return ( N/A ); } if (!shaRec) { return ( Prod en avance ); } if (!shaProd) { return ( Recette en avance ); } if (shaRec === shaProd) { return ( Synchronisé ); } // Comparer les dates pour savoir qui est en avance // Format fr-FR : dd/mm/yyyy const parseDate = (s) => { if (!s) return null; const parts = s.split('/'); if (parts.length === 3) return new Date(`${parts[2]}-${parts[1]}-${parts[0]}`); return new Date(s); }; const dRec = parseDate(dateRec); const dProd = parseDate(dateProd); if (dRec && dProd) { if (dRec > dProd) { return ( Recette en avance ); } else if (dProd > dRec) { return ( Prod en avance ); } } // SHA différents mais dates non comparables return ( Divergent ); } function VersionBadge({ version, loading }) { if (loading) { return ( Chargement... ); } if (!version) { return ( N/A ); } return ( {version} ); } function RepoBadge({ present, url, label }) { if (!present) { return (
Absent
); } return (
{label}
); } export default function InventairePage() { const [inventory, setInventory] = useState([]); const [loading, setLoading] = useState(true); const [lastUpdate, setLastUpdate] = useState(null); const fetchInventory = async () => { setLoading(true); try { const res = await api.get('/inventory'); setInventory(res.data); setLastUpdate(new Date()); } catch (err) { console.error('Erreur chargement inventaire:', err); // Fallback : construire l'inventaire depuis la config statique const fallback = APPS_CONFIG.map((app) => ({ ...app, repoRecette: { present: false, url: null, version: null }, repoProd: { present: false, url: null, version: null }, })); setInventory(fallback); } finally { setLoading(false); } }; // Auto-refresh toutes les 5 minutes useEffect(() => { fetchInventory(); const interval = setInterval(fetchInventory, 5 * 60 * 1000); return () => clearInterval(interval); }, []); return (
{/* Header */}

Inventaire des Applications

État des dépôts Git et versions déployées par environnement

{lastUpdate && ( Mis à jour : {lastUpdate.toLocaleTimeString('fr-FR')} )}
{/* Stats */} {!loading && inventory.length > 0 && (
{inventory.length}
Applications totales
{inventory.filter((a) => a.repoRecette?.present).length}
Dépôts Recette
{inventory.filter((a) => a.repoProd?.present).length}
Dépôts Production
{inventory.filter((a) => { const shaRec = a.repoRecette?.version?.split(' ')[0]; const shaProd = a.repoProd?.version?.split(' ')[0]; return shaRec && shaProd && shaRec === shaProd; }).length}
Synchronisés
{inventory.filter((a) => a.repoRecette?.present && a.repoProd?.present).length}
Déployés sur les 2 envs
)} {/* Légende */}
Dépôt présent
Dépôt absent
Version (dernier commit)
{/* Tableau */}
{loading && inventory.length === 0 ? APPS_CONFIG.map((app) => ( {[...Array(5)].map((_, i) => ( ))} )) : inventory.map((app) => { const isInfra = ['portail-santinova', 'manus-dashboard'].includes(app.id); return ( {/* Synchronisation */} {/* Recette - Dépôt */} {/* Recette - Version */} {/* Prod - Dépôt */} {/* Prod - Version */} ); })}
Application
Synchronisation
Recette
Production
Dépôt Git Version Dépôt Git Version
{app.name}
{app.name} {isInfra && ( Infra )}
{app.repoName}

); }