kundenplattform/templates/installationsmedium.html
Thomas Stallinger 19b39af0ce feat: Selfservice-UI fuer Organisation + Installationsmedium (Phase 5)
Zwei neue Menuepunkte fuer eingeloggte Kunden (nicht der
organisationsuebergreifende /admin/-Bereich):

- "Organisation": Name bearbeiten (ruft Phase-3-PATCH auf
  provisioning-server auf).
- "Installationsmedium": WLAN-SSID/-Passwort, Vorlagenwahl, Kontingent
  und Gueltigkeitsdauer speichern, personalisierte Kunden-ISO per
  Klick erstellen (Phase-4-Endpoints), Fortschritt/Status-Anzeige
  (Meta-Refresh waehrend pending/running), Download + dd/Rufus-
  Anleitung. Getrennte "aufladen"-Aktion erhoeht Kontingent/
  Gueltigkeit der bestehenden Enrollment Session (neuer PATCH-
  Endpoint), ohne eine neue ISO zu erzeugen - die bereits ausgeteilte
  bleibt gueltig.

Neue Tabelle installationsmedium_konfiguration (Migration 0004, eigene
DB, kein Foreign Key auf provisioning-server-Ressourcen, siehe
ADR-0011) haelt SSID/verschluesselten PSK/Vorlage/Kontingent-Defaults
sowie die IDs der aktuell gueltigen Enrollment Session und des
zugehoerigen ISO-Baus.

WLAN-PSK wird Fernet-verschluesselt gespeichert (encryption.py, neuer
KUNDENPLATTFORM_ENCRYPTION_KEY) - echte Zugangsdaten, kein
Passwort-Hash-Fall. Download laeuft ueber einen Streaming-Proxy
(anode_stream() in anode_client.py) statt eines direkten Links, weil
der Browser kein KUNDENPLATTFORM_TOKEN besitzt.

Lokal end-to-end verifiziert (Wegwerf-Postgres fuer beide Services,
echter build_customer_iso.sh-Lauf ueber die Kundenplattform-UI):
Formular -> Enrollment Session + ISO-Bau -> Status-Polling ->
Download-Proxy liefert byte-identische, korrekt personalisierte Datei
(SSID/PSK/Aktivierungscode per xorriso-Extraktion gegengeprueft) ->
Aufladen erhoeht Kontingent/Gueltigkeit der bestehenden Session ohne
neuen Bau -> "neues Installationsmedium erstellen" erzeugt neue
Session + ISO, alte Session bleibt unangetastet nutzbar (alter
ISO-Bau wird laut Phase-4-Cleanup automatisch entfernt), leeres
PSK-Feld uebernimmt das zuvor gespeicherte PSK korrekt.
Organisation-Name-Aenderung ueber PATCH verifiziert. Unauthentifizierter
Zugriff auf beide neuen Routen leitet korrekt zu /login um.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 01:36:46 +02:00

