"""Onglet « À propos » de la GUI V6 (G4 — alignement maquette). 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 from typing import Optional 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 _HELP_ABOUT = ( "Cet écran affiche la version de l'application, les moteurs utilisés et " "l'identifiant de ce poste.\n\n" "La licence s'active avec une clef fournie par votre administrateur. " "L'activation se fait sans envoyer aucun document : seule la clef est vérifiée.\n\n" "Le traitement des documents reste 100 % local sur ce poste." ) _STATUS_LABELS = { "active": "Licence active", "grace": "Licence en période de grâce", "expired": "Licence expirée", "revoked": "Poste révoqué", "invalid": "Licence invalide", "unavailable": "Serveur de licence indisponible", "none": "Aucune licence", } def _build_info() -> str: try: import build_info # type: ignore return f"{getattr(build_info, 'BUILD_COMMIT', '?')} ({getattr(build_info, 'BUILD_BRANCH', '?')})" except Exception: return "indisponible" class AboutTab(ctk.CTkFrame): def __init__( self, master, status: Optional[LicenseStatus] = None, theme_name: str = theme_mod.DEFAULT_THEME, license_client: Optional[LicenseClient] = None, palette: dict | None = None, **kwargs, ) -> None: 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() def _build(self) -> None: p = self._p # Bandeau d'introduction + aide « ? » intro = ctk.CTkFrame(self, fg_color="transparent") intro.pack(fill="x", padx=14, pady=(12, 0)) ctk.CTkLabel( intro, text="Informations sur l'application et activation de votre licence.", text_color=p["text_dim"], font=ui_kit.font(12), anchor="w", ).pack(side="left", padx=(2, 6)) ui_kit.help_button(intro, p, _HELP_ABOUT, title="À propos / Licence").pack(side="right", padx=2) # 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") # 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)) 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: 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 : {label}" if status.expires_at: text += f" · expire le {status.expires_at}" if status.message: text += f"\n{status.message}" 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: return token = self._key_entry.get().strip() if not token: return status = self._client.activate(token, self._machine_id) self.set_status(status) 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))