From a0c560b15f4b6673ef256eb0f36a453779aae643 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 29 Jul 2026 11:23:54 +0000 Subject: [PATCH] =?UTF-8?q?[IMP]=20script=20todo:=20fil=20d'Ariane=20t?= =?UTF-8?q?=C3=A9l=C3=A9m=C3=A9trie=20+=20arr=C3=AAt=20VM=20par=20signal?= =?UTF-8?q?=20(compte=20=C3=A0=20rebours)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deux points : - Fil d'Ariane depuis la télémétrie : une commande lancée DEPUIS le TUI de télémétrie ne passait par aucun menu, donc son chemin n'était jamais affiché. On imprime désormais « 📍 TODO › … › » (dernier segment traduit + icône) avant l'exécution, et on enregistre le chemin. - Arrêt de VM (réduction disque) par SIGNAL : virsh shutdown --mode acpi,agent (bouton ACPI puis agent invité) au lieu d'un arrêt implicite. Pendant l'attente, on affiche un compte à rebours du timeout (« ⏳ arrêt en cours… NNN s restantes ») et le délai max au départ. Co-Authored-By: Claude Opus 4.8 (1M context) --- script/todo/todo.py | 36 ++++++++++++++++++++++++++++++------ script/todo/todo_i18n.py | 12 ++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/script/todo/todo.py b/script/todo/todo.py index 6821440..84524cf 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -596,6 +596,20 @@ class TODO: if not action: return # quitté sans choisir de commande method, kwargs = action + # Fil d'Ariane : la commande étant lancée DEPUIS la télémétrie (et + # non via la navigation), aucun menu n'a affiché le chemin. On le + # montre ici (dernier segment traduit + icône) et on l'enregistre. + path = state.get("path") if isinstance(state, dict) else None + if path: + segs = path.split(" › ") + segs[-1] = t(segs[-1]) + print(f"\n📍 {' › '.join(segs)}") + try: + from script.todo import todo_telemetry + + todo_telemetry.record(path) + except Exception: + pass fn = getattr(self, method, None) if not callable(fn): print(f"{t('Command not found !')} ({method})") @@ -1359,22 +1373,32 @@ class TODO: return out if res.returncode == 0 and out else name def _qemu_shutdown_wait(self, name, timeout=120): - """Arrête la VM proprement (ACPI) et attend qu'elle soit « shut off ». - Si l'arrêt gracieux traîne, propose un arrêt forcé (destroy). + """Arrête la VM par SIGNAL (ACPI power-button, puis agent invité) et + attend qu'elle soit « shut off » en affichant le temps restant du + timeout. Si l'arrêt gracieux traîne, propose un arrêt forcé (destroy). Renvoie True si la VM est bien éteinte.""" name = self._qemu_domname(name) if self._qemu_domstate(name) == "shut off": return True - cmd = f"sudo virsh shutdown {shlex.quote(name)}" + # --mode acpi,agent : envoie le SIGNAL d'extinction (bouton ACPI) puis + # tente l'agent invité si présent — plus fiable qu'un arrêt brutal. + cmd = f"sudo virsh shutdown {shlex.quote(name)} --mode acpi,agent" print(f"{t('Will execute:')} {cmd}") self.execute.exec_command_live(cmd, source_erplibre=False) - print(t("Waiting for the VM to shut down...")) + print(f"{t('Waiting for the VM to shut down...')} " + f"({t('timeout')}: {timeout} s)") deadline = time.time() + timeout while time.time() < deadline: if self._qemu_domstate(name) == "shut off": - print(f"✅ {name}: {t('VM is off.')}") + # Efface la ligne de compte à rebours puis confirme. + print(f"\r{' ' * 40}\r✅ {name}: {t('VM is off.')}") return True - time.sleep(3) + remaining = int(deadline - time.time()) + print(f"\r ⏳ {t('shutting down')}… " + f"{remaining:>3d} s {t('remaining')}", + end="", flush=True) + time.sleep(2) + print() # newline après le compte à rebours # Arrêt gracieux trop long : proposer un arrêt forcé. if self._is_yes( input(t("Graceful shutdown timed out. Force off (destroy)? " diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index b5ec688..e1dded0 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -1432,6 +1432,18 @@ TRANSLATIONS = { "fr": "Attente de l'arrêt de la VM…", "en": "Waiting for the VM to shut down...", }, + "timeout": { + "fr": "délai max", + "en": "timeout", + }, + "shutting down": { + "fr": "arrêt en cours", + "en": "shutting down", + }, + "remaining": { + "fr": "restantes", + "en": "remaining", + }, "VM is off.": { "fr": "VM éteinte.", "en": "VM is off.",