Version 2.8.38: eine Rechnung fuer die Resttage, Module benannt

This commit is contained in:
alientim committed 2026-09-17 09:26:34 +02:00
1 parent 50b212caec
commit 68bda48d72
7 files changed
+53 -7

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm-license"
version = "2.8.18"
version = "2.8.19"
description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung"
requires-python = ">=3.11"
dependencies = [
@@ -779,6 +779,11 @@ def display_state(row: dict[str, Any], *, now: datetime | None = None) -> dict[s
Grenzen: mehr als 90 Tage = gruen, 30 bis 90 Tage = orange, unter 30 Tage
(auch abgelaufen) = rot. Unbefristete Lizenzen sind immer gruen.
Gezaehlt wird in **angefangenen** Tagen (``lic.tage_bis``) -- dieselbe
Rechnung, die auch der Client meldet. Die Vergleiche sind darauf
abgestimmt: 90 volle Tage und eine Stunde sind "mehr als 90" und damit
gruen, 29 Tage und eine Stunde sind "unter 30" und damit rot.
"""
moment = now or datetime.now(timezone.utc)
result = {
@@ -802,11 +807,18 @@ def display_state(row: dict[str, Any], *, now: datetime | None = None) -> dict[s
return {**result, "color": "red"}
delta = expires - moment
days_left = int(delta.total_seconds() // 86400)
# Dieselbe Rechnung wie im Client -- nicht eine zweite daneben. Hier stand
# eine abgerundete, und damit zeigte dieselbe Lizenz auf dieser Seite
# "21 Tage", waehrend der Abruf beim Kunden "22" meldete.
days_left = lic.tage_bis(expires, now=moment)
expired = delta.total_seconds() <= 0
if expired:
color = "red"
elif days_left < RED_BELOW_DAYS:
elif days_left <= RED_BELOW_DAYS:
# "<=", weil ``days_left`` **angefangene** Tage zaehlt: eine Lizenz mit
# 29 Tagen und einer Stunde steht als 30 da und liegt trotzdem unter
# der Schwelle von 30 vollen Tagen. Mit "<" waere sie orange -- die
# Warnung kaeme einen Tag zu spaet, und zwar genau am kritischen Rand.
color = "red"
elif days_left <= GREEN_ABOVE_DAYS:
color = "orange"
+1 -1
View File
@@ -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 = [
@@ -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,
+1 -1
View File
@@ -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"]
@@ -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",
@@ -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(