Files
anonymisation/gui_v6/tabs/tab_about.py

180 lines
7.4 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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 tkinter import messagebox
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",
"autre_poste": "Licence liée à un autre poste",
"none": "Aucune licence",
}
_UPDATE_CHECK_MESSAGE = (
"Aucune version publiée n'a été trouvée sur le portail.\n\n"
"Vérifiez que le serveur web est joignable ou contactez votre administrateur."
)
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 inclus · EDS/GLiNER optionnels non embarqués"),
("🔒", "Traitement", "100 % local — aucune donnée transmise"),
("📚", "Bases de données", "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")
ui_kit.secondary_button(info, p, "🔄 Rechercher une mise à jour", command=self._check_updates).pack(
anchor="w", padx=16, pady=(0, 14)
)
# 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))
def _check_updates(self) -> None:
if self._client is None:
messagebox.showinfo("Mise à jour", _UPDATE_CHECK_MESSAGE)
return
payload = self._client.latest_version()
if not payload:
messagebox.showinfo("Mise à jour", _UPDATE_CHECK_MESSAGE)
return
version = str(payload.get("version") or "version inconnue")
channel = str(payload.get("channel") or "canal non précisé")
filename = str(payload.get("filename") or "fichier non précisé")
messagebox.showinfo(
"Mise à jour",
f"Version publiée : {version}\nCanal : {channel}\nFichier : {filename}",
)