Files
tesm-license/apps/tesm-license/src/tesm_license/templates/license/dashboard.html
T
alientimandClaude Opus 5 ab5fda658e 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>
2026-09-02 23:54:35 +02:00

125 lines
5.5 KiB
HTML

{% extends "tesm_core/base.html" %}
{% from "tesm_core/macros.html" import pill, empty_state, alert, stat %}
{% block page_title %}Übersicht{% endblock %}
{% block page_sub %}
{% if summary %}
{{ summary.counts.customers }} Kunden &middot; {{ summary.counts.licenses_active }} aktive Lizenzen
{% else %}Willkommen{% endif %}
{% endblock %}
{% block content %}
{% if not summary %}
{{ empty_state('Keine Lizenzrechte',
'Ihrem Konto fehlt das Recht, Lizenzen zu sehen. Wenden Sie sich an die Administration.',
'lock') }}
{% else %}
<div class="grid grid--4">
{{ stat('Kunden', summary.counts.customers, href=url_for('customers.overview')) }}
{{ stat('Offene Tickets', summary.counts.tickets_open, href=url_for('tickets.overview')) }}
{{ stat('Aktive Lizenzen', summary.counts.licenses_active, kind='ok',
href=url_for('licenses.overview', status='active')) }}
{{ stat('Widerrufen', summary.counts.licenses_revoked, kind='muted',
href=url_for('licenses.overview', status='revoked')) }}
</div>
{% if summary.counts.tickets_blocked %}
{{ 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 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 %}
<details class="card" data-persist="dash-{{ key }}" {% if key != 'green' %}open{% endif %}>
<summary class="card__header" style="cursor:pointer">
<div class="flex-1">
<div class="card__title">
{{ pill(entries | length, kind if kind != 'ok' else 'online') }} {{ label }}
</div>
<div class="card__sub">{{ hint }}</div>
</div>
{{ icon('chevron-down', size=16) }}
</summary>
<div class="card__body">
<div class="grid grid--tiles">
{% for entry in entries %}
<a class="card" style="padding:var(--sp-4)" data-filter-item
href="{{ url_for('licenses.detail', license_id=entry.license_id) }}">
<div class="row row--between" style="align-items:flex-start">
<div class="flex-1" style="min-width:0">
<div class="fw-600 truncate">{{ entry.customer_name }}</div>
<div class="fs-xs faint truncate">
{{ entry.hostname or 'kein Host gemeldet' }}
</div>
</div>
{% if entry.lifetime %}{{ pill('unbefristet', 'online') }}
{% elif entry.is_expired %}{{ pill('abgelaufen', 'danger') }}
{% else %}{{ pill(entry.days_left ~ ' Tage', kind if kind != 'ok' else 'online') }}
{% endif %}
</div>
<dl class="kv fs-xs mt-3">
<dt>Typ</dt><dd>{{ entry.type }}{% if entry.role != 'customer' %}
&middot; {{ entry.role }}{% endif %}</dd>
<dt>Zustand</dt><dd>{{ entry.status }}</dd>
{% if entry.last_heartbeat_at %}
<dt>Heartbeat</dt>
<dd><span data-relative-time="{{ entry.last_heartbeat_at }}">{{ entry.last_heartbeat_at | dt }}</span></dd>
{% endif %}
</dl>
</a>
{% endfor %}
</div>
</div>
</details>
{% endif %}
{% endfor %}
{% if not summary.buckets.red and not summary.buckets.orange and not summary.buckets.green %}
{{ empty_state('Noch keine Lizenzen',
'Legen Sie einen Kunden an, erstellen Sie ein Ticket und stellen Sie daraus eine Lizenz aus.',
'shield') }}
{% endif %}
{% if sources %}
<section class="card">
<div class="card__header">
<div class="flex-1">
<div class="card__title">{{ icon('share', size=17) }} Sub-Lizenzserver</div>
<div class="card__sub">Von untergeordneten Lizenzservern gemeldeter Bestand</div>
</div>
<a class="btn btn--sm" href="{{ url_for('licenses.reports') }}">Alle anzeigen</a>
</div>
<div class="table-wrap">
<table class="table table--compact">
<thead><tr><th>Kunde</th><th>Host</th><th class="numeric">Kunden</th>
<th class="numeric">Tickets</th><th class="numeric">Lizenzen</th><th>Gemeldet</th></tr></thead>
<tbody>
{% for source in sources %}
<tr>
<td class="fw-600">
<a href="{{ url_for('licenses.report_detail', license_id=source.license_id) }}">
{{ source.customer_name }}</a>
</td>
<td class="fs-sm">{{ source.hostname or '&ndash;' | safe }}</td>
<td class="numeric">{{ source.customers }}</td>
<td class="numeric">{{ source.tickets }}</td>
<td class="numeric">{{ source.licenses }}</td>
<td class="fs-sm">
{% if source.last_reported_at %}
<span data-relative-time="{{ source.last_reported_at }}">{{ source.last_reported_at | dt }}</span>
{% else %}<span class="faint">noch nie</span>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endif %}
{% endif %}
{% endblock %}