Umlaute in der Oberflaeche, Sitzungskopplung, Statusanzeigen

Echte Umlaute in allen Texten der Oberflaeche und in den Markdown-Dateien.
Die Einbindung von Dateifreigaben haengt jetzt an der Sitzung: wird sie
widerrufen oder laeuft sie ab, verschwinden die Freigaben mit ihr. Neu
einstellbar ist die automatische Abmeldung bei Leerlauf (Vorgabe 30 Minuten).
Ein Geraet, das nicht antwortet, bietet nur noch "Einschalten (PoE)" an --
Neustart ueber SSH oder RPC braucht ein erreichbares Geraet. Der Zeitgeber der
naechsten Pruefung und die Restlaufzeit der Lizenz stehen jetzt oben in der
Kopfzeile; der doppelte Knopf darunter ist weg. Millisekunden werden ab einer
Sekunde als Sekunden und ab einer Minute als m:ss dargestellt.

Ausserdem: Release-Tarball nicht mehr im Repository -- er haengt am Release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 23:54:35 +02:00
co-authored by Claude Opus 5
parent 4e488625fe
commit ab5fda658e
63 changed files with 746 additions and 507 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tesm-license"
version = "2.0.0"
version = "2.0.2"
description = "TESM-Lizenzserver -- Kunden, Tickets, Lizenzen, Aktivierung"
requires-python = ">=3.11"
dependencies = [
@@ -23,7 +23,7 @@ from .permissions import PERMISSIONS, ROLE_PRESETS
from .schema import LICENSE_MIGRATIONS
from .settings_spec import LICENSE_SERVER_SETTINGS
__version__ = "2.0.0"
__version__ = "2.0.2"
APP_KEY = "tesm-license"
APP_NAME = "TESM-Lizenzserver"
@@ -86,7 +86,7 @@ NAVIGATION = NavTree(
),
NavItem(
key="logs_audit",
label="Aenderungen",
label="Audit",
icon="history",
endpoint="logs.audit_view",
permissions=("logs_audit.view", "logs_audit.export"),
@@ -1,26 +1,26 @@
{% extends "tesm_core/base.html" %}
{% from "tesm_core/macros.html" import field, alert, pill %}
{% block page_title %}Sicherung und Uebertragung{% endblock %}
{% block page_sub %}Verschluesselter Export &middot; zweistufiger Import{% endblock %}
{% block page_title %}Sicherung und Übertragung{% endblock %}
{% block page_sub %}Verschlüsselter Export &middot; zweistufiger Import{% endblock %}
{% block content %}
{{ alert('Der Export wird mit Argon2id aus Ihrer Passphrase verschluesselt (AES-256-GCM). Ohne
diese Passphrase ist die Datei nicht wiederherstellbar -- es gibt keine Hintertuer.', 'warning',
{{ alert('Der Export wird mit Argon2id aus Ihrer Passphrase verschlüsselt (AES-256-GCM). Ohne
diese Passphrase ist die Datei nicht wiederherstellbar -- es gibt keine Hintertür.', 'warning',
'Passphrase') }}
{{ alert('Der Signaturschluessel (' ~ signing_key_path ~ ') ist bewusst
nicht Teil dieser Sicherung. Er ist der Kern des Vertrauens und gehoert in eine
{{ alert('Der Signaturschlüssel (' ~ signing_key_path ~ ') ist bewusst
nicht Teil dieser Sicherung. Er ist der Kern des Vertrauens und gehört in eine
getrennte, bewusst verwahrte Sicherung -- nicht in dieselbe Datei wie die Kundendaten. Ohne ihn
ist nach einer Wiederherstellung keine ausgestellte Lizenz mehr pruefbar.', 'danger',
'Wichtig: Schluessel getrennt sichern') }}
ist nach einer Wiederherstellung keine ausgestellte Lizenz mehr prüfbar.', 'danger',
'Wichtig: Schlüssel getrennt sichern') }}
<div class="grid grid--split">
<section class="card">
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('download', size=17) }} Sicherung erstellen</div>
<div class="card__sub">Verlangt eine frische Passwortbestaetigung</div>
<div class="card__sub">Verlangt eine frische Passwortbestätigung</div>
</div>
</div>
<form method="post" action="{{ url_for('backup.export') }}" data-no-guard>
@@ -40,7 +40,7 @@ ist nach einer Wiederherstellung keine ausgestellte Lizenz mehr pruefbar.', 'dan
{% if category.admin_only %}{{ pill('nur Admin', 'accent') }}{% endif %}
<div class="fs-xs faint">{{ category.hint }}</div>
{% if not allowed %}
<div class="fs-xs danger-text">Dafuer fehlt Ihnen das Recht.</div>
<div class="fs-xs danger-text">Dafür fehlt Ihnen das Recht.</div>
{% endif %}
</span>
</label>
@@ -66,8 +66,8 @@ ist nach einer Wiederherstellung keine ausgestellte Lizenz mehr pruefbar.', 'dan
<section class="card card--accent">
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('upload', size=17) }} Geoeffnete Sicherung</div>
<div class="card__sub">Gueltig fuer {{ preview_ttl }} Minuten</div>
<div class="card__title">{{ icon('upload', size=17) }} Geöffnete Sicherung</div>
<div class="card__sub">Gültig für {{ preview_ttl }} Minuten</div>
</div>
</div>
<form method="post" action="{{ url_for('backup.import_apply') }}">
@@ -75,7 +75,7 @@ ist nach einer Wiederherstellung keine ausgestellte Lizenz mehr pruefbar.', 'dan
<div class="card__body">
<div class="table-wrap">
<table class="table table--compact">
<thead><tr><th></th><th>Kategorie</th><th class="numeric">Eintraege</th></tr></thead>
<thead><tr><th></th><th>Kategorie</th><th class="numeric">Einträge</th></tr></thead>
<tbody>
{% for entry in preview.summary %}
<tr>
@@ -89,17 +89,17 @@ ist nach einer Wiederherstellung keine ausgestellte Lizenz mehr pruefbar.', 'dan
</tbody>
</table>
</div>
{{ alert('Bereits vorhandene Lizenzen werden nie ueberschrieben -- ihr Inhalt ist
kryptographisch festgelegt. Kunden werden ueber ihren Namen zusammengefuehrt.',
{{ alert('Bereits vorhandene Lizenzen werden nie überschrieben -- ihr Inhalt ist
kryptographisch festgelegt. Kunden werden über ihren Namen zusammengeführt.',
'info') }}
</div>
<div class="card__footer modal__footer--split">
<button class="btn btn--sm btn--ghost" type="submit"
formaction="{{ url_for('backup.import_discard') }}">Verwerfen</button>
<button class="btn btn--primary" type="submit"
data-confirm="Ausgewaehlte Kategorien jetzt uebernehmen?"
data-confirm-title="Sicherung uebernehmen" data-confirm-ok="Uebernehmen"
data-confirm-style="primary">Uebernehmen</button>
data-confirm="Ausgewählte Kategorien jetzt übernehmen?"
data-confirm-title="Sicherung übernehmen" data-confirm-ok="Übernehmen"
data-confirm-style="primary">Übernehmen</button>
</div>
</form>
</section>
@@ -108,7 +108,7 @@ ist nach einer Wiederherstellung keine ausgestellte Lizenz mehr pruefbar.', 'dan
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('upload', size=17) }} Sicherung einspielen</div>
<div class="card__sub">Erst oeffnen und pruefen, dann uebernehmen</div>
<div class="card__sub">Erst öffnen und prüfen, dann übernehmen</div>
</div>
</div>
<form method="post" enctype="multipart/form-data" data-no-guard
@@ -120,8 +120,8 @@ ist nach einer Wiederherstellung keine ausgestellte Lizenz mehr pruefbar.', 'dan
autocomplete='off') }}
</div>
<div class="card__footer">
<button class="btn btn--primary" type="submit" data-busy-label="Oeffne ...">
Datei oeffnen
<button class="btn btn--primary" type="submit" data-busy-label="Öffne ...">
Datei öffnen
</button>
</div>
</form>
@@ -70,7 +70,7 @@
{% if licenses %}
<div class="table-wrap">
<table class="table table--compact">
<thead><tr><th>Lizenz</th><th>Typ</th><th>Zustand</th><th>Laeuft ab</th><th>Host</th></tr></thead>
<thead><tr><th>Lizenz</th><th>Typ</th><th>Zustand</th><th>Läuft ab</th><th>Host</th></tr></thead>
<tbody>
{% for entry in licenses %}
<tr>
@@ -119,7 +119,7 @@
</div>
<div class="card__body stack">
{{ alert('Diese Daten verwaltet der Sub-Lizenzserver selbst. Sie sind hier rein
informativ und werden bei jedem Bericht vollstaendig ersetzt.', 'info') }}
informativ und werden bei jedem Bericht vollständig ersetzt.', 'info') }}
{% for entry in reported.customers %}
<details class="card">
<summary class="card__header" style="cursor:pointer">
@@ -193,7 +193,7 @@
<div class="divider"></div>
<dl class="kv fs-xs">
<dt>Angelegt</dt><dd>{{ customer.created_at | dt }} von {{ customer.created_by or '-' }}</dd>
<dt>Geaendert</dt><dd>{{ customer.updated_at | dt }}</dd>
<dt>Geändert</dt><dd>{{ customer.updated_at | dt }}</dd>
<dt>Portal-Zugang</dt>
<dd><span class="mono break-all">{{ portal_url }}</span>
<button class="btn btn--sm btn--ghost" type="button" data-copy="{{ portal_url }}">
@@ -205,9 +205,9 @@
{% if current_user.has('customers.delete') %}
<button class="btn btn--sm btn--ghost danger-text" type="submit"
formaction="{{ url_for('customers.delete', customer_id=customer.id) }}"
data-confirm="Kunde {{ customer.name }} loeschen? Das ist nur moeglich, solange kein Ticket mehr an ihm haengt."
data-confirm-title="Kunde loeschen" data-confirm-ok="Loeschen">
Loeschen
data-confirm="Kunde {{ customer.name }} löschen? Das ist nur möglich, solange kein Ticket mehr an ihm hängt."
data-confirm-title="Kunde löschen" data-confirm-ok="Löschen">
Löschen
</button>
{% else %}<span></span>{% endif %}
<button class="btn btn--primary" type="submit">Speichern</button>
@@ -7,7 +7,7 @@
{{ field('name', 'Firmenname', value=customer.name if customer else '', required=True) }}
{{ field('contact_email', 'E-Mail', value=customer.contact_email if customer else '',
type='email', required=not customer,
hint_text='Pflicht bei der Neuanlage -- ueber diese Adresse laeuft das Kundenportal.') }}
hint_text='Pflicht bei der Neuanlage -- über diese Adresse läuft das Kundenportal.') }}
{{ field('contact_person', 'Ansprechpartner', value=customer.contact_person if customer else '') }}
{{ field('contact_phone', 'Telefon', value=customer.contact_phone if customer else '') }}
{{ field('street', 'Strasse', value=customer.street if customer else '') }}
@@ -72,7 +72,7 @@
{% block dialogs %}
{% if current_user.has('customers.create') %}
{% call modal('customer-new', 'Kunde anlegen', 'Adresse in Einzelfeldern -- fuer Rechnungen weiterverwendbar', 'wide') %}
{% call modal('customer-new', 'Kunde anlegen', 'Adresse in Einzelfeldern -- für Rechnungen weiterverwendbar', 'wide') %}
<form method="post" action="{{ url_for('customers.create') }}">
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<div class="modal__body">{{ customer_form() }}</div>
@@ -1,7 +1,7 @@
{% extends "tesm_core/base.html" %}
{% from "tesm_core/macros.html" import pill, empty_state, alert, stat %}
{% block page_title %}Uebersicht{% endblock %}
{% block page_title %}Übersicht{% endblock %}
{% block page_sub %}
{% if summary %}
{{ summary.counts.customers }} Kunden &middot; {{ summary.counts.licenses_active }} aktive Lizenzen
@@ -24,13 +24,13 @@
</div>
{% if summary.counts.tickets_blocked %}
{{ alert(summary.counts.tickets_blocked ~ ' Ticket(s) sind gesperrt -- fuer diese Kunden
{{ alert(summary.counts.tickets_blocked ~ ' Ticket(s) sind gesperrt -- für diese Kunden
lassen sich keine neuen Lizenzen ausstellen.', 'warning') }}
{% endif %}
{% for key, label, kind, hint in [
('red', 'Kritisch', 'danger', 'Weniger als 30 Tage Restlaufzeit oder bereits abgelaufen'),
('orange', 'Bald faellig', 'warning', '30 bis 90 Tage Restlaufzeit'),
('orange', 'Bald fällig', 'warning', '30 bis 90 Tage Restlaufzeit'),
('green', 'In Ordnung', 'ok', 'Mehr als 90 Tage oder unbefristet')] %}
{% set entries = summary.buckets[key] %}
{% if entries %}
@@ -26,13 +26,13 @@
{% block content %}
{% if state.revision_pending %}
{{ alert('Es wurde eine neue Revision signiert, die der Kunde noch nicht abgeholt hat.
Eine erreichbare Installation holt sie beim naechsten Heartbeat; eine Installation ohne
Verbindung braucht dafuer den Offline-Weg -- der Kunde findet ihn in seinem Portal.',
Eine erreichbare Installation holt sie beim nächsten Heartbeat; eine Installation ohne
Verbindung braucht dafür den Offline-Weg -- der Kunde findet ihn in seinem Portal.',
'warning', 'Revision ausstehend') }}
{% endif %}
{% if license.status == 'revoked' %}
{{ alert('Diese Lizenz ist widerrufen. Der Client erfaehrt das beim naechsten Heartbeat und
{{ alert('Diese Lizenz ist widerrufen. Der Client erfährt das beim nächsten Heartbeat und
schaltet seine lizenzpflichtigen Funktionen ab.' ~ ((' Grund: ' ~ license.revoke_reason) if
license.revoke_reason else ''), 'danger', 'Widerrufen') }}
{% endif %}
@@ -72,7 +72,7 @@
{% else %}<span class="faint">keine</span>{% endif %}
</dd>
<dt>Ausgestellt</dt><dd>{{ license.issued_at | dt }} von {{ license.created_by or '-' }}</dd>
<dt>Laeuft ab</dt>
<dt>Läuft ab</dt>
<dd>
{% if license.lifetime %}{{ pill('unbefristet', 'online') }}
{% else %}
@@ -105,9 +105,9 @@
<section class="card">
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('refresh', size=17) }} Nachtraeglich anpassen</div>
<div class="card__title">{{ icon('refresh', size=17) }} Nachträglich anpassen</div>
<div class="card__sub">
Signiert eine neue Revision. Der Client erhaelt sie beim naechsten Heartbeat --
Signiert eine neue Revision. Der Client erhält sie beim nächsten Heartbeat --
ohne erneute Aktivierung.
</div>
</div>
@@ -126,9 +126,9 @@
</div>
</fieldset>
{% endif %}
{{ field('extend_days', 'Laufzeit verlaengern (Tage)', type='number',
{{ field('extend_days', 'Laufzeit verlängern (Tage)', type='number',
placeholder='0',
hint_text='Wird auf das aktuelle Ablaufdatum aufgeschlagen.') }}
hint_text='Wird auf das aktülle Ablaufdatum aufgeschlagen.') }}
</div>
<div class="card__footer">
<button class="btn btn--primary" type="submit">Neue Revision signieren</button>
@@ -141,13 +141,13 @@
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('transfer', size=17) }} Zustellungen</div>
<div class="card__sub">{{ deliveries | length }} Vorgaenge</div>
<div class="card__sub">{{ deliveries | length }} Vorgänge</div>
</div>
</div>
{% if deliveries %}
<div class="table-wrap">
<table class="table table--compact">
<thead><tr><th>Zeit</th><th>Weg</th><th>Empfaenger</th><th>Durch</th><th>Detail</th></tr></thead>
<thead><tr><th>Zeit</th><th>Weg</th><th>Empfänger</th><th>Durch</th><th>Detail</th></tr></thead>
<tbody>
{% for entry in deliveries %}
<tr>
@@ -170,7 +170,7 @@
<summary class="card__header" style="cursor:pointer">
<div class="flex-1">
<div class="card__title">{{ icon('file-text', size=17) }} Signiertes Dokument</div>
<div class="card__sub">Genau so, wie es signiert wurde -- jede Aenderung bricht die Signatur</div>
<div class="card__sub">Genau so, wie es signiert wurde -- jede Änderung bricht die Signatur</div>
</div>
{{ icon('chevron-down', size=16) }}
</summary>
@@ -185,7 +185,7 @@
<div class="flex-1">
<div class="card__title">{{ icon('mail', size=17) }} Per E-Mail versenden</div>
<div class="card__sub">
{% if mail_ready %}Ueber Microsoft Graph{% else %}Nicht konfiguriert{% endif %}
{% if mail_ready %}Über Microsoft Graph{% else %}Nicht konfiguriert{% endif %}
</div>
</div>
</div>
@@ -193,7 +193,7 @@
data-no-guard>
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<div class="card__body">
{{ field('recipient', 'Empfaenger', value=license.contact_email, type='email',
{{ field('recipient', 'Empfänger', value=license.contact_email, type='email',
disabled=not mail_ready) }}
</div>
<div class="card__footer">
@@ -215,7 +215,7 @@
data-dialog-open="revoke-dialog">Widerrufen</button>
<p class="fs-xs faint">
Das Ticket bleibt offen -- eine neue Lizenz kann sofort ausgestellt werden. Genau so
laeuft ein Systemwechsel.
läuft ein Systemwechsel.
</p>
{% endif %}
{% if current_user.has('licenses.delete') %}
@@ -225,9 +225,9 @@
<button class="btn btn--sm btn--ghost danger-text btn--block" type="submit"
{% if license.status not in ('revoked', 'issued') %}disabled
title="Eine aktive Lizenz muss erst widerrufen werden"{% endif %}
data-confirm="Lizenz endgueltig aus der Datenbank entfernen? Die Historie geht verloren."
data-confirm-title="Lizenz loeschen" data-confirm-ok="Loeschen">
Endgueltig loeschen
data-confirm="Lizenz endgültig aus der Datenbank entfernen? Die Historie geht verloren."
data-confirm-title="Lizenz löschen" data-confirm-ok="Löschen">
Endgültig löschen
</button>
</form>
{% endif %}
@@ -244,7 +244,7 @@
<form method="post" action="{{ url_for('licenses.revoke', license_id=license.license_id) }}">
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<div class="modal__body">
{{ alert('Der Client erfaehrt den Widerruf beim naechsten Heartbeat und schaltet
{{ alert('Der Client erfährt den Widerruf beim nächsten Heartbeat und schaltet
lizenzpflichtige Funktionen ab. Das Ticket bleibt offen.', 'warning') }}
{{ field('reason', 'Grund (erscheint im Protokoll)', placeholder='Systemwechsel') }}
</div>
@@ -2,12 +2,12 @@
{% from "tesm_core/macros.html" import alert %}
{% block page_title %}Offline-Aktivierung{% endblock %}
{% block page_sub %}Code des Kunden verarbeiten und Antwortcode zurueckgeben{% endblock %}
{% block page_sub %}Code des Kunden verarbeiten und Antwortcode zurückgeben{% endblock %}
{% block content %}
{{ alert('Beide Codes sind signierte Protokollobjekte -- inhaltlich identisch mit dem
Online-Weg, nur als kopierbarer Text. Die Pruefungen (Signatur, Frische, Nonce, Fingerprint)
laufen exakt gleich; es gibt hier keine Abkuerzung.', 'info') }}
Online-Weg, nur als kopierbarer Text. Die Prüfungen (Signatur, Frische, Nonce, Fingerprint)
laufen exakt gleich; es gibt hier keine Abkürzung.', 'info') }}
<div class="grid grid--split">
<section class="card">
@@ -20,8 +20,8 @@ laufen exakt gleich; es gibt hier keine Abkuerzung.', 'info') }}
<label class="field">
<span class="field__label">Anfragecode</span>
<textarea class="textarea input--mono" name="code" rows="8" required
placeholder="Vom Kunden erhaltenen Code einfuegen ..."></textarea>
<span class="field__hint">Umbrueche und Leerzeichen sind erlaubt.</span>
placeholder="Vom Kunden erhaltenen Code einfügen ..."></textarea>
<span class="field__hint">Umbrüche und Leerzeichen sind erlaubt.</span>
</label>
</div>
<div class="card__footer">
@@ -5,14 +5,14 @@
{% block page_sub %}
{% if state and state.valid and state.activated %}
aktiv &middot; Rolle {{ role_labels.get(state.role, state.role) }}
{% elif state and state.valid %}gueltig, nicht aktiviert
{% elif state and state.present %}ungueltig
{% elif state and state.valid %}gültig, nicht aktiviert
{% elif state and state.present %}ungültig
{% else %}keine Lizenz installiert{% endif %}
{% endblock %}
{% block topbar_extra %}
<a class="btn btn--sm" href="{{ url_for('own_license.signing_key_info') }}">
{{ icon('key', size=15) }} Signaturschluessel
{{ icon('key', size=15) }} Signaturschlüssel
</a>
{% endblock %}
@@ -23,14 +23,14 @@
{% if state and state.error %}{{ alert(state.error, 'danger') }}{% endif %}
{% if state and state.role == 'master' %}
{{ alert('Diese Instanz ist die Wurzelinstanz. Nur sie darf Lizenzen fuer
weitere Lizenzserver ausstellen -- diese Grenze haengt an der signierten Rolle dieser
{{ alert('Diese Instanz ist die Wurzelinstanz. Nur sie darf Lizenzen für
weitere Lizenzserver ausstellen -- diese Grenze hängt an der signierten Rolle dieser
Lizenzdatei, nicht an einem Gruppenrecht. Ein lokaler Administrator eines Sub-Lizenzservers
kann sie sich damit nicht selbst einraeumen.', 'info', 'Rolle: Master') }}
kann sie sich damit nicht selbst einräumen.', 'info', 'Rolle: Master') }}
{% elif state and state.role == 'license_server' %}
{{ alert('Diese Instanz ist ein Sub-Lizenzserver. Sie darf eigene Kunden,
Tickets und Lizenzen verwalten -- aber keine weiteren Lizenzserver-Lizenzen ausstellen. Der
eigene Bestand wird automatisch an den uebergeordneten Master gemeldet.', 'info',
eigene Bestand wird automatisch an den übergeordneten Master gemeldet.', 'info',
'Rolle: Lizenzserver') }}
{% endif %}
@@ -57,7 +57,7 @@
<dt>Inhaber</dt><dd>{{ state.customer_name or '&ndash;' | safe }}</dd>
<dt>Lizenz-ID</dt><dd class="mono fs-xs break-all">{{ state.status.license_id }}</dd>
<dt>Revision</dt><dd>{{ state.status.seq }}</dd>
<dt>Laeuft ab</dt>
<dt>Läuft ab</dt>
<dd>
{% if state.status.lifetime %}{{ pill('unbefristet', 'online') }}
{% else %}
@@ -66,7 +66,7 @@
{{ pill('abgelaufen', 'danger') }}
{% if state.status.grace_active %}
<div class="fs-xs warning-text">
Kulanzfrist laeuft ({{ grace_days }} Tage).
Kulanzfrist läuft ({{ grace_days }} Tage).
</div>
{% endif %}
{% elif state.status.expiring_soon %}
@@ -76,7 +76,7 @@
{% endif %}
{% endif %}
</dd>
<dt>Uebergeordneter Server</dt>
<dt>Übergeordneter Server</dt>
<dd class="break-all">
{{ state.status.master_endpoint or 'keiner (Wurzelinstanz oder Offline-Betrieb)' }}
</dd>
@@ -116,7 +116,7 @@
{% endif %}
</section>
{% else %}
{{ empty_state('Keine gueltige Lizenz',
{{ empty_state('Keine gültige Lizenz',
'Spielen Sie eine Lizenzdatei ein oder erzeugen Sie mit "tesm-license-admin bootstrap"
die eigene Lizenz der Wurzelinstanz.', 'key') }}
{% endif %}
@@ -133,12 +133,12 @@
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<div class="card__body">
<label class="field">
<span class="field__label">Antwortcode des uebergeordneten Servers</span>
<span class="field__label">Antwortcode des übergeordneten Servers</span>
<textarea class="textarea input--mono" name="code" rows="4" required></textarea>
</label>
</div>
<div class="card__footer">
<button class="btn btn--primary" type="submit">Antwort uebernehmen</button>
<button class="btn btn--primary" type="submit">Antwort übernehmen</button>
</div>
</form>
</section>
@@ -150,7 +150,7 @@
<div class="flex-1">
<div class="card__title">{{ icon('upload', size=17) }} Lizenzdatei einspielen</div>
<div class="card__sub">
Fuer einen Sub-Lizenzserver: die Datei, die der Master ausgestellt hat
Für einen Sub-Lizenzserver: die Datei, die der Master ausgestellt hat
</div>
</div>
</div>
@@ -160,7 +160,7 @@
<div class="card__body stack">
<input class="input" type="file" name="license_file" accept=".json,.lic" required>
{% if trusted_keys and current_user.is_admin %}
{{ checkbox('allow_new_issuer', 'Wechsel des Ausstellers ausdruecklich zulassen') }}
{{ checkbox('allow_new_issuer', 'Wechsel des Ausstellers ausdrücklich zulassen') }}
{% endif %}
</div>
<div class="card__footer">
@@ -182,7 +182,7 @@
<dl class="kv fs-sm">
<dt>Hostname</dt><dd class="mono">{{ hostname }}</dd>
<dt>Fingerprint</dt><dd class="mono fs-xs break-all">{{ fingerprint }}</dd>
<dt>Signaturschluessel</dt>
<dt>Signaturschlüssel</dt>
<dd class="mono">{{ signing_key_health.get('key_id') or 'fehlt' }}</dd>
</dl>
</div>
@@ -191,8 +191,8 @@
<section class="card">
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('shield', size=17) }} Vertrauenswuerdige Aussteller</div>
<div class="card__sub">{{ trusted_keys | length }} gepinnte Schluessel</div>
<div class="card__title">{{ icon('shield', size=17) }} Vertrauenswürdige Aussteller</div>
<div class="card__sub">{{ trusted_keys | length }} gepinnte Schlüssel</div>
</div>
</div>
<div class="card__body">
@@ -19,7 +19,7 @@
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('shield', size=17) }} Ihre Lizenzen</div>
<div class="card__sub">{{ tickets | length }} Vertrag/Vertraege</div>
<div class="card__sub">{{ tickets | length }} Vertrag/Verträge</div>
</div>
</div>
<div class="card__body stack stack--tight">
@@ -68,7 +68,7 @@
{{ field('contact_person', 'Ansprechpartner', value=customer.contact_person) }}
{{ field('contact_phone', 'Telefon', value=customer.contact_phone) }}
{{ field('contact_email', 'E-Mail', value=customer.contact_email, type='email',
hint_text='Ueber diese Adresse laeuft der Zugang zu diesem Portal.') }}
hint_text='Über diese Adresse läuft der Zugang zu diesem Portal.') }}
{{ field('street', 'Strasse', value=customer.street) }}
{{ field('house_number', 'Hausnummer', value=customer.house_number) }}
{{ field('postal_code', 'PLZ', value=customer.postal_code) }}
@@ -6,7 +6,7 @@
{% block content %}
<p class="fs-sm muted center">
Geben Sie die bei uns hinterlegte E-Mail-Adresse ein. Sie erhalten einen Link, der
{{ minutes }} Minuten gueltig ist und sich einmal verwenden laesst.
{{ minutes }} Minuten gültig ist und sich einmal verwenden lässt.
</p>
<form method="post" class="stack" data-no-guard>
@@ -22,7 +22,7 @@
</form>
<p class="auth-footnote">
Aus Datenschutzgruenden bekommen Sie immer dieselbe Rueckmeldung -- unabhaengig davon, ob die
Aus Datenschutzgründen bekommen Sie immer dieselbe Rückmeldung -- unabhängig davon, ob die
Adresse bei uns bekannt ist.
</p>
{% endblock %}
@@ -13,7 +13,7 @@
{% block content %}
{{ alert('Diese Daten verwaltet der Sub-Lizenzserver selbst. Sie sind hier rein informativ und
werden bei jedem Bericht vollstaendig ersetzt.', 'info') }}
werden bei jedem Bericht vollständig ersetzt.', 'info') }}
{% if reported.customers %}
<div class="stack">
@@ -71,7 +71,7 @@ werden bei jedem Bericht vollstaendig ersetzt.', 'info') }}
</div>
{% else %}
{{ empty_state('Noch kein Bericht',
'Dieser Lizenzserver hat noch nichts gemeldet. Der erste Bericht kommt mit dem naechsten
'Dieser Lizenzserver hat noch nichts gemeldet. Der erste Bericht kommt mit dem nächsten
Heartbeat.', 'share') }}
{% endif %}
{% endblock %}
@@ -6,8 +6,8 @@
{% block content %}
{{ alert('Ein Sub-Lizenzserver meldet seinen kompletten Bestand -- huckepack auf dem
' ~ heartbeat_hours ~ '-Stunden-Heartbeat und zusaetzlich sofort nach jeder Aenderung. Jeder
Bericht ist ein vollstaendiger Schnappschuss: Geloeschtes verschwindet automatisch auch hier.',
' ~ heartbeat_hours ~ '-Stunden-Heartbeat und zusätzlich sofort nach jeder Änderung. Jeder
Bericht ist ein vollständiger Schnappschuss: Gelöschtes verschwindet automatisch auch hier.',
'info') }}
{% if sources %}
@@ -20,7 +20,7 @@
<div class="card__header">
<div class="flex-1">
<div class="card__title">
{{ icon('shield', size=17) }} Aktuelle Lizenz
{{ icon('shield', size=17) }} Aktülle Lizenz
{% if current.status == 'active' %}{{ pill('aktiv', 'online') }}
{% else %}{{ pill('bereit zum Einspielen', 'info') }}{% endif %}
</div>
@@ -34,7 +34,7 @@
</div>
<div class="card__body stack">
<dl class="kv fs-sm">
<dt>Gueltig bis</dt>
<dt>Gültig bis</dt>
<dd>{% if current.lifetime %}unbefristet{% else %}{{ current.expires_at | date }}{% endif %}</dd>
{% if current.hostname %}<dt>Installiert auf</dt><dd>{{ current.hostname }}</dd>{% endif %}
<dt>Ausgestellt</dt><dd>{{ current.issued_at | date }}</dd>
@@ -64,7 +64,7 @@
action="{{ url_for('selfservice.revoke', ticket_id=ticket.id, license_id=current.license_id) }}">
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<button class="btn btn--ghost danger-text btn--block" type="submit"
data-confirm="Lizenz freigeben? Ihre bisherige Installation verliert damit die lizenzpflichtigen Funktionen. Danach koennen Sie sofort eine neue Lizenz fuer Ihr neues System ausstellen."
data-confirm="Lizenz freigeben? Ihre bisherige Installation verliert damit die lizenzpflichtigen Funktionen. Danach können Sie sofort eine neue Lizenz für Ihr neues System ausstellen."
data-confirm-title="Lizenz freigeben" data-confirm-ok="Freigeben">
Lizenz freigeben (Systemwechsel)
</button>
@@ -81,7 +81,7 @@
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('shield', size=17) }} Noch keine Lizenz</div>
<div class="card__sub">Sie koennen Ihre Lizenz jetzt erstellen</div>
<div class="card__sub">Sie können Ihre Lizenz jetzt erstellen</div>
</div>
</div>
<div class="card__body">
@@ -101,8 +101,8 @@
<details class="card">
<summary class="card__header" style="cursor:pointer">
<div class="flex-1">
<div class="card__title">{{ icon('history', size=17) }} Frueher ausgestellt</div>
<div class="card__sub">{{ history | length }} Eintraege</div>
<div class="card__title">{{ icon('history', size=17) }} Früher ausgestellt</div>
<div class="card__sub">{{ history | length }} Einträge</div>
</div>
{{ icon('chevron-down', size=16) }}
</summary>
@@ -115,7 +115,7 @@
<td class="fs-sm">{{ entry.issued_at | date }}</td>
<td class="fs-sm">
{% if entry.status == 'revoked' %}freigegeben
{% elif entry.status == 'deactivated' %}abgeloest
{% elif entry.status == 'deactivated' %}abgelöst
{% else %}{{ entry.status }}{% endif %}
</td>
<td class="fs-sm">{{ entry.hostname or '&ndash;' | safe }}</td>
@@ -9,9 +9,9 @@
<section class="card">
<div class="card__body stack">
{{ alert('In Ihrer TESM-Installation unter Verwaltung -- Lizenz auf "Lizenz aktivieren"
klicken. Erscheint dort ein Anfragecode, fuegen Sie ihn hier ein. Sie bekommen einen
Antwortcode zurueck, den Sie in TESM eintragen. Beide Codes sind signiert und lassen
sich unterwegs nicht veraendern.', 'info', 'So geht es') }}
klicken. Erscheint dort ein Anfragecode, fügen Sie ihn hier ein. Sie bekommen einen
Antwortcode zurück, den Sie in TESM eintragen. Beide Codes sind signiert und lassen
sich unterwegs nicht verändern.', 'info', 'So geht es') }}
{% if response_code %}
<div class="field">
@@ -35,7 +35,7 @@
<label class="field">
<span class="field__label">Anfragecode aus Ihrer TESM-Installation</span>
<textarea class="textarea input--mono" name="code" rows="6" required
placeholder="Code einfuegen ..."></textarea>
placeholder="Code einfügen ..."></textarea>
</label>
<div class="row row--end mt-3">
<button class="btn btn--primary" type="submit">Antwortcode erzeugen</button>
@@ -44,7 +44,7 @@
{% endif %}
<p class="fs-xs faint">
<a href="{{ url_for('selfservice.overview', ticket_id=ticket.id) }}">Zurueck zur Uebersicht</a>
<a href="{{ url_for('selfservice.overview', ticket_id=ticket.id) }}">Zurück zur Übersicht</a>
</p>
</div>
</section>
@@ -4,7 +4,7 @@
{% block page_title %}E-Mail-Versand{% endblock %}
{% block page_sub %}
Microsoft Graph (App-only) &middot;
{% if ready %}einsatzbereit{% else %}nicht vollstaendig konfiguriert{% endif %}
{% if ready %}einsatzbereit{% else %}nicht vollständig konfiguriert{% endif %}
{% endblock %}
{% block topbar_extra %}
@@ -19,8 +19,8 @@
{% endblock %}
{% block content %}
{{ alert('Der Versand laeuft ueber den Client-Credentials-Flow von Microsoft Graph -- kein SMTP,
kein zusaetzliches Paket. Die Anwendung braucht in Azure die Anwendungsberechtigung
{{ alert('Der Versand läuft über den Client-Credentials-Flow von Microsoft Graph -- kein SMTP,
kein zusätzliches Paket. Die Anwendung braucht in Azure die Anwendungsberechtigung
Mail.Send. Der Verbindungstest holt nur ein Token und verschickt
bewusst keine echte Nachricht.', 'info') }}
@@ -46,7 +46,7 @@ bewusst keine echte Nachricht.', 'info') }}
</div>
<fieldset class="fieldset">
<legend>Client-Secret {% if has_secret %}{{ pill('gesetzt', 'online') }}{% else %}{{ pill('fehlt', 'warning') }}{% endif %}</legend>
{{ field('client_secret', 'Neues Secret (leer = unveraendert)', type='password',
{{ field('client_secret', 'Neues Secret (leer = unverändert)', type='password',
autocomplete='new-password', disabled=not can_secrets) }}
{% if not can_secrets %}
<p class="fs-xs faint mt-2">
@@ -63,17 +63,17 @@ bewusst keine echte Nachricht.', 'info') }}
<section class="card">
<div class="card__header">
<div class="flex-1"><div class="card__title">{{ icon('info', size=17) }} Wofuer der Versand genutzt wird</div></div>
<div class="flex-1"><div class="card__title">{{ icon('info', size=17) }} Wofür der Versand genutzt wird</div></div>
</div>
<div class="card__body">
<ul class="stack stack--tight fs-sm muted">
<li>Lizenzdatei an den Kunden senden (mit Anhang).</li>
<li>Zugangslink fuer das Kundenportal.</li>
<li>Zugangslink für das Kundenportal.</li>
</ul>
<div class="divider"></div>
<p class="fs-sm muted">
Das Secret liegt AES-256-GCM-verschluesselt in der Datenbank, gebunden an seinen
Verwendungszweck. Ohne die Schluesseldatei dieser Installation ist es nicht lesbar.
Das Secret liegt AES-256-GCM-verschlüsselt in der Datenbank, gebunden an seinen
Verwendungszweck. Ohne die Schlüsseldatei dieser Installation ist es nicht lesbar.
</p>
</div>
</section>
@@ -6,8 +6,8 @@
{% block content %}
{% if not sysops_available %}
{{ alert('Systemhelfer nicht verfuegbar -- Hostname und Zeitzone koennen hier nur angezeigt,
nicht geaendert werden.', 'info') }}
{{ alert('Systemhelfer nicht verfügbar -- Hostname und Zeitzone können hier nur angezeigt,
nicht geändert werden.', 'info') }}
{% endif %}
<div class="grid grid--split">
@@ -16,7 +16,7 @@
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('users', size=17) }} Kundenportal und Self-Service</div>
<div class="card__sub">Zwei unabhaengige Zeitfenster: Link und Sitzung</div>
<div class="card__sub">Zwei unabhängige Zeitfenster: Link und Sitzung</div>
</div>
</div>
<form method="post" action="{{ url_for('settings.system_save') }}">
@@ -24,10 +24,10 @@
<div class="card__body stack">
{{ checkbox('portal_enabled', 'Kundenportal aktiv', checked=values.portal_enabled) }}
<div class="form-grid">
{{ field('portal_link_minutes', 'Zugangslink gueltig (Minuten)',
{{ field('portal_link_minutes', 'Zugangslink gültig (Minuten)',
value=values.portal_link_minutes, type='number',
hint_text=specs['portal_link_minutes'].hint) }}
{{ field('portal_session_minutes', 'Portalsitzung gueltig (Minuten)',
{{ field('portal_session_minutes', 'Portalsitzung gültig (Minuten)',
value=values.portal_session_minutes, type='number',
hint_text=specs['portal_session_minutes'].hint) }}
</div>
@@ -35,7 +35,7 @@
{{ checkbox('self_service_enabled', 'Self-Service-Link je Ticket aktiv',
checked=values.self_service_enabled,
hint_text=specs['self_service_enabled'].hint) }}
{{ checkbox('allow_customer_revoke', 'Kunden duerfen ihre Lizenz selbst freigeben',
{{ checkbox('allow_customer_revoke', 'Kunden dürfen ihre Lizenz selbst freigeben',
checked=values.allow_customer_revoke,
hint_text=specs['allow_customer_revoke'].hint) }}
</div>
@@ -60,6 +60,9 @@
selected=values.log_rotate_interval) }}
{{ field('log_rotate_keep', 'Aufbewahrte Rotationen', value=values.log_rotate_keep,
type='number') }}
{{ field('session_idle_minutes', 'Automatische Abmeldung (Minuten)',
value=values.session_idle_minutes, type='number',
hint_text=specs['session_idle_minutes'].hint) }}
{{ field('trash_retention_days', 'Papierkorb-Aufbewahrung (Tage)',
value=values.trash_retention_days, type='number') }}
</div>
@@ -105,7 +108,7 @@
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('sliders', size=17) }} Navigationsreihenfolge</div>
<div class="card__sub">Gilt fuer alle Benutzer</div>
<div class="card__sub">Gilt für alle Benutzer</div>
</div>
</div>
<form method="post" action="{{ url_for('admin.nav_order') }}">
@@ -6,7 +6,7 @@
{% block content %}
{{ alert('Diese Angaben werden in jede Lizenzdatei eingebettet und beim Kunden auf der
Lizenzseite angezeigt -- inklusive Logo. Der oeffentliche Endpunkt entscheidet zusaetzlich,
Lizenzseite angezeigt -- inklusive Logo. Der öffentliche Endpunkt entscheidet zusätzlich,
welche Basis-URL in kundengerichteten Links steht.', 'info') }}
<div class="grid grid--split">
@@ -26,18 +26,18 @@ welche Basis-URL in kundengerichteten Links steht.', 'info') }}
disabled=not current_user.has('settings_vendor.edit')) }}
</div>
{{ textarea_field('vendor_address', 'Adresse', value=values.vendor_address, rows=3) }}
{{ field('master_endpoint', 'Oeffentlicher Endpunkt', value=values.master_endpoint,
{{ field('master_endpoint', 'Öffentlicher Endpunkt', value=values.master_endpoint,
placeholder='https://lizenz.firma.de',
disabled=not current_user.has('settings_vendor.edit'),
hint_text='Basis-URL, unter der Kundeninstanzen diesen Server erreichen. Wird in
jede Lizenz eingebettet und fuer alle kundengerichteten Links verwendet -- statt
des Host-Headers der aktuellen Anfrage, der bei internen Aufrufen falsch waere.',
jede Lizenz eingebettet und für alle kundengerichteten Links verwendet -- statt
des Host-Headers der aktüllen Anfrage, der bei internen Aufrufen falsch wäre.',
extra_class='field--full') }}
<fieldset class="fieldset">
<legend>Logo (maximal {{ max_logo_kb }} KB)</legend>
<div class="stack stack--tight">
{% if values.vendor_logo_base64 %}
<img src="{{ values.vendor_logo_base64 }}" alt="Aktuelles Logo"
<img src="{{ values.vendor_logo_base64 }}" alt="Aktülles Logo"
style="max-height:64px;background:var(--surface-2);padding:var(--sp-2);border-radius:var(--r-sm)">
{% else %}
<p class="faint fs-sm">Kein Logo hinterlegt.</p>
@@ -45,7 +45,7 @@ welche Basis-URL in kundengerichteten Links steht.', 'info') }}
<input class="input" type="file" name="vendor_logo" accept="image/png,image/jpeg,image/svg+xml"
{% if not current_user.has('settings_vendor.edit') %}disabled{% endif %}>
<span class="field__hint">
Ein zu grosses Logo wird abgelehnt, die uebrigen Angaben werden trotzdem gespeichert.
Ein zu grosses Logo wird abgelehnt, die übrigen Angaben werden trotzdem gespeichert.
</span>
</div>
</fieldset>
@@ -75,7 +75,7 @@ welche Basis-URL in kundengerichteten Links steht.', 'info') }}
<div class="divider"></div>
<p class="fs-sm muted">
Die Angaben werden zum Zeitpunkt der Ausstellung in die Lizenz kopiert und signiert. Eine
Aenderung hier wirkt auf neue Lizenzen -- bestehende behalten ihren Stand, bis eine neue
Änderung hier wirkt auf neue Lizenzen -- bestehende behalten ihren Stand, bis eine neue
Revision signiert wird.
</p>
</div>
@@ -1,34 +1,34 @@
{% extends "tesm_core/base.html" %}
{% from "tesm_core/macros.html" import alert %}
{% block page_title %}Signaturschluessel{% endblock %}
{% block page_sub %}Schluessel-ID {{ key_id }}{% endblock %}
{% block page_title %}Signaturschlüssel{% endblock %}
{% block page_sub %}Schlüssel-ID {{ key_id }}{% endblock %}
{% block breadcrumb %}
<nav class="breadcrumb">
<span><a href="{{ url_for('own_license.overview') }}">Eigene Lizenz</a></span><span>Signaturschluessel</span>
<span><a href="{{ url_for('own_license.overview') }}">Eigene Lizenz</a></span><span>Signaturschlüssel</span>
</nav>
{% endblock %}
{% block content %}
{{ alert('Der oeffentliche Teil ist kein Geheimnis -- er reist in jeder ausgestellten Lizenz mit.
Ihn hier anzuzeigen erlaubt es, ihn unabhaengig zu vergleichen, bevor ein Kunde ihn als
vertrauenswuerdig hinterlegt.', 'info') }}
{{ alert('Der öffentliche Teil ist kein Geheimnis -- er reist in jeder ausgestellten Lizenz mit.
Ihn hier anzuzeigen erlaubt es, ihn unabhängig zu vergleichen, bevor ein Kunde ihn als
vertrauenswürdig hinterlegt.', 'info') }}
{% if health.problems %}
{% for problem in health.problems %}{{ alert(problem, 'danger') }}{% endfor %}
{% endif %}
<section class="card" style="max-width:760px">
<div class="card__header">
<div class="flex-1"><div class="card__title">{{ icon('key', size=17) }} Oeffentlicher Schluessel</div>
<div class="flex-1"><div class="card__title">{{ icon('key', size=17) }} Öffentlicher Schlüssel</div>
<div class="card__sub">Erzeugt {{ created_at | dt }}</div></div>
<button class="btn btn--sm" type="button" data-copy="#pubkey">{{ icon('copy', size=14) }} Kopieren</button>
</div>
<div class="card__body stack">
<dl class="kv fs-sm">
<dt>Schluessel-ID</dt><dd class="mono">{{ key_id }}</dd>
<dt>Schlüssel-ID</dt><dd class="mono">{{ key_id }}</dd>
<dt>Verfahren</dt><dd>Ed25519 (EdDSA)</dd>
</dl>
<pre class="code-block code-block--wrap" id="pubkey">{{ public_key }}</pre>
{{ alert('Der private Teil liegt ausschliesslich auf diesem Host (Modus 0600) und ist aus dem
Deployment-Abgleich ausgeschlossen. Ginge er verloren, waere jede
ausgestellte Lizenz nicht mehr pruefbar -- bitte getrennt sichern.', 'warning') }}
Deployment-Abgleich ausgeschlossen. Ginge er verloren, wäre jede
ausgestellte Lizenz nicht mehr prüfbar -- bitte getrennt sichern.', 'warning') }}
</div>
</section>
{% endblock %}
@@ -17,11 +17,11 @@
{% block content %}
{% if endpoint_missing %}
{{ alert(endpoint_missing, 'danger', 'Keine Ausstellung moeglich') }}
{{ alert(endpoint_missing, 'danger', 'Keine Ausstellung möglich') }}
{% endif %}
{% if ticket.status == 'blocked' %}
{{ alert('Dieses Ticket ist gesperrt. Es lassen sich keine neuen Lizenzen ausstellen -- weder
hier noch ueber den Self-Service des Kunden. Bereits ausgestellte Lizenzen bleiben gueltig.',
hier noch über den Self-Service des Kunden. Bereits ausgestellte Lizenzen bleiben gültig.',
'danger', 'Gesperrt') }}
{% endif %}
@@ -58,7 +58,7 @@
<input type="hidden" name="as_trial" value="1">
<button class="btn" type="submit"
{% if has_open_license or ticket.status == 'blocked' %}disabled{% endif %}
data-confirm="Testlizenz ausstellen? {{ trial_days }} Tage Laufzeit mit allen Modulen. Das Ticket selbst bleibt unveraendert."
data-confirm="Testlizenz ausstellen? {{ trial_days }} Tage Laufzeit mit allen Modulen. Das Ticket selbst bleibt unverändert."
data-confirm-title="Testlizenz" data-confirm-ok="Ausstellen"
data-confirm-style="primary">
Als Testlizenz ({{ trial_days }} Tage, alle Module)
@@ -81,7 +81,7 @@
{% if licenses %}
<div class="table-wrap">
<table class="table table--compact">
<thead><tr><th>Lizenz</th><th>Ausgestellt</th><th>Zustand</th><th>Laeuft ab</th><th>Host</th></tr></thead>
<thead><tr><th>Lizenz</th><th>Ausgestellt</th><th>Zustand</th><th>Läuft ab</th><th>Host</th></tr></thead>
<tbody>
{% for entry in licenses %}
<tr>
@@ -115,7 +115,7 @@
<section class="card">
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('link', size=17) }} Self-Service fuer den Kunden</div>
<div class="card__title">{{ icon('link', size=17) }} Self-Service für den Kunden</div>
<div class="card__sub">
Ohne Anmeldung nutzbar &ndash; die Berechtigung ist der Besitz dieses Links
</div>
@@ -128,7 +128,7 @@
</div>
<p class="fs-xs faint">
Damit kann der Kunde seine Lizenz herunterladen, bei einem Systemwechsel freigeben und
neu ausstellen. Der Link enthaelt 192 Bit Zufall und ist nicht ratbar -- behandeln Sie
neu ausstellen. Der Link enthält 192 Bit Zufall und ist nicht ratbar -- behandeln Sie
ihn wie ein Passwort.
</p>
<div class="divider"></div>
@@ -143,7 +143,7 @@
</div>
</section>
{% elif ticket.role != 'customer' %}
{{ alert('Fuer Lizenzserver-Tickets ist der Self-Service vollstaendig gesperrt -- diese
{{ alert('Für Lizenzserver-Tickets ist der Self-Service vollständig gesperrt -- diese
Lizenzen werden ausschliesslich hier verwaltet.', 'info') }}
{% endif %}
</div>
@@ -158,8 +158,8 @@
<form method="post" action="{{ url_for('tickets.update', ticket_id=ticket.id) }}">
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<div class="card__body stack">
{{ alert('Aenderungen wirken auf die naechste Ausstellung. Kunde und Rolle sind
unveraenderlich.', 'info') }}
{{ alert('Änderungen wirken auf die nächste Ausstellung. Kunde und Rolle sind
unveränderlich.', 'info') }}
<label class="field">
<span class="field__label">Lizenztyp</span>
<select class="select" name="type" id="ticket-type"
@@ -206,7 +206,7 @@
{% if current_user.has('tickets.edit') or current_user.has('tickets.delete') %}
<section class="card">
<div class="card__header">
<div class="flex-1"><div class="card__title">{{ icon('lock', size=17) }} Sperre und Loeschung</div></div>
<div class="flex-1"><div class="card__title">{{ icon('lock', size=17) }} Sperre und Löschung</div></div>
</div>
<div class="card__body stack stack--tight">
{% if current_user.has('tickets.edit') %}
@@ -216,7 +216,7 @@
<button class="btn btn--sm btn--block {{ 'btn--danger' if ticket.status != 'blocked' }}"
type="submit"
data-confirm="{% if ticket.status == 'blocked' %}Ticket entsperren?{% else %}Ticket sperren? Der Kunde kann danach keine neuen Lizenzen mehr ausstellen.{% endif %}"
data-confirm-title="Ticketsperre" data-confirm-ok="Bestaetigen"
data-confirm-title="Ticketsperre" data-confirm-ok="Bestätigen"
data-confirm-style="{{ 'primary' if ticket.status == 'blocked' else 'danger' }}">
{{ 'Entsperren' if ticket.status == 'blocked' else 'Sperren' }}
</button>
@@ -228,9 +228,9 @@
<input type="hidden" name="{{ csrf_field_name }}" value="{{ csrf_token() }}">
<button class="btn btn--sm btn--ghost danger-text btn--block" type="submit"
{% if has_open_license %}disabled title="Erst die offene Lizenz widerrufen"{% endif %}
data-confirm="Ticket und seine gesamte Lizenzhistorie loeschen? Das laesst sich nicht rueckgaengig machen."
data-confirm-title="Ticket loeschen" data-confirm-ok="Loeschen">
Ticket loeschen
data-confirm="Ticket und seine gesamte Lizenzhistorie löschen? Das lässt sich nicht rückgängig machen."
data-confirm-title="Ticket löschen" data-confirm-ok="Löschen">
Ticket löschen
</button>
</form>
{% endif %}
@@ -23,7 +23,7 @@
<label class="field">
<span class="field__label">Kunde <span class="req">*</span></span>
<select class="select" name="customer_id" required>
<option value="">bitte waehlen</option>
<option value="">bitte wählen</option>
{% for customer in customers %}
<option value="{{ customer.id }}"
{% if preselected_customer == customer.id %}selected{% endif %}>
@@ -32,7 +32,7 @@
{% endfor %}
</select>
<span class="field__hint">
Der Kunde ist nach dem Anlegen unveraenderlich -- der Self-Service-Link ist an ihn
Der Kunde ist nach dem Anlegen unveränderlich -- der Self-Service-Link ist an ihn
gebunden.
</span>
</label>
@@ -47,13 +47,13 @@
{% endfor %}
</select>
<span class="field__hint">
Standard: Grundfunktionen. Individuell: Module frei waehlbar. Enterprise: alle
Standard: Grundfunktionen. Individüll: Module frei wählbar. Enterprise: alle
Module. Testlizenz: 30 Tage.
</span>
</label>
<fieldset class="fieldset" id="module-box">
<legend>Module (nur bei &bdquo;Individuell&ldquo;)</legend>
<legend>Module (nur bei &bdquo;Individüll&ldquo;)</legend>
<div class="stack stack--tight">
{% for module in modules %}
{{ checkbox('modules', module_labels[module], value=module) }}
@@ -65,7 +65,7 @@
{{ field('valid_days', 'Laufzeit (Tage)', value=default_valid_days, type='number') }}
</div>
{{ checkbox('lifetime', 'Unbefristet',
hint_text='Nicht kombinierbar mit einer Testlizenz -- das waere eine
hint_text='Nicht kombinierbar mit einer Testlizenz -- das wäre eine
permanente Lizenz ohne Module.') }}
{{ textarea_field('notes', 'Interne Notiz', rows=2) }}
</div>
@@ -77,15 +77,15 @@
<div class="flex-1">
<div class="card__title">{{ icon('share', size=17) }} Lizenzserver-Lizenz</div>
<div class="card__sub">
Erzeugt eine neue Vertrauensgrenze -- der Empfaenger verwaltet danach eigene Kunden
Erzeugt eine neue Vertrauensgrenze -- der Empfänger verwaltet danach eigene Kunden
</div>
</div>
</div>
<div class="card__body stack">
{{ alert('Ein so lizenzierter Lizenzserver darf alles, was ein Lizenzserver koennen
{{ alert('Ein so lizenzierter Lizenzserver darf alles, was ein Lizenzserver können
soll -- ausser selbst weitere Lizenzserver-Lizenzen auszustellen. Er meldet seinen
Bestand automatisch hierher zurueck. Module sind fuer diese Rolle bedeutungslos und
werden ignoriert. Self-Service und Kundenportal sind fuer solche Tickets gesperrt.',
Bestand automatisch hierher zurück. Module sind für diese Rolle bedeutungslos und
werden ignoriert. Self-Service und Kundenportal sind für solche Tickets gesperrt.',
'warning') }}
<label class="check">
<input type="checkbox" name="role" value="license_server">
@@ -17,11 +17,11 @@
{% block content %}
{% if endpoint_missing %}
{{ alert(endpoint_missing, 'danger', 'Keine Ausstellung moeglich') }}
{{ alert(endpoint_missing, 'danger', 'Keine Ausstellung möglich') }}
{% endif %}
{{ alert('Ein Ticket beschreibt den Anspruch (Typ, Module, Laufzeit); die Lizenz ist die daraus
erzeugte, signierte Datei. Aenderungen am Ticket wirken auf die naechste
Ausstellung -- bereits ausgestellte Lizenzen bleiben unveraendert.', 'info') }}
erzeugte, signierte Datei. Änderungen am Ticket wirken auf die nächste
Ausstellung -- bereits ausgestellte Lizenzen bleiben unverändert.', 'info') }}
<section class="card">
<div class="table-wrap">
@@ -85,7 +85,7 @@ Ausstellung -- bereits ausgestellte Lizenzen bleiben unveraendert.', 'info') }}
</table>
</div>
{% if not tickets %}
{{ empty_state('Keine Tickets', 'Legen Sie das erste Ticket fuer einen Kunden an.', 'ticket') }}
{{ empty_state('Keine Tickets', 'Legen Sie das erste Ticket für einen Kunden an.', 'ticket') }}
{% endif %}
</section>
{% endblock %}