diff --git a/apps/tesm-license/pyproject.toml b/apps/tesm-license/pyproject.toml index a500d6b..88eff7e 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.0.6" +version = "2.0.7" description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung" requires-python = ">=3.11" dependencies = [ diff --git a/apps/tesm-license/src/tesm_license/__init__.py b/apps/tesm-license/src/tesm_license/__init__.py index 0879abd..42470c5 100644 --- a/apps/tesm-license/src/tesm_license/__init__.py +++ b/apps/tesm-license/src/tesm_license/__init__.py @@ -23,12 +23,29 @@ from .permissions import PERMISSIONS, ROLE_PRESETS from .schema import LICENSE_MIGRATIONS from .settings_spec import LICENSE_SERVER_SETTINGS -__version__ = "2.0.2" +#: Version aus den Metadaten des installierten Pakets -- die einzige Quelle +#: ist ``pyproject.toml``. Vorher stand hier eine feste Zahl, und die +#: Fusszeile zeigte vier Releases lang 2.0.2. +#: +#: Im Quellbaum ohne ``pip install`` gibt es keine Metadaten; dann steht dort +#: "unbekannt" statt einer erfundenen Nummer. In einem editierbaren Install +#: zeigt sie den Stand des letzten ``pip install -e`` -- also die Version, die +#: tatsaechlich installiert ist. +try: # pragma: no cover - haengt an der Installationsart + from importlib.metadata import PackageNotFoundError, version as _paketversion + + __version__ = _paketversion("tesm-license") +except (ImportError, PackageNotFoundError): # pragma: no cover + __version__ = "unbekannt" APP_KEY = "tesm-license" APP_NAME = "TESM-Lizenzserver" APP_SHORT = "TL" APP_EDITION = "Lizenzverwaltung" + +#: Zeile unter der Wortmarke auf den Anmeldeseiten, in derselben Schrift wie +#: bei TESM. Ein Wort statt einer Liste: der Lizenzserver tut genau eine Sache. +AUTH_TAGLINE = "LIZENZIEREN" ENV_PREFIX = "TESM_LICENSE_" logger = logging.getLogger(__name__) @@ -56,6 +73,7 @@ def create_app(**overrides: Any) -> Flask: import_name="tesm_license", app_short=APP_SHORT, app_edition=APP_EDITION, + auth_tagline=AUTH_TAGLINE, app_version=__version__, enable_license=True, ) diff --git a/deploy/tesm-motd b/deploy/tesm-motd index 668c8ab..ee073c8 100644 --- a/deploy/tesm-motd +++ b/deploy/tesm-motd @@ -34,15 +34,12 @@ RESET = "\033[0m" #: Dieselbe Datei liest die Anwendung (``services/kea.py``: ``LEASE_FILE``). LEASE_FILE = "/var/lib/kea/kea-leases4.csv" -#: Zeile unter der Wortmarke. Wortlaut wie in der Anwendung -#: (``apps/tesm/src/tesm/__init__.py``: ``AUTH_TAGLINE``) -- ein Hygienetest -#: haelt beides zusammen. -#: -#: Der Lizenzserver hat bewusst keine: die Funktionsliste von TESM waere dort -#: falsch, und eine erfundene waere schlimmer als keine. +#: Zeile unter der Wortmarke. Wortlaut wie in der jeweiligen Anwendung +#: (``AUTH_TAGLINE`` in deren ``__init__.py``) -- ein Hygienetest haelt beides +#: zusammen. TAGLINE = { - "tesm": "UEBERWACHEN · BOOTEN · ANBINDEN (DHCP) · BETRIEBSSYSTEM GEBEN (PXE)", - "tesm-license": "", + "tesm": "ÜBERWACHEN · BOOTEN · ANBINDEN", + "tesm-license": "LIZENZIEREN", } TITEL = {"tesm": "TimEShepManager", "tesm-license": "Lizenzserver"} diff --git a/packages/tesm-core/pyproject.toml b/packages/tesm-core/pyproject.toml index 629af80..7d7b69c 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.0.6" +version = "2.0.7" 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/__init__.py b/packages/tesm-core/src/tesm_core/__init__.py index e1d5516..a1c695e 100644 --- a/packages/tesm-core/src/tesm_core/__init__.py +++ b/packages/tesm-core/src/tesm_core/__init__.py @@ -8,7 +8,20 @@ Design-System. from __future__ import annotations -__version__ = "2.0.2" +#: Version aus den Metadaten des installierten Pakets -- die einzige Quelle +#: ist ``pyproject.toml``. Vorher stand hier eine feste Zahl, und die +#: Fusszeile zeigte vier Releases lang 2.0.2. +#: +#: Im Quellbaum ohne ``pip install`` gibt es keine Metadaten; dann steht dort +#: "unbekannt" statt einer erfundenen Nummer. In einem editierbaren Install +#: zeigt sie den Stand des letzten ``pip install -e`` -- also die Version, die +#: tatsaechlich installiert ist. +try: # pragma: no cover - haengt an der Installationsart + from importlib.metadata import PackageNotFoundError, version as _paketversion + + __version__ = _paketversion("tesm-core") +except (ImportError, PackageNotFoundError): # pragma: no cover + __version__ = "unbekannt" from .app_factory import create_base_app from .config import CoreConfig, SecurityConfig, load_core_config diff --git a/packages/tesm-core/src/tesm_core/app_factory.py b/packages/tesm-core/src/tesm_core/app_factory.py index efad88c..286054d 100644 --- a/packages/tesm-core/src/tesm_core/app_factory.py +++ b/packages/tesm-core/src/tesm_core/app_factory.py @@ -130,11 +130,11 @@ def create_base_app( import_name: str = "tesm_core", app_short: str = "TESM", app_edition: str = "", - #: Zeile unter der Wortmarke auf den Anmeldeseiten. Leer = kein - #: Seitenhintergrund. Der Lizenzserver setzt sie bewusst nicht: die - #: Funktionsliste von TESM waere dort schlicht falsch. + #: Zeile unter der Wortmarke auf den Anmeldeseiten. Leer = nur die + #: Wortmarke, ohne Zeile darunter; die Marke selbst zeigen beide + #: Anwendungen. auth_tagline: str = "", - app_version: str = "2.0.6", + app_version: str = "2.0.7", built_in_trusted_keys: tuple[str, ...] = (), enable_license: bool = True, template_folder: str | None = None, diff --git a/packages/tesm-core/src/tesm_core/auth/models.py b/packages/tesm-core/src/tesm_core/auth/models.py index 3d29990..ade0eeb 100644 --- a/packages/tesm-core/src/tesm_core/auth/models.py +++ b/packages/tesm-core/src/tesm_core/auth/models.py @@ -200,7 +200,12 @@ def _deny(message: str) -> Any: abort(401, description="Sitzung abgelaufen.") if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html: abort(401, description="Anmeldung erforderlich.") - flash("Bitte melden Sie sich an.", "info") + # "Bitte melden Sie sich an." stand auf einer Seite, die aus einem + # Anmeldefeld, einem Passwortfeld und einem Knopf "Anmelden" besteht -- + # eine Meldung, die nichts hinzufuegt. Wer dagegen gerade noch + # angemeldet war, soll erfahren, warum er es nicht mehr ist. + if getattr(g, "session_expired", False): + flash("Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.", "info") return redirect(url_for("auth.login")) abort(403, description=message) diff --git a/packages/tesm-core/src/tesm_core/security/sessions.py b/packages/tesm-core/src/tesm_core/security/sessions.py index 6f8d0de..2450f7d 100644 --- a/packages/tesm-core/src/tesm_core/security/sessions.py +++ b/packages/tesm-core/src/tesm_core/security/sessions.py @@ -26,7 +26,7 @@ import sqlite3 from datetime import datetime, timedelta, timezone from typing import Any -from flask import Flask, Request, Response +from flask import Flask, Request, Response, g from flask import request as aktuelle_anfrage from flask.sessions import SessionInterface, SessionMixin from werkzeug.datastructures import CallbackDict @@ -209,6 +209,11 @@ class SqliteSessionInterface(SessionInterface): "WHERE id=?", (_iso(now), sid), ) + # Fuer die Anmeldeseite vermerken, **warum** sie erscheint. Ohne + # das steht dort entweder nichts -- dann wundert sich, wer + # gerade noch angemeldet war -- oder eine Meldung, die jeder + # Besucher zu sehen bekommt und die deshalb nichts sagt. + g.session_expired = True return self.session_class(new=True) try: diff --git a/packages/tesm-core/src/tesm_core/static/tesm_core/css/components.css b/packages/tesm-core/src/tesm_core/static/tesm_core/css/components.css index da61bc6..958efab 100644 --- a/packages/tesm-core/src/tesm_core/static/tesm_core/css/components.css +++ b/packages/tesm-core/src/tesm_core/static/tesm_core/css/components.css @@ -412,7 +412,10 @@ und beide sind rot. */ .pill--critical { background: var(--danger); - color: var(--surface-0); + /* Weiss wie bei .btn--danger. Vorher stand hier ein Token, das es nicht + gibt -- die Deklaration war damit unwirksam, und die Schrift blieb in + der Textfarbe: dunkelgrau auf Rot. */ + color: #fff; font-weight: 700; } .pill--critical::before { background: currentColor; } @@ -695,7 +698,9 @@ width: 100%; height: min(70dvh, 760px); border: 0; - background: var(--surface-0); + /* Die Vorschau zeigt fremde Dokumente; ein neutraler Grund gehoert + darunter. --surface-0 gibt es nicht, der Rahmen blieb durchsichtig. */ + background: var(--surface-1); } /* Der Dateiname ist ein Knopf (er oeffnet die Vorschau), soll aber wie ein Link aussehen -- ein Knopf mit Rahmen mitten in einer Tabellenzeile waere @@ -849,7 +854,10 @@ width: 17px; height: 17px; border-radius: 5px; - background: var(--surface-0); + /* Dunkler als die Karte -- genau das war die Absicht: "dunkler heisst, + es gibt das Recht nicht". Mit dem nicht existierenden --surface-0 war + das Feld durchsichtig und damit so hell wie die Karte. */ + background: var(--canvas); border: 1px solid var(--border-subtle); box-shadow: inset 0 1px 2px rgb(0 0 0 / 28%); vertical-align: -3px; @@ -943,9 +951,15 @@ display: grid; place-items: center; padding: var(--sp-6); + /* Zwei warme Kreise, Geometrie und Staerke aus der Fassung 1.x + uebernommen: oben links deutlich, unten rechts angedeutet. Die vorige + Fassung setzte den zweiten Kreis auf --info und 10 % -- ein kaltes + Blau, das man auf dunklem Grund nicht sah und das nicht zur Marke + gehoert. Ueber color-mix statt festem rgba, damit es im hellen Thema + nicht schmutzig wird. */ background: - radial-gradient(1100px 520px at 12% -10%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%), - radial-gradient(900px 480px at 100% 110%, color-mix(in srgb, var(--info) 10%, transparent), transparent 70%), + radial-gradient(circle at 15% 15%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 45%), + radial-gradient(circle at 85% 85%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 40%), var(--canvas); } /* Die Wortmarke liegt hinter allem (z-index 0), die Anmeldebox davor. @@ -1026,24 +1040,35 @@ color: var(--text-faint); } - .auth-page { position: relative; overflow: hidden; align-content: center; } + /* Marke und Karte stehen uebereinander im normalen Fluss. Vorher lag die + Marke absolut positioniert hinter der Karte -- auf einem flachen Fenster + schnitt die Karte mitten durch den Schriftzug und die Funktionszeile. Ein + Stapel kann nicht kollidieren, und die Seite darf notfalls scrollen. */ + .auth-stack { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--sp-8); + width: 100%; + } .auth-backdrop { - position: absolute; - top: 4vh; - left: 50%; - transform: translateX(-50%); width: min(72vw, 880px); pointer-events: none; - z-index: 0; - opacity: 0.5; + /* Volle Deckung: solange die Marke hinter der Karte lag, war sie ein + Wasserzeichen und musste zurueckhaltend sein. Jetzt steht sie ueber der + Karte und ist das Erste, was man sieht -- halbtransparent sah sie dort + nur blass aus. Die Farben kommen ohnehin aus zurueckhaltenden Token. */ + opacity: 1; } .auth-backdrop svg { width: 100%; height: auto; display: block; } @media (max-width: 640px) { - .auth-backdrop { width: min(90vw, 460px); opacity: 0.32; } + .auth-backdrop { width: min(90vw, 460px); } } /* Wer wenig Bewegung/Reize moechte, bekommt das Motiv dezenter. */ + /* Wer weniger Reize moechte, bekommt die Marke ruhiger -- aber nicht + unsichtbar: sie ist die Ueberschrift der Seite. */ @media (prefers-contrast: more) { - .auth-backdrop { opacity: 0.18; } + .auth-backdrop { opacity: 0.75; } } .auth-card { diff --git a/packages/tesm-core/src/tesm_core/templates/tesm_core/login.html b/packages/tesm-core/src/tesm_core/templates/tesm_core/login.html index c64405f..2e6a8e6 100644 --- a/packages/tesm-core/src/tesm_core/templates/tesm_core/login.html +++ b/packages/tesm-core/src/tesm_core/templates/tesm_core/login.html @@ -1,6 +1,9 @@ {% extends "tesm_core/standalone.html" %} +{# Die Ueberschrift bleibt fuer die Titelzeile des Browsers -- sichtbar ist sie + hier nicht: der leere ``brand``-Block laesst Kachel, Ueberschrift und + Unterzeile weg. Die Wortmarke darueber sagt schon, wo man ist. #} {% block heading %}Anmelden{% endblock %} -{% block subheading %}{{ app_name }} – {{ app_edition }}{% endblock %} +{% block brand %}{% endblock %} {% block content %}
diff --git a/packages/tesm-core/src/tesm_core/templates/tesm_core/standalone.html b/packages/tesm-core/src/tesm_core/templates/tesm_core/standalone.html index 04ef814..c710363 100644 --- a/packages/tesm-core/src/tesm_core/templates/tesm_core/standalone.html +++ b/packages/tesm-core/src/tesm_core/templates/tesm_core/standalone.html @@ -22,37 +22,51 @@
- {% if auth_tagline %} - {# Wortmarke mit Funktionszeile als tatsächlicher Seitenhintergrund -- - übernommen vom Vorgänger. Bewusst inline und mit Themen-Tokens statt - zweier Dateien für hell und dunkel: bei der Einstellung "System" und - beim Umschalten von Hand wäre die versteckte Variante die falsche. - ``aria-hidden``, weil der Name schon in der Überschrift steht. #} -
diff --git a/packages/tesm-licensing-server/pyproject.toml b/packages/tesm-licensing-server/pyproject.toml index a0d6709..325daad 100644 --- a/packages/tesm-licensing-server/pyproject.toml +++ b/packages/tesm-licensing-server/pyproject.toml @@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-licensing-server" -version = "2.0.6" +version = "2.0.7" description = "Serverhaelfte des TESM-Lizenzprotokolls: ausstellen, erneuern, antworten" requires-python = ">=3.11" -dependencies = ["tesm-licensing==2.0.6", "cryptography>=41"] +dependencies = ["tesm-licensing==2.0.7", "cryptography>=41"] [tool.setuptools.packages.find] where = ["src"] diff --git a/packages/tesm-licensing/pyproject.toml b/packages/tesm-licensing/pyproject.toml index 9c28542..84ed4fd 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.0.6" +version = "2.0.7" description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver" requires-python = ">=3.11" dependencies = ["cryptography>=42"]