feat(gui): binding licence-poste souple (P0-6/D-20.4, affichage sans blocage)
This commit is contained in:
@@ -20,6 +20,7 @@ from gui_v6 import theme as theme_mod
|
|||||||
from gui_v6 import ui_kit
|
from gui_v6 import ui_kit
|
||||||
from gui_v6.config_state import ConfigState
|
from gui_v6.config_state import ConfigState
|
||||||
from gui_v6.license_client import LicenseClient, LicenseStatus
|
from gui_v6.license_client import LicenseClient, LicenseStatus
|
||||||
|
from gui_v6.machine_id import default_machine_id
|
||||||
from gui_v6.tabs.tab_about import AboutTab
|
from gui_v6.tabs.tab_about import AboutTab
|
||||||
from gui_v6.tabs.tab_config import ConfigTab
|
from gui_v6.tabs.tab_config import ConfigTab
|
||||||
from gui_v6.tabs.tab_usage import UsageTab
|
from gui_v6.tabs.tab_usage import UsageTab
|
||||||
@@ -32,6 +33,26 @@ def resolve_portal_url() -> str:
|
|||||||
return os.environ.get("ANON_PORTAL_URL", DEFAULT_PORTAL_URL)
|
return os.environ.get("ANON_PORTAL_URL", DEFAULT_PORTAL_URL)
|
||||||
|
|
||||||
|
|
||||||
|
def bound_local_status(status: LicenseStatus, local_machine_id: str) -> LicenseStatus:
|
||||||
|
"""Annoter le statut licence selon le binding poste.
|
||||||
|
|
||||||
|
Souple (décision D1) : on N'EMPÊCHE PAS le traitement. Si la licence locale
|
||||||
|
est valide mais liée à un autre ``machine_id`` que le poste courant (ex.
|
||||||
|
``license.json`` copié), on le **signale** par un statut non valide d'affichage.
|
||||||
|
"""
|
||||||
|
if status.valid and status.machine_id and status.machine_id != local_machine_id:
|
||||||
|
return LicenseStatus(
|
||||||
|
valid=False,
|
||||||
|
status="autre_poste",
|
||||||
|
message="Licence liée à un autre poste",
|
||||||
|
expires_at=status.expires_at,
|
||||||
|
grace_days=status.grace_days,
|
||||||
|
machine_id=status.machine_id,
|
||||||
|
license_ref=status.license_ref,
|
||||||
|
)
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
_TABS = [
|
_TABS = [
|
||||||
("use", "📄 Utilisation"),
|
("use", "📄 Utilisation"),
|
||||||
("cfg", "⚙️ Administration"),
|
("cfg", "⚙️ Administration"),
|
||||||
@@ -89,7 +110,8 @@ class AnonymisationApp(ctk.CTk):
|
|||||||
|
|
||||||
def _safe_local_status(self) -> LicenseStatus:
|
def _safe_local_status(self) -> LicenseStatus:
|
||||||
try:
|
try:
|
||||||
return self._license_client.local_status()
|
status = self._license_client.local_status()
|
||||||
|
return bound_local_status(status, default_machine_id())
|
||||||
except Exception:
|
except Exception:
|
||||||
return LicenseStatus.unavailable()
|
return LicenseStatus.unavailable()
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ _STATUS_LABELS = {
|
|||||||
"revoked": "Poste révoqué",
|
"revoked": "Poste révoqué",
|
||||||
"invalid": "Licence invalide",
|
"invalid": "Licence invalide",
|
||||||
"unavailable": "Serveur de licence indisponible",
|
"unavailable": "Serveur de licence indisponible",
|
||||||
|
"autre_poste": "Licence liée à un autre poste",
|
||||||
"none": "Aucune licence",
|
"none": "Aucune licence",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ _STATUS_TOKEN = {
|
|||||||
"revoked": "danger",
|
"revoked": "danger",
|
||||||
"invalid": "danger",
|
"invalid": "danger",
|
||||||
"unavailable": "warning",
|
"unavailable": "warning",
|
||||||
|
"autre_poste": "warning",
|
||||||
"none": "text_muted",
|
"none": "text_muted",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
tests/unit/test_gui_v6_license_binding.py
Normal file
29
tests/unit/test_gui_v6_license_binding.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from gui_v6.app import bound_local_status
|
||||||
|
from gui_v6.license_client import LicenseStatus
|
||||||
|
|
||||||
|
|
||||||
|
def test_binding_flags_other_machine():
|
||||||
|
st = LicenseStatus(valid=True, status="active", machine_id="AAAA1111")
|
||||||
|
out = bound_local_status(st, "BBBB2222")
|
||||||
|
assert out.valid is False
|
||||||
|
assert out.status == "autre_poste"
|
||||||
|
assert "autre poste" in out.message.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_binding_ok_same_machine():
|
||||||
|
st = LicenseStatus(valid=True, status="active", machine_id="AAAA1111")
|
||||||
|
out = bound_local_status(st, "AAAA1111")
|
||||||
|
assert out.valid is True
|
||||||
|
assert out.status == "active"
|
||||||
|
|
||||||
|
|
||||||
|
def test_binding_noop_without_machine_id():
|
||||||
|
st = LicenseStatus(valid=True, status="active", machine_id=None)
|
||||||
|
assert bound_local_status(st, "AAAA1111").valid is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_binding_passes_through_invalid_status():
|
||||||
|
st = LicenseStatus(valid=False, status="expired", machine_id="OTHER")
|
||||||
|
out = bound_local_status(st, "AAAA1111")
|
||||||
|
assert out.status == "expired"
|
||||||
|
assert out.valid is False
|
||||||
Reference in New Issue
Block a user