feat(gui): GUI V6 G4 — alignement visuel sur la maquette v6 (option A)
Refonte de la couche présentation pour reprendre docs/ui_mockup_v6.html, sans changer de techno UI ni la logique G1-G3. - theme.py : 4 thèmes aux tokens EXACTS de la maquette (sombre #1a1a2e/#16213e/ #e94560, clair, médical, neutre), palette complète + status_color. - ui_kit.py (nouveau) : composants stylés (Card titrée, boutons primary/secondary/ success/pilule, StatCard, ToggleRow) appliquant la palette. - app.py : shell étroit, header identité + version + statut licence + liseré accent, barre d'onglets custom (plus de CTkTabview brut), navigation par recréation, changement de thème à chaud. - tab_usage : carte Apparence (sélecteur de thème), dropzone stylée, grille formats, barre d'actions, progression à étapes + journal, résultats en cartes statistiques. - tab_config : sous-navigation Réglages/Masquage/Partage/Règles ; Réglages câblé au ConfigState (profil, moteurs NER, dossier sortie). - tab_about : grille d'informations + bloc licence (logique inchangée). Logique inchangée : engine_bridge, config_state, license_client/store, runner. Tests : +9 (theme). self-test exit 0, 55 tests gui_v6, 202 tests/unit (0 régression). Smoke construction headless (Xvfb) : 3 onglets × 4 thèmes rendus sans erreur. Pas de pywebview, aucun .exe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
"""Onglet « À propos » : version, build, et activation/état de la licence (G3-C).
|
||||
"""Onglet « À propos » de la GUI V6 (G4 — alignement maquette).
|
||||
|
||||
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``).
|
||||
Grille d'informations (agrid) + bloc licence (activation par clef, vérification).
|
||||
La logique licence est inchangée : aucun token journalisé, aucun appel réseau au
|
||||
démarrage (seul ``local_status`` est lu).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -14,6 +13,7 @@ import customtkinter as ctk
|
||||
|
||||
from gui_v6 import __version__ as GUI_VERSION
|
||||
from gui_v6 import theme as theme_mod
|
||||
from gui_v6 import ui_kit
|
||||
from gui_v6.license_client import LicenseClient, LicenseStatus
|
||||
from gui_v6.machine_id import default_machine_id
|
||||
|
||||
@@ -29,15 +29,12 @@ _STATUS_LABELS = {
|
||||
|
||||
|
||||
def _build_info() -> str:
|
||||
"""Version / commit du build, si disponible. Best-effort, sans casser l'UI."""
|
||||
try:
|
||||
import build_info # type: ignore
|
||||
|
||||
commit = getattr(build_info, "BUILD_COMMIT", "?")
|
||||
branch = getattr(build_info, "BUILD_BRANCH", "?")
|
||||
return f"Build {commit} ({branch})"
|
||||
return f"{getattr(build_info, 'BUILD_COMMIT', '?')} ({getattr(build_info, 'BUILD_BRANCH', '?')})"
|
||||
except Exception:
|
||||
return "Build : information indisponible"
|
||||
return "indisponible"
|
||||
|
||||
|
||||
class AboutTab(ctk.CTkFrame):
|
||||
@@ -47,40 +44,60 @@ class AboutTab(ctk.CTkFrame):
|
||||
status: Optional[LicenseStatus] = None,
|
||||
theme_name: str = theme_mod.DEFAULT_THEME,
|
||||
license_client: Optional[LicenseClient] = None,
|
||||
palette: dict | None = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__(master, **kwargs)
|
||||
self._p = palette or theme_mod.get_palette(theme_name)
|
||||
super().__init__(master, fg_color=self._p["bg"], **kwargs)
|
||||
self._theme_name = theme_name
|
||||
self._client = license_client
|
||||
self._machine_id = default_machine_id()
|
||||
self._status = status or LicenseStatus.none()
|
||||
self._build()
|
||||
|
||||
ctk.CTkLabel(
|
||||
self,
|
||||
text="Pseudonymisation de vos documents",
|
||||
font=ctk.CTkFont(size=18, weight="bold"),
|
||||
).pack(anchor="w", padx=16, pady=(16, 4))
|
||||
def _build(self) -> None:
|
||||
p = self._p
|
||||
|
||||
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))
|
||||
# Grille d'informations
|
||||
info = ui_kit.Card(self, p, title="ℹ️ Informations")
|
||||
info.pack(fill="x", padx=14, pady=(14, 7))
|
||||
grid = ctk.CTkFrame(info, fg_color="transparent")
|
||||
grid.pack(fill="x", padx=16, pady=(0, 14))
|
||||
items = [
|
||||
("🏷️", "Version", f"Interface V6 — {GUI_VERSION}"),
|
||||
("📅", "Build", _build_info()),
|
||||
("🧠", "Moteurs NER", "CamemBERT · EDS-Pseudo · GLiNER"),
|
||||
("🔒", "Traitement", "100 % local — aucune donnée transmise"),
|
||||
("📚", "Gazetteers", "INSEE 219K · FINESS 108K · BDPM 7K"),
|
||||
("📁", "Formats", "PDF · DOCX · ODT · RTF · TXT · Images"),
|
||||
("🖥️", "Poste", self._machine_id),
|
||||
]
|
||||
for col in (0, 1):
|
||||
grid.grid_columnconfigure(col, weight=1)
|
||||
for idx, (icon, key, val) in enumerate(items):
|
||||
cell = ctk.CTkFrame(grid, fg_color="transparent")
|
||||
cell.grid(row=idx // 2, column=idx % 2, sticky="w", padx=4, pady=5)
|
||||
ctk.CTkLabel(cell, text=icon, font=ui_kit.font(18)).pack(side="left", padx=(0, 8))
|
||||
txt = ctk.CTkFrame(cell, fg_color="transparent")
|
||||
txt.pack(side="left")
|
||||
ctk.CTkLabel(txt, text=key.upper(), text_color=p["text_muted"], font=ui_kit.font(10), anchor="w").pack(anchor="w")
|
||||
ctk.CTkLabel(txt, text=val, text_color=p["text"], font=ui_kit.font(12, "bold"), anchor="w").pack(anchor="w")
|
||||
|
||||
self._status_label = ctk.CTkLabel(self, text="", anchor="w", justify="left")
|
||||
# Bloc licence
|
||||
lic = ui_kit.Card(self, p, title="🔑 Licence")
|
||||
lic.pack(fill="x", padx=14, pady=7)
|
||||
self._status_label = ctk.CTkLabel(lic, text="", text_color=p["text"], font=ui_kit.font(13), anchor="w", justify="left")
|
||||
self._status_label.pack(anchor="w", padx=16, pady=(0, 8))
|
||||
|
||||
# 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)
|
||||
|
||||
block = ctk.CTkFrame(lic, fg_color="transparent")
|
||||
block.pack(fill="x", padx=16, pady=(0, 14))
|
||||
self._key_entry = ctk.CTkEntry(block, width=240, placeholder_text="Clef d'activation",
|
||||
fg_color=p["btn_sec_bg"], border_color=p["btn_sec_border"], text_color=p["text"])
|
||||
self._key_entry.pack(side="left", padx=(0, 8))
|
||||
self._activate_btn = ui_kit.primary_button(block, p, "Activer", command=self._activate)
|
||||
self._activate_btn.pack(side="left", padx=(0, 8))
|
||||
self._check_btn = ui_kit.secondary_button(block, p, "Vérifier", command=self._check)
|
||||
self._check_btn.pack(side="left")
|
||||
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")
|
||||
|
||||
@@ -89,15 +106,12 @@ class AboutTab(ctk.CTkFrame):
|
||||
def set_status(self, status: LicenseStatus) -> None:
|
||||
self._status = status
|
||||
label = _STATUS_LABELS.get(status.status, status.status)
|
||||
text = f"État licence : {label}"
|
||||
text = f"État : {label}"
|
||||
if status.expires_at:
|
||||
text += f" · expire le {status.expires_at}"
|
||||
if status.message:
|
||||
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 --------------------------------------------------
|
||||
self._status_label.configure(text=text, text_color=theme_mod.status_color(self._theme_name, status.status))
|
||||
|
||||
def _activate(self) -> None:
|
||||
if self._client is None:
|
||||
@@ -107,7 +121,6 @@ class AboutTab(ctk.CTkFrame):
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user