From 4543fa82f8f628cdf01468dfa3566c16dd8157d7 Mon Sep 17 00:00:00 2001 From: ospab Date: Sat, 13 Jun 2026 22:30:01 +0300 Subject: [PATCH] fix(split-tunnel): hot-reload exclusions into running proxy tunnel without reconnect --- ostp-gui/src-tauri/src/lib.rs | 17 ++++++++++++----- ostp-gui/src/main.js | 12 ++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs index 20d0619..062fb5a 100644 --- a/ostp-gui/src-tauri/src/lib.rs +++ b/ostp-gui/src-tauri/src/lib.rs @@ -95,6 +95,7 @@ enum HelperMsg { struct InProcessState { shutdown_tx: Option>, + config_tx: Option>, metrics: Arc, handle: tokio::task::JoinHandle>, error_msg: Arc>>, @@ -443,10 +444,13 @@ async fn reload_tunnel(state: tauri::State<'_, AppState>) -> Result { - // Restarting in-process tunnel is not supported without re-calling start_tunnel, - // but we can just abort and we should really call start_tunnel again. - // For now, return false. + Some(TunnelHandle::InProcess(s)) => { + // Hot-reload exclusions by pushing new config into the watch channel. + // If config_tx is None (old tunnel without this feature), return false. + if let Some(ref tx) = s.config_tx { + let _ = tx.send(core_cfg); + return Ok(true); + } return Ok(false); } None => {} @@ -557,12 +561,14 @@ async fn start_proxy_in_process( }); let (shutdown_tx, shutdown_rx) = watch::channel(false); + // Config hot-reload channel: allows updating exclusions while tunnel is running. + let (config_tx, config_rx) = watch::channel(mapped.clone()); let metrics_clone = metrics.clone(); let error_msg = Arc::new(tokio::sync::Mutex::new(None)); let error_msg_clone = error_msg.clone(); let handle = tokio::spawn(async move { - match ostp_client::runner::run_client_core(mapped, metrics_clone, shutdown_rx, None).await { + match ostp_client::runner::run_client_core(mapped, metrics_clone, shutdown_rx, Some(config_rx)).await { Ok(_) => Ok(()), Err(e) => { let mut err_guard = error_msg_clone.lock().await; @@ -575,6 +581,7 @@ async fn start_proxy_in_process( guard.tunnel = Some(TunnelHandle::InProcess(InProcessState { shutdown_tx: Some(shutdown_tx), + config_tx: Some(config_tx), metrics, handle, error_msg, diff --git a/ostp-gui/src/main.js b/ostp-gui/src/main.js index 822f6c5..122ffb5 100644 --- a/ostp-gui/src/main.js +++ b/ostp-gui/src/main.js @@ -431,6 +431,9 @@ async function handleSave(silent = false) { const ok = await invoke('save_config', { jsonContent: JSON.stringify(rawConfig, null, 2) }); if (!ok && !silent) { showToast(t('toast_error'), 'error'); + } else if (ok && appState === 'connected') { + // Hot-reload exclusions into the running tunnel (no reconnect needed) + try { await invoke('reload_tunnel'); } catch { /* ignore */ } } } catch (err) { if (!silent) showToast(String(err), 'error'); @@ -598,14 +601,7 @@ window.addEventListener('DOMContentLoaded', async () => { // Auto-save wiring for standard form elements (excluding tag-inputs which wire themselves) const formInputs = document.querySelectorAll('#settings-screen input:not(#in-import-url):not(.tag-input-field), #settings-screen select'); formInputs.forEach(el => { - el.addEventListener('input', () => { - scheduleAutoSave(); - if (appState === 'connected') { - if (window.__TAURI__ && window.__TAURI__.invoke) { - window.__TAURI__.invoke('reload_tunnel'); - } - } - }); + el.addEventListener('input', scheduleAutoSave); el.addEventListener('change', scheduleAutoSave); });