diff --git a/apps/tesm-license/pyproject.toml b/apps/tesm-license/pyproject.toml index fb057ec..8fc99f5 100644 --- a/apps/tesm-license/pyproject.toml +++ b/apps/tesm-license/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-license" -version = "2.8.16" +version = "2.8.17" description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung" requires-python = ">=3.11" dependencies = [ diff --git a/packages/tesm-core/pyproject.toml b/packages/tesm-core/pyproject.toml index a197cd0..c0bc4e3 100644 --- a/packages/tesm-core/pyproject.toml +++ b/packages/tesm-core/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-core" -version = "2.8.18" +version = "2.8.19" description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver" requires-python = ">=3.11" dependencies = [ diff --git a/packages/tesm-core/src/tesm_core/licensing_client.py b/packages/tesm-core/src/tesm_core/licensing_client.py index 9ef0505..c827b7f 100644 --- a/packages/tesm-core/src/tesm_core/licensing_client.py +++ b/packages/tesm-core/src/tesm_core/licensing_client.py @@ -162,6 +162,56 @@ class LicenseState: return dict(self.status.vendor) if self.status else {} +#: Ausstiegscodes der Abfrage -- danach schlaegt ein RMM Alarm, nicht nach Text. +ABFRAGE_OK = 0 +ABFRAGE_LAEUFT_AB = 1 +ABFRAGE_UNGUELTIG = 2 + +#: Ab hier gilt eine Lizenz als "laeuft bald ab". Dieselbe Zahl wie in der +#: Oberflaeche -- zwei Schwellen fuer dieselbe Frage waeren eine zu viel. +ABFRAGE_WARNSCHWELLE_TAGE = 30 + + +def zustand_als_auskunft(state: "LicenseState") -> dict[str, Any]: + """Der Lizenzzustand als schlichtes Woerterbuch -- fuer JSON und Skripte. + + Gedacht fuer ein RMM, das auf der Maschine selbst laeuft: kein Netz, kein + Token, keine Anmeldung. Was hier steht, beantwortet die eine Frage, die ein + Kunde wirklich hat -- wie lange laeuft die Lizenz noch --, und nichts + darueber hinaus: keine Schluessel, keine Kennungen anderer Installationen. + """ + status = state.status + return { + "valid": bool(state.valid), + "activated": bool(state.activated), + "type": str(status.type) if status else "", + "role": state.role, + "lifetime": bool(status.lifetime) if status else False, + "expires_at": lic.iso(status.expires_at) if status and status.expires_at else "", + # Bei einer unbefristeten Lizenz ist die Zahl bewusst ``null`` und + # keine grosse Zahl: "noch 99999 Tage" waere eine Behauptung. + "days_left": (status.days_left if status and not status.lifetime else None), + "expiring_soon": bool(status.expiring_soon) if status else False, + "grace_active": bool(status.grace_active) if status else False, + "modules": sorted(state.modules_in_use), + "customer": state.customer_name, + "last_heartbeat_at": state.last_heartbeat_at, + "error": state.error, + } + + +def abfrage_ausstiegscode(auskunft: dict[str, Any]) -> int: + """0 heil, 1 laeuft bald ab, 2 ungueltig -- die Sprache eines RMM.""" + if not auskunft.get("valid"): + return ABFRAGE_UNGUELTIG + if auskunft.get("lifetime"): + return ABFRAGE_OK + tage = auskunft.get("days_left") + if tage is None or int(tage) <= ABFRAGE_WARNSCHWELLE_TAGE: + return ABFRAGE_LAEUFT_AB + return ABFRAGE_OK + + class LicenseManager: """Haelt den Lizenzzustand und fuehrt die Handshakes durch."""