From e60f18806e8bc6e6da364b32ba03b1848b664177 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 12 Aug 2026 03:58:58 -0400 Subject: [PATCH] [ADD] qemu monitor: VM architecture, scrolling and resizable columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The architecture was missing, though it alone explains why an install takes ten times longer: s390x and arm64 are EMULATED on an amd64 host. Without it you look for the fault elsewhere. An "Arch" column, right after the name. The table was pinned at 74 columns: beyond that nothing was reachable and no scrollbar showed, for want of reserved space. Automatic width capped at 60% of the screen, scrolling both ways, and VISIBLE bars rather than guessable ones. Columns can finally be adjusted: "+" widens the cursor's, "-" narrows it, "0" returns them all to their original width -- the same keys as the mail TUI. auto_width is turned off along the way, otherwise the setting did not survive the table's first update, and the resize aimed at the "#" column whatever the cursor was on. --- FR --- L'architecture manquait, alors qu'elle explique à elle seule pourquoi une installation dure dix fois plus : s390x et arm64 sont ÉMULÉES sur un hôte amd64. Sans elle, on cherche la faute ailleurs. Une colonne « Arch », juste après le nom. Le tableau était figé à 74 colonnes : au-delà rien n'était atteignable et aucune barre de défilement n'apparaissait, faute de place réservée. Largeur automatique plafonnée à 60 % de l'écran, défilement dans les deux sens, et barres VISIBLES plutôt que devinables. Les colonnes s'ajustent enfin : « + » élargit celle du curseur, « - » la rétrécit, « 0 » les ramène toutes à leur largeur d'origine — les mêmes touches que la TUI courriel. auto_width est désactivé au passage, sans quoi le réglage ne survivait pas à la première mise à jour du tableau, et le redimensionnement visait la colonne « # » quel que soit le curseur. Assisted-by: Claude Opus 5 --- script/todo/qemu_install_monitor.py | 100 ++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/script/todo/qemu_install_monitor.py b/script/todo/qemu_install_monitor.py index 9d33b2a..eb63c5c 100644 --- a/script/todo/qemu_install_monitor.py +++ b/script/todo/qemu_install_monitor.py @@ -842,6 +842,22 @@ def virsh_domstates() -> dict: # --------------------------------------------------------------------------- # # Dashboard Textual # --------------------------------------------------------------------------- # +# Largeurs de colonnes du tableau de suivi. Une seule source : la +# construction du tableau les lit ici, et « 0 » y revient. Les redéclarer à +# deux endroits, c'est garantir qu'un jour la remise à zéro rendra autre chose +# que ce qui était affiché au départ. +COL_DEFAULT_WIDTHS = { + "seq": 3, + "vm": 22, + "arch": 7, + "err": 4, + "state": 8, + "odoo": 6, + "elapsed": 7, + "disk": 8, +} + + # Parties d'une remise à jour, dans l'ordre où elles doivent tourner : les # paquets système d'abord (compilateurs et en-têtes), le code ensuite, les # dépendances Python en dernier — elles se compilent contre les deux premiers. @@ -1089,7 +1105,17 @@ def run_monitor(manifest_path: str, run_app: bool = True): class Monitor(App): CSS = """ - DataTable { width: 74; height: 1fr; overflow-x: auto; border: solid $accent; } + /* « width: 74 » figeait la table : au-delà, les colonnes élargies + n'étaient plus atteignables, et la barre horizontale ne s'affichait + pas faute de place réservée. « scrollbar-size » la rend VISIBLE plutôt + que devinable, et « max-width » laisse la table suivre l'élargissement + des colonnes sans manger tout l'écran. */ + DataTable { + width: auto; max-width: 60%; height: 1fr; + overflow-x: auto; overflow-y: auto; + scrollbar-size-horizontal: 1; scrollbar-size-vertical: 1; + border: solid $accent; + } RichLog { border: solid $accent; } #telemetry { height: 1; color: $text-muted; } #stats { height: 1; color: $accent; } @@ -1127,6 +1153,13 @@ def run_monitor(manifest_path: str, run_app: bool = True): ("c", "copy_log", "Copier log"), ("d", "details", "Détails erreurs"), ("a", "vm_actions", "Actions VM"), + # Mêmes touches que la TUI mail, qui redimensionne ses volets : + # « + » élargit, « - » rétrécit, « 0 » remet tout d'aplomb. + ("plus", "col_grow", "Colonne +"), + ("minus", "col_shrink", "Colonne -"), + ("0", "col_reset", "Colonnes par défaut"), + ("less_than_sign", "col_prev", "Colonne précédente"), + ("greater_than_sign", "col_next", "Colonne suivante"), ("p", "pause_all", "Pause tout"), ("o", "resume_all", "Reprendre tout"), ] @@ -1134,6 +1167,9 @@ def run_monitor(manifest_path: str, run_app: bool = True): def __init__(self): super().__init__() self._offsets = {vm["name"]: 0 for vm in vms} + # Colonne visee par « + » / « - » : le nom de VM, celle qu'on a + # vraiment besoin d'elargir. « < » et « > » la deplacent. + self._col_target = list(COL_DEFAULT_WIDTHS).index("vm") self._selected = vms[0]["name"] if vms else None self._follow = True # Statuts TERMINAUX mémorisés : une VM finie n'est plus relue @@ -1183,20 +1219,35 @@ def run_monitor(manifest_path: str, run_app: bool = True): # tronqué (« ❌ effacée », « ⏸ en pause » lisibles) ; la table # défile horizontalement (overflow-x) pour les noms de VM longs. # « # » : numéro de séquence de la VM (première colonne). - table.add_column("#", key="seq", width=3) - table.add_column("VM", key="vm", width=22) - table.add_column("⚠", key="err", width=4) + table.add_column("#", key="seq", width=COL_DEFAULT_WIDTHS["seq"]) + table.add_column("VM", key="vm", width=COL_DEFAULT_WIDTHS["vm"]) + # L'architecture explique à elle seule qu'une installation dure + # dix fois plus longtemps : s390x et arm64 sont ÉMULÉES sur un + # hôte amd64. La voir évite de chercher la panne ailleurs. + table.add_column( + "Arch", key="arch", width=COL_DEFAULT_WIDTHS["arch"] + ) + table.add_column("⚠", key="err", width=COL_DEFAULT_WIDTHS["err"]) # width=8 : le plus long libellé restant est « ⏸ pause » (7) — # « effacée » (10) est remplacé par l'icône 🗑 (voir plus bas). - table.add_column("État", key="state", width=8) + table.add_column( + "État", key="state", width=COL_DEFAULT_WIDTHS["state"] + ) # « Odoo » : l'UI web répond-elle sur :8069 ? (🟢 up / — down) - table.add_column("Odoo", key="odoo", width=6) - table.add_column("Durée", key="elapsed", width=7) - table.add_column("Disque", key="disk", width=8) + table.add_column( + "Odoo", key="odoo", width=COL_DEFAULT_WIDTHS["odoo"] + ) + table.add_column( + "Durée", key="elapsed", width=COL_DEFAULT_WIDTHS["elapsed"] + ) + table.add_column( + "Disque", key="disk", width=COL_DEFAULT_WIDTHS["disk"] + ) for i, vm in enumerate(vms, 1): table.add_row( str(i), vm["name"], + vm.get("arch") or "?", "", "⏳", "—", @@ -1761,14 +1812,20 @@ def run_monitor(manifest_path: str, run_app: bool = True): COL_MAX = 60 def _cursor_column(self): - """Colonne sous le curseur, ou None. DataTable expose ses colonnes - dans un dictionnaire ORDONNÉ : le rang du curseur y donne la clé. - """ + """Colonne VISEE par le redimensionnement. + + Surtout pas « table.cursor_column » : le curseur est en mode + LIGNE, sa colonne reste donc a 0 quoi qu'on fasse. Chaque « + » ou + « - » tombait sur « # », large de 3 et deja a sa butee — d'ou un + avertissement en boucle et aucune colonne atteignable. + + La cible se deplace avec « < » et « > », et part sur le nom de VM, + la seule qu'on ait vraiment besoin d'elargir.""" table = self.query_one("#vms", DataTable) keys = list(table.columns) - index = table.cursor_column - if not (0 <= index < len(keys)): + if not keys: return None, None + index = max(0, min(self._col_target, len(keys) - 1)) return table, keys[index] def _apply_column_widths(self, table) -> None: @@ -1812,6 +1869,23 @@ def run_monitor(manifest_path: str, run_app: bool = True): # une bulle par frappe rendait l'ajustement pénible. self._apply_column_widths(table) + def _move_col_target(self, delta) -> None: + """Deplace la cible, en boucle sur les colonnes.""" + table = self.query_one("#vms", DataTable) + keys = list(table.columns) + if not keys: + return + self._col_target = (self._col_target + delta) % len(keys) + self.notify( + f"Colonne visee : {table.columns[keys[self._col_target]].label}" + ) + + def action_col_prev(self) -> None: + self._move_col_target(-1) + + def action_col_next(self) -> None: + self._move_col_target(1) + def action_col_grow(self) -> None: self._resize_column(self.COL_STEP)