feat(gui): GUI V6 G3 — câblage moteur, Configuration, licence UI, build-prep
G3-A câblage moteur réel (engine_bridge.py) : EngineSettings + NerManagers à chargement paresseux (aucun manager à l'import), kwargs alignés CLI/V5 (make_vector_redaction=False, also_make_raster_burn=True, config_path, use_hf, ner/gliner/camembert_manager, ogc_label) ; make_process_fn engine injectable ; état managers not_loaded/loading/ready/unavailable, échecs optionnels tolérés. G3-B Configuration (config_state.py + tabs/tab_config.py) : ConfigState → EngineSettings, profils via profile_defaults (path injectable), options raster/NER local/profil/sortie, état managers, sections admin-only via admin_mode. G3-C Licence UI (machine_id.py + tab_about) : activation par clef (LicenseClient.activate), bouton vérifier (check), affichage statut, aucun token loggé, aucun appel réseau au démarrage (local_status seul). Intégration : tab_usage exécute via le moteur réel selon ConfigState (make_process_fn), anti double-lancement UI. app.py câble Config↔Usage↔licence. G3-D build-prep : anonymisation_gui_v6_onefile.spec (entry V6, customtkinter + modules gui_v6 en hiddenimports). Installateur Anonymisation.iss produit déjà la cible Anonymisation-Setup.exe. Aucun artefact .exe commité ; build Windows à part. Tests +14 (engine_bridge 8, config_state 6). self-test exit 0, 46 tests gui_v6, 193 tests/unit (0 régression). Moteur/V5/specs CLI intacts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""Onglet « À propos » : version, et état de la licence.
|
||||
"""Onglet « À propos » : version, build, et activation/état de la licence (G3-C).
|
||||
|
||||
Affiche le statut licence fourni par le client/stub. Dégrade proprement si la
|
||||
licence est indisponible (bandeau d'information, pas d'erreur bloquante).
|
||||
Affiche le statut licence et permet l'activation par clef (via
|
||||
``LicenseClient.activate``) et la vérification (``check``). Aucun appel réseau au
|
||||
démarrage : seul l'état local est lu. Aucun token n'est journalisé.
|
||||
Les widgets ne sont créés qu'à l'instanciation (import sûr pour ``--self-test``).
|
||||
"""
|
||||
|
||||
@@ -13,7 +14,8 @@ import customtkinter as ctk
|
||||
|
||||
from gui_v6 import __version__ as GUI_VERSION
|
||||
from gui_v6 import theme as theme_mod
|
||||
from gui_v6.license_client import LicenseStatus
|
||||
from gui_v6.license_client import LicenseClient, LicenseStatus
|
||||
from gui_v6.machine_id import default_machine_id
|
||||
|
||||
_STATUS_LABELS = {
|
||||
"active": "Licence active",
|
||||
@@ -31,11 +33,11 @@ def _build_info() -> str:
|
||||
try:
|
||||
import build_info # type: ignore
|
||||
|
||||
version = getattr(build_info, "VERSION", "?")
|
||||
commit = getattr(build_info, "COMMIT", "?")
|
||||
return f"Moteur {version} ({commit})"
|
||||
commit = getattr(build_info, "BUILD_COMMIT", "?")
|
||||
branch = getattr(build_info, "BUILD_BRANCH", "?")
|
||||
return f"Build {commit} ({branch})"
|
||||
except Exception:
|
||||
return "Moteur : information de build indisponible"
|
||||
return "Build : information indisponible"
|
||||
|
||||
|
||||
class AboutTab(ctk.CTkFrame):
|
||||
@@ -44,10 +46,14 @@ class AboutTab(ctk.CTkFrame):
|
||||
master,
|
||||
status: Optional[LicenseStatus] = None,
|
||||
theme_name: str = theme_mod.DEFAULT_THEME,
|
||||
license_client: Optional[LicenseClient] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__(master, **kwargs)
|
||||
self._theme_name = theme_name
|
||||
self._client = license_client
|
||||
self._machine_id = default_machine_id()
|
||||
self._status = status or LicenseStatus.none()
|
||||
|
||||
ctk.CTkLabel(
|
||||
self,
|
||||
@@ -55,17 +61,33 @@ class AboutTab(ctk.CTkFrame):
|
||||
font=ctk.CTkFont(size=18, weight="bold"),
|
||||
).pack(anchor="w", padx=16, pady=(16, 4))
|
||||
|
||||
ctk.CTkLabel(self, text=f"Interface V6 — {GUI_VERSION}").pack(
|
||||
anchor="w", padx=16
|
||||
)
|
||||
ctk.CTkLabel(self, text=_build_info()).pack(anchor="w", padx=16, pady=(0, 12))
|
||||
ctk.CTkLabel(self, text=f"Interface V6 — {GUI_VERSION}").pack(anchor="w", padx=16)
|
||||
ctk.CTkLabel(self, text=_build_info()).pack(anchor="w", padx=16, pady=(0, 4))
|
||||
ctk.CTkLabel(self, text=f"Poste : {self._machine_id}").pack(anchor="w", padx=16, pady=(0, 12))
|
||||
|
||||
self._status_label = ctk.CTkLabel(self, text="", anchor="w")
|
||||
self._status_label = ctk.CTkLabel(self, text="", anchor="w", justify="left")
|
||||
self._status_label.pack(anchor="w", padx=16, pady=(0, 8))
|
||||
|
||||
self.set_status(status or LicenseStatus.none())
|
||||
# Bloc activation licence
|
||||
block = ctk.CTkFrame(self)
|
||||
block.pack(fill="x", padx=16, pady=(0, 12))
|
||||
ctk.CTkLabel(block, text="Activation par clef :").pack(side="left", padx=(8, 8), pady=8)
|
||||
self._key_entry = ctk.CTkEntry(block, width=260, placeholder_text="Clef d'activation")
|
||||
self._key_entry.pack(side="left", padx=(0, 8), pady=8)
|
||||
self._activate_btn = ctk.CTkButton(block, text="Activer", command=self._activate)
|
||||
self._activate_btn.pack(side="left", padx=(0, 8), pady=8)
|
||||
self._check_btn = ctk.CTkButton(block, text="Vérifier", command=self._check)
|
||||
self._check_btn.pack(side="left", padx=(0, 8), pady=8)
|
||||
|
||||
if self._client is None:
|
||||
# Pas de client : activation désactivée, mode dev/bêta.
|
||||
self._activate_btn.configure(state="disabled")
|
||||
self._check_btn.configure(state="disabled")
|
||||
|
||||
self.set_status(self._status)
|
||||
|
||||
def set_status(self, status: LicenseStatus) -> None:
|
||||
self._status = status
|
||||
label = _STATUS_LABELS.get(status.status, status.status)
|
||||
text = f"État licence : {label}"
|
||||
if status.expires_at:
|
||||
@@ -74,3 +96,25 @@ class AboutTab(ctk.CTkFrame):
|
||||
text += f"\n{status.message}"
|
||||
color = theme_mod.status_color(self._theme_name, status.status)
|
||||
self._status_label.configure(text=text, text_color=color)
|
||||
|
||||
# -- actions licence --------------------------------------------------
|
||||
|
||||
def _activate(self) -> None:
|
||||
if self._client is None:
|
||||
return
|
||||
token = self._key_entry.get().strip()
|
||||
if not token:
|
||||
return
|
||||
status = self._client.activate(token, self._machine_id)
|
||||
self.set_status(status)
|
||||
# Ne jamais conserver le jeton saisi dans l'UI après usage.
|
||||
self._key_entry.delete(0, "end")
|
||||
|
||||
def _check(self) -> None:
|
||||
if self._client is None:
|
||||
return
|
||||
ref = self._status.license_ref
|
||||
if not ref:
|
||||
self.set_status(LicenseStatus.none("Aucune licence à vérifier"))
|
||||
return
|
||||
self.set_status(self._client.check(ref, self._machine_id))
|
||||
|
||||
Reference in New Issue
Block a user