From 7407f4bc0023c6e264fc934fb1d6b7738517508c Mon Sep 17 00:00:00 2001 From: Tim Eertmoed Date: Thu, 3 Sep 2026 14:44:32 +0200 Subject: [PATCH] Anmeldeseite: Wortmarke fuer beide, Karte ohne Wiederholung * Die Wortmarke stand nur bei TESM -- sie hing an der Funktionszeile, und die hatte der Lizenzserver nicht. Jetzt zeigen beide die Marke; die Zeile darunter ist optional und beim Lizenzserver "LIZENZIEREN". * Der Kartenkopf ist weg: eine Kachel mit zwei Buchstaben, die Ueberschrift "Anmelden" und der Anwendungsname darunter sagten dasselbe wie die Marke darueber und der Knopf darunter. Die Ueberschrift bleibt fuer die Titelzeile des Browsers. * Marke und Karte stehen jetzt uebereinander im Fluss statt uebereinander gestapelt: auf einem flachen Fenster schnitt die Karte mitten durch den Schriftzug. * Die Marke ist kuerzer und symmetrisch um die Mitte aufgebaut -- Schriftzug ueber text-anchor, Zacken spiegelbildlich, Linie links so lang wie der Punkt rechts. Volle Deckkraft, weil sie kein Wasserzeichen hinter der Karte mehr ist. * Funktionszeile gekuerzt auf "UEBERWACHEN . BOOTEN . ANBINDEN" -- mit echtem Umlaut. "(PXE)" stand fuer etwas, das es noch nicht gibt. * Der Farbverlauf im Hintergrund hat wieder Geometrie und Staerke der Fassung 1.x. Der zweite Kreis lag auf --info und 10 %: ein kaltes Blau, das man auf dunklem Grund nicht sah und das nicht zur Marke gehoert. * "Bitte melden Sie sich an." stand auf einer Seite aus Anmeldefeld, Passwortfeld und einem Knopf "Anmelden". Statt dessen erfaehrt jetzt der, dessen Sitzung gerade ablief, warum er wieder hier ist. Dabei drei Dinge gefunden, die lautlos nichts taten: * __version__ stand fest verdrahtet in drei __init__.py. Die Fusszeile zeigte vier Releases lang 2.0.2, waehrend die Anwendung 2.0.6 war. Die Nummer kommt jetzt aus den Metadaten des installierten Pakets; Quelle ist pyproject.toml. * "gap: var(--sp-7)" -- die Skala kennt kein --sp-7 (1 bis 6, dann 8, 10, 12, 16). Eine undefinierte Variable verwirft die ganze Deklaration ohne Meldung: der Abstand war "normal", und Marke und Karte stiessen auf den Pixel aneinander. * Ein Test dagegen fand sofort ein zweites: --surface-0 wird dreimal benutzt und nirgends definiert. Die Schrift auf der Kachel "kritisch" blieb dunkelgrau auf Rot, die Dateivorschau hatte keinen Grund, und das Feld "Recht nicht vorhanden" in der Rechtematrix war durchsichtig -- also so hell wie die Karte, obwohl es dunkler sein sollte. Version 2.0.7. Co-Authored-By: Claude Opus 5 --- apps/tesm/pyproject.toml | 2 +- apps/tesm/src/tesm/__init__.py | 25 +++++-- deploy/tesm-motd | 13 ++-- packages/tesm-core/pyproject.toml | 2 +- packages/tesm-core/src/tesm_core/__init__.py | 15 +++- .../tesm-core/src/tesm_core/app_factory.py | 8 +- .../tesm-core/src/tesm_core/auth/models.py | 7 +- .../src/tesm_core/security/sessions.py | 7 +- .../static/tesm_core/css/components.css | 53 ++++++++++---- .../tesm_core/templates/tesm_core/login.html | 5 +- .../templates/tesm_core/standalone.html | 73 +++++++++++-------- packages/tesm-licensing/pyproject.toml | 2 +- 12 files changed, 145 insertions(+), 67 deletions(-) diff --git a/apps/tesm/pyproject.toml b/apps/tesm/pyproject.toml index b649ec7..d759eeb 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.0.6" +version = "2.0.7" description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen" requires-python = ">=3.11" dependencies = [ diff --git a/apps/tesm/src/tesm/__init__.py b/apps/tesm/src/tesm/__init__.py index ea5cd7f..d5e6bf1 100644 --- a/apps/tesm/src/tesm/__init__.py +++ b/apps/tesm/src/tesm/__init__.py @@ -23,7 +23,20 @@ from .permissions import PERMISSIONS, ROLE_PRESETS from .schema import TESM_MIGRATIONS from .settings_spec import TESM_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") +except (ImportError, PackageNotFoundError): # pragma: no cover + __version__ = "unbekannt" APP_KEY = "tesm" APP_NAME = "TESM" @@ -32,10 +45,12 @@ APP_EDITION = "Geraeteverwaltung" #: Funktionszeile unter der Wortmarke auf der Anmeldeseite. Uebernommen vom #: Vorgaenger, wo sie Teil des Hintergrundmotivs war -- sie sagt in einer Zeile, #: was die Anwendung tut, und das gehoert auf die erste Seite. -AUTH_TAGLINE = ( - "UEBERWACHEN · BOOTEN · ANBINDEN (DHCP) · " - "BETRIEBSSYSTEM GEBEN (PXE)" -) +#: Zeile unter der Wortmarke auf den Anmeldeseiten. Kurz gehalten: sie ist +#: eine Marke, keine Funktionsliste. "(PXE)" stand ausserdem fuer etwas, das +#: es noch nicht gibt -- das Anmeldebanner hat es aus demselben Grund nie +#: gezeigt. Wortgleich in ``deploy/tesm-motd``; ein Hygienetest haelt beides +#: zusammen. +AUTH_TAGLINE = "ÜBERWACHEN · BOOTEN · ANBINDEN" ENV_PREFIX = "TESM_" logger = logging.getLogger(__name__) 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/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"]