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>
113 lines
5.2 KiB
HTML
113 lines
5.2 KiB
HTML
{% 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 %}
|