diff --git a/backend/windmill-api-settings/src/lib.rs b/backend/windmill-api-settings/src/lib.rs index bf493f9420..6b408724fd 100644 --- a/backend/windmill-api-settings/src/lib.rs +++ b/backend/windmill-api-settings/src/lib.rs @@ -43,8 +43,8 @@ use windmill_common::{ get_database_url, global_settings::{ APP_WORKSPACED_ROUTE_SETTING, AUTOMATE_USERNAME_CREATION_SETTING, - CRITICAL_ALERT_MUTE_UI_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, - ENV_SETTINGS, HUB_ACCESSIBLE_URL_SETTING, HUB_BASE_URL_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, }, instance_config::{self, ApplyMode, InstanceConfig}, server::Smtp, @@ -519,6 +519,7 @@ pub async fn get_global_setting( && key != DEFAULT_TAGS_WORKSPACES_SETTING && key != HUB_BASE_URL_SETTING && key != HUB_ACCESSIBLE_URL_SETTING + && key != DISABLE_HUB_SETTING && key != EMAIL_DOMAIN_SETTING && key != APP_WORKSPACED_ROUTE_SETTING { diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 3347127303..d4d8163ff2 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -44,6 +44,7 @@ pub const HUB_API_SECRET_SETTING: &str = "hub_api_secret"; pub const AUTOMATE_USERNAME_CREATION_SETTING: &str = "automate_username_creation"; pub const HUB_BASE_URL_SETTING: &str = "hub_base_url"; pub const HUB_ACCESSIBLE_URL_SETTING: &str = "hub_accessible_url"; +pub const DISABLE_HUB_SETTING: &str = "disable_hub"; pub const CRITICAL_ERROR_CHANNELS_SETTING: &str = "critical_error_channels"; pub const CRITICAL_ALERT_MUTE_UI_SETTING: &str = "critical_alert_mute_ui"; pub const CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING: &str = "critical_alerts_on_db_oversize"; diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index 4de90be5c5..4ef88f5b78 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -230,6 +230,8 @@ pub struct GlobalSettings { pub no_default_maven: Option, #[serde(skip_serializing_if = "Option::is_none")] pub default_tags_per_workspace: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_hub: Option, // String settings #[serde(skip_serializing_if = "Option::is_none")] diff --git a/frontend/src/lib/components/flows/pickers/PickHubApp.svelte b/frontend/src/lib/components/flows/pickers/PickHubApp.svelte index 7f534a924a..234f053f7b 100644 --- a/frontend/src/lib/components/flows/pickers/PickHubApp.svelte +++ b/frontend/src/lib/components/flows/pickers/PickHubApp.svelte @@ -7,6 +7,8 @@ import RowIcon from '$lib/components/common/table/RowIcon.svelte' import { loadHubApps } from '$lib/hub' import TextInput from '$lib/components/text_input/TextInput.svelte' + import { Alert } from '$lib/components/common' + import { disableHubStore } from '$lib/stores' interface Props { filter?: string @@ -30,11 +32,22 @@ const dispatch = createEventDispatcher() + let hubNotAvailable = $state(false) + onMount(async () => { - hubApps = await loadHubApps() + if ($disableHubStore) return + const result = await loadHubApps() + if (result === undefined) { + hubNotAvailable = true + } else { + hubApps = result + } }) +{#if $disableHubStore} + +{:else} -{#if hubApps} +{#if hubNotAvailable} + + Could not connect to the Windmill Hub. If you are in a closed environment, you can disable the Hub in the instance settings. + +{:else if hubApps} {#if filteredItems.length == 0} {:else} @@ -93,3 +110,4 @@ {/each} {/if} +{/if} diff --git a/frontend/src/lib/components/flows/pickers/PickHubFlow.svelte b/frontend/src/lib/components/flows/pickers/PickHubFlow.svelte index 2ab12aea36..1f3c659bf8 100644 --- a/frontend/src/lib/components/flows/pickers/PickHubFlow.svelte +++ b/frontend/src/lib/components/flows/pickers/PickHubFlow.svelte @@ -7,6 +7,8 @@ import RowIcon from '$lib/components/common/table/RowIcon.svelte' import { loadHubFlows } from '$lib/hub' import TextInput from '$lib/components/text_input/TextInput.svelte' + import { Alert } from '$lib/components/common' + import { disableHubStore } from '$lib/stores' interface Props { filter?: string @@ -30,11 +32,22 @@ const dispatch = createEventDispatcher() + let hubNotAvailable = $state(false) + onMount(async () => { - hubFlows = await loadHubFlows() + if ($disableHubStore) return + const result = await loadHubFlows() + if (result === undefined) { + hubNotAvailable = true + } else { + hubFlows = result + } }) +{#if $disableHubStore} + +{:else} -{#if hubFlows} +{#if hubNotAvailable} + + Could not connect to the Windmill Hub. If you are in a closed environment, you can disable the Hub in the instance settings. + +{:else if hubFlows} {#if filteredItems.length == 0} {:else} @@ -95,3 +112,4 @@ {/each} {/if} +{/if} diff --git a/frontend/src/lib/components/flows/pickers/PickHubScript.svelte b/frontend/src/lib/components/flows/pickers/PickHubScript.svelte index b5ba87327e..7ec0a8ccc3 100644 --- a/frontend/src/lib/components/flows/pickers/PickHubScript.svelte +++ b/frontend/src/lib/components/flows/pickers/PickHubScript.svelte @@ -8,6 +8,7 @@ import { IntegrationService, ScriptService, type HubScriptKind } from '$lib/gen' import { Loader2 } from 'lucide-svelte' import TextInput from '$lib/components/text_input/TextInput.svelte' + import { disableHubStore } from '$lib/stores' interface Props { kind?: HubScriptKind & string @@ -47,6 +48,7 @@ ) async function getAllApps(filterKind: typeof kind) { + if ($disableHubStore) return try { hubNotAvailable = false allApps = ( @@ -67,6 +69,7 @@ filterKind: typeof kind, appFilter: string | undefined ) { + if ($disableHubStore) return try { loading = true hubNotAvailable = false @@ -138,6 +141,9 @@ }) +{#if $disableHubStore} + +{:else}
{@render children?.()}
@@ -156,7 +162,9 @@
{#if hubNotAvailable} - + + Could not connect to the Windmill Hub. If you are in a closed environment, you can disable the Hub in the instance settings. + {:else if (items.length > 0 && apps.length > 0) || !loading} {#if items.length == 0} @@ -204,3 +212,4 @@ {/each} {/if} +{/if} diff --git a/frontend/src/lib/components/flows/pickers/PickHubScriptQuick.svelte b/frontend/src/lib/components/flows/pickers/PickHubScriptQuick.svelte index 2091f76340..3d929dead7 100644 --- a/frontend/src/lib/components/flows/pickers/PickHubScriptQuick.svelte +++ b/frontend/src/lib/components/flows/pickers/PickHubScriptQuick.svelte @@ -24,7 +24,7 @@ []) : undefined } catch (err) { - sendUserToast('Failed to fetch hub scripts: ' + err, 'error') + console.error('Failed to fetch hub scripts:', err) return undefined } }, @@ -44,9 +44,10 @@ import { Circle, ExternalLink } from 'lucide-svelte' import Popover from '$lib/components/Popover.svelte' import { usePromise } from '$lib/svelte5Utils.svelte' - import { hubBaseUrlStore, userStore } from '$lib/stores' + import { disableHubStore, hubBaseUrlStore, userStore } from '$lib/stores' import { get } from 'svelte/store' import Button from '$lib/components/common/button/Button.svelte' + import { Alert } from '$lib/components/common' let hubNotAvailable = $state(false) @@ -94,13 +95,14 @@ }) async function getAllApps(filterKind: typeof kind) { + if ($disableHubStore) return try { hubNotAvailable = false allApps = (await listHubIntegrationsCached({ kind: filterKind, refreshCount })).map( (x) => x.name ) } catch (err) { - sendUserToast('Failed to fetch hub integrations: ' + err, 'error') + console.error('Failed to fetch hub integrations:', err) allApps = [] hubNotAvailable = true } @@ -112,7 +114,9 @@ ) $effect(() => { ;[filter, kind, appFilter, refreshCount] - hubScriptsFilteredPromise.refresh() + if (!$disableHubStore) { + hubScriptsFilteredPromise.refresh() + } }) $effect(() => { loading = hubScriptsFilteredPromise.status === 'loading' @@ -175,9 +179,13 @@ -{#if hubNotAvailable} -
- Hub not available +{#if $disableHubStore} + +{:else if hubNotAvailable} +
+ + Could not connect to the Windmill Hub. If you are in a closed environment, you can disable the Hub in the instance settings. +
{:else if loading} {#each Array(15).fill(0) as _} diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index de4cd1528b..25955b74db 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -331,6 +331,16 @@ export const settings: Record = { storage: 'setting', ee_only: '', hiddenIfEmpty: true + }, + { + label: 'Disable Hub', + description: + 'Disable the Windmill Hub integration entirely. Enable this if your instance runs in a closed environment without internet access and you do not have a private hub setup.', + key: 'disable_hub', + fieldType: 'boolean', + storage: 'setting', + ee_only: '', + requiresReloadOnChange: true } ], SMTP: [ diff --git a/frontend/src/lib/stores.ts b/frontend/src/lib/stores.ts index 6a9a8e7805..fd0a66cfb3 100644 --- a/frontend/src/lib/stores.ts +++ b/frontend/src/lib/stores.ts @@ -83,6 +83,7 @@ export const superadmin = writable(undefined) export const devopsRole = writable(undefined) export const lspTokenStore = writable(undefined) export const hubBaseUrlStore = writable(DEFAULT_HUB_BASE_URL) +export const disableHubStore = writable(false) export const userWorkspaces: Readable> = derived( [usersWorkspaceStore, superadmin], ([store, superadmin]) => { diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index e9b1884df2..78de6c1535 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -26,6 +26,7 @@ type UserExt, defaultScripts, hubBaseUrlStore, + disableHubStore, usedTriggerKinds, devopsRole, whitelabelNameStore, @@ -157,6 +158,7 @@ loadUsage() syncTutorialsTodos() loadHubBaseUrl() + loadDisableHub() loadUsedTriggerKinds() } @@ -176,6 +178,11 @@ DEFAULT_HUB_BASE_URL } + async function loadDisableHub() { + $disableHubStore = + ((await SettingService.getGlobal({ key: 'disable_hub' })) as boolean) ?? false + } + async function loadFavorites() { const scripts = await ScriptService.listScripts({ workspace: $workspaceStore ?? '',