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:
parent
9fbe1ce728
commit
3bb04210c4
12 changed files with 211 additions and 27 deletions
49
web/db.py
49
web/db.py
|
|
@ -206,6 +206,14 @@ MIGRATIONS: list[str] = [
|
|||
"""
|
||||
ALTER TABLE user_filters ADD COLUMN max_age_hours INTEGER;
|
||||
""",
|
||||
# 0007: secrets table — API keys / scraper creds editable from admin UI
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS secrets (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
""",
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -237,6 +245,47 @@ def now_iso() -> str:
|
|||
return datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Secrets (admin-editable, source of truth for runtime creds)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SECRET_KEYS = ("ANTHROPIC_API_KEY", "BERLIN_WOHNEN_USERNAME", "BERLIN_WOHNEN_PASSWORD")
|
||||
|
||||
|
||||
def get_secret(key: str) -> Optional[str]:
|
||||
row = _conn.execute("SELECT value FROM secrets WHERE key = ?", (key,)).fetchone()
|
||||
return row["value"] if row else None
|
||||
|
||||
|
||||
def set_secret(key: str, value: str) -> None:
|
||||
with _lock:
|
||||
_conn.execute(
|
||||
"INSERT INTO secrets(key, value, updated_at) VALUES (?, ?, ?) "
|
||||
"ON CONFLICT(key) DO UPDATE SET value = excluded.value, "
|
||||
" updated_at = excluded.updated_at",
|
||||
(key, value, now_iso()),
|
||||
)
|
||||
|
||||
|
||||
def all_secrets() -> dict[str, str]:
|
||||
rows = _conn.execute("SELECT key, value FROM secrets").fetchall()
|
||||
return {r["key"]: r["value"] for r in rows}
|
||||
|
||||
|
||||
def seed_secrets_from_env() -> None:
|
||||
"""Copy env values into the DB for any secret key that's still empty.
|
||||
Idempotent: existing DB values are never overwritten."""
|
||||
import os
|
||||
for k in SECRET_KEYS:
|
||||
existing = get_secret(k)
|
||||
if existing:
|
||||
continue
|
||||
env_val = os.environ.get(k, "")
|
||||
if env_val:
|
||||
set_secret(k, env_val)
|
||||
logger.info("seeded secret %s from env", k)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# System state
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue