lazyflat/web/templates/_dashboard_body.html
Moritz 69f2f1f635 lazyflat: combined alert + apply behind authenticated web UI
Three isolated services (alert scraper, apply HTTP worker, web UI+DB)
with argon2 auth, signed cookies, CSRF, rate-limited login, kill switch,
apply circuit breaker, audit log, and strict CSP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 09:51:35 +02:00

176 lines
8.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.

<section class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="card p-4">
<div class="text-xs uppercase tracking-wide text-slate-400">alert</div>
<div class="mt-2 text-lg">
{% if last_alert_heartbeat %}
<span class="chip chip-ok">live</span>
{% else %}
<span class="chip chip-warn">kein Heartbeat</span>
{% endif %}
</div>
<div class="text-xs text-slate-400 mt-1">letzter Heartbeat: {{ last_alert_heartbeat or "—" }}</div>
</div>
<div class="card p-4">
<div class="text-xs uppercase tracking-wide text-slate-400">apply</div>
<div class="mt-2 text-lg">
{% if apply_reachable %}
<span class="chip chip-ok">reachable</span>
{% else %}
<span class="chip chip-bad">down</span>
{% endif %}
</div>
<div class="text-xs text-slate-400 mt-1">
{% if circuit_open %}
<span class="chip chip-bad">circuit open</span>
{% elif apply_failures > 0 %}
{{ apply_failures }} recent failure(s)
{% else %}
healthy
{% endif %}
</div>
</div>
<div class="card p-4">
<div class="text-xs uppercase tracking-wide text-slate-400">Modus</div>
<div class="mt-2 text-lg">
{% if mode == "auto" %}
<span class="chip chip-warn">full-auto</span>
{% else %}
<span class="chip chip-info">manuell</span>
{% endif %}
</div>
<form method="post" action="/actions/mode" class="mt-2 flex gap-2">
<input type="hidden" name="csrf" value="{{ csrf }}">
<input type="hidden" name="mode" value="{% if mode == 'auto' %}manual{% else %}auto{% endif %}">
<button class="btn btn-ghost text-sm" type="submit">
→ zu {% if mode == 'auto' %}manuell{% else %}full-auto{% endif %}
</button>
</form>
</div>
<div class="card p-4">
<div class="text-xs uppercase tracking-wide text-slate-400">KillSwitch</div>
<div class="mt-2 text-lg">
{% if kill_switch %}
<span class="chip chip-bad">apply gestoppt</span>
{% else %}
<span class="chip chip-ok">aktiv</span>
{% endif %}
</div>
<form method="post" action="/actions/kill-switch" class="mt-2 flex gap-2">
<input type="hidden" name="csrf" value="{{ csrf }}">
<input type="hidden" name="value" value="{% if kill_switch %}off{% else %}on{% endif %}">
<button class="btn {% if kill_switch %}btn-ghost{% else %}btn-danger{% endif %} text-sm" type="submit">
{% if kill_switch %}Freigeben{% else %}Alles stoppen{% endif %}
</button>
</form>
{% if circuit_open %}
<form method="post" action="/actions/reset-circuit" class="mt-2">
<input type="hidden" name="csrf" value="{{ csrf }}">
<button class="btn btn-ghost text-sm" type="submit">Circuit zurücksetzen</button>
</form>
{% endif %}
</div>
</section>
{% if not apply_allowed %}
<div class="card p-4 border-red-900/50">
<span class="chip chip-bad">apply blockiert</span>
<span class="ml-2 text-sm text-slate-300">{{ apply_block_reason }}</span>
</div>
{% endif %}
<section class="card">
<div class="flex items-center justify-between px-4 py-3 border-b border-[#1e2335]">
<h2 class="font-semibold">Wohnungen</h2>
<span class="text-xs text-slate-400">{{ flats|length }} zuletzt gesehen</span>
</div>
<div class="divide-y divide-[#1e2335]">
{% for flat in flats %}
<div class="px-4 py-3 flex flex-col md:flex-row md:items-center gap-3">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 flex-wrap">
<a class="font-medium truncate hover:underline" href="{{ flat.link }}" target="_blank" rel="noopener noreferrer">
{{ flat.address or flat.link }}
</a>
{% if flat.matched_criteria %}
<span class="chip chip-ok">match</span>
{% else %}
<span class="chip chip-info">info</span>
{% endif %}
{% if flat.last_application_success == 1 %}
<span class="chip chip-ok">beworben</span>
{% elif flat.last_application_success == 0 %}
<span class="chip chip-bad">apply fehlgeschlagen</span>
{% endif %}
</div>
<div class="text-xs text-slate-400 mt-0.5">
{% if flat.rooms %}{{ "%.1f"|format(flat.rooms) }} Z{% endif %}
{% if flat.size %} · {{ "%.0f"|format(flat.size) }} m²{% endif %}
{% if flat.total_rent %} · {{ "%.0f"|format(flat.total_rent) }} €{% endif %}
{% if flat.sqm_price %} ({{ "%.2f"|format(flat.sqm_price) }} €/m²){% endif %}
{% if flat.connectivity_morning_time %} · {{ "%.0f"|format(flat.connectivity_morning_time) }} min morgens{% endif %}
· entdeckt {{ flat.discovered_at }}
</div>
{% if flat.last_application_message %}
<div class="text-xs text-slate-500 mt-1 truncate">↳ {{ flat.last_application_message }}</div>
{% endif %}
</div>
<div class="flex gap-2">
{% if apply_allowed and not flat.last_application_success %}
<form method="post" action="/actions/apply">
<input type="hidden" name="csrf" value="{{ csrf }}">
<input type="hidden" name="flat_id" value="{{ flat.id }}">
<button class="btn btn-primary text-sm" type="submit"
onclick="return confirm('Bewerbung für {{ (flat.address or flat.link)|e }} ausführen?');">
Bewerben
</button>
</form>
{% endif %}
</div>
</div>
{% else %}
<div class="px-4 py-8 text-center text-slate-500">Noch keine Wohnungen gesehen.</div>
{% endfor %}
</div>
</section>
<section class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="card">
<div class="px-4 py-3 border-b border-[#1e2335]"><h2 class="font-semibold">Letzte Bewerbungen</h2></div>
<div class="divide-y divide-[#1e2335]">
{% for a in applications %}
<div class="px-4 py-3 text-sm">
<div class="flex items-center gap-2">
{% if a.success == 1 %}<span class="chip chip-ok">ok</span>
{% elif a.success == 0 %}<span class="chip chip-bad">fail</span>
{% else %}<span class="chip chip-warn">läuft</span>{% endif %}
<span class="chip chip-info">{{ a.triggered_by }}</span>
<span class="text-slate-400 text-xs">{{ a.started_at }}</span>
</div>
<div class="mt-1 truncate">{{ a.address or a.url }}</div>
{% if a.message %}<div class="text-xs text-slate-500 mt-0.5">{{ a.message }}</div>{% endif %}
</div>
{% else %}
<div class="px-4 py-8 text-center text-slate-500">Keine Bewerbungen bisher.</div>
{% endfor %}
</div>
</div>
<div class="card">
<div class="px-4 py-3 border-b border-[#1e2335]"><h2 class="font-semibold">Audit-Log</h2></div>
<div class="divide-y divide-[#1e2335]">
{% for e in audit %}
<div class="px-4 py-2 text-xs font-mono">
<span class="text-slate-500">{{ e.timestamp }}</span>
<span class="text-slate-400">{{ e.actor }}</span>
<span class="text-slate-200">{{ e.action }}</span>
{% if e.details %}<span class="text-slate-500">— {{ e.details }}</span>{% endif %}
</div>
{% else %}
<div class="px-4 py-8 text-center text-slate-500">leer</div>
{% endfor %}
</div>
</div>
</section>