feat(gui): add GUI V6 G1 foundation (license client/store, shell, About tab)
Socle de la refonte GUI V6 (couche présentation uniquement, aucune logique de détection) : - license_store: stockage licence hors dépôt (%LOCALAPPDATA%/Aivanov | XDG), read/write atomique/delete, ne journalise aucun token - license_client: LicenseStatus + activate/check/local_status, session HTTP injectable, serveur indisponible géré sans crash, aucune clé privée - theme: 4 thèmes + couleurs de statut licence - app + tab_about: shell customtkinter minimal (header, bandeau licence, 3 onglets), onglet À propos étoffé - Pseudonymisation_Gui_V6.py: point d'entrée + --self-test (exit 0 sans fenêtre) - requirements.txt: customtkinter==5.2.2 Tests: 20 nouveaux (store sur vrais fichiers, client sur session injectée). Suite tests/unit: 167 passed, 0 régression. V5/moteur/managers/specs intacts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
97
gui_v6/app.py
Normal file
97
gui_v6/app.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""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.license_client import LicenseClient, LicenseStatus
|
||||
from gui_v6.tabs.tab_about import AboutTab
|
||||
|
||||
_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()
|
||||
|
||||
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._about = AboutTab(
|
||||
tabview.tab("À propos"), status=status, theme_name=self._theme_name
|
||||
)
|
||||
self._about.pack(fill="both", expand=True)
|
||||
|
||||
# Placeholders G2/G3.
|
||||
ctk.CTkLabel(
|
||||
tabview.tab("Utilisation"),
|
||||
text="Onglet Utilisation — disponible au lot G2.",
|
||||
).pack(padx=16, pady=16, anchor="w")
|
||||
ctk.CTkLabel(
|
||||
tabview.tab("Configuration"),
|
||||
text="Onglet Configuration — disponible au lot G3.",
|
||||
).pack(padx=16, pady=16, anchor="w")
|
||||
|
||||
@staticmethod
|
||||
def _banner_text(status: LicenseStatus) -> str:
|
||||
return f"Licence : {status.status}"
|
||||
Reference in New Issue
Block a user