-
Script or flow to run on /windmill command
-
- {#if !teamName || (!$enterpriseLicense && platform === 'teams')}
-
- {/if}
+
+
+ {#if teamName && ($enterpriseLicense || platform === 'slack') && (scriptPath === '' || scriptPath === undefined)}
+ {#if itemKind === 'script'}
+
+ {:else if itemKind === 'flow'}
+
+ {/if}
+ {/if}
-
- Pick a script or flow meant to be triggered when the `/windmill` command is invoked. Upon
- connection, templates for a
script
- and
flow are available.
+ {#if !teamName}
+
Please connect your workspace to {capitalizedPlatform} to use this feature
+ {/if}
-
+
+
+ Upon connection, templates for a
script
+ and
flow are available.
- The script or flow chosen is passed the parameters `response_url: string` and `text: string`
- respectively the url to reply directly to the trigger and the text of the command.
+
-
+ The script or flow chosen is passed the parameters `response_url: string` and `text: string`
+ respectively the url to reply directly to the trigger and the text of the command.
- It can take additionally the following args: channel_id, user_name, user_id, command,
- trigger_id, api_app_id
+
-
+ It can take additionally the following args: channel_id, user_name, user_id, command,
+ trigger_id, api_app_id
-
- The script or flow is permissioned as group "{platform}" that will be automatically created
- after connection to {platform.charAt(0).toUpperCase() + platform.slice(1)}.
-
+
-
+
+ The script or flow is permissioned as group "{platform}" that will be automatically created
+ after connection to {platform.charAt(0).toUpperCase() + platform.slice(1)}.
+
- See more on
-
documentation.
-
+
+
+ See more on
+ documentation.
+
+
diff --git a/frontend/src/lib/components/CustomOauth.svelte b/frontend/src/lib/components/CustomOauth.svelte
index 5ae21602df..8678f46822 100644
--- a/frontend/src/lib/components/CustomOauth.svelte
+++ b/frontend/src/lib/components/CustomOauth.svelte
@@ -25,56 +25,59 @@
}
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/lib/components/DropdownV2.svelte b/frontend/src/lib/components/DropdownV2.svelte
index b9f8a408e4..b98a1ce907 100644
--- a/frontend/src/lib/components/DropdownV2.svelte
+++ b/frontend/src/lib/components/DropdownV2.svelte
@@ -40,6 +40,7 @@
btnText?: string
buttonReplacement?: import('svelte').Snippet
menu?: import('svelte').Snippet
+ maxHeight?: string | undefined
}
let {
@@ -60,7 +61,8 @@
size = 'md',
btnText = '',
buttonReplacement,
- menu
+ menu,
+ maxHeight = undefined
}: Props = $props()
let buttonEl: HTMLButtonElement | undefined = $state(undefined)
@@ -169,8 +171,8 @@
{@render menu?.()}
{:else}
diff --git a/frontend/src/lib/components/EEOnly.svelte b/frontend/src/lib/components/EEOnly.svelte
index 0bf54b7b68..267795f9b2 100644
--- a/frontend/src/lib/components/EEOnly.svelte
+++ b/frontend/src/lib/components/EEOnly.svelte
@@ -17,7 +17,6 @@
'flex text-xs items-center gap-1 text-yellow-500 whitespace-nowrap px-1',
className
)}
- title="Enterprise Edition only feature"
aria-label="Enterprise Edition only feature"
role="tooltip"
>
diff --git a/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte b/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte
index 2d0274ad7d..8ed2a7a192 100644
--- a/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte
+++ b/frontend/src/lib/components/ErrorOrRecoveryHandler.svelte
@@ -20,10 +20,13 @@
-
-
-
-
-
-
-
-
-
-{#if handlerSelected === 'custom'}
-
-
-
- {#if !handlerPath}
-
+ tooltip={customTabTooltip ? 'Custom error handler with script or flow' : undefined}
+ />
+ {/snippet}
+
+
+
+ {#if handlerSelected === 'custom'}
+
+
+
+ {#if !handlerPath}
+
+ {/if}
+
+ {#if showScriptHelpText}
+
+ Example of error handler scripts can be found on
+ Windmill Hub
+
+ {/if}
+ {#if handlerPath}
+
Extra arguments
+ {#await import('$lib/components/SchemaForm.svelte')}
+
+ {:then Module}
+
+ {/await}
+ {#if customHandlerSchema && customHandlerSchema.properties && Object.keys(customHandlerSchema.properties).length === 0}
+
This error handler takes no extra arguments
+ {/if}
+ {/if}
+ {:else if handlerSelected === 'slack'}
+
+
+
+ {#if workspaceConnectedToSlack}
+
{
+ if (e.detail && errorOrRecovery === 'error') {
+ handlerPath = slackHandlerScriptPath
+ } else if (e.detail && errorOrRecovery === 'recovery') {
+ handlerPath = slackRecoveryHandler
+ } else if (e.detail && errorOrRecovery === 'success') {
+ handlerPath = slackSuccessHandler
+ } else {
+ handlerPath = undefined
+ }
+ }}
+ />
+ {/if}
+
+ {#if workspaceConnectedToSlack && isSlackHandler(handlerPath)}
+
+ {#await import('$lib/components/SchemaForm.svelte')}
+
+ {:then Module}
+
+ {/await}
+
+ {#if $enterpriseLicense && isSlackHandler(handlerPath)}
+
+ {#if connectionTestJob !== undefined}
+
+
+ {#if connectionTestJob.in_progress}
+
+ Sending message...
+ {:else if connectionTestJob.is_success}
+
+ Message sent via Windmill job
+ {:else}
+
+ Message not sent
+ {/if}
+
+
+ {connectionTestJob.uuid}
+
+
+
+
+ {/if}
+ {/if}
+
+ {:else if workspaceConnectedToSlack == undefined}
+
+ {/if}
+ {:else if handlerSelected === 'teams'}
+
+
+
+ {#if workspaceConnectedToTeams}
+ {
+ if (e.detail && errorOrRecovery === 'error') {
+ handlerPath = teamsHandlerScriptPath
+ } else if (e.detail && errorOrRecovery === 'recovery') {
+ handlerPath = teamsRecoveryHandler
+ } else if (e.detail && errorOrRecovery === 'success') {
+ handlerPath = teamsSuccessHandler
+ } else {
+ handlerPath = undefined
+ }
+ }}
+ />
+ {/if}
+
+ {#if workspaceConnectedToTeams}
+
+
+
+
+
+ {
+ handlerExtraArgs['channel'] = channel?.channel_id
+ handlerExtraArgs['channel_name'] = channel?.channel_name
+ }}
+ onError={(e) => sendUserToast('Failed to load channels: ' + e.message, true)}
+ />
+
+
+ {#if $enterpriseLicense && isTeamsHandler(handlerPath) && workspaceConnectedToTeams}
+
+ {#if connectionTestJob !== undefined}
+
+ {#if connectionTestJob.in_progress}
+
+ {:else if connectionTestJob.is_success}
+
+ {:else}
+
+ {/if}
+ Message sent via Windmill job
+
+ {connectionTestJob.uuid}
+
+
+ {/if}
+ {/if}
+
+ {:else if workspaceConnectedToTeams == undefined}
+
+ {/if}
+ {:else if handlerSelected === 'email'}
+ {#if isCloudHosted()}
+
+ Email notifications for trigger failures are only available in self-hosted Windmill
+ instances.
+
+ {:else}
+
+
+
+ handlerExtraArgs[EMAIL_RECIPIENTS_KEY] ?? [],
+ (recipients) => (handlerExtraArgs[EMAIL_RECIPIENTS_KEY] = recipients)
+ }
+ placeholder="Enter email addresses..."
+ onCreateItem={(email) => {
+ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
+ if (!emailRegex.test(email)) {
+ sendUserToast('Invalid email format', true)
+ return
+ }
+ const currentArray = handlerExtraArgs[EMAIL_RECIPIENTS_KEY] ?? []
+ handlerExtraArgs[EMAIL_RECIPIENTS_KEY] = [...currentArray, email]
+ }}
+ class="w-full"
+ />
+ {#if handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length > 0}
+
+ {handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length} email{handlerExtraArgs[
+ EMAIL_RECIPIENTS_KEY
+ ]?.length === 1
+ ? ''
+ : 's'} configured
+
+ {/if}
+
+ {/if}
{/if}
- {#if showScriptHelpText}
-
- Example of error handler scripts can be found on
- Windmill Hub
-
- {/if}
- {#if handlerPath}
-
Extra arguments
- {#await import('$lib/components/SchemaForm.svelte')}
-
- {:then Module}
-
- {/await}
- {#if customHandlerSchema && customHandlerSchema.properties && Object.keys(customHandlerSchema.properties).length === 0}
-
This error handler takes no extra arguments
- {/if}
- {/if}
-{:else if handlerSelected === 'slack'}
-
- {
- if (e.detail && errorOrRecovery === 'error') {
- handlerPath = slackHandlerScriptPath
- } else if (e.detail && errorOrRecovery === 'recovery') {
- handlerPath = slackRecoveryHandler
- } else if (e.detail && errorOrRecovery === 'success') {
- handlerPath = slackSuccessHandler
- } else {
- handlerPath = undefined
- }
- }}
- />
-
- {#if workspaceConnectedToSlack}
- {#await import('$lib/components/SchemaForm.svelte')}
-
- {:then Module}
-
- {/await}
- {:else if workspaceConnectedToSlack == undefined}
-
- {/if}
- {#if $enterpriseLicense && isSlackHandler(handlerPath)}
- {#if workspaceConnectedToSlack == false}
-
-
-
- The workspace needs to be connected to Slack to use this feature. You can configure it here.
-
-
-
-
- {:else}
-
- {#if connectionTestJob !== undefined}
-
- {#if connectionTestJob.in_progress}
-
- {:else if connectionTestJob.is_success}
-
- {:else}
-
- {/if}
- Message sent via Windmill job
-
- {connectionTestJob.uuid}
-
-
- {/if}
- {/if}
- {/if}
-{:else if handlerSelected === 'teams'}
-
- {
- if (e.detail && errorOrRecovery === 'error') {
- handlerPath = teamsHandlerScriptPath
- } else if (e.detail && errorOrRecovery === 'recovery') {
- handlerPath = teamsRecoveryHandler
- } else if (e.detail && errorOrRecovery === 'success') {
- handlerPath = teamsSuccessHandler
- } else {
- handlerPath = undefined
- }
- }}
- />
-
- {#if workspaceConnectedToTeams}
-
-
-
-
- {
- handlerExtraArgs['channel'] = channel?.channel_id
- handlerExtraArgs['channel_name'] = channel?.channel_name
- }}
- onError={(e) => sendUserToast('Failed to load channels: ' + e.message, true)}
- />
-
-
-
-
- This workspace is connected to Team: {teams_team_name}
-
-
- Each workspace can only be connected to one Microsoft Teams team. You can configure it under workspace settings.
-
-
- {:else if workspaceConnectedToTeams == undefined}
-
- {/if}
- {#if $enterpriseLicense && isTeamsHandler(handlerPath)}
- {#if workspaceConnectedToTeams == false}
-
-
-
- The workspace needs to be connected to Teams to use this feature. You can configure it
- under workspace settings.
-
-
-
-
- {:else}
-
- {#if connectionTestJob !== undefined}
-
- {#if connectionTestJob.in_progress}
-
- {:else if connectionTestJob.is_success}
-
- {:else}
-
- {/if}
- Message sent via Windmill job
-
- {connectionTestJob.uuid}
-
-
- {/if}
- {/if}
- {/if}
-{:else if handlerSelected === 'email'}
- {#if isCloudHosted()}
-
- Email notifications for trigger failures are only available in self-hosted Windmill instances.
-
- {:else}
-
-
- Configure email addresses to receive notifications when jobs fail. This feature requires
- SMTP to be configured.
-
-
-
- handlerExtraArgs[EMAIL_RECIPIENTS_KEY] ?? [],
- (recipients) => (handlerExtraArgs[EMAIL_RECIPIENTS_KEY] = recipients)
- }
- placeholder="Enter email addresses..."
- onCreateItem={(email) => {
- const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
- if (!emailRegex.test(email)) {
- sendUserToast('Invalid email format', true)
- return
- }
- const currentArray = handlerExtraArgs[EMAIL_RECIPIENTS_KEY] ?? []
- handlerExtraArgs[EMAIL_RECIPIENTS_KEY] = [...currentArray, email]
- }}
- class="w-full"
- />
- {#if handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length > 0}
-
- {handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length} email{handlerExtraArgs[
- EMAIL_RECIPIENTS_KEY
- ]?.length === 1
- ? ''
- : 's'} configured
-
- {/if}
-
- {/if}
-{/if}
+
diff --git a/frontend/src/lib/components/InstanceSetting.svelte b/frontend/src/lib/components/InstanceSetting.svelte
index 0914654fa1..b1920a04ff 100644
--- a/frontend/src/lib/components/InstanceSetting.svelte
+++ b/frontend/src/lib/components/InstanceSetting.svelte
@@ -1,19 +1,19 @@
+{#snippet LabelSnippet()}
+
+
+{/snippet}
+
{#if (!setting.cloudonly || isCloudHosted()) && showSetting(setting.key, $values) && !(setting.hiddenIfNull && $values[setting.key] == null) && !(setting.hiddenIfEmpty && !$values[setting.key])}
- {#if setting.ee_only != undefined && !$enterpriseLicense}
-
- {#if setting.ee_only != ''}{setting.ee_only}{/if}
-
- {/if}
{#if setting.fieldType == 'select'}
-
-
+ {@render LabelSnippet()}
{#snippet children({ item: toggleButton })}
{#each setting.select_items ?? [] as item}
@@ -223,14 +194,7 @@
{:else if setting.fieldType == 'select_python'}
-
+ {@render LabelSnippet()}
{#snippet children({ item: toggleButtonn })}
@@ -283,23 +247,52 @@
{:else}
{#snippet settingContent()}
- {setting.label}
- {#if setting.description}
-
- {@html setting.description}
-
- {/if}
+
+
+
+
+ {#if setting.fieldType != 'smtp_connect'}
+
+ {setting.label}
+ {#if setting.ee_only != undefined && !$enterpriseLicense}
+ {#if setting.ee_only != ''}
+ {setting.ee_only}
+ {:else}
+
+ {/if}
+ {/if}
+
+ {/if}
+ {#if setting.actionButton}
+
+ {/if}
+
+ {#if setting.description}
+
+ {@html setting.description}
+
+ {/if}
+
+
{#if setting.tooltip}
{setting.tooltip}
{/if}
{#if $values}
{@const hasError = setting.isValid && !setting.isValid($values[setting.key])}
+
{#if loading}
{:else if setting.fieldType == 'text'}
{
@@ -496,13 +491,20 @@
{/if}
{:else if setting.fieldType == 'email'}
-
+
{:else if setting.key == 'critical_alert_mute_ui'}
-
+
{:else if setting.fieldType == 'critical_error_channels'}
-
-
- {#if $enterpriseLicense && Array.isArray($values[setting.key])}
- {#each $values[setting.key] ?? [] as v, i}
-
-
-
-
-
-
-
-
- {:else if setting.fieldType == 'slack_connect'}
-
- {#if $values[setting.key] && 'team_name' in $values[setting.key]}
-
- Connected to {$values[setting.key]['team_name']}
-
-
- {:else}
-
- {/if}
-
- {:else if setting.fieldType == 'indexer_rates'}
-
- {#if $values[setting.key]}
-
-
- {
- if (e.target instanceof HTMLInputElement) {
- if (e.target.valueAsNumber) {
- $values[setting.key].writer_memory_budget =
- e.target.valueAsNumber * (1024 * 1024)
- }
- }
+
+ {
+ clearJobsIndexModalOpen = false
}}
- value={$values[setting.key].writer_memory_budget / (1024 * 1024)}
- />
-
-
Completed Job Index
-
-
-
-
-
-
-
-
-
-
- {
- if (e.target instanceof HTMLInputElement) {
- if (e.target.valueAsNumber) {
- $values[setting.key].max_indexed_job_log_size =
- e.target.valueAsNumber * 1024
- }
- }
- }}
- value={$values[setting.key].max_indexed_job_log_size / 1024}
- />
-
-
Service logs index
-
-
-
-
-
-
-
-
-
Reset Index
-
- This buttons will clear the whole index, and the service will start reindexing from
- scratch. Full text search might be down during this time.
-
-
- {
- let r = await IndexSearchService.clearIndex({
+ on:confirmed={async () => {
+ const r = await IndexSearchService.clearIndex({
idxName: 'JobIndex'
})
- console.log('asasd')
sendUserToast(r)
- }}>Clear Jobs Index
- {
- let r = await IndexSearchService.clearIndex({
+ Are you sure you want to clear the jobs index? The service will start reindexing
+ from scratch. Full text search might be down during this time.
+
+ {
+ clearServiceLogsIndexModalOpen = false
+ }}
+ on:confirmed={async () => {
+ const r = await IndexSearchService.clearIndex({
idxName: 'ServiceLogIndex'
})
- console.log('asasd')
sendUserToast(r)
- }}>Clear Service logs index
-
- {/if}
-
- {:else if setting.fieldType == 'smtp_connect'}
-
- {#if $values[setting.key]}
-
+
+
+
{/if}
{:else if setting.fieldType == 'otel'}
@@ -975,38 +767,42 @@
/>
-
+
-
-
+
-
-
+
- gRPC
+ gRPC
-
- {#each settingsKeys as category}
-
- {/each}
-
- {#snippet content()}
- {#each Object.keys(settings) as category}
-
- {#if category == 'SMTP'}
- Setting SMTP unlocks sending emails upon adding new users to the workspace or the
- instance or sending critical alerts.
-
Learn more
- {:else if category == 'Indexer/Search'}
- The indexer service unlocks full text search across jobs and service logs. It
- requires spinning up its own separate container
-
Learn how to
- {:else if category == 'Registries'}
-
- Add private registries for Pip, Bun and npm.
Learn more
-
- {:else if category == 'Slack'}
-
- Connecting your instance to a Slack workspace enables critical alerts to be sent to a
- Slack channel.
-
Learn more
-
- {:else if category == 'SCIM/SAML'}
-
- Setting up SAML and SCIM allows you to authenticate users using your identity
- provider.
-
Learn more
-
- {:else if category == 'Debug'}
-
- Enable debug mode to get more detailed logs.
-
- {:else if category == 'Telemetry'}
-
- Anonymous usage data is collected to help improve Windmill.
-
The following information is collected:
-
- - version of your instances
- - instance base URL
- - job usage (language, total duration, count)
- - login type usage (login type, count)
- - worker usage (worker, worker instance, vCPUs, memory)
- - user usage (author count, operator count)
- - superadmin email addresses
- - vCPU usage
- - memory usage
- - development instance status
-
-
- {#if $enterpriseLicense}
-
- On Enterprise Edition, you must send data to check that usage is in line with the
- terms of the subscription. You can either enable telemetry or regularly send usage
- data by clicking the button below.
-
-
- {/if}
- {:else if category == 'Auth/OAuth/SAML'}
-
- {#snippet scim()}
-
- {#each scimSamlSetting as setting}
- closeDrawer?.()}
- {loading}
- {setting}
- {values}
- {version}
- />
- {/each}
-
- {/snippet}
-
- {/if}
-
-
- {#each settings[category] as setting}
- closeDrawer?.()}
- {loading}
- {setting}
- {values}
- {version}
- />
- {/each}
-
-
-
+ {#if hideTabs}
+ {@render tabsContent()}
+ {:else}
+
+ {#each settingsKeys as category}
+
{/each}
- {/snippet}
-
+
+ {#snippet content()}
+
+ {@render tabsContent()}
+ {/snippet}
+
+ {/if}
+
+ {#snippet tabsContent()}
+ {#each Object.keys(settings) as category}
+
+ {#if category == 'SMTP'}
+
+ Setting SMTP unlocks sending emails upon adding new users to the workspace or the
+ instance or sending critical alerts via email.
+
Learn more
+
+ {:else if category == 'Indexer/Search'}
+ The indexer service unlocks full text search across jobs and service logs. It requires
+ spinning up its own separate container
+
Learn how to
+ {:else if category == 'Alerts'}
+
+ Critical alerts automatically notify administrators about system events like job crashes,
+ license issues, worker failures, and queue delays through email, Slack, or Teams.
+
Learn more
+
+ {:else if category == 'Registries'}
+
+ Add private registries for Pip, Bun and npm.
Learn more
+
+ {:else if category == 'Slack'}
+
+ Connecting your instance to a Slack workspace enables critical alerts to be sent to a
+ Slack channel.
+
Learn more
+
+ {:else if category == 'SCIM/SAML'}
+
+ Setting up SAML and SCIM allows you to authenticate users using your identity provider.
+
Learn more
+
+ {:else if category == 'Debug'}
+
+ Enable debug mode to get more detailed logs.
+
+ {:else if category == 'Telemetry'}
+
+ Anonymous usage data is collected to help improve Windmill.
+
The following information is collected:
+
+ - version of your instances
+ - instance base URL
+ - job usage (language, total duration, count)
+ - login type usage (login type, count)
+ - worker usage (worker, worker instance, vCPUs, memory)
+ - user usage (author count, operator count)
+ - superadmin email addresses
+ - vCPU usage
+ - memory usage
+ - development instance status
+
+
+ {#if $enterpriseLicense}
+
+ On Enterprise Edition, you must send data to check that usage is in line with the
+ terms of the subscription. You can either enable telemetry or regularly send usage
+ data by clicking the button below.
+
+
+ {/if}
+ {:else if category == 'Auth/OAuth/SAML'}
+
+ {#snippet scim()}
+
+ {#each scimSamlSetting as setting}
+ closeDrawer?.()}
+ {loading}
+ {setting}
+ {values}
+ {version}
+ {oauths}
+ />
+ {/each}
+
+ {/snippet}
+
+ {/if}
+
+
+ {#each settings[category] as setting}
+
+ {#if setting.fieldType != 'slack_connect'}
+ closeDrawer?.()}
+ {loading}
+ {setting}
+ {values}
+ {version}
+ {oauths}
+ />
+ {/if}
+ {/each}
+
+
+ {/each}
+ {/snippet}
{#if !hideSave}
diff --git a/frontend/src/lib/components/KanidmSetting.svelte b/frontend/src/lib/components/KanidmSetting.svelte
index bec783880a..e8b2a8d647 100644
--- a/frontend/src/lib/components/KanidmSetting.svelte
+++ b/frontend/src/lib/components/KanidmSetting.svelte
@@ -1,5 +1,6 @@
-
-
+
+
{#if enabled}
-
+
{#if name != 'slack' && name != 'teams'}
-