per-step screenshot + html snapshots, matches-only list, full German UI, CSV export

* apply: Recorder.step_snap(page, name) captures both a JPEG screenshot and
  the page HTML for every major moment; every provider now calls step_snap at
  each logical step so failure reports contain the exact DOM and rendered
  state at every stage of the flow
* ZIP report: each snapshot becomes snapshots/NN_<label>.jpg +
  snapshots/NN_<label>.html for AI-assisted debugging
* web: Wohnungsliste zeigt nur noch Flats, die die eigenen Filter treffen;
  Match-Chip entfernt (Liste ist jetzt implizit matchend)
* UI komplett auf Deutsch: Protokoll statt Logs, Administrator statt admin,
  Trockenmodus statt dry-run, Automatik pausiert statt circuit open,
  Alarm statt Alert, Abmelden statt Logout
* Wohnungen-Header: Zeile 1 Info (Alarm + Filter), Zeile 2 Schalter mit
  echten Radio-Paaren (An/Aus) für Automatisch bewerben und Trockenmodus;
  hx-confirm auf den kritischen Radios; per-form CSS für sichtbaren Check-State
* Protokoll: von/bis-Datumsfilter (Berliner Zeit) + CSV-Download
  (/logs/export.csv) mit UTC + lokaler Zeit

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Moritz 2026-04-21 11:40:12 +02:00
parent 04b591fa9e
commit 7444f90d6a
16 changed files with 360 additions and 202 deletions

View file

@ -8,10 +8,10 @@
{% if application.success == 1 %}<span class="chip chip-ok">erfolgreich</span>
{% elif application.success == 0 %}<span class="chip chip-bad">fehlgeschlagen</span>
{% else %}<span class="chip chip-warn">läuft</span>{% endif %}
<span class="chip chip-info">{{ application.triggered_by }}</span>
<span class="chip chip-info">{% if application.triggered_by == 'auto' %}automatisch{% else %}manuell{% endif %}</span>
{% if application.provider %}<span class="chip chip-info">{{ application.provider }}</span>{% endif %}
{% if application.submit_forms_used %}<span class="chip chip-warn">echt gesendet</span>
{% else %}<span class="chip chip-info">dry-run</span>{% endif %}
{% else %}<span class="chip chip-info">Trockenmodus</span>{% endif %}
{% if application.success == 0 %}
<a class="btn btn-danger text-sm ml-auto"
@ -53,12 +53,20 @@
{% if forensics.screenshots %}
<details>
<summary class="font-medium">Screenshots ({{ forensics.screenshots|length }})</summary>
<summary class="font-medium">Momentaufnahmen ({{ forensics.screenshots|length }})</summary>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-3">
{% for s in forensics.screenshots %}
<div class="border border-soft rounded-lg p-2">
<div class="text-xs text-slate-500 mb-1">{{ s.label }} @ {{ "%.2f"|format(s.ts) }}s — {{ s.url }}</div>
<div class="border border-soft rounded-lg p-2 space-y-2">
<div class="text-xs text-slate-500">{{ s.label }} @ {{ "%.2f"|format(s.ts) }}s — {{ s.url }}</div>
{% if s.b64_jpeg %}
<img src="data:image/jpeg;base64,{{ s.b64_jpeg }}" class="w-full rounded" alt="{{ s.label }}">
{% endif %}
{% if s.html %}
<details>
<summary class="text-xs text-slate-500">Quellcode anzeigen ({{ s.html_size }} B)</summary>
<pre class="mono whitespace-pre-wrap break-all mt-2 p-2 bg-[#f6fafd] rounded border border-soft max-h-72 overflow-auto">{{ s.html }}</pre>
</details>
{% endif %}
</div>
{% endfor %}
</div>