mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-17 00:02:59 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
17d9e8b996
commit
e361399ea2
@@ -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)
|
||||
}
|
||||
|
||||
@@ -614,15 +614,15 @@
|
||||
<div class="text-red-500 text-xs space-y-2">
|
||||
<div>Failed to load service configuration component for {service}.</div>
|
||||
<div class="text-secondary">
|
||||
Ensure your workspace has a connected {serviceInfo?.serviceDisplayName} integration.
|
||||
Check that {serviceInfo?.serviceDisplayName} is configured in the native trigger settings.
|
||||
</div>
|
||||
<Button
|
||||
unifiedSize="xs"
|
||||
variant="default"
|
||||
href="/workspace_settings?tab=integrations"
|
||||
href="/workspace_settings?tab=native_triggers"
|
||||
target="_blank"
|
||||
>
|
||||
Manage integrations
|
||||
Native trigger settings
|
||||
</Button>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
+4
-7
@@ -118,16 +118,14 @@
|
||||
{#if eventsError}
|
||||
<div class="break-words">Could not load the available events: {eventsError}</div>
|
||||
{:else}
|
||||
<div
|
||||
>No events available. Please ensure your workspace has a connected Nextcloud integration.</div
|
||||
>
|
||||
<div>No events available. Check that the selected Nextcloud account can reach the server.</div>
|
||||
{/if}
|
||||
<div class="flex gap-2">
|
||||
<Button variant="default" on:click={loadAvailableEvents} {disabled}>
|
||||
Retry loading events
|
||||
</Button>
|
||||
<Button variant="subtle" href="/workspace_settings?tab=integrations" target="_blank">
|
||||
Manage integrations
|
||||
<Button variant="subtle" href="/workspace_settings?tab=native_triggers" target="_blank">
|
||||
Native trigger settings
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,8 +140,7 @@
|
||||
/>
|
||||
{:else}
|
||||
<div class="text-secondary text-xs">
|
||||
Please ensure Nextcloud workspace integration is connected to load available configuration
|
||||
options.
|
||||
Select a Nextcloud account to load the available configuration options.
|
||||
</div>
|
||||
{/if}
|
||||
</Section>
|
||||
|
||||
@@ -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 @@
|
||||
<div class="text-xs text-secondary">The address of your {serviceDisplayName} server</div>
|
||||
<TextInput
|
||||
bind:value={baseUrl}
|
||||
error={!!baseUrl.trim() && !baseUrlValid}
|
||||
inputProps={{ type: 'url', placeholder: baseUrlPlaceholder, disabled: saving }}
|
||||
/>
|
||||
{#if baseUrl && baseUrlSettingsPath}
|
||||
{#if baseUrl.trim() && !baseUrlValid}
|
||||
<div class="text-2xs text-red-500">Enter a full address, starting with https://</div>
|
||||
{/if}
|
||||
{#if baseUrlValid && baseUrlSettingsPath}
|
||||
<div class="flex">
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import ConfirmationModal from '../common/confirmationModal/ConfirmationModal.svelte'
|
||||
import { createAsyncConfirmationModal } from '../common/confirmationModal/asyncConfirmationModal.svelte'
|
||||
import NativeConnectionPicker from '$lib/components/triggers/native/NativeConnectionPicker.svelte'
|
||||
import { connectNativeAccount, nativeOAuthRedirectUri } from '$lib/components/triggers/native/utils'
|
||||
import { nativeOAuthRedirectUri } from '$lib/components/triggers/native/utils'
|
||||
|
||||
interface WorkspaceIntegration {
|
||||
service_name: string
|
||||
@@ -95,7 +95,6 @@
|
||||
let connections = $state<Record<string, NativeTriggerConnection[]>>({})
|
||||
let loading = $state(false)
|
||||
// The service whose OAuth app dialog is open.
|
||||
let connectingShared = $state<string | null>(null)
|
||||
let configOpen = $state<Record<string, boolean>>(
|
||||
Object.fromEntries(Object.keys(supportedServices).map((s) => [s, false]))
|
||||
)
|
||||
@@ -195,21 +194,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function connectAndShare(serviceName: string) {
|
||||
if (!$workspaceStore) return
|
||||
connectingShared = serviceName
|
||||
try {
|
||||
const path = await connectNativeAccount($workspaceStore, serviceName as NativeServiceName)
|
||||
await loadConnections(serviceName)
|
||||
await setWorkspaceConnection(serviceName, path)
|
||||
} catch (err: any) {
|
||||
const displayName = supportedServices[serviceName]?.displayName ?? serviceName
|
||||
sendUserToast(`Could not connect ${displayName}: ${err.body ?? err.message}`, true)
|
||||
} finally {
|
||||
connectingShared = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function docsItem(config: ServiceConfig): Item[] {
|
||||
return config.docsUrl
|
||||
@@ -337,13 +321,6 @@
|
||||
<div class="text-sm font-semibold text-emphasis">{config.displayName}</div>
|
||||
<div class="text-xs text-secondary">{config.description}</div>
|
||||
</div>
|
||||
<div class="text-xs text-secondary shrink-0">
|
||||
{workspaceApp
|
||||
? 'Configured in workspace'
|
||||
: usesInstanceApp
|
||||
? 'Configured by instance'
|
||||
: 'Not configured'}
|
||||
</div>
|
||||
{#if workspaceApp || usesInstanceApp}
|
||||
<DropdownV2
|
||||
items={workspaceApp
|
||||
@@ -382,56 +359,44 @@
|
||||
</div>
|
||||
|
||||
{#if !workspaceApp && !usesInstanceApp}
|
||||
<div class="text-xs text-secondary">
|
||||
Members can't create {config.displayName} triggers yet.
|
||||
<div class="text-xs text-primary">
|
||||
Not configured. Members can't create {config.displayName} triggers yet.
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-start gap-1.5 text-xs text-primary">
|
||||
<Check size={14} class="shrink-0 mt-0.5" />
|
||||
<span>
|
||||
{workspaceApp ? 'Configured in workspace.' : 'Configured by instance.'}
|
||||
Members connect their own {config.displayName} account when they create a trigger.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if serviceName === 'nextcloud' && !workspaceConnection}
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="text-xs text-secondary">
|
||||
Nextcloud only accepts webhooks from admin accounts, so members without one need a
|
||||
shared account.
|
||||
</div>
|
||||
<Button
|
||||
unifiedSize="sm"
|
||||
variant="default"
|
||||
loading={connectingShared === serviceName}
|
||||
onclick={() => connectAndShare(serviceName)}
|
||||
>
|
||||
Connect and share
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center gap-1.5 text-xs text-secondary">
|
||||
<Check size={14} class="shrink-0" />
|
||||
Members can connect their own {config.displayName} account when they create a trigger{workspaceConnection
|
||||
? ', or use the shared one'
|
||||
: ''}.
|
||||
</div>
|
||||
<Alert type="info" title="Creating Nextcloud triggers requires a Nextcloud admin account">
|
||||
We recommend connecting one below and sharing it with the workspace.
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-xs font-semibold text-emphasis shrink-0">Shared account</span>
|
||||
<div class="flex-1 min-w-0 max-w-md">
|
||||
<!-- Remounted so its list includes an account connected from elsewhere on the page. -->
|
||||
{#key workspaceConnection}
|
||||
<NativeConnectionPicker
|
||||
service={serviceName as NativeServiceName}
|
||||
value={workspaceConnection}
|
||||
autoSelect={false}
|
||||
clearable
|
||||
labelWorkspace={false}
|
||||
placeholder="None"
|
||||
onSelect={(path) => setWorkspaceConnection(serviceName, path)}
|
||||
onConnectionsChange={() => loadConnections(serviceName)}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-2xs text-hint">
|
||||
<div class="text-xs font-semibold text-emphasis">Shared account</div>
|
||||
<div class="text-xs text-secondary">
|
||||
Members can create triggers as this account without seeing its token.
|
||||
</div>
|
||||
<div class="max-w-md">
|
||||
<!-- Remounted so its list includes an account connected from elsewhere on the page. -->
|
||||
{#key workspaceConnection}
|
||||
<NativeConnectionPicker
|
||||
service={serviceName as NativeServiceName}
|
||||
value={workspaceConnection}
|
||||
autoSelect={false}
|
||||
clearable
|
||||
labelWorkspace={false}
|
||||
placeholder="None"
|
||||
onSelect={(path) => setWorkspaceConnection(serviceName, path)}
|
||||
onConnectionsChange={() => loadConnections(serviceName)}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<div class="flex flex-col gap-2 mt-2">
|
||||
The workspace doesn't have a {serviceConfig?.serviceDisplayName || serviceName} integration
|
||||
connected
|
||||
{serviceConfig?.serviceDisplayName || serviceName} isn't configured in this workspace yet.
|
||||
<div class="flex gap-2">
|
||||
<Button variant="accent" href="/workspace_settings?tab=native_triggers">
|
||||
Manage native triggers
|
||||
|
||||
Reference in New Issue
Block a user