* Wohnungen header: single slim row with Alert status · Filter summary · Auto-Bewerben toggle · Trockenmodus toggle. Big filter panel removed — filters live only in /einstellungen/filter. * Alert status: 'nicht eingerichtet' until the user has actual filters (+ valid notification creds if telegram/email). 'aktiv' otherwise. * Logs tab: admin-only (gated both in layout and server-side). Shows merged audit + errors across all users, sorted newest-first, capped at 300. * Apply, auto-apply, trockenmodus and circuit reset buttons post via HTMX and swap the Wohnungen body. While any application is still running for the user the poll interval drops from 30s to 3s so status flips to 'beworben' or 'fehlgeschlagen' almost immediately. * Browser tab title is now always 'lazyflat'. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
119 lines
5.3 KiB
HTML
119 lines
5.3 KiB
HTML
{% extends "_layout.html" %}
|
|
{% block content %}
|
|
<a href="/bewerbungen" class="text-sm">← zurück zu den Bewerbungen</a>
|
|
|
|
<section class="card p-5">
|
|
<div class="flex items-center gap-2 flex-wrap mb-3">
|
|
<h2 class="font-semibold text-lg">Bewerbung #{{ application.id }}</h2>
|
|
{% 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>
|
|
{% 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 %}
|
|
|
|
{% if application.success == 0 %}
|
|
<a class="btn btn-danger text-sm ml-auto"
|
|
href="/bewerbungen/{{ application.id }}/report.zip">
|
|
Fehler-Report herunterladen (ZIP)
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
<div class="text-sm text-slate-600 space-y-1">
|
|
<div><span class="text-slate-500">URL:</span> <a href="{{ application.url }}" target="_blank" rel="noopener">{{ application.url }}</a></div>
|
|
<div><span class="text-slate-500">gestartet:</span> {{ application.started_at|de_dt }}</div>
|
|
<div><span class="text-slate-500">beendet:</span> {{ application.finished_at|de_dt if application.finished_at else "—" }}</div>
|
|
{% if application.message %}<div><span class="text-slate-500">Meldung:</span> {{ application.message }}</div>{% endif %}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card p-5">
|
|
<details>
|
|
<summary class="font-semibold">Profil-Snapshot zum Bewerbungszeitpunkt</summary>
|
|
<pre class="mono whitespace-pre-wrap break-all mt-3 p-3 bg-[#f6fafd] rounded-lg border border-soft">{{ profile_snapshot | tojson(indent=2) }}</pre>
|
|
</details>
|
|
</section>
|
|
|
|
{% if forensics %}
|
|
<section class="card p-5 space-y-4">
|
|
<h3 class="font-semibold">Forensik</h3>
|
|
|
|
<details open>
|
|
<summary class="font-medium">Step-Log ({{ forensics.steps|length }} Einträge, {{ forensics.duration_s }} s)</summary>
|
|
<div class="mono mt-2 space-y-0.5">
|
|
{% for s in forensics.steps %}
|
|
<div class="{% if s.status != 'ok' %}text-[#b8404e]{% else %}text-slate-700{% endif %}">
|
|
[{{ "%.2f"|format(s.ts) }}s] {{ s.step }} {% if s.status != 'ok' %}({{ s.status }}){% endif %}
|
|
{% if s.detail %}— {{ s.detail }}{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</details>
|
|
|
|
{% if forensics.screenshots %}
|
|
<details>
|
|
<summary class="font-medium">Screenshots ({{ 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>
|
|
<img src="data:image/jpeg;base64,{{ s.b64_jpeg }}" class="w-full rounded" alt="{{ s.label }}">
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</details>
|
|
{% endif %}
|
|
|
|
{% if forensics.console %}
|
|
<details>
|
|
<summary class="font-medium">Browser-Konsole ({{ forensics.console|length }})</summary>
|
|
<div class="mono mt-2 space-y-0.5">
|
|
{% for c in forensics.console %}
|
|
<div class="text-slate-700">[{{ "%.2f"|format(c.ts) }}s] [{{ c.type }}] {{ c.text }}</div>
|
|
{% endfor %}
|
|
</div>
|
|
</details>
|
|
{% endif %}
|
|
|
|
{% if forensics.errors %}
|
|
<details open>
|
|
<summary class="font-medium text-[#b8404e]">Browser-Errors ({{ forensics.errors|length }})</summary>
|
|
<div class="mono mt-2 space-y-0.5 text-[#b8404e]">
|
|
{% for e in forensics.errors %}
|
|
<div>[{{ "%.2f"|format(e.ts) }}s] {{ e.message }}</div>
|
|
{% endfor %}
|
|
</div>
|
|
</details>
|
|
{% endif %}
|
|
|
|
{% if forensics.network %}
|
|
<details>
|
|
<summary class="font-medium">Netzwerk ({{ forensics.network|length }})</summary>
|
|
<div class="mono mt-2 space-y-0.5">
|
|
{% for n in forensics.network %}
|
|
<div class="text-slate-700 break-all">
|
|
[{{ "%.2f"|format(n.ts) }}s]
|
|
{% if n.kind == 'response' %}← {{ n.status }} {{ n.url }}
|
|
{% if n.body_snippet %}<div class="pl-4 text-slate-500">{{ n.body_snippet[:500] }}</div>{% endif %}
|
|
{% else %}→ {{ n.method }} {{ n.url }} [{{ n.resource_type }}]
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</details>
|
|
{% endif %}
|
|
|
|
{% if forensics.final_html %}
|
|
<details>
|
|
<summary class="font-medium">Page HTML ({{ forensics.final_html|length }} B)</summary>
|
|
<pre class="mono whitespace-pre-wrap break-all mt-3 p-3 bg-[#f6fafd] rounded-lg border border-soft max-h-96 overflow-auto">{{ forensics.final_html }}</pre>
|
|
</details>
|
|
{% endif %}
|
|
</section>
|
|
{% elif application.finished_at %}
|
|
<section class="card p-5 text-sm text-slate-500">
|
|
Forensik für diese Bewerbung ist nicht mehr verfügbar (älter als Retention-Zeitraum).
|
|
</section>
|
|
{% endif %}
|
|
{% endblock %}
|