From e361399ea2eec3050b5e6c7a188423248e8166c6 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Thu, 15 Oct 2026 19:41:49 +0200 Subject: [PATCH] feat: one status line per native trigger service, a Nextcloud admin hint, and clearer refusals The shared account is a regular form field, Nextcloud shows an alert while no admin account is shared, a non-admin Nextcloud account gets a message saying why it cannot create triggers, and the OAuth dialog only accepts a full server URL. Co-Authored-By: Claude Opus 5.5 --- .../src/nextcloud/external.rs | 15 ++- .../native/NativeTriggerEditor.svelte | 6 +- .../nextcloud/NextcloudTriggerForm.svelte | 11 +-- .../OAuthClientConfig.svelte | 16 ++- .../WorkspaceIntegrations.svelte | 97 ++++++------------- .../[service_name]/+page.svelte | 22 ++--- 6 files changed, 73 insertions(+), 94 deletions(-) diff --git a/backend/windmill-native-triggers/src/nextcloud/external.rs b/backend/windmill-native-triggers/src/nextcloud/external.rs index e662a981ee..7a1ed5f8d3 100644 --- a/backend/windmill-native-triggers/src/nextcloud/external.rs +++ b/backend/windmill-native-triggers/src/nextcloud/external.rs @@ -136,7 +136,20 @@ impl External for NextCloud { Some(headers), Some(&full_nextcloud_payload), ) - .await?; + .await + .map_err(|e| { + // Nextcloud lets only its admins register webhooks. + if http_error_status(&e) == Some(StatusCode::FORBIDDEN) { + Error::BadRequest( + "This Nextcloud account isn't a Nextcloud admin, so it can't register \ + webhooks. Use the shared account, or ask a workspace admin to share a \ + Nextcloud admin account." + .to_string(), + ) + } else { + e + } + })?; Ok(ocs_response.ocs.data) } diff --git a/frontend/src/lib/components/triggers/native/NativeTriggerEditor.svelte b/frontend/src/lib/components/triggers/native/NativeTriggerEditor.svelte index 1817db8a4f..bd2584cb1d 100644 --- a/frontend/src/lib/components/triggers/native/NativeTriggerEditor.svelte +++ b/frontend/src/lib/components/triggers/native/NativeTriggerEditor.svelte @@ -614,15 +614,15 @@
Failed to load service configuration component for {service}.
- Ensure your workspace has a connected {serviceInfo?.serviceDisplayName} integration. + Check that {serviceInfo?.serviceDisplayName} is configured in the native trigger settings.
diff --git a/frontend/src/lib/components/triggers/native/services/nextcloud/NextcloudTriggerForm.svelte b/frontend/src/lib/components/triggers/native/services/nextcloud/NextcloudTriggerForm.svelte index f4fa82a187..d4f01478ef 100644 --- a/frontend/src/lib/components/triggers/native/services/nextcloud/NextcloudTriggerForm.svelte +++ b/frontend/src/lib/components/triggers/native/services/nextcloud/NextcloudTriggerForm.svelte @@ -118,16 +118,14 @@ {#if eventsError}
Could not load the available events: {eventsError}
{:else} -
No events available. Please ensure your workspace has a connected Nextcloud integration.
+
No events available. Check that the selected Nextcloud account can reach the server.
{/if}
-
@@ -142,8 +140,7 @@ /> {:else}
- Please ensure Nextcloud workspace integration is connected to load available configuration - options. + Select a Nextcloud account to load the available configuration options.
{/if} diff --git a/frontend/src/lib/components/workspaceSettings/OAuthClientConfig.svelte b/frontend/src/lib/components/workspaceSettings/OAuthClientConfig.svelte index 4e71118e88..14244ca5c2 100644 --- a/frontend/src/lib/components/workspaceSettings/OAuthClientConfig.svelte +++ b/frontend/src/lib/components/workspaceSettings/OAuthClientConfig.svelte @@ -57,8 +57,16 @@ } }) + const baseUrlValid = $derived.by(() => { + try { + return ['http:', 'https:'].includes(new URL(baseUrl.trim()).protocol) + } catch { + return false + } + }) + const canSave = $derived( - !saving && !!clientId.trim() && !!clientSecret.trim() && (!requiresBaseUrl || !!baseUrl.trim()) + !saving && !!clientId.trim() && !!clientSecret.trim() && (!requiresBaseUrl || baseUrlValid) ) async function save() { @@ -104,9 +112,13 @@
The address of your {serviceDisplayName} server
- {#if baseUrl && baseUrlSettingsPath} + {#if baseUrl.trim() && !baseUrlValid} +
Enter a full address, starting with https://
+ {/if} + {#if baseUrlValid && baseUrlSettingsPath}
-
- {workspaceApp - ? 'Configured in workspace' - : usesInstanceApp - ? 'Configured by instance' - : 'Not configured'} -
{#if workspaceApp || usesInstanceApp} {#if !workspaceApp && !usesInstanceApp} -
- Members can't create {config.displayName} triggers yet. +
+ Not configured. Members can't create {config.displayName} triggers yet.
{:else} +
+ + + {workspaceApp ? 'Configured in workspace.' : 'Configured by instance.'} + Members connect their own {config.displayName} account when they create a trigger. + +
+ {#if serviceName === 'nextcloud' && !workspaceConnection} -
-
- Nextcloud only accepts webhooks from admin accounts, so members without one need a - shared account. -
- -
- {:else} -
- - Members can connect their own {config.displayName} account when they create a trigger{workspaceConnection - ? ', or use the shared one' - : ''}. -
+ + We recommend connecting one below and sharing it with the workspace. + {/if}
-
- Shared account -
- - {#key workspaceConnection} - setWorkspaceConnection(serviceName, path)} - onConnectionsChange={() => loadConnections(serviceName)} - /> - {/key} -
-
-
+
Shared account
+
Members can create triggers as this account without seeing its token.
+
+ + {#key workspaceConnection} + setWorkspaceConnection(serviceName, path)} + onConnectionsChange={() => loadConnections(serviceName)} + /> + {/key} +
{/if} diff --git a/frontend/src/routes/(root)/(logged)/native_triggers/[service_name]/+page.svelte b/frontend/src/routes/(root)/(logged)/native_triggers/[service_name]/+page.svelte index 004799baed..d911b1dcfb 100644 --- a/frontend/src/routes/(root)/(logged)/native_triggers/[service_name]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/native_triggers/[service_name]/+page.svelte @@ -110,7 +110,7 @@ serviceAvailable = await isServiceAvailable(serviceName, $workspaceStore) if (!serviceAvailable) { sendUserToast( - `${serviceName} integration not available, set it in the workspace settings`, + `${serviceConfig.serviceDisplayName} isn't configured in this workspace`, true ) } @@ -145,7 +145,10 @@ } if (!serviceAvailable) { - sendUserToast('Cannot sync triggers: workspace integration not connected', true) + sendUserToast( + `Cannot sync: ${serviceConfig.serviceDisplayName} isn't configured in this workspace`, + true + ) return } @@ -159,17 +162,7 @@ loadTriggers() } catch (err: any) { const errorMessage = err.body || err.message - if ( - errorMessage.includes('workspace integration') || - errorMessage.includes('not connected') - ) { - sendUserToast( - `Cannot sync: ${serviceConfig.serviceDisplayName} workspace integration not connected. Please connect it in workspace settings.`, - true - ) - } else { - sendUserToast(`Failed to sync triggers: ${errorMessage}`, true) - } + sendUserToast(`Failed to sync triggers: ${errorMessage}`, true) } } @@ -237,8 +230,7 @@ type="warning" >
- The workspace doesn't have a {serviceConfig?.serviceDisplayName || serviceName} integration - connected + {serviceConfig?.serviceDisplayName || serviceName} isn't configured in this workspace yet.