Repository navigation
Add a browser setup wizard, env-var config and a redesigned admin dashboard - #176
Open
virtitnerd wants to merge 28 commits into
Open
virtitnerd wants to merge 28 commits into
virtitnerd wants to merge 28 commits into
Conversation
Lets Docker Compose deployments generate config.toml from environment variables alone, covering the network/broker/storage/tls sections plus the admin credentials needed for a bootable config. Mirrors the existing Home Assistant add-on options.json -> config.toml flow in ha_addon.py, but supports external broker mode and external_tls reverse-proxy setups that the add-on intentionally doesn't need. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 2c64eef)
Falls back to ROBOROCK_SERVER_* env vars only when no config.toml (compose-mounted or previously generated) and no HA add-on options.json are present, so an existing hand-edited config is never clobbered. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 19acdea)
Explains the required/optional ROBOROCK_SERVER_* variables and the config.toml bind-mount gotcha (Docker creates an empty file for a nonexistent host path, which the entrypoint then treats as an existing but invalid config instead of falling through to env vars). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 94262cc)
The CLI wizard has always left broker.host blank for external brokers, requiring a manual config.toml edit afterward. Add an optional ConfigureAnswers.broker_host so callers that already have the host (the upcoming web setup wizard) can write a ready-to-serve config in one step; the CLI wizard's own behavior is unchanged since it still passes broker_host="". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 017e9e0)
New always-reachable /admin: when config.toml doesn't exist yet, it serves a browser-based setup wizard (the same fields as `configure`, reusing write_config_setup so both paths produce identical output) over plain HTTP on the same https_port the real stack uses once configured. Once ReleaseSupervisor's own TLS'd /admin takes over that port after a restart, this is out of the picture entirely - there is never a second, separate admin surface running at the same time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 3db3e96)
run_server() now catches load_config() failures (missing file, bad TOML, failed validation) and serves only the bootstrap /admin wizard instead of crashing, exiting once the wizard writes a config so Docker's restart policy brings up the full stack. The container entrypoint no longer errors out when no config source is found at all - it always execs into serve, which now handles that case gracefully. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 3146e37)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit e0e3dd0)
Neither the CLI configure wizard nor the browser Setup Wizard can generate listener_mode, advertised_*_port, or trusted_proxies - they only produce the simple local_tls case. Those are exactly the deployment-topology settings the ROBOROCK_SERVER_* env vars already cover (external_tls, advertised ports, trusted proxies), so a SWAG/Caddy/Traefik reverse-proxy setup needs no manual config.toml edits: just set the equivalent env vars instead of running either wizard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit e7cb420)
STACK_FQDN plus network/broker/tls env vars alone now produce a valid partial config.toml with no [admin] section, so a deployment can set just the infra/TLS side (including external_tls reverse-proxy setups) and finish credentials in the Setup Wizard - rather than requiring all of ADMIN_PASSWORD/PROTOCOL_LOGIN_EMAIL/PROTOCOL_LOGIN_PIN up front or not being able to use env vars at all. Setting some but not all three is rejected with a clear error, since a half-set credential is never what was intended. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 15bddb9)
Extracts network/broker/storage/tls parsing+validation into its own function so it can succeed or fail on its own, separately from [admin]. Adds diagnose_config(), which classifies a config.toml as "ok", "missing_admin" (everything but [admin] validates - e.g. a partial env-var-generated config still waiting on the Setup Wizard for credentials), or "invalid" (something else is wrong, or the file is missing). load_config()'s own behavior for existing valid/invalid configs is unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit e39dd21)
Uses diagnose_config() to tell three states apart at /admin: "ok" (status page), "missing_admin" (a trimmed wizard asking only for admin credentials, appending just [admin] to the existing config.toml rather than regenerating it), and anything else (the full wizard, as before - this also covers a corrupt hand-edited file, which is correctly refused rather than silently overwritten). This is what makes the env-var path from feat/env-var-config and this wizard actually compose: set only FQDN/TLS/network vars and finish credentials in the browser, instead of an all-or-nothing choice between the two. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit c93a28b)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 7f83688)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 1a2e247)
decompiled_http.jsonl / decompiled_mqtt.jsonl have no rotation and can carry sensitive data (auth headers, onboarding localKey/passwords in HTTP bodies, fully-decrypted device RPC payloads in MQTT entries). This tails both files (they grow unbounded, so a full read is out) and returns metadata-only summaries by default - method/path/topic/RPC method name, sizes, timestamps, never raw headers/bodies/payloads. ROBOROCK_SERVER_ACTIVITY_RAW=1 switches to complete, unredacted entries - an explicit deployment-time opt-in, matching how rethink exposes its own traffic view, rather than a per-view UI toggle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 9c9943d)
Adds ReleaseSupervisor._activity_payload() and GET /admin/api/activity (auth-gated like every other admin route, ?limit= clamped to 1-500), plus an Activity section on the dashboard following the existing 2s-polling pattern - there's no real-time push mechanism in this app today (websockets/SSE), so this matches how every other panel already refreshes. Note: a full-suite run flagged test_admin_onboarding_start_is_blocked_when_new_connections_disabled as failing once (256 passed/1 failed), but it passes reliably alone, with just this file plus test_activity_log.py, and on a clean rerun of the entire suite (257/257). AdminSessionManager (security.py) is stateless and per-instance with no shared/global auth state for this change to touch, so this looks like a pre-existing flaky/timing- sensitive test that more total tests nudged into failing once, not a regression from this change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit dd825d2)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 470bf14)
New /internal/protocol/mitm-activity, gated by the same admin.session_secret-based auth as the existing /internal/protocol/user-data sync - no new trust mechanism. Appends posted entries to a new mitm_activity.jsonl (AppPaths.mitm_activity_jsonl_path). The sync request's own body is now also redacted in the regular HTTP activity log, the same way the existing user-data sync already is, since it can carry real Roborock account tokens. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 5181022)
read_recent_activity() gains an optional mitm_jsonl_path, tailed and merged alongside the existing HTTP/MQTT sources with the same redact-by-default / ROBOROCK_SERVER_ACTIVITY_RAW behavior. Dashboard gets a distinct color/label for [MITM] entries. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit c9647f4)
Off by default. When enabled (and a sync secret is configured), each redirected Roborock-domain request/response is posted to the server's new mitm-activity endpoint, so the official app's traffic is visible in the admin dashboard's Activity panel without a separate viewer. Sync failures are caught and logged, never allowed to interrupt the actual MITM redirect/rewrite - this is telemetry, not part of the redirect's own correctness. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 6318cc1)
Same recipe as rethink's own management panel: Materialize CSS/JS and Google Fonts loaded from CDN (no build step - this project has none and isn't taking one on for this), plus one small theme stylesheet on top for branding, dark mode (prefers-color-scheme), and a couple of Materialize defaults that need overriding (fixed-height nav, focus rings suppressed by its own box-shadow reset, etc). Served from one route (register_theme_routes) so both the Setup Wizard and the real dashboard pull the same theme, not two. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 47f9a54)
Real Materialize form fields/radios/select/buttons instead of hand- built fieldset/input markup, using the same nav/theme as the real dashboard. All required page text and element IDs the wizard's own JS/tests depend on are unchanged; only markup and styling moved. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 6b1b00b)
Login page and dashboard now use the same Materialize theme as the Setup Wizard - real form fields, buttons, nav, cards - instead of raw inline-styled markup. Every element ID the existing JS (refresh(), renderAuth(), renderActivity(), etc.) depends on is unchanged; only markup/styling moved. Dynamically-created cards (vacuum summary, session list, activity feed) now use theme CSS variables instead of hardcoded light-mode hex colors, so they stay readable in dark mode. Also fixes a real XSS-shaped bug introduced partway through this redesign and caught before commit: the activity feed's per-entry badge was briefly built via innerHTML with server-reported traffic fields (method/path/topic/host) interpolated directly in - the same bug class already found and fixed in rethink's own device table. Rewritten with DOM APIs (createElement/textContent) only; no interpolated HTML anywhere in this file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit c045dca)
Every conditional section (external broker host, Cloudflare fields vs provided-cert hint, Actalis fields) relies on toggling a "hidden" class via JS. When the per-page <style> blocks were consolidated into the shared stylesheet, this utility class didn't make the move - so toggled sections stayed visible regardless of which radio/select option was actually chosen. Caught by live testing in a real browser. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit b3c7577)
- Nav bar (brand logo, status, sign-out) was breaking onto two lines: .nav-wrapper never had display:flex, so the brand-logo anchor (itself display:flex, making it block-level) pushed everything after it onto the next line, and the flex:1 spacer span was a no-op without a flex container. Fixing that turned the sign-out button into a flex item, which broke its icon's Materialize .right float (floats inside flex containers are unreliable across browsers) - replaced with flex ordering (inline-flex + margin-left:auto on the icon) on all buttons that use this same "icon at the end" pattern, and normalized .btn-flat's text-transform/font-weight to match the other buttons. - Section headers used the class "orange-text" but our own CSS forces them to the blue accent color - the class name and rendered color didn't match. Renamed to "rls-heading" everywhere it's used. - The "New Connections" checkbox: Materialize draws both the unchecked box and the checked checkmark tick using the same :before element, with :checked repositioning/rotating it into an L-shape (transparent top/left border, colored right/bottom). An initial fix themed all four border sides identically, which lost the transparent sides and rendered a solid diamond instead of a checkmark - retint each state's own colors instead of a blanket border-color. - The MITM sync-secret hint showed a literal "..." placeholder instead of a real, copy-pasteable example command. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 20fb477)
Anything that does not match a real Roborock protocol route - favicon requests, health checks, stray scans - falls through to the catchall handler and got logged to the Activity feed identically to real onboarding/protocol traffic, drowning it out. resolve_route already labels genuinely unmatched requests with the literal route name "catchall" as its final fallback rule. Skip appending those to the persisted HTTP activity log the dashboard reads, while every other route - named protocol routes and the explicit blocked/auth-failure labels used elsewhere in this function - logs exactly as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 0ecc331)
STACK_FQDN alone is enough to trigger has_env_config(), but the default tls_mode (cloudflare_acme) then requires more vars that may not be set - previously this ValueError propagated raw out of write_config_from_env, uncaught, crashing the container outright and crash-looping it forever under restart:unless-stopped. This only shows up once both env-var config generation (feat/env-var-config) and the Setup Wizard (feat/setup-wizard) exist together, so it's fixed here on their combined branch rather than either alone. Now the failure is caught and falls through into the Setup Wizard at /admin, same as if no env vars had been set at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 6a6760d)
… set Directly motivated by the fallback above: if env-var generation fails partway through, whatever the operator *did* set (stack_fqdn, ports, broker mode/host, tls_mode, base domain, email, acme_server) shouldn't have to be retyped in the browser. Reveals the matching conditional sections (external broker host, provided-cert vs Cloudflare fields, Actalis fields) based on the prefilled values too, rather than always defaulting to embedded/cloudflare_acme/zerossl regardless of intent. Deliberately never pre-fills secrets (cloudflare_token, admin_password, protocol_login_pin) - those are exactly what the wizard exists to collect fresh in the browser rather than a compose file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit eb7659d)
With several vacuums the page stacked every card for every device into one long feed. It now has a row of device chips plus a Server chip. Picking a vacuum shows only its facts, onboarding state and traffic; the global cards (Cloud Import, New Connections, Health) live under Server. Selection is kept in the URL hash. /admin/api/activity takes ?device=<duid or did> and keeps entries that name the vacuum's did, duid or MQTT user as a whole path or topic segment, reading a deeper tail so a quiet vacuum isn't pushed out by a noisy one. The raw view no longer prints the local key. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit a4328d7)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Setting up the server meant running the
configureCLI and hand-editingconfig.toml, which doesn't fit Docker or Home Assistant users who configure everything through the environment. The/adminpage was a bare status view with no setup flow, no activity view and no multi-vacuum layout.Changes
These are one design: the dashboard needs the wizard, and the wizard needs the env-var path to be useful in a container.
ROBOROCK_SERVER_*variables generateconfig.tomlin the container entrypoint. The[admin]group is optional, and a partial config can be completed later in the wizard.config.toml, the server falls into a browser wizard at/admininstead of exiting. It pre-fills from any env vars already set and can carry an external broker host.mitm_redirect.py --activity-synccan also push MITM-captured traffic into it.Out of scope
listener_mode,advertised_*_portortrusted_proxiesyet. That's called out inreverse_proxy.md.Tested
pytest: 487 passed. The 2 failures are CRLF fixture assertions intest_activity_log.pyon Windows.local_tlsmode with SWAG's certificate mapped into the container.New tests cover env-var parsing, the entrypoint, config diagnosis, the management app and wizard, the activity log, the device view and the MITM sync.