"""Onglet « À propos » : version, build, et activation/état de la licence (G3-C). 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``). """ 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.license_client import LicenseClient, LicenseStatus from gui_v6.machine_id import default_machine_id _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: """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})" except Exception: return "Build : information 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, **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, text="Pseudonymisation de vos documents", 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, 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", 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) 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: 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 -------------------------------------------------- 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))