113 lines
5.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Installationsmedium — Tuxflotte{% endblock %}
{% block content %}
<h2>Installationsmedium</h2>
{% if iso_build and iso_build.status in ("pending", "running") %}
<meta http-equiv="refresh" content="5">
{% endif %}
{% if konfiguration %}
<section>
<h3>Aktuelles Installationsmedium</h3>
<table>
<tbody>
<tr><th>WLAN-SSID</th><td>{{ konfiguration.wifi_ssid or "—" }}</td></tr>
<tr><th>Vorlage</th><td>
{% set vorlage = vorlagen | selectattr("id", "equalto", konfiguration.bereitstellungsvorlage_id) | first %}
{{ vorlage.label if vorlage else "—" }}
</td></tr>
{% if enrollment_session %}
<tr><th>Kontingent</th><td>{{ enrollment_session.devices_used }} / {{ enrollment_session.max_devices }} Geräte</td></tr>
<tr><th>Gültig bis</th><td>{{ enrollment_session.expires_at }}</td></tr>
<tr><th>Widerrufen</th><td>{{ "ja" if enrollment_session.revoked_at else "nein" }}</td></tr>
{% endif %}
</tbody>
</table>
{% if iso_build %}
{% if iso_build.status in ("pending", "running") %}
<p>⏳ Wird gerade erstellt (Status: {{ iso_build.status }}) — diese Seite aktualisiert sich automatisch alle 5 Sekunden.</p>
{% elif iso_build.status == "completed" %}
<p><a href="/installationsmedium/download" role="button">Installationsmedium herunterladen</a></p>
<details>
<summary role="button" class="secondary outline">Anleitung: auf USB-Stick übertragen</summary>
<p>
Unter Windows: <a href="https://rufus.ie/" target="_blank" rel="noopener">Rufus</a> verwenden —
heruntergeladene .iso-Datei auswählen, USB-Stick auswählen, "Start" klicken.
</p>
<p>
Unter Linux/macOS:
<code>sudo dd if=tuxflotte-installationsmedium.iso of=/dev/sdX bs=4M status=progress conv=fsync</code>
(<code>/dev/sdX</code> durch den tatsächlichen USB-Stick ersetzen — <strong>alle Daten darauf werden überschrieben</strong>).
</p>
</details>
{% elif iso_build.status == "failed" %}
<p><mark>Der Bau ist fehlgeschlagen. Bitte weiter unten erneut versuchen.</mark></p>
{% endif %}
{% endif %}
</section>
{% if enrollment_session and not enrollment_session.revoked_at %}
<section>
<h3>Kontingent/Gültigkeit aufladen</h3>
<p>
Erhöht Kontingent und/oder Gültigkeit der bestehenden Sitzung — das bereits heruntergeladene
Installationsmedium bleibt gültig, es muss <strong>kein</strong> neues erstellt werden.
</p>
<form method="post" action="/installationsmedium/aufladen">
<div class="grid">
<label>Zusätzliche Geräte
<input type="number" name="additional_devices" min="0" value="0">
</label>
<label>Zusätzliche Tage Gültigkeit
<input type="number" name="additional_days" min="0" value="0">
</label>
</div>
<button type="submit">Aufladen</button>
</form>
</section>
{% endif %}
{% endif %}
<section>
<h3>{{ "Neues Installationsmedium erstellen" if konfiguration else "Installationsmedium erstellen" }}</h3>
{% if konfiguration %}
<p>
Erstellt ein komplett neues Installationsmedium mit eigener Sitzung — nötig, wenn sich die
WLAN-Zugangsdaten geändert haben. Das bisherige Medium bleibt bis zu seinem Ablauf weiter nutzbar.
</p>
{% endif %}
<form method="post" action="/installationsmedium">
<label for="wifi_ssid">WLAN-SSID</label>
<input type="text" id="wifi_ssid" name="wifi_ssid" value="{{ konfiguration.wifi_ssid if konfiguration else '' }}">
<label for="wifi_psk">WLAN-Passwort</label>
<input type="password" id="wifi_psk" name="wifi_psk"
placeholder="{{ '•••••••• (gespeichert zum Ändern neu eingeben)' if konfiguration and konfiguration.hat_wifi_psk else '' }}">
<label for="bereitstellungsvorlage_id">Vorlage</label>
<select id="bereitstellungsvorlage_id" name="bereitstellungsvorlage_id" required>
{% for v in vorlagen %}
<option value="{{ v.id }}"
{% if konfiguration and konfiguration.bereitstellungsvorlage_id == v.id %}selected
{% elif not konfiguration and v.is_default %}selected{% endif %}>
{{ v.label }} ({{ v.backend.name }} {{ v.backend.version }})
</option>
{% endfor %}
</select>
<div class="grid">
<label>Kontingent (Anzahl Geräte)
<input type="number" name="max_devices" min="1" value="{{ konfiguration.max_devices if konfiguration else 20 }}" required>
</label>
<label>Gültigkeit (Tage)
<input type="number" name="validity_days" min="1" value="{{ konfiguration.validity_days if konfiguration else 30 }}" required>
</label>
</div>
<button type="submit">{{ "Neues Installationsmedium erstellen" if konfiguration else "Installationsmedium erstellen" }}</button>
</form>
</section>
{% endblock %}