diff --git a/CHANGELOG.md b/CHANGELOG.md index f32631f..2c871a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,16 @@ (`/api/sondes`, en parallèle) et affiche un état réel — **● vivante** / **● injoignable** — sur les tuiles et dans le détail (« Plan : … · Réel : … »), distinct du plan. - **Auto-actif** : un hôte qu'on **matérialise** (clone `creer` réussi) ou qu'on - **déploie** passe automatiquement `actif` dans le plan (matérialisé = actif). + **déploie** passe automatiquement `actif` dans le plan (matérialisé = actif), puis + l'inventaire est **régénéré** pour que le changement se voie partout (en-tête inclus). + - **Compteur « vivantes »** dans l'en-tête (depuis la sonde), à côté de actifs/planifiés. + +### Corrigé +- **Le bouton ⚙ « Appliquer le plan » du GUI refusait en silence** dès que le plan + divergeait de l'inventaire (il appelait `instancier appliquer` **sans** `--force`). + Résultat : après une édition (disque, état, auto-actif), l'inventaire n'était jamais + régénéré et les compteurs restaient figés. Le clic « Appliquer » **est** l'intention + explicite → le GUI force désormais (git reste le filet). - **Bouton « Pousser » dans le GUI — le flux devient 100 % cliquable.** Chaque objet du détail se matérialise sur son hôte, en streaming console (avec mot de passe vault diff --git a/scripts/inventory_gui.py b/scripts/inventory_gui.py index 52dd12c..ebcde76 100644 --- a/scripts/inventory_gui.py +++ b/scripts/inventory_gui.py @@ -979,12 +979,15 @@ HTML = r""" function dessinerResume() { const actifs = hotes.filter(h => h.etat === 'actif').length; const bloques = hotes.filter(h => hoteBloque(h)).length; + const vivantes = hotes.filter(h => sondes[h.nom] === true).length; + const sonde = Object.keys(sondes).length > 0; document.getElementById('resume').innerHTML = ` ${estProduction ? 'PROD' : 'bac à sable'} ${hotes.length} hôtes ${actifs} actifs ${hotes.length - actifs} planifiés - ${bloques ? `${bloques} bloqués` : ''}`; + ${bloques ? `${bloques} bloqués` : ''} + ${sonde ? `${vivantes} vivantes` : ''}`; } function correspondServeur(s) { @@ -1727,8 +1730,8 @@ HTML = r""" if (rc === null) rc = 1; finirConsole(rc); if (mode === 'verifier' && rc === 0) { verifie[hote] = true; message('Vérification réussie — déploiement débloqué pour ' + hote + '.', 'ok'); } - else if (mode === 'creer') { message(rc === 0 ? 'VM « ' + hote + ' » créée.' : 'Échec de la création.', rc === 0 ? 'ok' : 'erreur'); if (rc === 0) charger(); } - else if (mode === 'deployer' || mode === 'pousser') { verifie[hote] = false; message(rc === 0 ? 'Poussée terminée.' : 'Poussée en échec.', rc === 0 ? 'ok' : 'erreur'); } + else if (mode === 'creer') { message(rc === 0 ? 'VM « ' + hote + ' » créée — hôte marqué actif.' : 'Échec de la création.', rc === 0 ? 'ok' : 'erreur'); if (rc === 0) appliquerPlan(); else sonder(); } + else if (mode === 'deployer' || mode === 'pousser') { verifie[hote] = false; message(rc === 0 ? 'Poussée terminée — hôte marqué actif.' : 'Poussée en échec.', rc === 0 ? 'ok' : 'erreur'); if (rc === 0) appliquerPlan(); else sonder(); } } catch (e) { ecrireConsole('Erreur client : ' + e.message + '\n'); finirConsole(1); } @@ -2012,8 +2015,10 @@ class Gestionnaire(BaseHTTPRequestHandler): ecrire_intrants(donnees) self.repondre_json(200, inventaire_api(self.inventaire)) elif chemin == "/api/instancier": + # Cliquer « Appliquer le plan » EST l'intention explicite : on force + # (le diff-refus est un garde-fou CLI ; ici le clic le confirme). git = filet. resultat = subprocess.run( - ["python3", str(RACINE / "scripts/instancier.py"), "appliquer"], + ["python3", str(RACINE / "scripts/instancier.py"), "appliquer", "--force"], capture_output=True, text=True, cwd=str(RACINE)) self.repondre_json(200, {"ok": resultat.returncode == 0, "sortie": (resultat.stdout + resultat.stderr).strip()})