[ADD] todo: preferences and Configuration menu
Stores the per-user settings in ~/.erplibre/todo_prefs.json: which deploy interface to use, what to display while deploying, which migration interface. A missing or corrupt file reads as defaults. --- FR --- Range les réglages propres à l'utilisateur dans ~/.erplibre/todo_prefs.json : interface de déploiement, affichage pendant le déploiement, interface de migration. Un fichier absent ou corrompu se lit comme les valeurs par défaut. Assisted-by: Claude Opus 4.8
This commit is contained in:
parent
4dfe058c70
commit
1208c919a1
3 changed files with 266 additions and 0 deletions
|
|
@ -27,6 +27,8 @@ from script.config import config_file
|
|||
from script.execute import execute
|
||||
from script.todo.database_manager import DatabaseManager
|
||||
from script.todo.kdbx_manager import KdbxManager
|
||||
from script.todo import todo_prefs
|
||||
from script.todo.todo_i18n import get_lang, lang_is_configured, set_lang, t
|
||||
from script.todo.version_manager import get_odoo_version
|
||||
|
||||
ERROR_LOG_PATH = ".erplibre.error.txt"
|
||||
|
|
@ -447,6 +449,79 @@ class TODO:
|
|||
),
|
||||
}
|
||||
|
||||
def _pref_label(self, key):
|
||||
"""Libellé traduit de la valeur courante d'une préférence."""
|
||||
value = todo_prefs.get(key)
|
||||
for stored, label in self._PREF_CHOICES[key][1]:
|
||||
if stored == value:
|
||||
return t(label)
|
||||
return str(value)
|
||||
|
||||
def _pref_edit(self, key):
|
||||
"""Fait choisir une valeur parmi celles proposées pour `key`."""
|
||||
title, options = self._PREF_CHOICES[key]
|
||||
current = todo_prefs.get(key)
|
||||
print(f"\n{t(title)} :")
|
||||
for i, (stored, label) in enumerate(options, 1):
|
||||
star = " *" if stored == current else ""
|
||||
print(f" [{i}] {t(label)}{star}")
|
||||
sel = input(f"{t('Choice (number, blank = keep):')} ").strip()
|
||||
try:
|
||||
idx = int(sel) - 1
|
||||
except ValueError:
|
||||
return
|
||||
if 0 <= idx < len(options):
|
||||
todo_prefs.set(key, options[idx][0])
|
||||
print(f" ✅ {t(title)} : {self._pref_label(key)}")
|
||||
|
||||
def prompt_configuration(self):
|
||||
"""Réglages persistants de l'utilisateur (~/.erplibre/todo_prefs.json).
|
||||
La langue vit à part, dans env_var.sh, et garde son propre mécanisme.
|
||||
"""
|
||||
while True:
|
||||
lang = "français" if get_lang() == "fr" else "English"
|
||||
choices = [
|
||||
{"section": t("Interface")},
|
||||
{"prompt_description": f"{t('Language / Langue')} ({lang})"},
|
||||
{
|
||||
"prompt_description": (
|
||||
f"{t('QEMU deployment interface')} "
|
||||
f"({self._pref_label('qemu_deploy_ui')})"
|
||||
)
|
||||
},
|
||||
{
|
||||
"prompt_description": (
|
||||
f"{t('Display while deploying')} "
|
||||
f"({self._pref_label('qemu_deploy_progress')})"
|
||||
)
|
||||
},
|
||||
{
|
||||
"prompt_description": (
|
||||
f"{t('Odoo migration interface')} "
|
||||
f"({self._pref_label('migration_ui')})"
|
||||
)
|
||||
},
|
||||
{"section": t("Maintenance")},
|
||||
{"prompt_description": t("Reset all preferences")},
|
||||
]
|
||||
status = click.prompt(self.fill_help_info(choices))
|
||||
print()
|
||||
if status == "0":
|
||||
return
|
||||
elif status == "1":
|
||||
self._change_language()
|
||||
elif status == "2":
|
||||
self._pref_edit("qemu_deploy_ui")
|
||||
elif status == "3":
|
||||
self._pref_edit("qemu_deploy_progress")
|
||||
elif status == "4":
|
||||
self._pref_edit("migration_ui")
|
||||
elif status == "5":
|
||||
n = todo_prefs.reset()
|
||||
print(f"✅ {t('Preferences reset')} ({n})")
|
||||
else:
|
||||
print(t("Command not found !"))
|
||||
|
||||
def fill_help_info(self, choices):
|
||||
# Une entrée {"section": "..."} affiche un titre de section SANS
|
||||
# consommer de numéro : la numérotation reste continue sur les vraies
|
||||
|
|
@ -1149,6 +1224,22 @@ class TODO:
|
|||
# serait prématurée. Elle est posée à la première identité refusée.
|
||||
self._qemu_ssh_walk(roots, max_depth)
|
||||
|
||||
def _qemu_pick_domains(self):
|
||||
"""Fait choisir des VM parmi celles définies. Vide = toutes."""
|
||||
names = self._qemu_list_domains()
|
||||
if not names:
|
||||
print(t("No VM found."))
|
||||
return []
|
||||
for i, name in enumerate(names, 1):
|
||||
print(f" [{i}] {name}")
|
||||
raw = input(
|
||||
t("Which VMs? (numbers, comma-separated; blank = all): ")
|
||||
).strip()
|
||||
if not raw:
|
||||
return names
|
||||
chosen = self._parse_index_selection(raw, names)
|
||||
return chosen or names
|
||||
|
||||
def _ssh_ensure_key(self):
|
||||
"""Chemin de la clé PUBLIQUE, générée si aucune n'existe.
|
||||
|
||||
|
|
|
|||
|
|
@ -1246,6 +1246,54 @@ TRANSLATIONS = {
|
|||
"fr": "📊 Télémétrie de navigation (TUI)",
|
||||
"en": "📊 Navigation telemetry (TUI)",
|
||||
},
|
||||
"Configuration": {
|
||||
"fr": "⚙ Configuration",
|
||||
"en": "⚙ Configuration",
|
||||
},
|
||||
"Interface": {
|
||||
"fr": "Interface",
|
||||
"en": "Interface",
|
||||
},
|
||||
"Maintenance": {
|
||||
"fr": "Maintenance",
|
||||
"en": "Maintenance",
|
||||
},
|
||||
"Language / Langue": {
|
||||
"fr": "🌐 Langue / Language",
|
||||
"en": "🌐 Language / Langue",
|
||||
},
|
||||
"QEMU deployment interface": {
|
||||
"fr": "🖥 Interface de déploiement QEMU",
|
||||
"en": "🖥 QEMU deployment interface",
|
||||
},
|
||||
"Display while deploying": {
|
||||
"fr": "📜 Affichage pendant le déploiement",
|
||||
"en": "📜 Display while deploying",
|
||||
},
|
||||
"Ask every time": {
|
||||
"fr": "Demander à chaque fois",
|
||||
"en": "Ask every time",
|
||||
},
|
||||
"TUI form": {
|
||||
"fr": "Formulaire TUI",
|
||||
"en": "TUI form",
|
||||
},
|
||||
"Classic questions (line by line)": {
|
||||
"fr": "Questions classiques (ligne par ligne)",
|
||||
"en": "Classic questions (line by line)",
|
||||
},
|
||||
"CLI output (easy to copy)": {
|
||||
"fr": "Sortie CLI (facile à copier)",
|
||||
"en": "CLI output (easy to copy)",
|
||||
},
|
||||
"TUI, collapsible blocks per VM": {
|
||||
"fr": "TUI, blocs repliables par VM",
|
||||
"en": "TUI, collapsible blocks per VM",
|
||||
},
|
||||
"Choice (number, blank = keep):": {
|
||||
"fr": "Choix (numéro, vide = garder) :",
|
||||
"en": "Choice (number, blank = keep):",
|
||||
},
|
||||
"SSH port forwarding (open Odoo in the browser)": {
|
||||
"fr": "🔌 Redirection de port SSH (ouvrir Odoo dans le navigateur)",
|
||||
"en": "🔌 SSH port forwarding (open Odoo in the browser)",
|
||||
|
|
@ -1351,6 +1399,14 @@ TRANSLATIONS = {
|
|||
"fr": "Tunnel refermé.",
|
||||
"en": "Tunnel closed.",
|
||||
},
|
||||
"SSH configuration (~/.ssh/config, ProxyJump)": {
|
||||
"fr": "🔑 Configuration SSH (~/.ssh/config, ProxyJump)",
|
||||
"en": "🔑 SSH configuration (~/.ssh/config, ProxyJump)",
|
||||
},
|
||||
"SSH configuration for QEMU VMs": {
|
||||
"fr": "Configuration SSH des VM QEMU",
|
||||
"en": "SSH configuration for QEMU VMs",
|
||||
},
|
||||
"Where should the machines come from?": {
|
||||
"fr": "D'où viennent les machines à configurer ?",
|
||||
"en": "Where should the machines come from?",
|
||||
|
|
@ -1392,6 +1448,35 @@ TRANSLATIONS = {
|
|||
"fr": "Profondeur (1 = ces machines seulement, défaut :",
|
||||
"en": "Depth (1 = these machines only, default:",
|
||||
},
|
||||
"Which VMs? (numbers, comma-separated; blank = all): ": {
|
||||
"fr": "Quelles VM ? (numéros séparés par des virgules ; "
|
||||
"vide = toutes) : ",
|
||||
"en": "Which VMs? (numbers, comma-separated; blank = all): ",
|
||||
},
|
||||
"Depth (default:": {
|
||||
"fr": "Profondeur (défaut :",
|
||||
"en": "Depth (default:",
|
||||
},
|
||||
"entry": {
|
||||
"fr": "entrée",
|
||||
"en": "entry",
|
||||
},
|
||||
"written": {
|
||||
"fr": "écrite(s)",
|
||||
"en": "written",
|
||||
},
|
||||
"skipped": {
|
||||
"fr": "ignorée(s)",
|
||||
"en": "skipped",
|
||||
},
|
||||
"Level": {
|
||||
"fr": "Niveau",
|
||||
"en": "Level",
|
||||
},
|
||||
"machines to probe": {
|
||||
"fr": "machines à sonder",
|
||||
"en": "machines to probe",
|
||||
},
|
||||
"SSH refused the identity.": {
|
||||
"fr": "SSH a refusé l'identité.",
|
||||
"en": "SSH refused the identity.",
|
||||
|
|
@ -1489,6 +1574,22 @@ TRANSLATIONS = {
|
|||
"fr": "les noms s'appliquent à chaud",
|
||||
"en": "the names apply live",
|
||||
},
|
||||
"SSH hosts written": {
|
||||
"fr": "Hôtes SSH écrits",
|
||||
"en": "SSH hosts written",
|
||||
},
|
||||
"via": {
|
||||
"fr": "via",
|
||||
"en": "via",
|
||||
},
|
||||
"Reset all preferences": {
|
||||
"fr": "🧹 Réinitialiser toutes les préférences",
|
||||
"en": "🧹 Reset all preferences",
|
||||
},
|
||||
"Preferences reset": {
|
||||
"fr": "Préférences réinitialisées",
|
||||
"en": "Preferences reset",
|
||||
},
|
||||
"Interface:": {
|
||||
"fr": "Interface :",
|
||||
"en": "Interface:",
|
||||
|
|
|
|||
74
script/todo/todo_prefs.py
Normal file
74
script/todo/todo_prefs.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python3
|
||||
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
"""Préférences persistantes du CLI TODO.
|
||||
|
||||
Réglages qui survivent d'une session à l'autre et qui appartiennent à
|
||||
l'UTILISATEUR, pas au dépôt : ils vivent donc dans ~/.erplibre (comme la
|
||||
télémétrie de navigation) et non dans un fichier versionné.
|
||||
|
||||
- get(key, default) / set(key, value) : accès unitaire.
|
||||
- reset() : efface tout et revient aux défauts.
|
||||
|
||||
Tout est best-effort : une préférence illisible ou un disque plein ne doivent
|
||||
JAMAIS empêcher le CLI de démarrer.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Clés connues et leur valeur par défaut. Une clé absente de ce dictionnaire
|
||||
# reste lisible/écrivable, mais n'apparaît pas dans l'écran de configuration.
|
||||
DEFAULTS = {
|
||||
# Interface du déploiement QEMU : "ask" pose la question à chaque fois,
|
||||
# "tui" ouvre le formulaire directement, "cli" garde les invites en ligne.
|
||||
"qemu_deploy_ui": "ask",
|
||||
# Affichage pendant le déploiement : "cli" (sortie texte, facile à copier
|
||||
# depuis le terminal) ou "tui" (blocs repliables + copie OSC 52).
|
||||
"qemu_deploy_progress": "cli",
|
||||
# Interface de la migration Odoo : "ask" / "tui" / "cli".
|
||||
"migration_ui": "ask",
|
||||
}
|
||||
|
||||
|
||||
def _path() -> Path:
|
||||
base = Path(os.path.expanduser("~/.erplibre"))
|
||||
base.mkdir(parents=True, exist_ok=True)
|
||||
return base / "todo_prefs.json"
|
||||
|
||||
|
||||
def load() -> dict:
|
||||
try:
|
||||
data = json.loads(_path().read_text())
|
||||
except (OSError, ValueError):
|
||||
return {}
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
|
||||
def _save(data: dict) -> None:
|
||||
try:
|
||||
_path().write_text(json.dumps(data, ensure_ascii=False, indent=2))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def get(key: str, default=None):
|
||||
"""Valeur d'une préférence : fichier, puis DEFAULTS, puis `default`."""
|
||||
if default is None:
|
||||
default = DEFAULTS.get(key)
|
||||
return load().get(key, default)
|
||||
|
||||
|
||||
def set(key: str, value) -> None: # noqa: A001 - API voulue : prefs.set(...)
|
||||
data = load()
|
||||
data[key] = value
|
||||
_save(data)
|
||||
|
||||
|
||||
def reset() -> int:
|
||||
"""Efface toutes les préférences. Renvoie le nombre de clés effacées."""
|
||||
count = len(load())
|
||||
_save({})
|
||||
return count
|
||||
Loading…
Reference in a new issue