[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: if not action:
return # quitté sans choisir de commande return # quitté sans choisir de commande
method, kwargs = action 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) fn = getattr(self, method, None)
if not callable(fn): if not callable(fn):
print(f"{t('Command not found !')} ({method})") print(f"{t('Command not found !')} ({method})")
@ -1359,22 +1373,32 @@ class TODO:
return out if res.returncode == 0 and out else name return out if res.returncode == 0 and out else name
def _qemu_shutdown_wait(self, name, timeout=120): def _qemu_shutdown_wait(self, name, timeout=120):
"""Arrête la VM proprement (ACPI) et attend qu'elle soit « shut off ». """Arrête la VM par SIGNAL (ACPI power-button, puis agent invité) et
Si l'arrêt gracieux traîne, propose un arrêt forcé (destroy). 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.""" Renvoie True si la VM est bien éteinte."""
name = self._qemu_domname(name) name = self._qemu_domname(name)
if self._qemu_domstate(name) == "shut off": if self._qemu_domstate(name) == "shut off":
return True 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}") print(f"{t('Will execute:')} {cmd}")
self.execute.exec_command_live(cmd, source_erplibre=False) 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 deadline = time.time() + timeout
while time.time() < deadline: while time.time() < deadline:
if self._qemu_domstate(name) == "shut off": 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 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é. # Arrêt gracieux trop long : proposer un arrêt forcé.
if self._is_yes( if self._is_yes(
input(t("Graceful shutdown timed out. Force off (destroy)? " 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…", "fr": "Attente de l'arrêt de la VM…",
"en": "Waiting for the VM to shut down...", "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.": { "VM is off.": {
"fr": "VM éteinte.", "fr": "VM éteinte.",
"en": "VM is off.", "en": "VM is off.",