[IMP] script todo: fil d'Ariane télémétrie + arrêt VM par signal (compte à rebours)

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 › … › <commande> » (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) <noreply@anthropic.com>
This commit is contained in:
Mathieu Benoit 2026-07-29 11:23:54 +00:00
parent ec88b5f1f0
commit a0c560b15f
2 changed files with 42 additions and 6 deletions

View file

@ -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)? "

View file

@ -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.",