[IMP] script todo: section the big menus + QEMU console & all-IPs

Group the longer menus with section headers (reordering items so the
numbering stays sequential within each section):

- Execute: Development / Data / Sources & documentation / AI &
  automation / Deployment, network & security / Preferences.
- QEMU/KVM: Deployment / Manage / Catalog.
- Database: Backup / Restore / Danger zone.
- RTK: Setup / Status / Optimize.
- Config: Generate / Advanced.

The QEMU config-entry lookup now skips section rows so appended
makefile entries still map to the right number.

Also in the QEMU menu:
- new "Open the console on a VM" entry (lists VMs, asks which, reminds
  Ctrl+] to quit, runs virsh console);
- "Show a VM IP address" accepts "all" to print every VM's IP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mathieu Benoit 2026-07-19 07:41:00 +00:00
parent 2020e15b5e
commit 9876a03ac7
2 changed files with 215 additions and 85 deletions

View file

@ -197,21 +197,33 @@ class TODO:
def prompt_execute(self):
help_info = f"""{self._menu_header()}
[1] {t("Automation - Demonstration of developed features")}
[2] {t("Code - Developer tools")}
[3] {t("Config - Configuration file management")}
[4] {t("Database - Database tools")}
[5] {t("Doc - Documentation search")}
[6] {t("Git - Git tools")}
[7] {t("GPT code - AI assistant tools")}
[8] {t("Language - Change language / Changer la langue")}
[9] {t("Network - Network tools")}
[10] {t("Process - Execution tools")}
[11] {t("Run - Execute and install an instance")}
[12] {t("Security - Dependency security audit")}
[13] {t("Test - Test an Odoo module")}
[14] {t("Update - Update all developed staging source code")}
[15] {t("Deploy - Deploy ERPLibre locally")}
{t("Development")}
[1] {t("Code - Developer tools")}
[2] {t("Config - Configuration file management")}
[3] {t("Run - Execute and install an instance")}
[4] {t("Test - Test an Odoo module")}
[5] {t("Process - Execution tools")}
{t("Data")}
[6] {t("Database - Database tools")}
{t("Sources & documentation")}
[7] {t("Git - Git tools")}
[8] {t("Update - Update all developed staging source code")}
[9] {t("Doc - Documentation search")}
{t("AI & automation")}
[10] {t("GPT code - AI assistant tools")}
[11] {t("Automation - Demonstration of developed features")}
{t("Deployment, network & security")}
[12] {t("Deploy - Deploy ERPLibre locally")}
[13] {t("Network - Network tools")}
[14] {t("Security - Dependency security audit")}
{t("Preferences")}
[15] {t("Language - Change language / Changer la langue")}
[0] {t("Back")}
"""
while True:
@ -220,65 +232,65 @@ class TODO:
if status == "0":
return
elif status == "1":
status = self.prompt_execute_function()
if status is not False:
return
elif status == "2":
status = self.prompt_execute_code()
if status is not False:
return
elif status == "3":
elif status == "2":
status = self.prompt_execute_config()
if status is not False:
return
elif status == "4":
status = self.prompt_execute_database()
if status is not False:
return
elif status == "5":
status = self.prompt_execute_doc()
if status is not False:
return
elif status == "6":
status = self.prompt_execute_git()
if status is not False:
return
elif status == "7":
status = self.prompt_execute_gpt_code()
if status is not False:
return
elif status == "8":
status = self._change_language()
if status is not False:
return
elif status == "9":
status = self.prompt_execute_network()
if status is not False:
return
elif status == "10":
status = self.prompt_execute_process()
if status is not False:
return
elif status == "11":
elif status == "3":
status = self.prompt_execute_instance()
if status is not False:
return
elif status == "12":
status = self.prompt_execute_security()
if status is not False:
return
elif status == "13":
elif status == "4":
status = self.prompt_execute_test()
if status is not False:
return
elif status == "14":
elif status == "5":
status = self.prompt_execute_process()
if status is not False:
return
elif status == "6":
status = self.prompt_execute_database()
if status is not False:
return
elif status == "7":
status = self.prompt_execute_git()
if status is not False:
return
elif status == "8":
status = self.prompt_execute_update()
if status is not False:
return
elif status == "15":
elif status == "9":
status = self.prompt_execute_doc()
if status is not False:
return
elif status == "10":
status = self.prompt_execute_gpt_code()
if status is not False:
return
elif status == "11":
status = self.prompt_execute_function()
if status is not False:
return
elif status == "12":
status = self.prompt_execute_deploy()
if status is not False:
return
elif status == "13":
status = self.prompt_execute_network()
if status is not False:
return
elif status == "14":
status = self.prompt_execute_security()
if status is not False:
return
elif status == "15":
status = self._change_language()
if status is not False:
return
else:
print(t("Command not found !"))
@ -854,6 +866,7 @@ class TODO:
print(f"{t('QEMU deploy script not found: ')}{script_path}")
return False
choices = [
{"section": t("Deployment")},
{"prompt_description": t("Deploy a new VM")},
{
"prompt_description": t(
@ -861,15 +874,18 @@ class TODO:
)
},
{"prompt_description": t("Download a cloud image only")},
{"prompt_description": t("List VMs (virsh list --all)")},
{"prompt_description": t("Show a VM IP address")},
{"prompt_description": t("List available images and specs")},
{
"prompt_description": t(
"Deploy ERPLibre infra (one minimal VM per image)"
)
},
{"section": t("Manage")},
{"prompt_description": t("List VMs (virsh list --all)")},
{"prompt_description": t("Show a VM IP address")},
{"prompt_description": t("Open the console on a VM")},
{"prompt_description": t("Delete VM(s)")},
{"section": t("Catalog")},
{"prompt_description": t("List available images and specs")},
]
config_entries = self.config_file.get_config("qemu_from_makefile")
if config_entries:
@ -888,23 +904,27 @@ class TODO:
elif status == "3":
self._qemu_download_image()
elif status == "4":
self._qemu_list_vms()
elif status == "5":
self._qemu_show_ip()
elif status == "6":
self._qemu_list_images()
elif status == "7":
self._qemu_deploy_infra()
elif status == "5":
self._qemu_list_vms()
elif status == "6":
self._qemu_show_ip()
elif status == "7":
self._qemu_console()
elif status == "8":
self._qemu_delete_vm()
elif status == "9":
self._qemu_list_images()
else:
cmd_no_found = True
try:
int_cmd = int(status)
if 0 < int_cmd <= len(choices):
# Ignore les entrées de section pour mapper le numéro
# affiché sur la bonne commande (config incluse).
real = [c for c in choices if not c.get("section")]
if 0 < int_cmd <= len(real):
cmd_no_found = False
instance = choices[int_cmd - 1]
self.execute_from_configuration(instance)
self.execute_from_configuration(real[int_cmd - 1])
except ValueError:
pass
if cmd_no_found:
@ -998,14 +1018,36 @@ class TODO:
def _qemu_show_ip(self):
# Affiche d'abord les VM (avec leur ID) pour que l'utilisateur sache
# quel nom/ID saisir, puis demande lequel.
# quel nom/ID saisir, puis demande lequel (ou « all » pour toutes).
self._qemu_list_vms()
print()
name = input(t("VM name or ID (or 'all'): ")).strip()
if not name:
print(t("VM name is required!"))
return
if name.lower() in ("all", "tous", "*"):
targets = self._qemu_list_domains()
if not targets:
print(t("No VM found."))
return
else:
targets = [name]
for tgt in targets:
cmd = f"sudo virsh domifaddr {shlex.quote(tgt)} --source lease"
print(f"\n{t('Will execute:')} {cmd}")
self.execute.exec_command_live(cmd, source_erplibre=False)
def _qemu_console(self):
# Liste les VM, demande laquelle, rappelle comment quitter (Ctrl+])
# puis ouvre la console série interactive.
self._qemu_list_vms()
print()
name = input(t("VM name or ID: ")).strip()
if not name:
print(t("VM name is required!"))
return
cmd = f"sudo virsh domifaddr {shlex.quote(name)} --source lease"
print(f"\n💡 {t('To leave the console, press Ctrl+] (then Enter).')}")
cmd = f"sudo virsh console {shlex.quote(name)}"
print(f"{t('Will execute:')} {cmd}")
self.execute.exec_command_live(cmd, source_erplibre=False)
@ -2140,13 +2182,16 @@ class TODO:
def prompt_execute_database(self):
print(f"🤖 {t('Make changes to databases!')}")
choices = [
{"section": t("Backup")},
{"prompt_description": t("Create backup (.zip)")},
{
"prompt_description": t(
"Download database to create backup (.zip)"
)
},
{"section": t("Restore")},
{"prompt_description": t("Restore from backup (.zip)")},
{"prompt_description": t("Create backup (.zip)")},
{"section": t("Danger zone")},
{"prompt_description": t("Erase a database")},
]
help_info = self.fill_help_info(choices)
@ -2157,11 +2202,11 @@ class TODO:
if status == "0":
return False
elif status == "1":
self.db_manager.download_database_backup_cli()
elif status == "2":
self.db_manager.restore_from_database()
elif status == "3":
self.db_manager.create_backup_from_database()
elif status == "2":
self.db_manager.download_database_backup_cli()
elif status == "3":
self.db_manager.restore_from_database()
elif status == "4":
self.db_manager.drop_database()
else:
@ -2199,12 +2244,15 @@ class TODO:
f"🤖 {t('Manage RTK (Rust Token Killer) for token optimization!')}"
)
choices = [
{"section": t("Setup")},
{"prompt_description": t("Install RTK")},
{"prompt_description": t("Check RTK version")},
{"prompt_description": t("Show cumulative token savings")},
{"prompt_description": t("Discover optimization opportunities")},
{"prompt_description": t("Initialize global auto-rewrite hook")},
{"section": t("Status")},
{"prompt_description": t("Check RTK version")},
{"prompt_description": t("Check RTK status")},
{"prompt_description": t("Show cumulative token savings")},
{"section": t("Optimize")},
{"prompt_description": t("Discover optimization opportunities")},
]
help_info = self.fill_help_info(choices)
@ -2216,15 +2264,15 @@ class TODO:
elif status == "1":
self.rtk_install()
elif status == "2":
self.rtk_check_version()
elif status == "3":
self.rtk_show_gain()
elif status == "4":
self.rtk_discover()
elif status == "5":
self.rtk_init_global()
elif status == "6":
elif status == "3":
self.rtk_check_version()
elif status == "4":
self.rtk_check_status()
elif status == "5":
self.rtk_show_gain()
elif status == "6":
self.rtk_discover()
else:
print(t("Command not found !"))
@ -2313,10 +2361,12 @@ class TODO:
def prompt_execute_config(self):
print(f"🤖 {t('Manage ERPLibre and Odoo configuration!')}")
choices = [
{"section": t("Generate")},
{"prompt_description": t("Generate all configuration")},
{"prompt_description": t("Generate from pre-configuration")},
{"prompt_description": t("Generate from backup file")},
{"prompt_description": t("Generate from database")},
{"section": t("Advanced")},
{"prompt_description": t("Setup queue job for parallelism")},
]
help_info = self.fill_help_info(choices)

