From 2eee924759bfd13f4f055c668fbd0380601bb06c Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Thu, 13 Aug 2026 00:46:12 -0400 Subject: [PATCH] [ADD] tui qemu: an ERPLibre version per VM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A branch list per row. The form's branch stays the default; picking another for a VM creates an override, going back clears it -- the VM then follows the form again, which is what one expects when restoring the common choice. It reaches all the way down to execution, like VM type before it. "branch" now takes either a string for the whole fleet or a {name: branch} map, and the remote command is built per machine as soon as branches differ. The ground was ready: launch_installs already knows how to take one command per VM. A VM can therefore carry develop while another validates 1.6.0, in the same deployment and the same monitoring table. One trap: a row's branch fell back to develop on remount, the list being rebuilt from the form rather than from the override it already held. --- FR --- Une liste de branches par rangée. La branche du formulaire reste le défaut ; en choisir une autre pour une VM crée une surcharge, y revenir l'efface — la VM suit alors de nouveau le formulaire, ce qui est le plus attendu quand on remet le choix commun. Elle descend jusqu'à l'exécution, comme le type de VM avant elle. « branch » accepte désormais une chaîne pour tout le parc ou une carte {nom: branche}, et la commande distante est bâtie par machine dès que les branches diffèrent. Le terrain était prêt : launch_installs sait déjà prendre une commande par VM. Une VM peut donc porter develop pendant qu'une autre valide 1.6.0, sur le même déploiement et dans le même tableau de suivi. Un piège : la branche d'une rangée retombait sur develop au remontage, la liste étant rebâtie depuis le formulaire plutôt que depuis la surcharge qu'elle portait déjà. Assisted-by: Claude Opus 5 --- script/todo/qemu_deploy_form.py | 35 +++++++++++++++++++++++++++++---- script/todo/todo.py | 24 +++++++++++++++++----- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/script/todo/qemu_deploy_form.py b/script/todo/qemu_deploy_form.py index e982af9..1f1f83e 100644 --- a/script/todo/qemu_deploy_form.py +++ b/script/todo/qemu_deploy_form.py @@ -166,6 +166,9 @@ def apply_profile( # spec entière depuis qu'il se choisit machine par machine ; # `desktop` n'est plus que le défaut commun. "desktop": desktop, + # Branche ERPLibre. Même raison : « » signifie « celle du + # formulaire », et une surcharge la remplace pour cette VM. + "branch": "", } ) return out @@ -861,6 +864,11 @@ def run_deploy_form(ctx, run_app: bool = True): def _mise_usable(self): return any(vm["arch"] in mise_arches for vm in self.vms) + def _branch(self): + """Branche du formulaire : le défaut de chaque VM.""" + value = self.query_one("#f_branch", Select).value + return value if isinstance(value, str) else branches[0] + def _default_desktop(self): """« » pour un serveur, sinon la clé de la saveur choisie.""" """Type de VM par défaut. Chaque rangée peut s'en écarter.""" @@ -983,11 +991,17 @@ def run_deploy_form(ctx, run_app: bool = True): Select( [(b, b) for b in branches], classes="vmbranch", + # Repli sur la branche du FORMULAIRE, jamais sur + # branches[0] : les rangées sont remontées dès que le + # jeu de VM change (une entrée cochée, une copie + # ajoutée, un renommage), et elles retombaient alors + # toutes sur « develop » quel que soit le choix commun. value=( - item.get("branch") - or self.rows[i]["vm"].get("branch") - or branches[0] - ), + self.rows[i]["vm"].get("branch") + if i < len(self.rows) + else "" + ) + or self._branch(), allow_blank=False, id=f"v{i}_branch", ), @@ -1322,6 +1336,19 @@ def run_deploy_form(ctx, run_app: bool = True): # suivra donc le profil s'il change — ce qui est aussi le plus # attendu quand on n'a rien changé de visible. vm_now = self.rows[index]["vm"] + if field == "branch": + # « la branche du formulaire » n'est pas une surcharge : + # la VM doit suivre si on la change en haut. + current = vm_now.get("branch") or self._branch() + if event.value == current: + return + self._set_override( + index, + "branch", + "" if event.value == self._branch() else event.value, + ) + self._recompute() + return if field == "type": new_desk = "" if event.value == SERVER else event.value if new_desk == (vm_now.get("desktop") or ""): diff --git a/script/todo/todo.py b/script/todo/todo.py index db3253e..c706508 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -4898,8 +4898,12 @@ class TODO: # commande distante en dépend, donc elle se construit par VM ; celle-ci # reste le défaut pour les noms absents du dict. desk_map = desktop if isinstance(desktop, dict) else {} + # Même contrat que `desktop` : une chaîne pour tout le parc, ou + # une carte {nom: branche} quand elles diffèrent d'une VM à l'autre. + branch_map = branch if isinstance(branch, dict) else {} + branch_def = "" if branch_map else branch remote = self._qemu_erplibre_remote_cmd( - branch, + branch_def, final_cmd, prod, "" if desk_map else desktop, @@ -4928,9 +4932,9 @@ class TODO: "version": v, "arch": a, } - if desk_map: + if desk_map or branch_map: entry["remote_cmd"] = self._qemu_erplibre_remote_cmd( - branch, + branch_map.get(name, branch_def), final_cmd, prod, desk_map.get(name, ""), @@ -4943,7 +4947,9 @@ class TODO: if not vms: print(t("No VM to install.")) return - manifest = launch_installs(vms, branch, remote) + manifest = launch_installs( + vms, branch_def or next(iter(branch_map.values()), ""), remote + ) print(f"\n🖥 {t('Opening the interactive monitor...')}") # Affiche tous les chemins de log (pour les consulter/partager même si # on quitte le dashboard avant la fin). @@ -6291,6 +6297,14 @@ class TODO: desktop = next((d for d in desktop_map.values() if d), "") python_provider = spec.get("python_provider") or "" app_store = spec.get("app_store") or "deb" + # Branche par VM : « » sur une VM veut dire « celle du formulaire ». + branch_map = { + vm["name"]: (vm.get("branch") or install_branch or "") + for vm in pending + } + for _n in deployed: + branch_map.setdefault(_n, install_branch or "") + branch_multi = len(set(branch_map.values())) > 1 ssh_key = spec.get("ssh_key") add_ssh_config = spec["add_ssh_config"] parallelism = spec["parallelism"] @@ -6360,7 +6374,7 @@ class TODO: # Installs détachées en parallèle + dashboard Textual. self._qemu_install_erplibre_monitored( deployed, - install_branch, + branch_map if branch_multi else install_branch, ip_map, install["cmd"] if install else None, install["prod"] if install else False,