secrets tab, drop commute filter, favicon, robust error reports

1. Admin → Geheimnisse sub-tab lets you edit ANTHROPIC_API_KEY +
   BERLIN_WOHNEN_USERNAME/PASSWORD at runtime. Migration v7 adds a
   secrets(key,value,updated_at) table; startup seeds missing keys from
   env (idempotent). web reads secrets DB-first (env fallback) via
   llm._api_key(); alert fetches them from web /internal/secrets on each
   scan, passes them into Scraper(). Rotating creds no longer needs a
   redeploy.
   Masked display: 6 leading + 4 trailing chars, "…" in the middle.
   Blank form fields leave the stored value untouched.

2. Drop the max_morning_commute filter from UI + server + FILTER_KEYS +
   filter summary (the underlying Maps.calculate_score code stays for
   potential future re-enable).

3. /static/didi.webp wired as favicon via <link rel="icon"> in base.html.

4. apply.open_page wraps page.goto in try/except so a failed load still
   produces a "goto.failed" step + screenshot instead of returning an
   empty forensics blob. networkidle + post-submission sleep are also
   made best-effort. The error ZIP export already writes screenshot+HTML
   per step and final_html — with this change every apply run leaves a
   reconstructable trail even when the listing is already offline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
EiSiMo 2026-04-21 17:56:57 +02:00
parent 9fbe1ce728
commit 3bb04210c4
12 changed files with 211 additions and 27 deletions

View file

@ -0,0 +1,46 @@
<h2 class="font-semibold mb-2">Geheimnisse</h2>
<p class="text-sm text-slate-600 mb-4">
Hier hinterlegte Werte überschreiben die entsprechenden Umgebungsvariablen zur Laufzeit.
Leerlassen bedeutet: der gespeicherte Wert bleibt unverändert.
</p>
{% if secret_flash %}
<div class="chip chip-ok mb-4">Gespeichert.</div>
{% endif %}
<form method="post" action="/actions/secrets" class="space-y-5 max-w-xl"
autocomplete="off" data-lpignore="true" data-1p-ignore data-bwignore data-form-type="other">
<input type="hidden" name="csrf" value="{{ csrf }}">
<div>
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">Anthropic API Key</label>
<input class="input" type="text" name="ANTHROPIC_API_KEY"
placeholder="{{ secrets_masked.ANTHROPIC_API_KEY or 'nicht gesetzt' }}"
autocomplete="off" data-lpignore="true" data-1p-ignore data-bwignore>
<p class="text-xs text-slate-500 mt-1">Wird für die Bild-URL-Auswahl durch Haiku verwendet.</p>
</div>
<div class="border-t border-soft pt-4">
<h3 class="font-semibold text-sm mb-3">inberlinwohnen.de Login</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">Benutzer / E-Mail</label>
<input class="input" type="text" name="BERLIN_WOHNEN_USERNAME"
placeholder="{{ secrets_masked.BERLIN_WOHNEN_USERNAME or 'nicht gesetzt' }}"
autocomplete="off" data-lpignore="true" data-1p-ignore data-bwignore>
</div>
<div>
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">Passwort</label>
<input class="input" type="password" name="BERLIN_WOHNEN_PASSWORD"
placeholder="{{ secrets_masked.BERLIN_WOHNEN_PASSWORD or 'nicht gesetzt' }}"
autocomplete="new-password" data-lpignore="true" data-1p-ignore data-bwignore>
</div>
</div>
<p class="text-xs text-slate-500 mt-2">
Wird vom Scraper beim Login auf inberlinwohnen.de verwendet. Änderungen greifen
automatisch beim nächsten Scrape-Zyklus.
</p>
</div>
<button class="btn btn-primary" type="submit">Speichern</button>
</form>

View file

@ -26,10 +26,6 @@
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">min Größe (m²)</label>
<input class="input" name="min_size" value="{{ filters.min_size if filters.min_size is not none else '' }}">
</div>
<div>
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">max Anfahrt morgens (min)</label>
<input class="input" name="max_morning_commute" value="{{ filters.max_morning_commute if filters.max_morning_commute is not none else '' }}">
</div>
<div>
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">WBS benötigt</label>
<select class="input" name="wbs_required">

View file

@ -6,7 +6,7 @@
{% block content %}
<section class="card">
<nav class="flex flex-wrap border-b border-soft px-4">
{% set sections = [('protokoll','Protokoll'), ('benutzer','Benutzer')] %}
{% set sections = [('protokoll','Protokoll'), ('benutzer','Benutzer'), ('geheimnisse','Geheimnisse')] %}
{% for key, label in sections %}
<a href="/admin/{{ key }}"
class="tab {% if section == key %}active{% endif %}">{{ label }}</a>
@ -16,6 +16,7 @@
<div class="p-5">
{% if section == 'protokoll' %}{% include "_admin_logs.html" %}
{% elif section == 'benutzer' %}{% include "_settings_users.html" %}
{% elif section == 'geheimnisse' %}{% include "_admin_secrets.html" %}
{% endif %}
</div>
</section>

View file

@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<link rel="icon" type="image/webp" href="/static/didi.webp">
<title>{% block title %}wohnungsdidi{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@2.0.3"></script>