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]
|
[project]
|
||||||
name = "tesm"
|
name = "tesm"
|
||||||
version = "2.0.6"
|
version = "2.0.7"
|
||||||
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -23,7 +23,20 @@ from .permissions import PERMISSIONS, ROLE_PRESETS
|
|||||||
from .schema import TESM_MIGRATIONS
|
from .schema import TESM_MIGRATIONS
|
||||||
from .settings_spec import TESM_SETTINGS
|
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_KEY = "tesm"
|
||||||
APP_NAME = "TESM"
|
APP_NAME = "TESM"
|
||||||
@@ -32,10 +45,12 @@ APP_EDITION = "Geraeteverwaltung"
|
|||||||
#: Funktionszeile unter der Wortmarke auf der Anmeldeseite. Uebernommen vom
|
#: Funktionszeile unter der Wortmarke auf der Anmeldeseite. Uebernommen vom
|
||||||
#: Vorgaenger, wo sie Teil des Hintergrundmotivs war -- sie sagt in einer Zeile,
|
#: Vorgaenger, wo sie Teil des Hintergrundmotivs war -- sie sagt in einer Zeile,
|
||||||
#: was die Anwendung tut, und das gehoert auf die erste Seite.
|
#: was die Anwendung tut, und das gehoert auf die erste Seite.
|
||||||
AUTH_TAGLINE = (
|
#: Zeile unter der Wortmarke auf den Anmeldeseiten. Kurz gehalten: sie ist
|
||||||
"UEBERWACHEN · BOOTEN · ANBINDEN (DHCP) · "
|
#: eine Marke, keine Funktionsliste. "(PXE)" stand ausserdem fuer etwas, das
|
||||||
"BETRIEBSSYSTEM GEBEN (PXE)"
|
#: 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_"
|
ENV_PREFIX = "TESM_"
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
+5
-8
@@ -34,15 +34,12 @@ RESET = "\033[0m"
|
|||||||
#: Dieselbe Datei liest die Anwendung (``services/kea.py``: ``LEASE_FILE``).
|
#: Dieselbe Datei liest die Anwendung (``services/kea.py``: ``LEASE_FILE``).
|
||||||
LEASE_FILE = "/var/lib/kea/kea-leases4.csv"
|
LEASE_FILE = "/var/lib/kea/kea-leases4.csv"
|
||||||
|
|
||||||
#: Zeile unter der Wortmarke. Wortlaut wie in der Anwendung
|
#: Zeile unter der Wortmarke. Wortlaut wie in der jeweiligen Anwendung
|
||||||
#: (``apps/tesm/src/tesm/__init__.py``: ``AUTH_TAGLINE``) -- ein Hygienetest
|
#: (``AUTH_TAGLINE`` in deren ``__init__.py``) -- ein Hygienetest haelt beides
|
||||||
#: haelt beides zusammen.
|
#: zusammen.
|
||||||
#:
|
|
||||||
#: Der Lizenzserver hat bewusst keine: die Funktionsliste von TESM waere dort
|
|
||||||
#: falsch, und eine erfundene waere schlimmer als keine.
|
|
||||||
TAGLINE = {
|
TAGLINE = {
|
||||||
"tesm": "UEBERWACHEN · BOOTEN · ANBINDEN (DHCP) · BETRIEBSSYSTEM GEBEN (PXE)",
|
"tesm": "ÜBERWACHEN · BOOTEN · ANBINDEN",
|
||||||
"tesm-license": "",
|
"tesm-license": "LIZENZIEREN",
|
||||||
}
|
}
|
||||||
|
|
||||||
TITEL = {"tesm": "TimEShepManager", "tesm-license": "Lizenzserver"}
|
TITEL = {"tesm": "TimEShepManager", "tesm-license": "Lizenzserver"}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm-core"
|
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"
|
description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -8,7 +8,20 @@ Design-System.
|
|||||||
|
|
||||||
from __future__ import annotations
|
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 .app_factory import create_base_app
|
||||||
from .config import CoreConfig, SecurityConfig, load_core_config
|
from .config import CoreConfig, SecurityConfig, load_core_config
|
||||||
|
|||||||
@@ -130,11 +130,11 @@ def create_base_app(
|
|||||||
import_name: str = "tesm_core",
|
import_name: str = "tesm_core",
|
||||||
app_short: str = "TESM",
|
app_short: str = "TESM",
|
||||||
app_edition: str = "",
|
app_edition: str = "",
|
||||||
#: Zeile unter der Wortmarke auf den Anmeldeseiten. Leer = kein
|
#: Zeile unter der Wortmarke auf den Anmeldeseiten. Leer = nur die
|
||||||
#: Seitenhintergrund. Der Lizenzserver setzt sie bewusst nicht: die
|
#: Wortmarke, ohne Zeile darunter; die Marke selbst zeigen beide
|
||||||
#: Funktionsliste von TESM waere dort schlicht falsch.
|
#: Anwendungen.
|
||||||
auth_tagline: str = "",
|
auth_tagline: str = "",
|
||||||
app_version: str = "2.0.6",
|
app_version: str = "2.0.7",
|
||||||
built_in_trusted_keys: tuple[str, ...] = (),
|
built_in_trusted_keys: tuple[str, ...] = (),
|
||||||
enable_license: bool = True,
|
enable_license: bool = True,
|
||||||
template_folder: str | None = None,
|
template_folder: str | None = None,
|
||||||
|
|||||||
@@ -200,7 +200,12 @@ def _deny(message: str) -> Any:
|
|||||||
abort(401, description="Sitzung abgelaufen.")
|
abort(401, description="Sitzung abgelaufen.")
|
||||||
if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:
|
if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:
|
||||||
abort(401, description="Anmeldung erforderlich.")
|
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"))
|
return redirect(url_for("auth.login"))
|
||||||
abort(403, description=message)
|
abort(403, description=message)
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import sqlite3
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any
|
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 import request as aktuelle_anfrage
|
||||||
from flask.sessions import SessionInterface, SessionMixin
|
from flask.sessions import SessionInterface, SessionMixin
|
||||||
from werkzeug.datastructures import CallbackDict
|
from werkzeug.datastructures import CallbackDict
|
||||||
@@ -209,6 +209,11 @@ class SqliteSessionInterface(SessionInterface):
|
|||||||
"WHERE id=?",
|
"WHERE id=?",
|
||||||
(_iso(now), sid),
|
(_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)
|
return self.session_class(new=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -412,7 +412,10 @@
|
|||||||
und beide sind rot. */
|
und beide sind rot. */
|
||||||
.pill--critical {
|
.pill--critical {
|
||||||
background: var(--danger);
|
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;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
.pill--critical::before { background: currentColor; }
|
.pill--critical::before { background: currentColor; }
|
||||||
@@ -695,7 +698,9 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: min(70dvh, 760px);
|
height: min(70dvh, 760px);
|
||||||
border: 0;
|
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
|
/* Der Dateiname ist ein Knopf (er oeffnet die Vorschau), soll aber wie ein
|
||||||
Link aussehen -- ein Knopf mit Rahmen mitten in einer Tabellenzeile waere
|
Link aussehen -- ein Knopf mit Rahmen mitten in einer Tabellenzeile waere
|
||||||
@@ -849,7 +854,10 @@
|
|||||||
width: 17px;
|
width: 17px;
|
||||||
height: 17px;
|
height: 17px;
|
||||||
border-radius: 5px;
|
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);
|
border: 1px solid var(--border-subtle);
|
||||||
box-shadow: inset 0 1px 2px rgb(0 0 0 / 28%);
|
box-shadow: inset 0 1px 2px rgb(0 0 0 / 28%);
|
||||||
vertical-align: -3px;
|
vertical-align: -3px;
|
||||||
@@ -943,9 +951,15 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
padding: var(--sp-6);
|
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:
|
background:
|
||||||
radial-gradient(1100px 520px at 12% -10%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%),
|
radial-gradient(circle at 15% 15%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 45%),
|
||||||
radial-gradient(900px 480px at 100% 110%, color-mix(in srgb, var(--info) 10%, transparent), transparent 70%),
|
radial-gradient(circle at 85% 85%, color-mix(in srgb, var(--accent) 8%, transparent), transparent 40%),
|
||||||
var(--canvas);
|
var(--canvas);
|
||||||
}
|
}
|
||||||
/* Die Wortmarke liegt hinter allem (z-index 0), die Anmeldebox davor.
|
/* Die Wortmarke liegt hinter allem (z-index 0), die Anmeldebox davor.
|
||||||
@@ -1026,24 +1040,35 @@
|
|||||||
color: var(--text-faint);
|
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 {
|
.auth-backdrop {
|
||||||
position: absolute;
|
|
||||||
top: 4vh;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: min(72vw, 880px);
|
width: min(72vw, 880px);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 0;
|
/* Volle Deckung: solange die Marke hinter der Karte lag, war sie ein
|
||||||
opacity: 0.5;
|
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; }
|
.auth-backdrop svg { width: 100%; height: auto; display: block; }
|
||||||
@media (max-width: 640px) {
|
@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 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) {
|
@media (prefers-contrast: more) {
|
||||||
.auth-backdrop { opacity: 0.18; }
|
.auth-backdrop { opacity: 0.75; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-card {
|
.auth-card {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
{% extends "tesm_core/standalone.html" %}
|
{% 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 heading %}Anmelden{% endblock %}
|
||||||
{% block subheading %}{{ app_name }} – {{ app_edition }}{% endblock %}
|
{% block brand %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post" class="stack" data-no-guard autocomplete="on">
|
<form method="post" class="stack" data-no-guard autocomplete="on">
|
||||||
|
|||||||
@@ -22,37 +22,51 @@
|
|||||||
</head>
|
</head>
|
||||||
<body data-csrf="{{ csrf_token() }}">
|
<body data-csrf="{{ csrf_token() }}">
|
||||||
<div class="auth-page">
|
<div class="auth-page">
|
||||||
{% if auth_tagline %}
|
<div class="auth-stack">
|
||||||
{# Wortmarke mit Funktionszeile als tatsächlicher Seitenhintergrund --
|
{# Wortmarke als Seitenhintergrund -- fuer beide Anwendungen. Bewusst inline
|
||||||
übernommen vom Vorgänger. Bewusst inline und mit Themen-Tokens statt
|
und mit Themen-Tokens statt zweier Dateien fuer hell und dunkel: bei der
|
||||||
zweier Dateien für hell und dunkel: bei der Einstellung "System" und
|
Einstellung "System" und beim Umschalten von Hand waere die versteckte
|
||||||
beim Umschalten von Hand wäre die versteckte Variante die falsche.
|
Variante die falsche. ``aria-hidden``, weil der Name in der Titelzeile
|
||||||
``aria-hidden``, weil der Name schon in der Überschrift steht. #}
|
steht.
|
||||||
<div class="auth-backdrop" aria-hidden="true">
|
|
||||||
<svg viewBox="0 0 620 190" xmlns="http://www.w3.org/2000/svg">
|
Die Geometrie ist symmetrisch um die Mitte (x=260) aufgebaut, damit die
|
||||||
<g transform="translate(150,15)">
|
Marke ohne Nachrechnen mittig sitzt: der Schriftzug haengt an
|
||||||
<circle cx="298" cy="70" r="15" fill="var(--logo-mark)" opacity="0.22"/>
|
``text-anchor="middle"``, die beiden Zacken liegen spiegelbildlich, und
|
||||||
<polyline points="0,70 90,70 100,70 108,20 116,120 124,70 134,70 200,70 210,70
|
die Linie beginnt links so weit vor der Mitte, wie der Punkt rechts nach
|
||||||
218,35 226,105 234,70 244,70 300,70"
|
ihr endet. Wer die Linie verlaengert, muss beide Enden anfassen.
|
||||||
fill="none" stroke="var(--logo-mark)" stroke-width="3.2"
|
|
||||||
stroke-linecap="round" stroke-linejoin="round" opacity="0.85"/>
|
Ohne Funktionszeile (Lizenzserver) ist der Kasten kuerzer -- sonst
|
||||||
<text x="14" y="98" font-family="var(--font-mono)" font-weight="800"
|
stuende unter der Marke ein leerer Streifen. #}
|
||||||
font-size="82" letter-spacing="2" fill="var(--logo-text)">TESM</text>
|
<div class="auth-backdrop" aria-hidden="true">
|
||||||
<circle cx="298" cy="70" r="8" fill="var(--logo-mark)"/>
|
<svg viewBox="0 0 520 {{ 150 if auth_tagline else 116 }}" xmlns="http://www.w3.org/2000/svg">
|
||||||
</g>
|
<circle cx="452" cy="62" r="15" fill="var(--logo-mark)" opacity="0.22"/>
|
||||||
<text x="310" y="178" text-anchor="middle" font-family="var(--font-sans)"
|
<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"
|
font-weight="600" font-size="13" letter-spacing="1.5"
|
||||||
fill="var(--logo-mark)">{{ auth_tagline }}</text>
|
fill="var(--logo-mark)">{{ auth_tagline }}</text>
|
||||||
</svg>
|
{% endif %}
|
||||||
</div>
|
</svg>
|
||||||
{% endif %}
|
</div>
|
||||||
<div class="auth-card {% block card_class %}{% endblock %}">
|
<div class="auth-card {% block card_class %}{% endblock %}">
|
||||||
<div class="auth-brand">
|
{# Die Anmeldeseite laesst diesen Kopf weg: die Wortmarke darueber sagt
|
||||||
<span class="auth-brand__mark" aria-hidden="true">{{ app_short }}</span>
|
schon, wo man ist, und "Anmelden" steht auf dem Knopf. Die uebrigen
|
||||||
<h1>{% block heading %}{{ app_name }}{% endblock %}</h1>
|
Standalone-Seiten behalten ihn -- "Zweiter Faktor" oder der Kundenname
|
||||||
{% set sub %}{% block subheading %}{% endblock %}{% endset %}
|
im Portal sind dort die Auskunft, um die es geht. #}
|
||||||
{% if sub | trim %}<p>{{ sub }}</p>{% endif %}
|
{% block brand %}
|
||||||
</div>
|
<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) %}
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
@@ -72,6 +86,7 @@
|
|||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
<p class="auth-footnote">{{ app_name }} {{ app_version }} · © {{ current_year }} {{ copyright_notice }}</p>
|
<p class="auth-footnote">{{ app_name }} {{ app_version }} · © {{ current_year }} {{ copyright_notice }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="{{ asset_url('js/app.js') }}" nonce="{{ csp_nonce }}"></script>
|
<script type="module" src="{{ asset_url('js/app.js') }}" nonce="{{ csp_nonce }}"></script>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "tesm-licensing"
|
name = "tesm-licensing"
|
||||||
version = "2.0.6"
|
version = "2.0.7"
|
||||||
description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver"
|
description = "Ed25519-basiertes Lizenz- und Aktivierungsprotokoll fuer TESM und TESM-Lizenzserver"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = ["cryptography>=42"]
|
dependencies = ["cryptography>=42"]
|
||||||
|
|||||||
Reference in New Issue
Block a user