Compare commits

...

2 commits

Author SHA1 Message Date
06687f5f7e [FIX] selenium: importer Optional (NameError sur l'annotation de type)
extraire_svg_graphique_by_3d annote « -> Optional[str] » mais le module
n'importait pas Optional -> « NameError: name 'Optional' is not defined »
au chargement. Ajout de « from typing import Optional ».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 15:11:12 -04:00
12d1c0c8ae [IMP] script todo: déploiement — personnaliser nom + disque + RAM par VM
« Renommer quelles VM ? » devient « Modifier quelles VM ? » : pour chaque
VM choisie, on peut changer le NOM, la taille de DISQUE et la RAM (vide =
garder). _qemu_customize_names -> _qemu_customize_vms renvoie (names,
selected) avec les valeurs mises à jour.

Le multiplicateur de ressources est désormais CUIT dans `selected` (RAM
finale) et le nombre de vCPU fixé une fois (uniforme) -> un override de RAM
par VM est une valeur ABSOLUE, sans ambiguïté avec le multiplicateur. La
fonction eff_res disparaît (plan, dry-run et déploiement lisent les valeurs
finales de `selected`).

Validé : modif VM 1 (nom/disque/RAM) appliquée, VM 2 inchangée.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-30 14:58:50 -04:00
3 changed files with 81 additions and 28 deletions

View file

@ -16,6 +16,7 @@ import time
import tkinter as tk import tkinter as tk
from pathlib import Path from pathlib import Path
from tkinter import filedialog from tkinter import filedialog
from typing import Optional
from pykeepass import PyKeePass from pykeepass import PyKeePass
from randomwordfr import RandomWordFr from randomwordfr import RandomWordFr

View file

@ -3362,19 +3362,28 @@ class TODO:
pass pass
return 1, base_vcpus return 1, base_vcpus
def _qemu_customize_names(self, selected): def _qemu_customize_vms(self, selected):
"""Noms des VM (liste parallèle à `selected`). Défaut = nom auto """Personnalise chaque VM avant déploiement : NOM, taille de DISQUE et
(_qemu_infra_name) ; on peut en renommer certains À LA DEMANDE, par RAM. `selected` = liste de (d, v, ram, disk, a) où ram (Mo) et disk
leurs numéros séparés de virgules (vide = garder tous les noms auto).""" (« 20G ») sont les valeurs FINALES (multiplicateur déjà appliqué).
Renvoie (names, selected_maj). Défaut : rien ne change."""
names = [ names = [
self._qemu_infra_name(d, v, a) for d, v, _r, _dk, a in selected self._qemu_infra_name(d, v, a) for d, v, _r, _dk, a in selected
] ]
print(f"\n{t('VM names (default = auto):')}") sel = [list(s) for s in selected] # mutable (d, v, ram, disk, a)
for i, (nm, s) in enumerate(zip(names, selected), 1):
d, v, _r, _dk, a = s def show():
print(f" [{i}] {nm} ({d} {v} [{a}])") print(f"\n{t('VMs (default = no change):')}")
for i, (nm, s) in enumerate(zip(names, sel), 1):
d, v, ram, disk, a = s
print(
f" [{i}] {nm} ({d} {v} [{a}]) "
f"RAM {ram}Mo {t('disk')} {disk}"
)
show()
raw = input( raw = input(
t("Rename which VMs? (numbers, comma-separated; blank = none): ") t("Modify which VMs? (numbers, comma-separated; blank = none): ")
).strip() ).strip()
for tok in re.split(r"[\s,]+", raw): for tok in re.split(r"[\s,]+", raw):
if not tok: if not tok:
@ -3383,13 +3392,34 @@ class TODO:
i = int(tok) - 1 i = int(tok) - 1
except ValueError: except ValueError:
continue continue
if 0 <= i < len(names): if not (0 <= i < len(sel)):
new = input(f" {names[i]} {t('->')} ").strip() continue
if new: # Pour la VM i : nom, disque, RAM (vide = garder la valeur).
names[i] = new new = input(
f" {names[i]} — {t('new name (blank = keep):')} "
).strip()
if new:
names[i] = new
dk = input(
f" {t('New disk size in G, blank = keep')} "
f"({sel[i][3]}): "
).strip().upper().rstrip("G")
if dk:
try:
sel[i][3] = f"{int(float(dk))}G"
except ValueError:
print(f" ⚠ {t('Invalid size.')}")
rm = input(
f" {t('New RAM in MB, blank = keep')} ({sel[i][2]}): "
).strip()
if rm:
try:
sel[i][2] = int(rm)
except ValueError:
print(f" ⚠ {t('Invalid size.')}")
if len(set(names)) != len(names): if len(set(names)) != len(names):
print(f" ⚠ {t('Duplicate names detected; keeping as entered.')}") print(f" ⚠ {t('Duplicate names detected; keeping as entered.')}")
return names return names, [tuple(s) for s in sel]
def _qemu_build_deploy_parts( def _qemu_build_deploy_parts(
self, d, v, arch, name, eram, evcpus, disk, ssh_key, branch, dry_run self, d, v, arch, name, eram, evcpus, disk, ssh_key, branch, dry_run
@ -3569,23 +3599,26 @@ class TODO:
res_mult, base_vcpus = self._qemu_prompt_resource_mult( res_mult, base_vcpus = self._qemu_prompt_resource_mult(
selected, host_cpu, free_ram selected, host_cpu, free_ram
) )
# On CUIT le multiplicateur dans `selected` (RAM finale) et on fixe un
# nombre de vCPU uniforme -> la personnalisation par VM (RAM absolue)
# n'est plus ambiguë. vCPU n'est pas éditable par VM (choix global).
evcpus = min(base_vcpus * res_mult, host_cpu)
selected = [
(d, v, ram * res_mult, disk, a)
for (d, v, ram, disk, a) in selected
]
def eff_res(ram): # 2c) Personnalisation par VM : nom, disque, RAM (à la demande).
"""(RAM effective, vCPU) pour une VM selon le multiplicateur.""" names, selected = self._qemu_customize_vms(selected)
return ram * res_mult, min(base_vcpus * res_mult, host_cpu)
# 2c) Noms des VM (auto par défaut, renommage granulaire à la demande). # 3) Plan + estimation des ressources.
names = self._qemu_customize_names(selected) total_ram = sum(s[2] for s in selected)
# 3) Plan + estimation des ressources (avec le multiplicateur appliqué).
total_ram = sum(s[2] * res_mult for s in selected)
total_disk = sum(self._parse_disk_gb(s[3]) for s in selected) total_disk = sum(self._parse_disk_gb(s[3]) for s in selected)
print(f"\n{t('Deployment plan')} ({len(selected)} VM, x{res_mult}) :") print(f"\n{t('Deployment plan')} ({len(selected)} VM, x{res_mult}) :")
for i, (d, v, ram, disk, a) in enumerate(selected): for i, (d, v, ram, disk, a) in enumerate(selected):
eram, evcpus = eff_res(ram)
print( print(
f" - {names[i]:<30} {d} {v:<7} [{a:<5}] " f" - {names[i]:<30} {d} {v:<7} [{a:<5}] "
f"{evcpus} vCPU RAM {eram}Mo {t('disk')} {disk}" f"{evcpus} vCPU RAM {ram}Mo {t('disk')} {disk}"
) )
print(f"\n {t('Total RAM (all running):')} {total_ram} Mo") print(f"\n {t('Total RAM (all running):')} {total_ram} Mo")
print(f" {t('Total virtual disk (thin qcow2):')} ~{total_disk} G") print(f" {t('Total virtual disk (thin qcow2):')} ~{total_disk} G")
@ -3604,9 +3637,8 @@ class TODO:
default_key = self._qemu_default_ssh_key() default_key = self._qemu_default_ssh_key()
print(f"\n{t('Preview (dry-run):')}") print(f"\n{t('Preview (dry-run):')}")
for i, (d, v, ram, disk, a) in enumerate(selected): for i, (d, v, ram, disk, a) in enumerate(selected):
eram, evcpus = eff_res(ram)
parts = self._qemu_build_deploy_parts( parts = self._qemu_build_deploy_parts(
d, v, a, names[i], eram, evcpus, disk, d, v, a, names[i], ram, evcpus, disk,
default_key, None, dry_run=True, default_key, None, dry_run=True,
) )
print(" " + " ".join(shlex.quote(p) for p in parts)) print(" " + " ".join(shlex.quote(p) for p in parts))
@ -3677,9 +3709,8 @@ class TODO:
# si les résultats reviennent dans le désordre (exécution parallèle). # si les résultats reviennent dans le désordre (exécution parallèle).
jobs = [] # (id, name, parts) jobs = [] # (id, name, parts)
for k, (name, d, v, ram, disk, a) in enumerate(pending, 1): for k, (name, d, v, ram, disk, a) in enumerate(pending, 1):
eram, evcpus = eff_res(ram)
parts = self._qemu_build_deploy_parts( parts = self._qemu_build_deploy_parts(
d, v, a, name, eram, evcpus, disk, d, v, a, name, ram, evcpus, disk,
ssh_key, install_branch, dry_run=False, ssh_key, install_branch, dry_run=False,
) )
jobs.append((f"{k}/{n_jobs}", name, parts)) jobs.append((f"{k}/{n_jobs}", name, parts))

View file

@ -1270,6 +1270,27 @@ TRANSLATIONS = {
"fr": "->", "fr": "->",
"en": "->", "en": "->",
}, },
"VMs (default = no change):": {
"fr": "VM (défaut = aucune modification) :",
"en": "VMs (default = no change):",
},
"Modify which VMs? (numbers, comma-separated; blank = none): ": {
"fr": "Modifier quelles VM ? (numéros séparés par des virgules ; "
"vide = aucune) : ",
"en": "Modify which VMs? (numbers, comma-separated; blank = none): ",
},
"new name (blank = keep):": {
"fr": "nouveau nom (vide = garder) :",
"en": "new name (blank = keep):",
},
"New disk size in G, blank = keep": {
"fr": "Nouvelle taille disque en G, vide = garder",
"en": "New disk size in G, blank = keep",
},
"New RAM in MB, blank = keep": {
"fr": "Nouvelle RAM en Mo, vide = garder",
"en": "New RAM in MB, blank = keep",
},
"Duplicate names detected; keeping as entered.": { "Duplicate names detected; keeping as entered.": {
"fr": "Noms en double détectés ; conservés tels quels.", "fr": "Noms en double détectés ; conservés tels quels.",
"en": "Duplicate names detected; keeping as entered.", "en": "Duplicate names detected; keeping as entered.",