View file

@ -153,6 +153,82 @@ TRANSLATIONS = {
"fr": "Virtualisation & notifications",
"en": "Virtualization & notifications",
},
"Development": {
"fr": "Développement",
"en": "Development",
},
"Data": {
"fr": "Données",
"en": "Data",
},
"Sources & documentation": {
"fr": "Sources & documentation",
"en": "Sources & documentation",
},
"AI & automation": {
"fr": "IA & automatisation",
"en": "AI & automation",
},
"Deployment, network & security": {
"fr": "Déploiement, réseau & sécurité",
"en": "Deployment, network & security",
},
"Preferences": {
"fr": "Préférences",
"en": "Preferences",
},
"Deployment": {
"fr": "Déploiement",
"en": "Deployment",
},
"Manage": {
"fr": "Gérer",
"en": "Manage",
},
"Catalog": {
"fr": "Catalogue",
"en": "Catalog",
},
"Open the console on a VM": {
"fr": "Ouvrir la console d'une VM",
"en": "Open the console on a VM",
},
"Backup": {
"fr": "Sauvegarde",
"en": "Backup",
},
"Restore": {
"fr": "Restauration",
"en": "Restore",
},
"Danger zone": {
"fr": "Zone dangereuse",
"en": "Danger zone",
},
"Setup": {
"fr": "Installation",
"en": "Setup",
},
"Status": {
"fr": "Statut",
"en": "Status",
},
"Optimize": {
"fr": "Optimisation",
"en": "Optimize",
},
"Generate": {
"fr": "Génération",
"en": "Generate",
},
"Advanced": {
"fr": "Avancé",
"en": "Advanced",
},
"To leave the console, press Ctrl+] (then Enter).": {
"fr": "Pour quitter la console, appuyez sur Ctrl+] (puis Entrée).",
"en": "To leave the console, press Ctrl+] (then Enter).",
},
"SSH address input method": {
"fr": "Méthode de saisie de l'adresse SSH",
"en": "SSH address input method",
@ -1222,6 +1298,10 @@ TRANSLATIONS = {
"fr": "Nom ou ID de la VM : ",
"en": "VM name or ID: ",
},
"VM name or ID (or 'all'): ": {
"fr": "Nom ou ID de la VM (ou « all ») : ",
"en": "VM name or ID (or 'all'): ",
},
"RAM in MB (blank = version minimum): ": {
"fr": "RAM en Mo (vide = minimum de la version) : ",
"en": "RAM in MB (blank = version minimum): ",