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>
105 lines
3.6 KiB
Python
105 lines
3.6 KiB
Python
"""Shell minimal de la GUI V6 (lot G1).
|
|
|
|
Header + bandeau de statut licence + navigation 3 onglets
|
|
(Utilisation, Configuration, À propos). Seul « À propos » est étoffé en G1 ;
|
|
les deux autres sont des placeholders qui seront remplis en G2/G3.
|
|
|
|
Aucune logique de détection ici : ce module orchestre uniquement. La fenêtre
|
|
n'est créée qu'à l'instanciation de :class:`AnonymisationApp` (import sûr).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
import customtkinter as ctk
|
|
|
|
from gui_v6 import theme as theme_mod
|
|
from gui_v6.config_state import ConfigState
|
|
from gui_v6.license_client import LicenseClient, LicenseStatus
|
|
from gui_v6.tabs.tab_about import AboutTab
|
|
from gui_v6.tabs.tab_config import ConfigTab
|
|
from gui_v6.tabs.tab_usage import UsageTab
|
|
|
|
_TABS = ("Utilisation", "Configuration", "À propos")
|
|
|
|
|
|
class AnonymisationApp(ctk.CTk):
|
|
"""Fenêtre principale (socle G1)."""
|
|
|
|
def __init__(
|
|
self,
|
|
license_client: Optional[LicenseClient] = None,
|
|
theme_name: str = theme_mod.DEFAULT_THEME,
|
|
) -> None:
|
|
super().__init__()
|
|
self._theme_name = theme_name
|
|
theme_mod.apply_theme(theme_name)
|
|
|
|
# Client licence : par défaut, lecture du statut local uniquement
|
|
# (aucun appel réseau au démarrage). Injectable pour les tests.
|
|
self._license_client = license_client or LicenseClient("http://localhost")
|
|
status = self._safe_local_status()
|
|
|
|
# État de configuration partagé entre Configuration et Utilisation.
|
|
self._config = ConfigState()
|
|
|
|
self.title("Pseudonymisation de vos documents")
|
|
self.geometry("960x640")
|
|
|
|
self._build_header(status)
|
|
self._build_tabs(status)
|
|
|
|
# -- statut licence ---------------------------------------------------
|
|
|
|
def _safe_local_status(self) -> LicenseStatus:
|
|
try:
|
|
return self._license_client.local_status()
|
|
except Exception:
|
|
# Licence indisponible → dégradation silencieuse (mode bêta).
|
|
return LicenseStatus.unavailable()
|
|
|
|
# -- construction UI --------------------------------------------------
|
|
|
|
def _build_header(self, status: LicenseStatus) -> None:
|
|
header = ctk.CTkFrame(self, height=56)
|
|
header.pack(fill="x", padx=12, pady=(12, 6))
|
|
|
|
ctk.CTkLabel(
|
|
header,
|
|
text="Pseudonymisation",
|
|
font=ctk.CTkFont(size=16, weight="bold"),
|
|
).pack(side="left", padx=12, pady=10)
|
|
|
|
color = theme_mod.status_color(self._theme_name, status.status)
|
|
self._status_banner = ctk.CTkLabel(
|
|
header, text=self._banner_text(status), text_color=color
|
|
)
|
|
self._status_banner.pack(side="right", padx=12, pady=10)
|
|
|
|
def _build_tabs(self, status: LicenseStatus) -> None:
|
|
tabview = ctk.CTkTabview(self)
|
|
tabview.pack(fill="both", expand=True, padx=12, pady=(6, 12))
|
|
for name in _TABS:
|
|
tabview.add(name)
|
|
|
|
self._config_tab = ConfigTab(tabview.tab("Configuration"), state=self._config)
|
|
self._config_tab.pack(fill="both", expand=True)
|
|
|
|
self._usage = UsageTab(
|
|
tabview.tab("Utilisation"), config_provider=lambda: self._config
|
|
)
|
|
self._usage.pack(fill="both", expand=True)
|
|
|
|
self._about = AboutTab(
|
|
tabview.tab("À propos"),
|
|
status=status,
|
|
theme_name=self._theme_name,
|
|
license_client=self._license_client,
|
|
)
|
|
self._about.pack(fill="both", expand=True)
|
|
|
|
@staticmethod
|
|
def _banner_text(status: LicenseStatus) -> str:
|
|
return f"Licence : {status.status}"
|