From 0ac93dfeb6e746f50e8ac322b89ee717bf900762 Mon Sep 17 00:00:00 2001 From: nessi Date: Wed, 18 Mar 2026 12:35:25 +0100 Subject: [PATCH] refactor: wrap tunnel connect/disconnect operations in spawn_blocking and add pending state UI feedback Move tunnel_manager::connect and disconnect calls into spawn_blocking tasks to prevent blocking async runtime. Clone app handle and profile path before spawning. Add map_err for task join failures. Add tunnelActionPending state to track in-progress tunnel operations. Pass busy prop to AppHeader and disable sync/logout/connect buttons during tunnel actions. Update connect button text to show " --- desktop-client/src-tauri/src/lib.rs | 17 ++++++++++++++--- desktop-client/src/App.tsx | 7 ++++++- desktop-client/src/components/AppHeader.tsx | 10 ++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/desktop-client/src-tauri/src/lib.rs b/desktop-client/src-tauri/src/lib.rs index 95c85e4..1cd13c7 100644 --- a/desktop-client/src-tauri/src/lib.rs +++ b/desktop-client/src-tauri/src/lib.rs @@ -393,20 +393,31 @@ async fn select_access_profile(app: AppHandle, profile_id: String) -> Result Result { let session_state = sync_current_session(&app).await?; - let result = tunnel_manager::connect(&app, std::path::Path::new(&session_state.profile_path)); + let app_handle = app.clone(); + let profile_path = session_state.profile_path.clone(); + let result = tauri::async_runtime::spawn_blocking(move || { + tunnel_manager::connect(&app_handle, std::path::Path::new(&profile_path)) + }) + .await + .map_err(|err| format!("Unable to join tunnel connect task: {err}"))?; refresh_tray_menu(&app); result?; Ok(session_state.enrollment) } #[tauri::command] -fn disconnect_tunnel(app: AppHandle, state: State<'_, AppState>) -> Result<(), String> { +async fn disconnect_tunnel(app: AppHandle, state: State<'_, AppState>) -> Result<(), String> { let profile_path = { let session = state.session.lock().map_err(|_| "Unable to read client state".to_string())?; let session = session.as_ref().ok_or_else(|| "No active session is available".to_string())?; session.profile_path.clone() }; - let result = tunnel_manager::disconnect(&app, std::path::Path::new(&profile_path)); + let app_handle = app.clone(); + let result = tauri::async_runtime::spawn_blocking(move || { + tunnel_manager::disconnect(&app_handle, std::path::Path::new(&profile_path)) + }) + .await + .map_err(|err| format!("Unable to join tunnel disconnect task: {err}"))?; refresh_tray_menu(&app); result } diff --git a/desktop-client/src/App.tsx b/desktop-client/src/App.tsx index 0a14500..a108632 100644 --- a/desktop-client/src/App.tsx +++ b/desktop-client/src/App.tsx @@ -69,6 +69,7 @@ export function App() { const [loading, setLoading] = useState(false); const [syncing, setSyncing] = useState(false); const [selectingProfile, setSelectingProfile] = useState(false); + const [tunnelActionPending, setTunnelActionPending] = useState(false); const [error, setError] = useState(null); const [connected, setConnected] = useState(false); const [state, setState] = useState(null); @@ -187,6 +188,7 @@ export function App() { async function toggleConnection() { const command = connected ? "disconnect_tunnel" : "connect_tunnel"; + setTunnelActionPending(true); try { if (!connected) { const syncedState = await invoke("connect_tunnel"); @@ -213,6 +215,8 @@ export function App() { } } setError(formatInvokeError(err, "Tunnel action failed")); + } finally { + setTunnelActionPending(false); } } @@ -231,6 +235,7 @@ export function App() {
diff --git a/desktop-client/src/components/AppHeader.tsx b/desktop-client/src/components/AppHeader.tsx index 1269738..53991ff 100644 --- a/desktop-client/src/components/AppHeader.tsx +++ b/desktop-client/src/components/AppHeader.tsx @@ -1,6 +1,7 @@ import { ActionButton } from "./ActionButton"; type AppHeaderProps = { + busy: boolean; enrolled: boolean; connected: boolean; syncing: boolean; @@ -10,6 +11,7 @@ type AppHeaderProps = { }; export function AppHeader({ + busy, enrolled, connected, syncing, @@ -36,10 +38,10 @@ export function AppHeader({
{enrolled ? ( <> - + {syncing ? "Syncing..." : "Sync"} - + Logout @@ -47,11 +49,11 @@ export function AppHeader({
- {!enrolled ? "Provision first" : connected ? "Disconnect" : "Connect"} + {!enrolled ? "Provision first" : busy ? connected ? "Disconnecting..." : "Connecting..." : connected ? "Disconnect" : "Connect"}