Files
nico d56be7b2c0 Wichtel-Werkstatt: Secret-Santa-App mit Räumen, Ausschlüssen und Recovery-Links
- FastAPI + SQLite, serverseitig gerenderte Templates, mobil-zuerst
- Admin-Passwort, einseitige Ausschlüsse, eingefrorene Auslosung
- Teilnehmer sehen Ergebnis per Cookie; Einmal-Recovery-Links bei Verlust
- Docker/podman-tauglich (Entrypoint mit Privilegien-Drop, SELinux-:z)
- Unit-Tests für Auslosung, E2E-Testskript (30 Checks)
2026-08-05 16:07:21 +02:00

168 lines
6.6 KiB
HTML
Raw Permalink 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 %}Admin {{ room.name }}{% endblock %}
{% block content %}
<div class="admin-head">
<h1 class="room-title">{{ room.name }}</h1>
{% if drawn %}
<span class="tag tag-gold">✨ Ausgelost</span>
{% else %}
<span class="tag">Wartet auf Auslosung</span>
{% endif %}
</div>
{% if message %}
<div class="alert {{ 'alert-error' if message_kind == 'error' else 'alert-ok' }}" role="alert">
{{ message }}
</div>
{% endif %}
{% if recovery_link %}
<section class="card card-highlight">
<h2>🔑 Wiederherstellungslink für {{ recovery_for }}</h2>
<p>Schicke diesen Link direkt an <strong>{{ recovery_for }}</strong>.
Er funktioniert <strong>ein einziges Mal</strong> und stellt dort das
Ergebnis wieder her:</p>
<div class="copy-row">
<input type="text" readonly value="{{ recovery_link }}" id="recovery-link">
<button type="button" class="btn btn-secondary" data-copy="#recovery-link">Kopieren</button>
</div>
<p class="hint">Ein eventuell älterer, unbenutzter Link für {{ recovery_for }}
wurde dadurch ersetzt.</p>
</section>
{% endif %}
<section class="card">
<h2>Einladung teilen</h2>
<p>Mit diesem Link melden sich die Teilnehmer an:</p>
<div class="copy-row">
<input type="text" readonly value="{{ share_link }}" id="share-link">
<button type="button" class="btn btn-secondary" data-copy="#share-link">Kopieren</button>
</div>
<p class="code-line">Raumcode: <span class="code">{{ room.code }}</span></p>
</section>
{% if not drawn %}
<section class="card">
<h2>Selbst mitwichteln 🎅</h2>
{% if admin_participant %}
<p>Du bist als <strong>{{ admin_participant.name }}</strong> mit dabei. ✅
Deine Teilnehmeransicht und später dein Ergebnis erreichst du über
den Einladungslink.</p>
{% else %}
<p>Auch der Admin braucht ein Los: Melde dich selbst als Teilnehmer an.</p>
<form method="post" action="/r/{{ room.code }}/admin/self-register" class="stack">
<label>
Dein Name
<input type="text" name="name" maxlength="50" required
autocomplete="name" placeholder="z. B. Nico">
</label>
<button type="submit" class="btn btn-secondary">Mich anmelden 🎁</button>
</form>
{% endif %}
</section>
{% endif %}
<section class="card">
<h2>Teilnehmer ({{ participants|length }})</h2>
{% if participants %}
<ul class="list">
{% for p in participants %}
<li class="list-item">
<span class="list-name">{{ p.name }}</span>
{% if not drawn %}
<form method="post" action="/r/{{ room.code }}/participants/{{ p.id }}/delete"
data-confirm="»{{ p.name }}« wirklich aus dem Raum entfernen?">
<button type="submit" class="btn btn-small btn-danger" aria-label="{{ p.name }} entfernen"></button>
</form>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p class="hint">Noch niemand dabei teile den Einladungslink!</p>
{% endif %}
{% if drawn %}<p class="hint">Nach der Auslosung ist die Teilnehmerliste eingefroren.</p>{% endif %}
</section>
<section class="card">
<h2>Ausschlüsse</h2>
<p class="hint">Wer darf <em>wen nicht</em> ziehen? (z. B. Ehepartner gegenseitig
ausschließen das sind zwei Einträge)</p>
{% if not drawn %}
<form method="post" action="/r/{{ room.code }}/exclusions/add" class="exclusion-form">
<select name="from_id" required aria-label="Wer darf nicht ziehen?">
<option value="" disabled selected>Wer darf nicht…</option>
{% for p in participants %}<option value="{{ p.id }}">{{ p.name }}</option>{% endfor %}
</select>
<select name="to_id" required aria-label="Wen nicht?">
<option value="" disabled selected>…wen ziehen?</option>
{% for p in participants %}<option value="{{ p.id }}">{{ p.name }}</option>{% endfor %}
</select>
<button type="submit" class="btn btn-secondary" {% if participants|length < 2 %}disabled{% endif %}>
Ausschließen
</button>
</form>
{% endif %}
{% if exclusions %}
<ul class="list">
{% for e in exclusions %}
<li class="list-item">
<span class="list-name">{{ e.from_name }} ➜ darf nicht ➜ {{ e.to_name }}</span>
{% if not drawn %}
<form method="post" action="/r/{{ room.code }}/exclusions/{{ e.from_id }}/{{ e.to_id }}/delete">
<button type="submit" class="btn btn-small btn-danger" aria-label="Ausschluss entfernen"></button>
</form>
{% endif %}
</li>
{% endfor %}
</ul>
{% elif drawn %}
<p class="hint">Keine Ausschlüsse festgelegt.</p>
{% endif %}
{% if drawn %}<p class="hint">Nach der Auslosung sind die Ausschlüsse eingefroren.</p>{% endif %}
</section>
<section class="card">
<h2>Auslosung</h2>
{% if drawn %}
<div class="alert alert-ok">🎉 Es wurde ausgelost! Wer wen beschenkt, sehen nur die
Teilnehmer selbst auf ihrer Seite hier im Admin-Bereich bleibt das geheim.</div>
{% else %}
<p>Jede Person zieht genau eine andere Person niemand zieht sich selbst,
Ausschlüsse werden respektiert.</p>
<form method="post" action="/r/{{ room.code }}/draw"
data-confirm="Wirklich jetzt auslosen? Danach kann nichts mehr geändert werden.">
<button type="submit" class="btn btn-primary btn-big" {% if not can_draw %}disabled{% endif %}>
🎲 Jetzt auslosen
</button>
</form>
{% if not can_draw %}
<p class="hint">Mindestens {{ min_draw }} Teilnehmer nötig
(aktuell {{ participants|length }}).</p>
{% endif %}
{% endif %}
</section>
<section class="card">
<h2>Zugriff wiederherstellen 🔑</h2>
<p class="hint">Hat jemand das Cookie gelöscht oder ein neues Gerät?
Erstelle hier einen Einmal-Link, mit dem die Person ihr Ergebnis wiedersehen kann
auch nach der Auslosung. Du erfährst dabei weiterhin nicht, wer wen gezogen hat.</p>
<ul class="list">
{% for p in participants %}
<li class="list-item">
<span class="list-name">{{ p.name }}</span>
<form method="post" action="/r/{{ room.code }}/recovery/{{ p.id }}">
<button type="submit" class="btn btn-small btn-secondary">Link erstellen</button>
</form>
</li>
{% endfor %}
</ul>
{% if not participants %}<p class="hint">Noch keine Teilnehmer im Raum.</p>{% endif %}
</section>
<form method="post" action="/r/{{ room.code }}/admin/logout" class="logout-row">
<button type="submit" class="btn btn-ghost">Abmelden</button>
</form>
{% endblock %}