lazyflat/web/templates/logs.html
Moritz 7444f90d6a 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>
2026-04-21 11:40:12 +02:00

54 lines
2.3 KiB
HTML

{% extends "_layout.html" %}
{% block content %}
<section class="card p-4">
<form method="get" action="/logs" class="flex flex-wrap items-end gap-3">
<div>
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">von</label>
<input class="input" type="date" name="from" value="{{ from_str }}">
</div>
<div>
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">bis</label>
<input class="input" type="date" name="to" value="{{ to_str }}">
</div>
<button class="btn btn-primary text-sm" type="submit">Anwenden</button>
<a class="btn btn-ghost text-sm" href="/logs">zurücksetzen</a>
<a class="btn btn-ghost text-sm"
href="/logs/export.csv?from={{ from_str }}&to={{ to_str }}">
CSV herunterladen
</a>
</form>
</section>
<section class="card">
<div class="px-4 py-3 border-b border-soft flex items-center justify-between">
<h2 class="font-semibold">System-Protokoll</h2>
<span class="text-xs text-slate-500">
{{ events|length }} Einträge
{% if from_str or to_str %}
· Zeitraum:
{{ from_str or "alles davor" }} — {{ to_str or "heute" }}
{% else %}
· ohne Filter (Anzeige bis 500; Aufbewahrung 14 Tage)
{% endif %}
</span>
</div>
<div class="divide-y divide-soft">
{% for e in events %}
<div class="px-4 py-2 mono flex items-start gap-3 flex-wrap">
<span class="text-slate-500 shrink-0">{{ e.ts|de_dt }}</span>
{% if e.kind == 'error' %}
<span class="chip chip-bad">{{ e.source }}</span>
{% else %}
<span class="chip chip-info">{{ e.source }}</span>
{% endif %}
<span class="text-slate-700">{{ e.action }}</span>
{% if e.user %}<span class="text-slate-500">Benutzer: {{ e.user }}</span>{% endif %}
{% if e.details %}<span class="text-slate-500">— {{ e.details }}</span>{% endif %}
{% if e.ip %}<span class="text-slate-400">[{{ e.ip }}]</span>{% endif %}
</div>
{% else %}
<div class="px-4 py-8 text-center text-slate-500">Keine Einträge im gewählten Zeitraum.</div>
{% endfor %}
</div>
</section>
{% endblock %}