From 372023e99560885a76e8da3487ae705fd2f861d4 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 17 Mar 2026 12:48:59 +0000 Subject: [PATCH] feat: add ws_base_url instance setting for WebSocket URL override (#8405) * feat: add ws_base_url instance setting to override WebSocket base URL Allow deployments behind reverse proxies to route WebSocket traffic (LSP, debugger, multiplayer) to a different host/port than the main frontend via a new instance setting. Co-Authored-By: Claude Opus 4.6 (1M context) * feat: move ws_base_url to Advanced section with toggle and connectivity test - Move setting from Core to Advanced > WebSocket section - Render as toggle "Custom websocket base url from frontend to multiplayer/lsp/debugger" with conditional URL text field - Add Test connectivity button (always visible) that checks HTTP health and WebSocket ping for all three services (LSP, Multiplayer, Debugger) - Add /ws/ping and /ws/health endpoints to LSP service - Add /ws_mp/health HTTP and __ping__ WS handlers to multiplayer service - Add /ping WS handler to debugger service - Add CORS headers to health endpoints for cross-origin testing Co-Authored-By: Claude Opus 4.6 (1M context) * fix: toggle enabled check and testWs promise resolution - Fix enabled derived to check only for null (not empty string), otherwise the toggle never turns on since toggleEnabled sets '' - Fix testWs onclose handler to resolve(false) so the promise doesn't hang if the server closes without sending a message Co-Authored-By: Claude Opus 4.6 (1M context) * fix: make connectivity test work with existing services - HTTP test: accept plain text "ok"/"okay" (old services) in addition to JSON {"status": "ok"} (new services), reject HTML (SPA fallback) - WS test: resolve on onopen (connection established) instead of waiting for a specific pong message, so the test works even with services that don't have the new /ping handler yet Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- backend/windmill-api-settings/src/lib.rs | 2 + .../windmill-common/src/global_settings.rs | 1 + .../windmill-common/src/instance_config.rs | 2 + debugger/dap_debug_service.ts | 13 +- frontend/src/lib/components/Editor.svelte | 45 ++-- .../src/lib/components/InstanceSetting.svelte | 15 +- .../src/lib/components/ScriptEditor.svelte | 7 +- frontend/src/lib/components/debug/index.ts | 9 +- .../src/lib/components/instanceSettings.ts | 30 ++- .../WsConnectivityTest.svelte | 199 ++++++++++++++++++ .../components/sidebar/MultiplayerMenu.svelte | 16 +- frontend/src/lib/stores.ts | 1 + frontend/src/lib/wsUrl.ts | 16 ++ .../src/routes/(root)/(logged)/+layout.svelte | 9 + lsp/pyls_launcher.py | 24 ++- multiplayer/server.mjs | 24 ++- 16 files changed, 349 insertions(+), 64 deletions(-) create mode 100644 frontend/src/lib/components/instanceSettings/WsConnectivityTest.svelte create mode 100644 frontend/src/lib/wsUrl.ts diff --git a/backend/windmill-api-settings/src/lib.rs b/backend/windmill-api-settings/src/lib.rs index 252c21580e..e026386474 100644 --- a/backend/windmill-api-settings/src/lib.rs +++ b/backend/windmill-api-settings/src/lib.rs @@ -45,6 +45,7 @@ use windmill_common::{ APP_WORKSPACED_ROUTE_SETTING, AUTOMATE_USERNAME_CREATION_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, DISABLE_HUB_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS, HUB_ACCESSIBLE_URL_SETTING, HUB_BASE_URL_SETTING, + WS_BASE_URL_SETTING, }, instance_config::{self, ApplyMode, InstanceConfig}, server::Smtp, @@ -526,6 +527,7 @@ pub async fn get_global_setting( && key != DISABLE_HUB_SETTING && key != EMAIL_DOMAIN_SETTING && key != APP_WORKSPACED_ROUTE_SETTING + && key != WS_BASE_URL_SETTING { require_super_admin(&db, &authed.email).await?; } diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 1d2aeafce8..fc984096c3 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -2,6 +2,7 @@ pub const CUSTOM_TAGS_SETTING: &str = "custom_tags"; pub const DEFAULT_TAGS_PER_WORKSPACE_SETTING: &str = "default_tags_per_workspace"; pub const DEFAULT_TAGS_WORKSPACES_SETTING: &str = "default_tags_workspaces"; pub const BASE_URL_SETTING: &str = "base_url"; +pub const WS_BASE_URL_SETTING: &str = "ws_base_url"; pub const OAUTH_SETTING: &str = "oauths"; pub const RETENTION_PERIOD_SECS_SETTING: &str = "retention_period_secs"; pub const AUDIT_LOG_RETENTION_DAYS_SETTING: &str = "audit_log_retention_days"; diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index fb70db90ae..e8c1a8479f 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -245,6 +245,8 @@ pub struct GlobalSettings { // String settings #[serde(skip_serializing_if = "Option::is_none")] + pub ws_base_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub email_domain: Option, #[serde(skip_serializing_if = "Option::is_none")] pub hub_base_url: Option, diff --git a/debugger/dap_debug_service.ts b/debugger/dap_debug_service.ts index 2a7a8fc727..2043dd138e 100644 --- a/debugger/dap_debug_service.ts +++ b/debugger/dap_debug_service.ts @@ -1076,10 +1076,14 @@ const server = Bun.serve({ if (path === '/health') { return new Response(JSON.stringify({ status: 'ok', + service: 'debugger', endpoints: ['/python', '/typescript', '/bun'], nsjail: config.nsjail.enabled }), { - headers: { 'Content-Type': 'application/json' } + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + } }) } @@ -1096,6 +1100,13 @@ const server = Bun.serve({ path = path.slice('/ws_debug'.length) } + // Handle ping test — respond and close immediately + if (path === '/ping') { + ws.send(JSON.stringify({ type: 'pong', service: 'debugger' })) + ws.close() + return + } + logger.info(`New client connected: ${path}`) // Create appropriate session based on path diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index 779bc2bf93..709688f9a2 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -7,6 +7,7 @@ diff --git a/frontend/src/lib/components/InstanceSetting.svelte b/frontend/src/lib/components/InstanceSetting.svelte index 1c69ac8a80..8caacaa553 100644 --- a/frontend/src/lib/components/InstanceSetting.svelte +++ b/frontend/src/lib/components/InstanceSetting.svelte @@ -23,6 +23,7 @@ import SmtpSettings from './instanceSettings/SmtpSettings.svelte' import SecretBackendConfig from './instanceSettings/SecretBackendConfig.svelte' import GhesAppSettings from './instanceSettings/GhesAppSettings.svelte' + import WsConnectivityTest from './instanceSettings/WsConnectivityTest.svelte' import IndexerMemorySettings from './instanceSettings/IndexerMemorySettings.svelte' import IndexerJobIndexSettings from './instanceSettings/IndexerJobIndexSettings.svelte' import IndexerLogIndexSettings from './instanceSettings/IndexerLogIndexSettings.svelte' @@ -285,12 +286,16 @@ {:else} {:else if setting.fieldType == 'github_enterprise_app'} + {:else if setting.fieldType == 'ws_connectivity'} + {/if} {#if hasError} diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index 600dfde5af..4324b45ba6 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -1,6 +1,5 @@ + +
+
+ +
+ + {#if results.length > 0} +
+ {#each results as result (result.name)} +
+ {result.name} + + {#if result.http === 'pending'} + + {:else if result.http === 'ok'} + + {:else} + + {/if} + HTTP + + + {#if result.ws === 'pending'} + + {:else if result.ws === 'ok'} + + {:else} + + {/if} + WebSocket + +
+ {/each} +
+ {/if} + +
+ +
+ + {#if enabled} + + {@const val = $values['ws_base_url']} + {#if val && (!val.startsWith('ws') || !val.includes('://') || val.endsWith('/') || val.endsWith(' '))} + + Must start with ws:// or wss:// and not end with / or a space + + {/if} + {/if} +
diff --git a/frontend/src/lib/components/sidebar/MultiplayerMenu.svelte b/frontend/src/lib/components/sidebar/MultiplayerMenu.svelte index 81057f0f77..b4665026fb 100644 --- a/frontend/src/lib/components/sidebar/MultiplayerMenu.svelte +++ b/frontend/src/lib/components/sidebar/MultiplayerMenu.svelte @@ -1,16 +1,14 @@