mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-04 16:03:06 +00:00
fat(frontend): simplify flow settings menu
This commit is contained in:
@@ -24,8 +24,7 @@
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
import Required from './Required.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
import { Eye, Folder, Plus, SearchCode, User } from 'lucide-svelte'
|
||||
|
||||
type PathKind =
|
||||
@@ -292,6 +291,7 @@
|
||||
)
|
||||
</script>
|
||||
|
||||
<!-- Drawers -->
|
||||
<Drawer bind:this={newFolder}>
|
||||
<DrawerContent
|
||||
title="New Folder"
|
||||
@@ -319,121 +319,150 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 pb-0 mb-1">
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- full path -->
|
||||
<div class="flex grow flex-col w-full">
|
||||
<div class="flex justify-start w-full grow">
|
||||
<Badge
|
||||
color="gray"
|
||||
class="center-center !bg-surface-secondary !text-tertiary !w-[70px] !h-[24px] rounded-r-none border"
|
||||
>
|
||||
Full path
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={path}
|
||||
size={path?.length || 50}
|
||||
class="font-mono !text-xs max-w-[calc(100%-70px)] !w-auto !h-[24px] !py-0 !border-l-0 !rounded-l-none grow"
|
||||
on:focus={({ currentTarget }) => {
|
||||
currentTarget.select()
|
||||
}}
|
||||
/>
|
||||
<!-- <span class="font-mono text-sm break-all">{path}</span> -->
|
||||
</div>
|
||||
<div class="text-red-600 dark:text-red-400 text-2xs">{error}</div>
|
||||
</div>
|
||||
|
||||
<!-- path selector-->
|
||||
<div class="flex flex-row items-center gap-4">
|
||||
<!-- new folder picker -->
|
||||
{#if meta != undefined}
|
||||
<div class="flex gap-x-4 shrink">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
{#if !hideUser}
|
||||
<div class="block">
|
||||
<span class="text-secondary text-sm whitespace-nowrap"> </span>
|
||||
|
||||
<ToggleButtonGroup
|
||||
class="mt-0.5"
|
||||
bind:selected={meta.ownerKind}
|
||||
on:selected={(e) => {
|
||||
setDirty()
|
||||
const kind = e.detail
|
||||
if (meta) {
|
||||
if (kind === 'folder') {
|
||||
meta.owner = folders?.[0]?.name ?? ''
|
||||
} else if (kind === 'group') {
|
||||
meta.owner = 'all'
|
||||
} else {
|
||||
meta.owner = $userStore?.username?.split('@')[0] ?? ''
|
||||
}
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
{#if !hideUser}
|
||||
<!-- Toggle User/Folder -->
|
||||
<div class="block">
|
||||
<ToggleButtonGroup
|
||||
class="items-center"
|
||||
bind:selected={meta.ownerKind}
|
||||
on:selected={(e) => {
|
||||
setDirty()
|
||||
const kind = e.detail
|
||||
if (meta) {
|
||||
if (kind === 'folder') {
|
||||
meta.owner = folders?.[0]?.name ?? ''
|
||||
} else if (kind === 'group') {
|
||||
meta.owner = 'all'
|
||||
} else {
|
||||
meta.owner = $userStore?.username?.split('@')[0] ?? ''
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
icon={User}
|
||||
{disabled}
|
||||
light
|
||||
size="xs"
|
||||
value="user"
|
||||
position="left"
|
||||
label="User"
|
||||
/>
|
||||
<!-- <ToggleButton light size="xs" value="group" position="center">Group</ToggleButton> -->
|
||||
<ToggleButton
|
||||
icon={Folder}
|
||||
{disabled}
|
||||
light
|
||||
size="xs"
|
||||
value="folder"
|
||||
position="right"
|
||||
label="Folder"
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{/if}
|
||||
{#if meta.ownerKind === 'user'}
|
||||
<label class="block shrink min-w-0">
|
||||
<span class="text-secondary text-sm">User</span>
|
||||
<input
|
||||
class="!w-36"
|
||||
type="text"
|
||||
bind:value={meta.owner}
|
||||
placeholder={$userStore?.username ?? ''}
|
||||
disabled={disabled || !($superadmin || ($userStore?.is_admin ?? false))}
|
||||
on:keydown={setDirty}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
icon={User}
|
||||
{disabled}
|
||||
light
|
||||
size="xs"
|
||||
value="user"
|
||||
position="left"
|
||||
label="User"
|
||||
/>
|
||||
</label>
|
||||
{:else if meta.ownerKind === 'folder'}
|
||||
<label class="block grow w-48">
|
||||
<span class="text-secondary text-sm">
|
||||
Folder
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/groups_and_folders"
|
||||
>
|
||||
Read and write permissions are given to groups and users at the folder level and
|
||||
shared by all items inside the folder.
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
||||
<div class="flex flex-row items-center gap-1 w-full">
|
||||
<select class="grow w-full" {disabled} bind:value={meta.owner}>
|
||||
{#if folders?.length == 0}
|
||||
<option disabled>No folders</option>
|
||||
{/if}
|
||||
{#each folders as { name, write }}
|
||||
<option disabled={!write}>{name}{write ? '' : ' (read-only)'}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button
|
||||
title="View folder"
|
||||
btnClasses="!p-1.5"
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
disabled={!meta.owner || meta.owner == ''}
|
||||
on:click={viewFolder.openDrawer}
|
||||
iconOnly
|
||||
startIcon={{ icon: Eye }}
|
||||
/>
|
||||
<Button
|
||||
title="New folder"
|
||||
btnClasses="!p-1.5"
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
{disabled}
|
||||
on:click={newFolder.openDrawer}
|
||||
iconOnly
|
||||
startIcon={{ icon: Plus }}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
<label class="block grow w-full max-w-md">
|
||||
<div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
|
||||
<div>
|
||||
Name
|
||||
<Required required={true} />
|
||||
</div>
|
||||
<div class="text-2xs text-tertiary"> '/' for subfolders </div>
|
||||
<!-- <ToggleButton light size="xs" value="group" position="center">Group</ToggleButton> -->
|
||||
<ToggleButton
|
||||
icon={Folder}
|
||||
{disabled}
|
||||
light
|
||||
size="xs"
|
||||
value="folder"
|
||||
position="right"
|
||||
label="Folder"
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="text-secondary text-sm">/</div>
|
||||
|
||||
{#if meta.ownerKind === 'user'}
|
||||
<label class="block grow shrink min-w-36">
|
||||
<input
|
||||
class="w-full"
|
||||
type="text"
|
||||
bind:value={meta.owner}
|
||||
placeholder={$userStore?.username ?? ''}
|
||||
disabled={disabled || !($superadmin || ($userStore?.is_admin ?? false))}
|
||||
on:keydown={setDirty}
|
||||
/>
|
||||
</label>
|
||||
{:else if meta.ownerKind === 'folder'}
|
||||
<label class="block grow min-w-48">
|
||||
<!-- <span class="text-secondary text-sm">
|
||||
Folder
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/groups_and_folders"
|
||||
>
|
||||
Read and write permissions are given to groups and users at the folder level and
|
||||
shared by all items inside the folder.
|
||||
</Tooltip>
|
||||
</span> -->
|
||||
|
||||
<div class="flex flex-row items-center gap-1 w-full">
|
||||
<select class="grow w-full" {disabled} bind:value={meta.owner}>
|
||||
{#if folders?.length == 0}
|
||||
<option disabled>No folders</option>
|
||||
{/if}
|
||||
{#each folders as { name, write }}
|
||||
<option disabled={!write}>{name}{write ? '' : ' (read-only)'}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button
|
||||
title="View folder"
|
||||
btnClasses="!p-1.5"
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
disabled={!meta.owner || meta.owner == ''}
|
||||
on:click={viewFolder.openDrawer}
|
||||
iconOnly
|
||||
startIcon={{ icon: Eye }}
|
||||
/>
|
||||
<Button
|
||||
title="New folder"
|
||||
btnClasses="!p-1.5"
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
{disabled}
|
||||
on:click={newFolder.openDrawer}
|
||||
iconOnly
|
||||
startIcon={{ icon: Plus }}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<div class="text-secondary text-sm">/</div>
|
||||
|
||||
<!-- The name -->
|
||||
<label class="block grow w-full max-w-md">
|
||||
<!-- <div class="text-secondary text-sm flex items-center gap-1 w-full justify-between">
|
||||
<div>
|
||||
Name
|
||||
<Required required={true} />
|
||||
</div>
|
||||
<div class="text-2xs text-tertiary"> '/' for subfolders </div>
|
||||
</div> -->
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
{disabled}
|
||||
@@ -453,29 +482,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-4">
|
||||
<div class="flex justify-start w-full">
|
||||
<Badge
|
||||
color="gray"
|
||||
class="center-center !bg-surface-secondary !text-tertiary !w-[70px] !h-[24px] rounded-r-none border"
|
||||
>
|
||||
Full path
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={path}
|
||||
size={path?.length || 50}
|
||||
class="font-mono !text-xs max-w-[calc(100%-70px)] !w-auto !h-[24px] !py-0 !border-l-0 !rounded-l-none"
|
||||
on:focus={({ currentTarget }) => {
|
||||
currentTarget.select()
|
||||
}}
|
||||
/>
|
||||
<!-- <span class="font-mono text-sm break-all">{path}</span> -->
|
||||
</div>
|
||||
<div class="text-red-600 dark:text-red-400 text-2xs">{error}</div>
|
||||
</div>
|
||||
|
||||
{#if kind != 'app' && kind != 'schedule' && kind != 'http_trigger' && initialPath != '' && initialPath != undefined && initialPath != path}
|
||||
<Alert type="warning" class="mt-4" title="Moving may break other items relying on it">
|
||||
You are renaming an item that may be depended upon by other items. This may break apps, flows
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
export let eeOnly = false
|
||||
export let small: boolean = false
|
||||
|
||||
export let subsection: boolean = false
|
||||
|
||||
export let collapsable: boolean = false
|
||||
let collapsed: boolean = true
|
||||
</script>
|
||||
@@ -17,7 +19,9 @@
|
||||
<div class="flex flex-row justify-between items-center mb-2">
|
||||
<h2
|
||||
class={twMerge(
|
||||
'font-semibold flex flex-row items-center gap-1',
|
||||
subsection ? 'font-normal' : 'font-semibold',
|
||||
subsection ? 'text-secondary' : 'text-primary',
|
||||
'flex flex-row items-center gap-1',
|
||||
small ? 'text-sm' : 'text-base'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
export let size: 'sm' | 'xs' = 'sm'
|
||||
|
||||
export let lightToogle = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const bothOptions = Boolean(options.left) && Boolean(options.right)
|
||||
</script>
|
||||
@@ -68,7 +70,9 @@
|
||||
color == 'red'
|
||||
? 'peer-checked:bg-red-600'
|
||||
: 'peer-checked:bg-blue-600 dark:peer-checked:bg-blue-500',
|
||||
size === 'sm'
|
||||
lightToogle
|
||||
? 'w-9 h-5 after:top-0.5 after:left-[2px] after:h-4 after:w-4'
|
||||
: size === 'sm'
|
||||
? 'w-11 h-6 after:top-0.5 after:left-[2px] after:h-5 after:w-5'
|
||||
: 'w-7 h-4 after:top-0.5 after:left-[2px] after:h-3 after:w-3'
|
||||
)}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
|
||||
import Tab from '$lib/components/common/tabs/Tab.svelte'
|
||||
import TabContent from '$lib/components/common/tabs/TabContent.svelte'
|
||||
|
||||
import { BROWSER } from 'esm-env'
|
||||
import { base } from '$lib/base'
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import FlowCard from '../common/FlowCard.svelte'
|
||||
import FlowSchedules from './FlowSchedules.svelte'
|
||||
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { Alert, Button, SecondsInput } from '$lib/components/common'
|
||||
import { getContext } from 'svelte'
|
||||
@@ -29,7 +25,7 @@
|
||||
|
||||
export let noEditor: boolean
|
||||
|
||||
const { selectedId, flowStore, initialPath, previewArgs, pathStore, schedule, customUi } =
|
||||
const { flowStore, initialPath, previewArgs, pathStore, customUi } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
let hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
|
||||
@@ -43,511 +39,478 @@
|
||||
let dirtyPath = false
|
||||
</script>
|
||||
|
||||
<div class="h-full overflow-hidden">
|
||||
<div class="h-full overflow-auto flex flex-col">
|
||||
<FlowCard {noEditor} title="Settings">
|
||||
<div class="h-full flex-1">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<Tabs bind:selected={$selectedId}>
|
||||
<Tab value="settings-metadata">Metadata</Tab>
|
||||
{#if !noEditor && customUi?.settingsTabs?.schedule != false}
|
||||
<Tab value="settings-schedule" active={$schedule.enabled}>Schedule</Tab>
|
||||
{/if}
|
||||
{#if customUi?.settingsTabs?.sharedDiretory != false}
|
||||
<Tab value="settings-same-worker" active={$flowStore.value.same_worker}>
|
||||
Shared Directory
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if customUi?.settingsTabs?.earlyStop != false}
|
||||
<Tab value="settings-early-stop" active={Boolean($flowStore.value.skip_expr)}>
|
||||
Early Stop
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if customUi?.settingsTabs?.earlyReturn != false}
|
||||
<Tab value="settings-early-return" active={Boolean($flowStore.value.early_return)}>
|
||||
Early Return
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if customUi?.settingsTabs?.workerGroup != false}
|
||||
<Tab value="settings-worker-group">Worker Group</Tab>
|
||||
{/if}
|
||||
{#if customUi?.settingsTabs?.concurrency != false}
|
||||
<Tab value="settings-concurrency">Concurrency</Tab>
|
||||
{/if}
|
||||
{#if customUi?.settingsTabs?.cache != false}
|
||||
<Tab value="settings-cache" active={Boolean($flowStore.value.cache_ttl)}>Cache</Tab>
|
||||
{/if}
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="settings-metadata" class="p-4 h-full overflow-auto">
|
||||
<Section label="Metadata">
|
||||
<div class="h-full gap-8 flex flex-col">
|
||||
<Label label="Summary">
|
||||
<MetadataGen
|
||||
bind:content={$flowStore.summary}
|
||||
promptConfigName="flowSummary"
|
||||
flow={$flowStore.value}
|
||||
on:change={() => {
|
||||
if (initialPath == '' && $flowStore.summary?.length > 0 && !dirtyPath) {
|
||||
path?.setName(
|
||||
$flowStore.summary
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_]/g, '_')
|
||||
.replace(/-+/g, '_')
|
||||
.replace(/^-|-$/g, '')
|
||||
)
|
||||
}
|
||||
}}
|
||||
elementProps={{
|
||||
type: 'text',
|
||||
id: 'flow-summary',
|
||||
placeholder: 'Short summary to be displayed when listed'
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{#if !noEditor}
|
||||
<Label label="Path">
|
||||
<Path
|
||||
autofocus={false}
|
||||
bind:this={path}
|
||||
bind:dirty={dirtyPath}
|
||||
bind:path={$pathStore}
|
||||
{initialPath}
|
||||
namePlaceholder="flow"
|
||||
kind="flow"
|
||||
/>
|
||||
</Label>
|
||||
{/if}
|
||||
|
||||
<Label label="Description">
|
||||
<MetadataGen
|
||||
bind:content={$flowStore.description}
|
||||
promptConfigName="flowDescription"
|
||||
flow={$flowStore.value}
|
||||
class="w-full"
|
||||
elementType="textarea"
|
||||
elementProps={{
|
||||
id: 'inp',
|
||||
placeholder: 'What this flow does and how to use it.'
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<!-- TODO: Add EE-only badge when we have it -->
|
||||
<Toggle
|
||||
disabled={!$enterpriseLicense || isCloudHosted()}
|
||||
checked={$flowStore.value.priority !== undefined && $flowStore.value.priority > 0}
|
||||
on:change={() => {
|
||||
if ($flowStore.value.priority) {
|
||||
$flowStore.value.priority = undefined
|
||||
} else {
|
||||
$flowStore.value.priority = 100
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: `Label as high priority`,
|
||||
rightTooltip: `All jobs scheduled by flows labeled as high priority take precedence over the other jobs in the jobs queue. ${
|
||||
!$enterpriseLicense
|
||||
? 'This is a feature only available on enterprise edition.'
|
||||
: ''
|
||||
}`
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="right">
|
||||
<input
|
||||
type="number"
|
||||
class="!w-16 ml-4"
|
||||
disabled={$flowStore.value.priority === undefined}
|
||||
bind:value={$flowStore.value.priority}
|
||||
on:focus
|
||||
on:change={() => {
|
||||
if ($flowStore.value.priority && $flowStore.value.priority > 100) {
|
||||
$flowStore.value.priority = 100
|
||||
} else if ($flowStore.value.priority && $flowStore.value.priority < 0) {
|
||||
$flowStore.value.priority = 0
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Toggle>
|
||||
|
||||
<Toggle
|
||||
size="sm"
|
||||
checked={Boolean($flowStore.visible_to_runner_only)}
|
||||
on:change={() => {
|
||||
if ($flowStore.visible_to_runner_only) {
|
||||
$flowStore.visible_to_runner_only = undefined
|
||||
} else {
|
||||
$flowStore.visible_to_runner_only = true
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Make runs invisible to others',
|
||||
rightTooltip:
|
||||
'When this option is enabled, manual executions of this script are invisible to users other than the user running it, including the owner(s). This setting can be overridden when this script is run manually from the advanced menu.'
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<ErrorHandlerToggleButton
|
||||
kind="flow"
|
||||
scriptOrFlowPath={$pathStore}
|
||||
bind:errorHandlerMuted={$flowStore.ws_error_handler_muted}
|
||||
iconOnly={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Slider text="How to trigger flows?">
|
||||
<div class="text-sm text-tertiary border p-4 mb-20">
|
||||
On-demand:
|
||||
<ul class="pt-4">
|
||||
<li>
|
||||
1. <a
|
||||
href="https://www.windmill.dev/docs/core_concepts/auto_generated_uis"
|
||||
target="_blank">Auto-generated UIs</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
2. <a href="{base}/apps/add?nodraft=true" target="_blank"> App Editor</a> for
|
||||
customized-UIs
|
||||
</li>
|
||||
<li>
|
||||
3. <a href="{base}/schedules" target="_blank">Scheduling</a>
|
||||
</li>
|
||||
<li>
|
||||
4. <a href="https://www.windmill.dev/docs/advanced/cli" target="_blank"
|
||||
>Windmill CLI</a
|
||||
>
|
||||
</li>
|
||||
<br />
|
||||
<li class="mt-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<p> From external events: </p>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mt-2">
|
||||
5. Send a <a
|
||||
href="https://www.windmill.dev/docs/core_concepts/webhooks"
|
||||
target="_blank">webhook</a
|
||||
>
|
||||
after each event:
|
||||
<ul class="list-disc pl-4"
|
||||
><li
|
||||
>Async <Tooltip
|
||||
>Return an uuid instantly that you can use to fetch status and result</Tooltip
|
||||
>:
|
||||
<a
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
copyToClipboard(url)
|
||||
}}
|
||||
href={url}
|
||||
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
|
||||
>
|
||||
{url}
|
||||
<span class="text-secondary ml-2">
|
||||
<Clipboard />
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
>Sync <Tooltip>Wait for result within a timeout of 20s</Tooltip>:
|
||||
<a
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
copyToClipboard(syncedUrl)
|
||||
}}
|
||||
href={syncedUrl}
|
||||
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
|
||||
>
|
||||
{syncedUrl}
|
||||
<span class="text-secondary ml-2">
|
||||
<Clipboard />
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul></li
|
||||
>
|
||||
<br />
|
||||
<li>
|
||||
6. Use a <a
|
||||
href="https://www.windmill.dev/docs/flows/flow_trigger"
|
||||
target="_blank">trigger script</a
|
||||
>
|
||||
and schedule this flow to run as frequently as needed and compare a state persisted
|
||||
in Windmill to the state of the external system. If a difference is detected,
|
||||
then the rest of the flow is triggered. Oftentimes, the second step of a flow
|
||||
is a for-loop that will iterate over every elements. When using a trigger, a
|
||||
default schedule will be created.
|
||||
<img
|
||||
class="shadow-lg border rounded"
|
||||
alt="static button"
|
||||
src="{base}/trigger_button.png"
|
||||
/>
|
||||
</li></ul
|
||||
>
|
||||
</div>
|
||||
</Slider>
|
||||
</div>
|
||||
</Section>
|
||||
</TabContent>
|
||||
<TabContent value="settings-schedule" class="p-4 h-full overflow-scroll">
|
||||
<Alert
|
||||
type="info"
|
||||
title="Primary Schedule"
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/scheduling"
|
||||
>
|
||||
Flows can be triggered by any schedules, their webhooks or their UI but they have only
|
||||
one primary schedule with which they share the same path. The primary schedule can be
|
||||
set here.
|
||||
</Alert>
|
||||
<div class="mt-4" />
|
||||
<FlowSchedules />
|
||||
</TabContent>
|
||||
|
||||
<TabContent value="settings-same-worker" class="p-4 flex flex-col">
|
||||
<Section label="Shared Directory" class="flex flex-col">
|
||||
<Alert type="info" title="Shared Directory">
|
||||
Steps will share a folder at `./shared` in which they can store heavier data and
|
||||
pass them to the next step. <br /><br />Beware that the `./shared` folder is not
|
||||
preserved across suspends and sleeps. <br /><br />
|
||||
Furthermore, steps' worker groups is not respected and only the flow's worker group will
|
||||
be respected.
|
||||
</Alert>
|
||||
<Toggle
|
||||
bind:checked={$flowStore.value.same_worker}
|
||||
options={{
|
||||
right: 'Shared Directory on `./shared`'
|
||||
}}
|
||||
class="mt-2"
|
||||
/>
|
||||
</Section>
|
||||
</TabContent>
|
||||
<TabContent value="settings-cache" class="p-4 flex flex-col">
|
||||
<Section label="Cache">
|
||||
<svelte:fragment slot="action">
|
||||
<Toggle
|
||||
size="xs"
|
||||
checked={Boolean($flowStore.value.cache_ttl)}
|
||||
on:change={() => {
|
||||
if ($flowStore.value.cache_ttl && $flowStore.value.cache_ttl != undefined) {
|
||||
$flowStore.value.cache_ttl = undefined
|
||||
} else {
|
||||
$flowStore.value.cache_ttl = 300
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Cache the results for each possible inputs'
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex gap-x-4 flex-col gap-2">
|
||||
<div class="text-xs">How long to keep the cache valid</div>
|
||||
<div>
|
||||
{#if $flowStore.value.cache_ttl}
|
||||
<SecondsInput bind:seconds={$flowStore.value.cache_ttl} />
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</TabContent>
|
||||
|
||||
<TabContent value="settings-worker-group" class="p-4 flex flex-col">
|
||||
<Alert type="info" title="Worker Group Tag (Queue)">
|
||||
When a worker group tag is defined at the flow level, any steps inside the flow will
|
||||
run on any worker group that listen to that tag, regardless of the steps' tag. If no
|
||||
worker group tags is defined, the flow controls will be executed with the default tag
|
||||
'flow' and the steps will be executed with their respective tag
|
||||
</Alert>
|
||||
<span class="my-4 text-lg font-bold">Worker Group Tag (Queue)</span>
|
||||
<WorkerTagPicker bind:tag={$flowStore.tag} />
|
||||
|
||||
<div class="py-6" />
|
||||
<span class="my-4 text-lg font-bold flex items-baseline gap-8"
|
||||
>Dedicated Worker {#if !$enterpriseLicense}<div
|
||||
class="flex text-xs items-center gap-1 text-yellow-500 whitespace-nowrap"
|
||||
>
|
||||
<AlertTriangle size={16} />
|
||||
EE only
|
||||
</div>{/if}</span
|
||||
>
|
||||
|
||||
<Toggle
|
||||
disabled={!$enterpriseLicense || isCloudHosted()}
|
||||
size="sm"
|
||||
checked={Boolean($flowStore.dedicated_worker)}
|
||||
<div class="grow min-h-0 p-4 h-full flex flex-col gap-4">
|
||||
<!-- Metadata Section -->
|
||||
<Section label="Metadata">
|
||||
<div class="h-full gap-8 flex flex-col">
|
||||
<Label label="Summary">
|
||||
<MetadataGen
|
||||
bind:content={$flowStore.summary}
|
||||
promptConfigName="flowSummary"
|
||||
flow={$flowStore.value}
|
||||
on:change={() => {
|
||||
if ($flowStore.dedicated_worker) {
|
||||
$flowStore.dedicated_worker = undefined
|
||||
} else {
|
||||
$flowStore.dedicated_worker = true
|
||||
if (initialPath == '' && $flowStore.summary?.length > 0 && !dirtyPath) {
|
||||
path?.setName(
|
||||
$flowStore.summary
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_]/g, '_')
|
||||
.replace(/-+/g, '_')
|
||||
.replace(/^-|-$/g, '')
|
||||
)
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Flow is run on dedicated workers'
|
||||
elementProps={{
|
||||
type: 'text',
|
||||
id: 'flow-summary',
|
||||
placeholder: 'Short summary to be displayed when listed'
|
||||
}}
|
||||
/>
|
||||
{#if $flowStore.dedicated_worker}
|
||||
<div class="py-2">
|
||||
<Alert type="info" title="Require dedicated workers">
|
||||
One worker in a worker group needs to be configured with dedicated worker set to: <pre
|
||||
>{$workspaceStore}:flow/{$pathStore}</pre
|
||||
>
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip
|
||||
>In this mode, every scripts of this flow is run on the workers dedicated to this
|
||||
flow that keep the scripts "hot" so that there is not cold start cost incurred.
|
||||
Steps can run at >1500 rps in this mode.</Tooltip
|
||||
>
|
||||
</svelte:fragment>
|
||||
</TabContent>
|
||||
<TabContent value="settings-early-stop" class="p-4">
|
||||
<Section label="Early stop">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip documentationLink="https://www.windmill.dev/docs/flows/early_stop">
|
||||
If defined, at the beginning of the step the predicate expression will be
|
||||
evaluated to decide if the flow should stop early.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
<Toggle
|
||||
checked={Boolean($flowStore.value.skip_expr)}
|
||||
on:change={() => {
|
||||
if (Boolean($flowStore.value.skip_expr) && $flowStore.value.skip_expr) {
|
||||
$flowStore.value.skip_expr = undefined
|
||||
} else {
|
||||
$flowStore.value.skip_expr = 'flow_input.foo == undefined'
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Early stop if condition met'
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<div
|
||||
class="w-full border mt-2 p-2 flex flex-col {$flowStore.value.skip_expr
|
||||
? ''
|
||||
: 'bg-surface-secondary'}"
|
||||
>
|
||||
{#if $flowStore.value.skip_expr}
|
||||
<div class="border w-full">
|
||||
<SimpleEditor
|
||||
lang="javascript"
|
||||
bind:code={$flowStore.value.skip_expr}
|
||||
class="small-editor"
|
||||
extraLib={`declare const flow_input = ${JSON.stringify(
|
||||
schemaToObject(asSchema($flowStore.schema), $previewArgs)
|
||||
)};
|
||||
declare const WM_SCHEDULED_FOR: string;`}
|
||||
/>
|
||||
<div class="text-xs text-tertiary mt-2">
|
||||
You can use the variable `flow_input` to access the inputs of the flow. <br
|
||||
/>The variable `WM_SCHEDULED_FOR` contains the time the flow was scheduled for
|
||||
which you can use to stop early non fresh jobs:
|
||||
<pre>new Date().getTime() - new Date(WM_SCHEDULED_FOR).getTime() {'>'} X</pre>
|
||||
{#if !noEditor}
|
||||
<Label label="Path">
|
||||
<Path
|
||||
autofocus={false}
|
||||
bind:this={path}
|
||||
bind:dirty={dirtyPath}
|
||||
bind:path={$pathStore}
|
||||
{initialPath}
|
||||
namePlaceholder="flow"
|
||||
kind="flow"
|
||||
/>
|
||||
</Label>
|
||||
{/if}
|
||||
|
||||
<Label label="Description">
|
||||
<MetadataGen
|
||||
bind:content={$flowStore.description}
|
||||
promptConfigName="flowDescription"
|
||||
flow={$flowStore.value}
|
||||
class="w-full"
|
||||
elementType="textarea"
|
||||
elementProps={{
|
||||
id: 'inp',
|
||||
placeholder: 'What this flow does and how to use it.'
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Triggers">
|
||||
<Slider text="Triggers">
|
||||
<div class="text-sm text-tertiary border p-4 mb-20">
|
||||
On-demand:
|
||||
<ul class="pt-4">
|
||||
<li>
|
||||
1. <a
|
||||
href="https://www.windmill.dev/docs/core_concepts/auto_generated_uis"
|
||||
target="_blank">Auto-generated UIs</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
2. <a href="{base}/apps/add?nodraft=true" target="_blank"> App Editor</a> for customized-UIs
|
||||
</li>
|
||||
<li>
|
||||
3. <a href="{base}/schedules" target="_blank">Scheduling</a>
|
||||
</li>
|
||||
<li>
|
||||
4. <a href="https://www.windmill.dev/docs/advanced/cli" target="_blank"
|
||||
>Windmill CLI</a
|
||||
>
|
||||
</li>
|
||||
<br />
|
||||
<li class="mt-2">
|
||||
<div class="flex flex-col gap-2">
|
||||
<p> From external events: </p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="mt-2">
|
||||
5. Send a <a
|
||||
href="https://www.windmill.dev/docs/core_concepts/webhooks"
|
||||
target="_blank">webhook</a
|
||||
>
|
||||
after each event:
|
||||
<ul class="list-disc pl-4"
|
||||
><li
|
||||
>Async <Tooltip
|
||||
>Return an uuid instantly that you can use to fetch status and result</Tooltip
|
||||
>:
|
||||
<a
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
copyToClipboard(url)
|
||||
}}
|
||||
href={url}
|
||||
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
|
||||
>
|
||||
{url}
|
||||
<span class="text-secondary ml-2">
|
||||
<Clipboard />
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
>Sync <Tooltip>Wait for result within a timeout of 20s</Tooltip>:
|
||||
<a
|
||||
on:click={(e) => {
|
||||
e.preventDefault()
|
||||
copyToClipboard(syncedUrl)
|
||||
}}
|
||||
href={syncedUrl}
|
||||
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
|
||||
>
|
||||
{syncedUrl}
|
||||
<span class="text-secondary ml-2">
|
||||
<Clipboard />
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul></li
|
||||
>
|
||||
<br />
|
||||
<li>
|
||||
6. Use a <a
|
||||
href="https://www.windmill.dev/docs/flows/flow_trigger"
|
||||
target="_blank">trigger script</a
|
||||
>
|
||||
and schedule this flow to run as frequently as needed and compare a state persisted
|
||||
in Windmill to the state of the external system. If a difference is detected, then
|
||||
the rest of the flow is triggered. Oftentimes, the second step of a flow is a for-loop
|
||||
that will iterate over every elements. When using a trigger, a default schedule will
|
||||
be created.
|
||||
<img
|
||||
class="shadow-lg border rounded"
|
||||
alt="static button"
|
||||
src="{base}/trigger_button.png"
|
||||
/>
|
||||
</li></ul
|
||||
>
|
||||
</div>
|
||||
</Slider>
|
||||
</Label>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- Deployable Section -->
|
||||
<Section label="Advanced" collapsable={true} class="h-full grow min-h-0 flex flex-col gap-4">
|
||||
<!-- Metadata Advanced Section -->
|
||||
<!-- TODO: Add EE-only badge when we have it -->
|
||||
|
||||
<!-- Priority Section -->
|
||||
<Toggle
|
||||
lightToogle={true}
|
||||
disabled={!$enterpriseLicense || isCloudHosted()}
|
||||
checked={$flowStore.value.priority !== undefined && $flowStore.value.priority > 0}
|
||||
on:change={() => {
|
||||
if ($flowStore.value.priority) {
|
||||
$flowStore.value.priority = undefined
|
||||
} else {
|
||||
$flowStore.value.priority = 100
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: `Label as high priority`,
|
||||
rightTooltip: `All jobs scheduled by flows labeled as high priority take precedence over the other jobs in the jobs queue. ${
|
||||
!$enterpriseLicense ? 'This is a feature only available on enterprise edition.' : ''
|
||||
}`
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="right">
|
||||
<input
|
||||
type="number"
|
||||
class="!w-16 ml-4"
|
||||
disabled={$flowStore.value.priority === undefined}
|
||||
bind:value={$flowStore.value.priority}
|
||||
on:focus
|
||||
on:change={() => {
|
||||
if ($flowStore.value.priority && $flowStore.value.priority > 100) {
|
||||
$flowStore.value.priority = 100
|
||||
} else if ($flowStore.value.priority && $flowStore.value.priority < 0) {
|
||||
$flowStore.value.priority = 0
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Toggle>
|
||||
|
||||
<!-- Visibility Section -->
|
||||
<Toggle
|
||||
lightToogle={true}
|
||||
size="sm"
|
||||
checked={Boolean($flowStore.visible_to_runner_only)}
|
||||
on:change={() => {
|
||||
if ($flowStore.visible_to_runner_only) {
|
||||
$flowStore.visible_to_runner_only = undefined
|
||||
} else {
|
||||
$flowStore.visible_to_runner_only = true
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Make runs invisible to others',
|
||||
rightTooltip:
|
||||
'When this option is enabled, manual executions of this script are invisible to users other than the user running it, including the owner(s). This setting can be overridden when this script is run manually from the advanced menu.'
|
||||
}}
|
||||
/>
|
||||
|
||||
<!-- Error Handler Section -->
|
||||
<div class="flex flex-row items-center">
|
||||
<ErrorHandlerToggleButton
|
||||
kind="flow"
|
||||
scriptOrFlowPath={$pathStore}
|
||||
bind:errorHandlerMuted={$flowStore.ws_error_handler_muted}
|
||||
iconOnly={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Shared Directory Section -->
|
||||
{#if customUi?.settingsTabs?.sharedDiretory != false}
|
||||
<Toggle
|
||||
lightToogle={true}
|
||||
bind:checked={$flowStore.value.same_worker}
|
||||
options={{
|
||||
right: 'Shared Directory on `./shared`',
|
||||
rightTooltip:
|
||||
'Steps will share a folder at `./shared` in which they can store heavier data and ' +
|
||||
'pass them to the next step. Beware that the `./shared` folder is not ' +
|
||||
'preserved across suspends and sleeps.'
|
||||
}}
|
||||
class="mt-2"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Cache Section -->
|
||||
{#if customUi?.settingsTabs?.cache != false}
|
||||
<Toggle
|
||||
lightToogle={true}
|
||||
size="sm"
|
||||
checked={Boolean($flowStore.value.cache_ttl)}
|
||||
on:change={() => {
|
||||
if ($flowStore.value.cache_ttl && $flowStore.value.cache_ttl != undefined) {
|
||||
$flowStore.value.cache_ttl = undefined
|
||||
} else {
|
||||
$flowStore.value.cache_ttl = 300
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Cache the results for each possible inputs'
|
||||
}}
|
||||
/>
|
||||
{#if $flowStore.value.cache_ttl}
|
||||
<div class="flex gap-x-4 flex-col gap-2">
|
||||
<div class="text-xs">How long to keep the cache valid</div>
|
||||
<div>
|
||||
{#if $flowStore.value.cache_ttl}
|
||||
<SecondsInput bind:seconds={$flowStore.value.cache_ttl} />
|
||||
{:else}
|
||||
<textarea disabled rows="3" class="min-h-[80px]" />
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
</div>
|
||||
</Section>
|
||||
</TabContent>
|
||||
<TabContent value="settings-early-return" class="p-4">
|
||||
<Section label="Early Return">
|
||||
<div class="py-2">
|
||||
<Alert type="info" title="Return sync endpoints early">
|
||||
If defined, sync endpoints will return early at the node defined here while the
|
||||
rest of the flow continue asynchronously.
|
||||
</Alert>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={Boolean($flowStore.value.early_return)}
|
||||
on:change={() => {
|
||||
if (Boolean($flowStore.value.early_return) && $flowStore.value.early_return) {
|
||||
$flowStore.value.early_return = undefined
|
||||
} else {
|
||||
$flowStore.value.early_return = $flowStore.value.modules?.[0]?.id ?? 'a'
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Early return sync endpoint at a top-level step'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="border max-w-[120px] mt-2 p-2 flex flex-col {$flowStore.value.early_return
|
||||
? ''
|
||||
: 'bg-surface-secondary'}"
|
||||
<!-- Early Stop Section -->
|
||||
{#if customUi?.settingsTabs?.earlyStop != false}
|
||||
<!-- documentationLink="https://www.windmill.dev/docs/flows/early_stop -->
|
||||
<Toggle
|
||||
lightToogle={true}
|
||||
checked={Boolean($flowStore.value.skip_expr)}
|
||||
on:change={() => {
|
||||
if (Boolean($flowStore.value.skip_expr) && $flowStore.value.skip_expr) {
|
||||
$flowStore.value.skip_expr = undefined
|
||||
} else {
|
||||
$flowStore.value.skip_expr = 'flow_input.foo == undefined'
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Early stop if condition met',
|
||||
rightTooltip:
|
||||
'If defined, at the beginning of the step the predicate expression will be evaluated' +
|
||||
'to decide if the flow should stop early.'
|
||||
}}
|
||||
/>
|
||||
{#if $flowStore.value.skip_expr}
|
||||
<div
|
||||
class="w-full border mt-2 p-2 flex flex-col {$flowStore.value.skip_expr
|
||||
? ''
|
||||
: 'bg-surface-secondary'}"
|
||||
>
|
||||
<div class="border w-full">
|
||||
<SimpleEditor
|
||||
lang="javascript"
|
||||
bind:code={$flowStore.value.skip_expr}
|
||||
class="small-editor"
|
||||
extraLib={`declare const flow_input = ${JSON.stringify(
|
||||
schemaToObject(asSchema($flowStore.schema), $previewArgs)
|
||||
)};
|
||||
declare const WM_SCHEDULED_FOR: string;`}
|
||||
/>
|
||||
<div class="text-xs text-tertiary mt-2">
|
||||
You can use the variable `flow_input` to access the inputs of the flow. <br />The
|
||||
variable `WM_SCHEDULED_FOR` contains the time the flow was scheduled for which you
|
||||
can use to stop early non fresh jobs:
|
||||
<pre>new Date().getTime() - new Date(WM_SCHEDULED_FOR).getTime() {'>'} X</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Early Return Section -->
|
||||
{#if customUi?.settingsTabs?.earlyReturn != false}
|
||||
<Toggle
|
||||
lightToogle={true}
|
||||
checked={Boolean($flowStore.value.early_return)}
|
||||
on:change={() => {
|
||||
if (Boolean($flowStore.value.early_return) && $flowStore.value.early_return) {
|
||||
$flowStore.value.early_return = undefined
|
||||
} else {
|
||||
$flowStore.value.early_return = $flowStore.value.modules?.[0]?.id ?? 'a'
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Early return sync endpoint at a top-level step',
|
||||
rightTooltip:
|
||||
'If defined, sync endpoints will return early at the node defined here while the rest of the flow continue asynchronously.'
|
||||
}}
|
||||
/>
|
||||
{#if $flowStore.value.early_return}
|
||||
<div
|
||||
class="border max-w-[120px] mt-2 p-2 flex flex-col {$flowStore.value.early_return
|
||||
? ''
|
||||
: 'bg-surface-secondary'}"
|
||||
>
|
||||
<select name="oauth_name" id="oauth_name" bind:value={$flowStore.value.early_return}>
|
||||
<option value={undefined}>Node's id</option>
|
||||
{#each $flowStore.value?.modules?.map((x) => x.id) as name}
|
||||
<option value={name}>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Worker Group Section -->
|
||||
{#if customUi?.settingsTabs?.workerGroup != false}
|
||||
<Label label="Worker Group Tag (Queue)">
|
||||
<WorkerTagPicker bind:tag={$flowStore.tag} />
|
||||
|
||||
{#if !$enterpriseLicense}
|
||||
<div class="flex text-xs items-center gap-1 text-yellow-500 whitespace-nowrap">
|
||||
<AlertTriangle size={16} />
|
||||
EE only
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip
|
||||
>When a worker group tag is defined at the flow level, any steps inside the flow
|
||||
will run on any worker group that listen to that tag, regardless of the steps' tag.
|
||||
If no worker group tags is defined, the flow controls will be executed with the
|
||||
default tag 'flow' and the steps will be executed with their respective tag</Tooltip
|
||||
>
|
||||
<select
|
||||
name="oauth_name"
|
||||
id="oauth_name"
|
||||
bind:value={$flowStore.value.early_return}
|
||||
>
|
||||
<option value={undefined}>Node's id</option>
|
||||
{#each $flowStore.value?.modules?.map((x) => x.id) as name}
|
||||
<option value={name}>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</Section>
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
</Label>
|
||||
|
||||
<TabContent value="settings-concurrency" class="p-4 flex flex-col">
|
||||
<Section label="Concurrency Limits" eeOnly>
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip>Allowed concurrency within a given timeframe</Tooltip>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col gap-4">
|
||||
<Label label="Max number of executions within the time window">
|
||||
<div class="flex flex-row gap-2 max-w-sm">
|
||||
<input
|
||||
disabled={!$enterpriseLicense}
|
||||
bind:value={$flowStore.value.concurrent_limit}
|
||||
type="number"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
color="light"
|
||||
on:click={() => {
|
||||
$flowStore.value.concurrent_limit = undefined
|
||||
}}
|
||||
variant="border">Remove Limits</Button
|
||||
>
|
||||
</div>
|
||||
</Label>
|
||||
<Label label="Time window in seconds">
|
||||
<SecondsInput
|
||||
disabled={!$enterpriseLicense}
|
||||
bind:seconds={$flowStore.value.concurrency_time_window_s}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Custom concurrency key (optional)">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip>
|
||||
Concurrency keys are global, you can have them be workspace specific using the
|
||||
variable `$workspace`. You can also use an argument's value using
|
||||
`$args[name_of_arg]`</Tooltip
|
||||
>
|
||||
</svelte:fragment>
|
||||
<Toggle
|
||||
lightToogle={true}
|
||||
disabled={!$enterpriseLicense || isCloudHosted()}
|
||||
size="sm"
|
||||
checked={Boolean($flowStore.dedicated_worker)}
|
||||
on:change={() => {
|
||||
if ($flowStore.dedicated_worker) {
|
||||
$flowStore.dedicated_worker = undefined
|
||||
} else {
|
||||
$flowStore.dedicated_worker = true
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Flow is run on dedicated workers'
|
||||
}}
|
||||
/>
|
||||
{#if $flowStore.dedicated_worker}
|
||||
<div class="py-2">
|
||||
<Alert type="info" title="Require dedicated workers">
|
||||
One worker in a worker group needs to be configured with dedicated worker set to: <pre
|
||||
>{$workspaceStore}:flow/{$pathStore}</pre
|
||||
>
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- <Tooltip
|
||||
>In this mode, every scripts of this flow is run on the workers dedicated to this flow
|
||||
that keep the scripts "hot" so that there is not cold start cost incurred. Steps can run
|
||||
at >1500 rps in this mode.</Tooltip
|
||||
> -->
|
||||
{/if}
|
||||
|
||||
<!-- Concurrency Section -->
|
||||
{#if customUi?.settingsTabs?.concurrency != false}
|
||||
<Toggle
|
||||
lightToogle={true}
|
||||
disabled={!$enterpriseLicense}
|
||||
size="sm"
|
||||
checked={Boolean($flowStore.value.concurrent_limit)}
|
||||
on:change={() => {
|
||||
if ($flowStore.value.concurrent_limit) {
|
||||
$flowStore.value.concurrent_limit = undefined
|
||||
} else {
|
||||
$flowStore.value.concurrent_limit = 1
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Concurrency Limits',
|
||||
rightTooltip: 'Allowed concurrency within a given timeframe'
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if $flowStore.value.concurrent_limit}
|
||||
<div class="flex flex-col gap-4">
|
||||
<Label label="Max number of executions within the time window">
|
||||
<div class="flex flex-row gap-2 max-w-sm">
|
||||
<input
|
||||
type="text"
|
||||
autofocus
|
||||
disabled={!$enterpriseLicense}
|
||||
bind:value={$flowStore.value.concurrency_key}
|
||||
placeholder={`$workspace/script/${$pathStore}-$args[foo]`}
|
||||
bind:value={$flowStore.value.concurrent_limit}
|
||||
type="number"
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
</Section>
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
<Button
|
||||
size="sm"
|
||||
color="light"
|
||||
on:click={() => {
|
||||
$flowStore.value.concurrent_limit = undefined
|
||||
}}
|
||||
variant="border">Remove Limits</Button
|
||||
>
|
||||
</div>
|
||||
</Label>
|
||||
<Label label="Time window in seconds">
|
||||
<SecondsInput
|
||||
disabled={!$enterpriseLicense}
|
||||
bind:seconds={$flowStore.value.concurrency_time_window_s}
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Custom concurrency key (optional)">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip>
|
||||
Concurrency keys are global, you can have them be workspace specific using the
|
||||
variable `$workspace`. You can also use an argument's value using
|
||||
`$args[name_of_arg]`</Tooltip
|
||||
>
|
||||
</svelte:fragment>
|
||||
<input
|
||||
type="text"
|
||||
autofocus
|
||||
disabled={!$enterpriseLicense}
|
||||
bind:value={$flowStore.value.concurrency_key}
|
||||
placeholder={`$workspace/script/${$pathStore}-$args[foo]`}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</Section>
|
||||
</div>
|
||||
</FlowCard>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user