feat(remote): add built-in remote management HTTP server with Web UI#2450
feat(remote): add built-in remote management HTTP server with Web UI#2450YongmaoLuo wants to merge 1 commit intofarion1231:mainfrom
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9acf2ad699
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@farion1231 已根据 review 意见修复,请再次检阅。最新 commit: `d80a73a`。 P1: Avoid blocking Tokio runtime in provider-switch broadcast问题: `broadcast_provider_switch` 使用 `blocking_read()`,在 async runtime 线程中调用会 panic。 修改:
效果: 在 tray 切换、failover 切换、provider 切换等路径下,SSE 广播不再 panic,状态同步正常执行。 P2: Restart remote server when toggling Tailscale access问题: Tailscale 开关仅持久化设置,不重新配置运行中的服务器。 修改:
效果: 服务器运行中切换 Tailscale 时,自动重启并绑定/解绑 Tailscale IP,UI 实时反馈重启进度。 P2: Preserve enabled state when changing remote server port问题: 端口变更时停止服务器并设置 `remoteEnabled: false`,导致用户必须手动重新启用。 修改:
效果: 端口变更即时生效,服务器自动在新端口重启,无需手动重新启用。经过测试,从 3999 → 4000 等切换均正常。 额外补充
|
eb3443d to
d80a73a
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 136daa55b0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let listener = tokio::net::TcpListener::bind(addr) | ||
| .await | ||
| .map_err(|e| format!("Failed to bind {addr}: {e}"))?; |
There was a problem hiding this comment.
Roll back already-started listeners on bind error
If tailscale_enabled is on, this loop can successfully bind and spawn the localhost server, then fail on the Tailscale bind and return Err immediately; the already-started listener keeps running and its shutdown handle remains registered. That leaves the app in a partially-running state (command reports failure, but one server is alive) and can cause later start calls to behave as “already running.”
Useful? React with 👍 / 👎.
| let app = app.clone(); | ||
| let app_type_str = app_type_str.to_string(); | ||
| let provider_id = provider_id.to_string(); | ||
| tauri::async_runtime::spawn(async move { | ||
| crate::remote::broadcast_provider_switch(&app, &app_type_str, &provider_id).await; |
There was a problem hiding this comment.
Remove duplicate SSE broadcast in tray provider switch
This path now sends the same remote switch event twice: commands::switch_provider already spawns broadcast_provider_switch, and this additional spawn sends it again for tray-triggered switches. Clients listening on /api/events will receive duplicate switch events for a single user action, which can cause duplicate notifications and unnecessary reloads.
Useful? React with 👍 / 👎.
| setToggling(true); | ||
| onChange({ remoteEnabled: value }); | ||
|
|
There was a problem hiding this comment.
Revert remoteEnabled when start/stop command fails
The setting is persisted before the start/stop RPC succeeds, and the catch path only shows a toast. If start_remote_server fails (for example, port already in use), remoteEnabled remains saved as enabled even though the server is not running, so the UI/state becomes inconsistent and can mislead users on next launch.
Useful? React with 👍 / 👎.
| std::process::Command::new("/usr/bin/which") | ||
| .arg("tailscale") |
There was a problem hiding this comment.
Use cross-platform Tailscale binary resolution
Tailscale detection is hardcoded to Unix paths and /usr/bin/which, so check_tailscale_available will incorrectly return false on Windows even when Tailscale is installed. This makes the new Tailscale toggle effectively unusable on supported Windows environments.
Useful? React with 👍 / 👎.
|
@claude review |
|
Claude finished @farion1231's task in 4m 0s —— View job Claude PR ReviewThree blocking issues and one nit. The async fixes from the previous round look correct; new findings are about double-broadcast wiring, a cross-platform regression, and an SSE message sent for a no-op. 🔴 Important — Duplicate SSE broadcast on tray-clicked provider switchFile:
The same was already noted by Codex; flagging because it's still present in 🔴 Important — Tailscale detection unusable on WindowsFile:
Suggested fix: gate the Unix paths behind 🔴 Important — Spurious "switched to empty provider" SSE on failover-disableFile: When {"type":"switch","provider_id":"","name":""}Browsers will surface this as a switch toast for an empty provider. Move the broadcast inside the 🟡 Nit —
|
Add a lightweight Axum-based HTTP server for remote provider switching via browser/mobile. Replaces the external Python remote script to avoid race conditions and state inconsistency. Key features: - Web UI at http://localhost:4000 for provider switching - SSE events for real-time sync across multiple browser tabs - Per-address listener management: localhost is preserved when Tailscale IP is toggled (no "connection lost" page) - AtomicBool runtime gate: stopped server rejects new requests even on keep-alive connections - Graceful shutdown with abort fallback for timed-out listeners - Tailscale IP support for remote access from mobile devices Architecture: - remote/mod.rs: RemoteServer with HashMap<SocketAddr, ListenerEntry> for incremental address updates - remote/handlers.rs: Axum handlers with running flag checks - remote/html.rs: Embedded responsive web UI (dark mode support) - commands/remote.rs: Tauri commands (start/stop/restart/check) - Frontend: RemoteSettings component with health polling and restart flow Bug fixes in this commit: - health_check now returns 503 when server is stopped - restartServer only persists settings after confirming server is up - ManagedRemoteServer encapsulation (private field + lock() method) - start_addrs simplified to return Vec<String> instead of Result - Removed hardcoded "claude" in favor of REMOTE_APP constant Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
136daa5 to
a07c254
Compare
Review 意见回复本次提交将所有 commit 压缩为 1 个( Codex ReviewP1: Avoid blocking Tokio runtime in provider-switch broadcast
P2: Restart remote server when toggling Tailscale access
P2: Preserve enabled state when changing remote server port
P1: Roll back already-started listeners on bind error
P2: Remove duplicate SSE broadcast in tray provider switch
P2: Revert remoteEnabled when start/stop command fails
P2: Use cross-platform Tailscale binary resolution
Claude ReviewDuplicate SSE broadcast on tray-clicked provider switch
Tailscale detection unusable on Windows
Spurious "switched to empty provider" SSE on failover-disable
Nit: remoteEnabled not reverted when start RPC fails
额外修复(squash 过程中发现)
检查结果
|
Summary
Adds a lightweight HTTP server embedded in the CC Switch Tauri app, providing a mobile-friendly Web UI and REST API for provider switching. The server shares state with the desktop app via
ProviderService::switch(), ensuring backfill and consistency. SSE keeps all connected browsers and the desktop app in sync in real-time.Closes #2418
Architecture
Backend Components
remote/mod.rsRemoteServerwith start/stop lifecycle, SSE broadcast, Tailscale IP detectionremote/handlers.rsremote/html.rsprefers-color-scheme), EventSource for real-time SSE updatescommands/remote.rsstart_remote_server,stop_remote_server,check_tailscale_available,get_tailscale_iplib.rssettings.rsremote_enabled,remote_port,remote_tailscale_enabledsettings fieldstray.rs,provider.rs,failover.rs,failover_switch.rsFrontend Components
RemoteSettings.tsxtypes.tsen.json,zh.json,ja.jsonKey Design Decisions
ProviderService::switch()directly, no separate binary needed/usr/local/bin,/opt/homebrew/bin,/usr/bin) to avoid macOS GUI app PATH limitations@media (prefers-color-scheme)so remote devices follow their own OS appearanceExpected Behavior
http://localhost:4000(configurable port 1024-65535)GET /api/providers,POST /api/switch,GET /api/current,GET /api/eventsfor programmatic access (e.g., cc-connect / IM bots)http://100.x.x.x:4000) for mobile accessScreenshots
Checklist
pnpm typecheckpassespnpm format:checkpassescargo clippypasses (0 warnings)cargo fmt --checkpassespnpm test:unitpasses (32 files, 204 tests)