From f481a917732498fd250a2ca8cea08a6cc8c7167f Mon Sep 17 00:00:00 2001 From: Tim Eertmoed Date: Thu, 17 Sep 2026 09:26:31 +0200 Subject: [PATCH] Version 2.8.38: eine Rechnung fuer die Resttage, Module benannt --- apps/tesm/pyproject.toml | 2 +- packages/tesm-core/pyproject.toml | 2 +- .../src/tesm_core/licensing_client.py | 13 ++++++++++- packages/tesm-licensing/pyproject.toml | 2 +- .../src/tesm_licensing/__init__.py | 2 ++ .../src/tesm_licensing/license.py | 23 ++++++++++++++++++- 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/apps/tesm/pyproject.toml b/apps/tesm/pyproject.toml index 68bb73c..9e662b4 100644 --- a/apps/tesm/pyproject.toml +++ b/apps/tesm/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm" -version = "2.8.37" +version = "2.8.38" description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen" requires-python = ">=3.11" dependencies = [ diff --git a/packages/tesm-core/pyproject.toml b/packages/tesm-core/pyproject.toml index c0bc4e3..bf9bc6a 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.19" +version = "2.8.20" 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 c827b7f..e7a73bf 100644 --- a/packages/tesm-core/src/tesm_core/licensing_client.py +++ b/packages/tesm-core/src/tesm_core/licensing_client.py @@ -193,7 +193,18 @@ def zustand_als_auskunft(state: "LicenseState") -> dict[str, Any]: "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), + # Drei Angaben, weil zwei davon verschieden sein koennen und die + # Verwechslung teuer ist: "modules" ist, was die **Lizenz** freigibt -- + # dieselbe Liste, die der Lizenzserver zeigt. Was davon vor Ort + # abgeschaltet wurde, steht daneben. + # + # Vorher stand hier allein das Laufende. Beim Kunden fehlte darin + # "dhcp", waehrend die Lizenzseite es auswies -- es war lediglich unter + # Verwaltung -> System abgeschaltet. Aus der Antwort ging das nicht + # hervor, und damit sah es nach einem Lizenzproblem aus. + "modules": sorted(state.modules), + "modules_active": sorted(state.modules_in_use), + "modules_off": sorted(state.modules_off), "customer": state.customer_name, "last_heartbeat_at": state.last_heartbeat_at, "error": state.error, diff --git a/packages/tesm-licensing/pyproject.toml b/packages/tesm-licensing/pyproject.toml index 7f97cdb..1f79d89 100644 --- a/packages/tesm-licensing/pyproject.toml +++ b/packages/tesm-licensing/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-licensing" -version = "2.7.7" +version = "2.7.8" description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver" requires-python = ">=3.11" dependencies = ["cryptography>=42"] diff --git a/packages/tesm-licensing/src/tesm_licensing/__init__.py b/packages/tesm-licensing/src/tesm_licensing/__init__.py index 6e666bd..55ea3f2 100644 --- a/packages/tesm-licensing/src/tesm_licensing/__init__.py +++ b/packages/tesm-licensing/src/tesm_licensing/__init__.py @@ -46,6 +46,7 @@ from .license import ( key_id, license_status, parse_iso, + tage_bis, split_bundle, utcnow, verify_license, @@ -103,6 +104,7 @@ __all__ = [ "machine_identity", "new_nonce", "parse_iso", + "tage_bis", "sign", "split_bundle", "system_fingerprint", diff --git a/packages/tesm-licensing/src/tesm_licensing/license.py b/packages/tesm-licensing/src/tesm_licensing/license.py index 7ebd714..0e706bd 100644 --- a/packages/tesm-licensing/src/tesm_licensing/license.py +++ b/packages/tesm-licensing/src/tesm_licensing/license.py @@ -221,6 +221,27 @@ def verify_license( ) +def tage_bis(expires_at: datetime, *, now: datetime | None = None) -> int: + """Restlaufzeit in **angefangenen** Tagen. + + Aufgerundet, und das ist eine Entscheidung: eine Lizenz, die in zwei + Stunden ablaeuft, hat "1 Tag" und nicht "0". Null bedeutet hier + ausschliesslich *abgelaufen* -- eine gueltige Lizenz, die mit "0 Tage" + dasteht, liest sich wie eine tote. + + **Es gibt genau diese eine Rechnung.** Der Lizenzserver hatte eine zweite, + abgerundete: dieselbe Lizenz zeigte in seiner Oberflaeche "21 Tage" und im + Abruf beim Kunden "22". Zwei Zahlen fuer dieselbe Frage sind schlimmer als + eine ungenaue -- sie kosten jemanden einen Nachmittag mit der Suche nach + einem Fehler, den es nicht gibt. + """ + moment = now or utcnow() + sekunden = int((expires_at - moment).total_seconds()) + if sekunden <= 0: + return 0 + return -(-sekunden // 86400) + + def license_status( document: Mapping[str, Any], *, @@ -278,7 +299,7 @@ def license_status( days_left = 0 days_since_expiry = int((moment - expires_at).total_seconds() // 86400) else: - days_left = -(-int(delta.total_seconds()) // 86400) + days_left = tage_bis(expires_at, now=moment) days_since_expiry = None grace_active = bool(