[IMP] script todo: suivi install — auto-refresh coupé par défaut (moins de lag)
Sur un gros parc, les ticks périodiques (logs 1s / table 2s / domstate 10s) causaient du lag à la lecture des logs. Nouveau bouton « a » pour couper/ activer le rafraîchissement automatique, COUPÉ par défaut ; « r » pour rafraîchir une seule fois à la demande. - Chaque tick est scindé en wrapper _tick_* (ne fait rien si auto coupé) + corps _do_* (le travail réel), réutilisable pour les rafraîchissements forcés (init à l'ouverture, toggle, refresh manuel, après pause/reprise). - Un relevé initial unique (table + domstate) à l'ouverture affiche l'état courant même auto coupé ; changer de VM recharge son log (synchrone). - Indicateur #autobar : « ⏸ Rafraîchissement auto : COUPÉ (a=activer · r=rafraîchir) » / « ▶ … : ACTIVÉ (a=couper) ». Validé headless : défaut OFF, toggle a, refresh r, table peuplée à l'init. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c60b3f5cbd
commit
4f0c3cd217
2 changed files with 86 additions and 8 deletions
|
|
@ -545,6 +545,7 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
DataTable { width: 74; height: 1fr; overflow-x: auto; border: solid $accent; }
|
DataTable { width: 74; height: 1fr; overflow-x: auto; border: solid $accent; }
|
||||||
RichLog { border: solid $accent; }
|
RichLog { border: solid $accent; }
|
||||||
#telemetry { height: 1; color: $text-muted; }
|
#telemetry { height: 1; color: $text-muted; }
|
||||||
|
#autobar { height: 1; color: $warning; }
|
||||||
#stats { height: 1; color: $accent; }
|
#stats { height: 1; color: $accent; }
|
||||||
#statsdetail { display: none; height: auto; color: $text-muted; }
|
#statsdetail { display: none; height: auto; color: $text-muted; }
|
||||||
#sshbar { height: 2; color: $text-muted; }
|
#sshbar { height: 2; color: $text-muted; }
|
||||||
|
|
@ -563,6 +564,8 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
("f", "follow", "Suivre"),
|
("f", "follow", "Suivre"),
|
||||||
("c", "copy_log", "Copier log"),
|
("c", "copy_log", "Copier log"),
|
||||||
("d", "details", "Détails erreurs"),
|
("d", "details", "Détails erreurs"),
|
||||||
|
("a", "toggle_auto", "Auto-refresh on/off"),
|
||||||
|
("r", "refresh_now", "Rafraîchir"),
|
||||||
("p", "pause_all", "Pause tout"),
|
("p", "pause_all", "Pause tout"),
|
||||||
("o", "resume_all", "Reprendre tout"),
|
("o", "resume_all", "Reprendre tout"),
|
||||||
]
|
]
|
||||||
|
|
@ -588,6 +591,11 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
self._errcount = {}
|
self._errcount = {}
|
||||||
# Sommaire de stats déplié (clic) ou non.
|
# Sommaire de stats déplié (clic) ou non.
|
||||||
self._stats_open = False
|
self._stats_open = False
|
||||||
|
# Rafraîchissement automatique COUPÉ par défaut : sur un gros parc,
|
||||||
|
# les ticks périodiques (logs/table/domstate) causent du lag quand
|
||||||
|
# on lit les logs. On l'active à la demande (touche « a ») ; sinon
|
||||||
|
# « r » rafraîchit une seule fois.
|
||||||
|
self._auto_refresh = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _fmt(secs):
|
def _fmt(secs):
|
||||||
|
|
@ -625,6 +633,8 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
with Horizontal():
|
with Horizontal():
|
||||||
yield table
|
yield table
|
||||||
yield self._log
|
yield self._log
|
||||||
|
# Indicateur d'auto-refresh (a = on/off · r = rafraîchir).
|
||||||
|
yield Static("", id="autobar")
|
||||||
# Barre de télémétrie hôte (CPU, disque, ETA parc).
|
# Barre de télémétrie hôte (CPU, disque, ETA parc).
|
||||||
yield Static("", id="telemetry")
|
yield Static("", id="telemetry")
|
||||||
# Sommaire de stats en CHIFFRES (cliquable -> détail).
|
# Sommaire de stats en CHIFFRES (cliquable -> détail).
|
||||||
|
|
@ -640,15 +650,17 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
self.sub_title = f"0/{len(vms)} {t('completed')}"
|
self.sub_title = f"0/{len(vms)} {t('completed')}"
|
||||||
self._refresh_ssh()
|
self._refresh_ssh()
|
||||||
self._load_selected_log(reset=True)
|
self._load_selected_log(reset=True)
|
||||||
# Table toutes les 2 s (30 lectures de fin de log), suivi du log
|
self._update_autobar()
|
||||||
# sélectionné toutes les 1 s (une seule lecture incrémentale).
|
# Ticks périodiques : ils ne font RIEN tant que l'auto-refresh est
|
||||||
|
# coupé (wrappers gardés). On garde les intervalles en place pour
|
||||||
|
# ne pas avoir à les (re)créer au moment du toggle.
|
||||||
self.set_interval(2.0, self._tick_table)
|
self.set_interval(2.0, self._tick_table)
|
||||||
self.set_interval(1.0, self._tick_log)
|
self.set_interval(1.0, self._tick_log)
|
||||||
# État libvirt (pause / effacée) : check LENT (appel virsh) toutes
|
|
||||||
# les 10 s ; le tableau applique le cache à chaque tick (2 s).
|
|
||||||
self.set_interval(10.0, self._tick_domstate)
|
self.set_interval(10.0, self._tick_domstate)
|
||||||
# Premier relevé domstate immédiat (async -> via worker).
|
# Un SEUL relevé initial (table + domstate) pour afficher l'état
|
||||||
self.run_worker(self._tick_domstate(), exclusive=False)
|
# courant à l'ouverture, même auto-refresh coupé.
|
||||||
|
self.run_worker(self._do_domstate(), exclusive=False)
|
||||||
|
self.run_worker(self._do_table(), exclusive=False)
|
||||||
|
|
||||||
# -- helpers -------------------------------------------------------- #
|
# -- helpers -------------------------------------------------------- #
|
||||||
def _vm_by_name(self, name):
|
def _vm_by_name(self, name):
|
||||||
|
|
@ -661,6 +673,7 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
bar.update(
|
bar.update(
|
||||||
f" {vm['ssh']} (s = SSH · w = web :8069 · "
|
f" {vm['ssh']} (s = SSH · w = web :8069 · "
|
||||||
"c = copier le log · d = détails erreurs · "
|
"c = copier le log · d = détails erreurs · "
|
||||||
|
"a = auto-refresh · r = rafraîchir · "
|
||||||
"Maj+glisser = sélectionner)\n"
|
"Maj+glisser = sélectionner)\n"
|
||||||
f" Log : {vm['log']}"
|
f" Log : {vm['log']}"
|
||||||
)
|
)
|
||||||
|
|
@ -719,6 +732,10 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
return disks, status, self._collect_tele(), errors
|
return disks, status, self._collect_tele(), errors
|
||||||
|
|
||||||
async def _tick_table(self):
|
async def _tick_table(self):
|
||||||
|
if self._auto_refresh:
|
||||||
|
await self._do_table()
|
||||||
|
|
||||||
|
async def _do_table(self):
|
||||||
# I/O (lectures de logs, stat disque, /proc) DÉPORTÉES en thread ->
|
# I/O (lectures de logs, stat disque, /proc) DÉPORTÉES en thread ->
|
||||||
# la boucle d'événements Textual reste fluide même sous forte
|
# la boucle d'événements Textual reste fluide même sous forte
|
||||||
# charge ou disque lent. Les mises à jour d'UI restent sur la boucle.
|
# charge ou disque lent. Les mises à jour d'UI restent sur la boucle.
|
||||||
|
|
@ -866,6 +883,10 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
async def _tick_log(self):
|
async def _tick_log(self):
|
||||||
|
if self._auto_refresh:
|
||||||
|
await self._do_log()
|
||||||
|
|
||||||
|
async def _do_log(self):
|
||||||
if not self._follow:
|
if not self._follow:
|
||||||
return
|
return
|
||||||
vm = self._vm_by_name(self._selected)
|
vm = self._vm_by_name(self._selected)
|
||||||
|
|
@ -884,6 +905,10 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
self._log.write(line)
|
self._log.write(line)
|
||||||
|
|
||||||
async def _tick_domstate(self):
|
async def _tick_domstate(self):
|
||||||
|
if self._auto_refresh:
|
||||||
|
await self._do_domstate()
|
||||||
|
|
||||||
|
async def _do_domstate(self):
|
||||||
# Relevé LENT (subprocess virsh) DÉPORTÉ en thread : ne bloque plus
|
# Relevé LENT (subprocess virsh) DÉPORTÉ en thread : ne bloque plus
|
||||||
# la boucle. Une VM absente de « virsh list » est EFFACÉE (« gone »).
|
# la boucle. Une VM absente de « virsh list » est EFFACÉE (« gone »).
|
||||||
try:
|
try:
|
||||||
|
|
@ -893,6 +918,33 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
for vm in vms:
|
for vm in vms:
|
||||||
self._domstate[vm["name"]] = states.get(vm["name"], "gone")
|
self._domstate[vm["name"]] = states.get(vm["name"], "gone")
|
||||||
|
|
||||||
|
# -- auto-refresh (a = on/off · r = rafraîchir une fois) ------------- #
|
||||||
|
def _update_autobar(self):
|
||||||
|
if self._auto_refresh:
|
||||||
|
msg = f" ▶ {t('Auto-refresh: ON')} (a = {t('turn off')})"
|
||||||
|
else:
|
||||||
|
msg = (
|
||||||
|
f" ⏸ {t('Auto-refresh: OFF')} "
|
||||||
|
f"(a = {t('turn on')} · r = {t('refresh once')})"
|
||||||
|
)
|
||||||
|
self.query_one("#autobar", Static).update(msg)
|
||||||
|
|
||||||
|
def action_toggle_auto(self) -> None:
|
||||||
|
self._auto_refresh = not self._auto_refresh
|
||||||
|
self._update_autobar()
|
||||||
|
if self._auto_refresh:
|
||||||
|
# Rafraîchit tout de suite en activant.
|
||||||
|
self.run_worker(self._do_domstate(), exclusive=False)
|
||||||
|
self.run_worker(self._do_table(), exclusive=False)
|
||||||
|
self.run_worker(self._do_log(), exclusive=False)
|
||||||
|
|
||||||
|
def action_refresh_now(self) -> None:
|
||||||
|
# Une seule actualisation, même auto-refresh coupé.
|
||||||
|
self.run_worker(self._do_domstate(), exclusive=False)
|
||||||
|
self.run_worker(self._do_table(), exclusive=False)
|
||||||
|
self.run_worker(self._do_log(), exclusive=False)
|
||||||
|
self.notify(t("Refreshed."))
|
||||||
|
|
||||||
# -- events --------------------------------------------------------- #
|
# -- events --------------------------------------------------------- #
|
||||||
def on_data_table_row_highlighted(self, event) -> None:
|
def on_data_table_row_highlighted(self, event) -> None:
|
||||||
name = event.row_key.value
|
name = event.row_key.value
|
||||||
|
|
@ -1084,8 +1136,10 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
await asyncio.to_thread(self._virsh_bulk, action, targets)
|
await asyncio.to_thread(self._virsh_bulk, action, targets)
|
||||||
verb = t("paused") if action == "suspend" else t("resumed")
|
verb = t("paused") if action == "suspend" else t("resumed")
|
||||||
self.notify(f"{len(targets)} VM {verb}.")
|
self.notify(f"{len(targets)} VM {verb}.")
|
||||||
# Rafraîchit tout de suite l'état libvirt (pause/reprise visible).
|
# Rafraîchit tout de suite l'état libvirt (pause/reprise visible),
|
||||||
self.run_worker(self._tick_domstate(), exclusive=False)
|
# même si l'auto-refresh est coupé -> _do_domstate (forcé).
|
||||||
|
self.run_worker(self._do_domstate(), exclusive=False)
|
||||||
|
self.run_worker(self._do_table(), exclusive=False)
|
||||||
|
|
||||||
app = Monitor()
|
app = Monitor()
|
||||||
if run_app:
|
if run_app:
|
||||||
|
|
|
||||||
|
|
@ -1460,6 +1460,30 @@ TRANSLATIONS = {
|
||||||
"fr": "Aucune erreur détectée.",
|
"fr": "Aucune erreur détectée.",
|
||||||
"en": "No error detected.",
|
"en": "No error detected.",
|
||||||
},
|
},
|
||||||
|
"Auto-refresh: ON": {
|
||||||
|
"fr": "Rafraîchissement auto : ACTIVÉ",
|
||||||
|
"en": "Auto-refresh: ON",
|
||||||
|
},
|
||||||
|
"Auto-refresh: OFF": {
|
||||||
|
"fr": "Rafraîchissement auto : COUPÉ",
|
||||||
|
"en": "Auto-refresh: OFF",
|
||||||
|
},
|
||||||
|
"turn off": {
|
||||||
|
"fr": "couper",
|
||||||
|
"en": "turn off",
|
||||||
|
},
|
||||||
|
"turn on": {
|
||||||
|
"fr": "activer",
|
||||||
|
"en": "turn on",
|
||||||
|
},
|
||||||
|
"refresh once": {
|
||||||
|
"fr": "rafraîchir",
|
||||||
|
"en": "refresh once",
|
||||||
|
},
|
||||||
|
"Refreshed.": {
|
||||||
|
"fr": "Rafraîchi.",
|
||||||
|
"en": "Refreshed.",
|
||||||
|
},
|
||||||
"Test a VM (open Odoo in a CLI browser)": {
|
"Test a VM (open Odoo in a CLI browser)": {
|
||||||
"fr": "🧪 Tester une VM (ouvrir Odoo dans un navigateur CLI)",
|
"fr": "🧪 Tester une VM (ouvrir Odoo dans un navigateur CLI)",
|
||||||
"en": "🧪 Test a VM (open Odoo in a CLI browser)",
|
"en": "🧪 Test a VM (open Odoo in a CLI browser)",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue