Skip to content

Commit a71abb9

Browse files
feat: enhanced support export with anonymized preview modal and Protokoll tab (#95)
* feat: API key auto-generate on first start + anonymized support export - Auto-generate 64-char hex API key on first start if not set via env or DB; saved to config_entries so it survives restarts and is visible in Settings → Security (like Sonarr/Radarr) - Add GET /api/v1/auth/bootstrap (localhost-only) so the SPA can self-configure its API key without manual copy-paste; stored in localStorage, picked up by the existing axios interceptor - bootstrapApiKey() called in main.tsx before first render — no key already in localStorage = one silent preflight fetch, then normal flow - Add GET /api/v1/logs/support-export: anonymized ZIP bundle for developer debugging; strips API keys, passwords, IPs, email addresses, and absolute paths before packaging log files + system-info.txt * docs: support export design spec * docs: finalize support export design spec; fix vite ws proxy changeOrigin * docs: add support export implementation plan * feat: add module-level _anonymize() with RFC1918 IP classification and dynamic hostname Adds _classify_ip() and _anonymize() helpers to backend/routes/system.py for redacting IPs (RFC1918 partial, public full), API keys, emails, Unix home paths, and arbitrary file paths from log text. Hostname redaction is resolved at call time via socket.gethostname() unless passed explicitly. Creates backend/tests/test_support_export.py with 10 TDD unit tests covering all anonymization rules (all green). * fix: correct _UNIX_HOME_RE to handle /root/ paths; add test - Change _UNIX_HOME_RE from `/(?:home|root)/[^/\s]+(/[^\s]*)` to `/(?:home/[^/\s]+|root)(/[^\s]+)` so /root/.bashrc → ~/.bashrc (previously /root was parsed as the home dir, requiring a second / that never appeared, causing the pattern to not match) - Add _IP_RE comment documenting the version-string over-redaction trade-off - Add test_root_path_shortened to TestAnonymize * feat: add _build_diagnostic(), _extract_top_errors(), _get_last_scan_minutes() helpers * feat: add /logs/support-preview endpoint; enhance /logs/support-export with full ZIP contents * feat: add i18n keys for ProtokollTab and support export modal (en + de) * feat: add fetchSupportPreview and downloadSupportBundle to API client * feat: add ProtokollTab with log settings, viewer prefs, and support export modal * feat: add Protokoll tab to System settings group; remove log_level from General tab * feat: remove rotation UI from Logs; apply log viewer category filter and display prefs - Remove useLogRotation, useUpdateLogRotation hooks and rotation state - Remove rotation collapsible JSX block (lines 227-304) - Remove ChevronDown, ChevronUp, Save, Loader2, toast imports (no longer used) - Add CATEGORY_PREFIXES module-level constant for log category filtering - Add logViewPrefs (localStorage reader), isLineVisible, formatLine, visibleLogs - Wire virtualizer and empty-state check to visibleLogs instead of filtered - Apply formatLine() and wrapLines style to each rendered log entry - Remove rotation i18n keys from en/logs.json and de/logs.json * fix: correct i18n key for Protokoll tab in TAB_KEYS protokoll_tab is defined at the root of settings.json, not under the tabs namespace. TAB_KEYS was using 'tabs.protokoll_tab' which would silently fall back to the raw key string in the UI. --------- Co-authored-by: Abrechen2 <179815050+DennisSico@users.noreply.github.com>
1 parent 58c256b commit a71abb9

File tree

17 files changed

+3197
-143
lines changed

17 files changed

+3197
-143
lines changed

backend/app.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ def add_security_headers(response):
339339
init_webhook_subscribers(webhook_dispatcher)
340340

341341
# Apply DB config overrides on startup (settings saved via UI take precedence)
342-
from db.config import get_all_config_entries
342+
from db.config import get_all_config_entries, save_config_entry
343343

344344
_db_overrides = get_all_config_entries()
345345
if _db_overrides:
@@ -348,6 +348,15 @@ def add_security_headers(response):
348348
else:
349349
logger.info("No config overrides in database, using env/defaults")
350350

351+
# Auto-generate API key on first start if not set via env or DB
352+
if not settings.api_key:
353+
import secrets
354+
355+
_generated_key = secrets.token_hex(32)
356+
save_config_entry("api_key", _generated_key)
357+
settings = reload_settings(get_all_config_entries())
358+
logger.info("API key auto-generated on first start (64 hex chars)")
359+
351360
# Initialize plugin system
352361
plugins_dir = getattr(settings, "plugins_dir", "")
353362
if plugins_dir:

backend/routes/auth_ui.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,27 @@ def get_status():
3636
)
3737

3838

39+
@auth_ui_bp.get("/bootstrap")
40+
def bootstrap():
41+
"""Return the API key for the local frontend.
42+
43+
Only accessible from localhost or when a valid UI session exists.
44+
This allows the SPA to auto-configure its API key without requiring
45+
manual copy-paste, similar to how Sonarr/Radarr work.
46+
"""
47+
from config import get_settings
48+
49+
remote = request.remote_addr or ""
50+
is_local = remote in ("127.0.0.1", "::1", "localhost")
51+
is_session_auth = _is_session_authenticated()
52+
53+
if not is_local and not is_session_auth:
54+
return jsonify({"error": "Forbidden"}), 403
55+
56+
settings = get_settings()
57+
return jsonify({"api_key": settings.api_key})
58+
59+
3960
@auth_ui_bp.post("/setup")
4061
def setup():
4162
if ui_auth.is_ui_auth_configured():

0 commit comments

Comments
 (0)