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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 %}
|
||||
<form method="post" class="stack" data-no-guard autocomplete="on">
|
||||
|
||||
@@ -22,37 +22,51 @@
|
||||
</head>
|
||||
<body data-csrf="{{ csrf_token() }}">
|
||||
<div class="auth-page">
|
||||
{% 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. #}
|
||||
<div class="auth-backdrop" aria-hidden="true">
|
||||
<svg viewBox="0 0 620 190" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(150,15)">
|
||||
<circle cx="298" cy="70" r="15" fill="var(--logo-mark)" opacity="0.22"/>
|
||||
<polyline points="0,70 90,70 100,70 108,20 116,120 124,70 134,70 200,70 210,70
|
||||
218,35 226,105 234,70 244,70 300,70"
|
||||
fill="none" stroke="var(--logo-mark)" stroke-width="3.2"
|
||||
stroke-linecap="round" stroke-linejoin="round" opacity="0.85"/>
|
||||
<text x="14" y="98" font-family="var(--font-mono)" font-weight="800"
|
||||
font-size="82" letter-spacing="2" fill="var(--logo-text)">TESM</text>
|
||||
<circle cx="298" cy="70" r="8" fill="var(--logo-mark)"/>
|
||||
</g>
|
||||
<text x="310" y="178" text-anchor="middle" font-family="var(--font-sans)"
|
||||
<div class="auth-stack">
|
||||
{# Wortmarke als Seitenhintergrund -- fuer beide Anwendungen. Bewusst inline
|
||||
und mit Themen-Tokens statt zweier Dateien fuer hell und dunkel: bei der
|
||||
Einstellung "System" und beim Umschalten von Hand waere die versteckte
|
||||
Variante die falsche. ``aria-hidden``, weil der Name in der Titelzeile
|
||||
steht.
|
||||
|
||||
Die Geometrie ist symmetrisch um die Mitte (x=260) aufgebaut, damit die
|
||||
Marke ohne Nachrechnen mittig sitzt: der Schriftzug haengt an
|
||||
``text-anchor="middle"``, die beiden Zacken liegen spiegelbildlich, und
|
||||
die Linie beginnt links so weit vor der Mitte, wie der Punkt rechts nach
|
||||
ihr endet. Wer die Linie verlaengert, muss beide Enden anfassen.
|
||||
|
||||
Ohne Funktionszeile (Lizenzserver) ist der Kasten kuerzer -- sonst
|
||||
stuende unter der Marke ein leerer Streifen. #}
|
||||
<div class="auth-backdrop" aria-hidden="true">
|
||||
<svg viewBox="0 0 520 {{ 150 if auth_tagline else 116 }}" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="452" cy="62" r="15" fill="var(--logo-mark)" opacity="0.22"/>
|
||||
<polyline points="60,62 163,62 171,12 179,112 187,62 333,62 341,12 349,112 357,62 452,62"
|
||||
fill="none" stroke="var(--logo-mark)" stroke-width="3.2"
|
||||
stroke-linecap="round" stroke-linejoin="round" opacity="0.85"/>
|
||||
<text x="260" y="90" text-anchor="middle" font-family="var(--font-mono)"
|
||||
font-weight="800" font-size="82" letter-spacing="2"
|
||||
fill="var(--logo-text)">TESM</text>
|
||||
<circle cx="452" cy="62" r="8" fill="var(--logo-mark)"/>
|
||||
{% if auth_tagline %}
|
||||
<text x="260" y="140" text-anchor="middle" font-family="var(--font-sans)"
|
||||
font-weight="600" font-size="13" letter-spacing="1.5"
|
||||
fill="var(--logo-mark)">{{ auth_tagline }}</text>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="auth-card {% block card_class %}{% endblock %}">
|
||||
<div class="auth-brand">
|
||||
<span class="auth-brand__mark" aria-hidden="true">{{ app_short }}</span>
|
||||
<h1>{% block heading %}{{ app_name }}{% endblock %}</h1>
|
||||
{% set sub %}{% block subheading %}{% endblock %}{% endset %}
|
||||
{% if sub | trim %}<p>{{ sub }}</p>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</svg>
|
||||
</div>
|
||||
<div class="auth-card {% block card_class %}{% endblock %}">
|
||||
{# Die Anmeldeseite laesst diesen Kopf weg: die Wortmarke darueber sagt
|
||||
schon, wo man ist, und "Anmelden" steht auf dem Knopf. Die uebrigen
|
||||
Standalone-Seiten behalten ihn -- "Zweiter Faktor" oder der Kundenname
|
||||
im Portal sind dort die Auskunft, um die es geht. #}
|
||||
{% block brand %}
|
||||
<div class="auth-brand">
|
||||
<span class="auth-brand__mark" aria-hidden="true">{{ app_short }}</span>
|
||||
<h1>{% block heading %}{{ app_name }}{% endblock %}</h1>
|
||||
{% set sub %}{% block subheading %}{% endblock %}{% endset %}
|
||||
{% if sub | trim %}<p>{{ sub }}</p>{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
@@ -72,6 +86,7 @@
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<p class="auth-footnote">{{ app_name }} {{ app_version }} · © {{ current_year }} {{ copyright_notice }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="{{ asset_url('js/app.js') }}" nonce="{{ csp_nonce }}"></script>
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user