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()})