mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 16:05:58 +00:00
@@ -2,6 +2,7 @@
|
||||
import { createPopperActions, type PopperOptions } from 'svelte-popperjs'
|
||||
import type { PopoverPlacement } from './Popover.model'
|
||||
import Portal from 'svelte-portal'
|
||||
import { ExternalLink } from 'lucide-svelte'
|
||||
|
||||
export let placement: PopoverPlacement = 'auto'
|
||||
export let notClickable = false
|
||||
@@ -9,6 +10,7 @@
|
||||
export let disablePopup = false
|
||||
export let disappearTimeout = 100
|
||||
export let appearTimeout = 300
|
||||
export let documentationLink: string | undefined = undefined
|
||||
|
||||
const [popperRef, popperContent] = createPopperActions({ placement })
|
||||
|
||||
@@ -46,6 +48,7 @@
|
||||
</script>
|
||||
|
||||
{#if notClickable}
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<span use:popperRef on:mouseenter={open} on:mouseleave={close} class={$$props.class}>
|
||||
<slot />
|
||||
</span>
|
||||
@@ -56,15 +59,23 @@
|
||||
{/if}
|
||||
{#if showTooltip && !disablePopup}
|
||||
<Portal>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
use:popperContent={popperOptions}
|
||||
on:mouseenter={open}
|
||||
on:mouseleave={close}
|
||||
class="z-[5001] py-2 px-3 rounded-md text-sm font-normal !text-gray-300 bg-gray-800
|
||||
whitespace-normal text-left {popupClass}"
|
||||
class="z-[5001] py-2 px-3 rounded-md text-sm font-normal !text-gray-300 bg-gray-800 whitespace-normal text-left {popupClass}"
|
||||
>
|
||||
<div class="max-w-sm">
|
||||
<slot name="text" />
|
||||
{#if documentationLink}
|
||||
<a href={documentationLink} target="_blank" class="text-blue-300 text-xs">
|
||||
<div class="flex flex-row gap-2 mt-4">
|
||||
See documentation
|
||||
<ExternalLink size="16" />
|
||||
</div>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
|
||||
@@ -10,12 +10,24 @@
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import { Alert, Badge, Button, Drawer, Kbd, SecondsInput, Tab, TabContent, Tabs } from './common'
|
||||
import { faPlus, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import { Loader2, Pen, X } from 'lucide-svelte'
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import {
|
||||
Bug,
|
||||
CheckCircle,
|
||||
Code,
|
||||
ExternalLink,
|
||||
Loader2,
|
||||
Pen,
|
||||
Plus,
|
||||
Rocket,
|
||||
X
|
||||
} from 'lucide-svelte'
|
||||
import autosize from 'svelte-autosize'
|
||||
import type Editor from './Editor.svelte'
|
||||
import { SCRIPT_SHOW_BASH, SCRIPT_SHOW_GO } from '$lib/consts'
|
||||
@@ -23,11 +35,11 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import Awareness from './Awareness.svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import { fade } from 'svelte/transition'
|
||||
import Popover from './Popover.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import ScriptSchema from './ScriptSchema.svelte'
|
||||
import Section from './Section.svelte'
|
||||
|
||||
export let script: NewScript
|
||||
export let initialPath: string = ''
|
||||
@@ -77,33 +89,39 @@
|
||||
langs.push(['Snowflake', Script.language.SNOWFLAKE])
|
||||
langs.push(['GraphQL', Script.language.GRAPHQL])
|
||||
langs.push(['PowerShell', Script.language.POWERSHELL])
|
||||
|
||||
const scriptKindOptions: {
|
||||
value: Script.kind
|
||||
title: string
|
||||
Icon: any
|
||||
desc?: string
|
||||
documentationLink?: string
|
||||
}[] = [
|
||||
{
|
||||
value: Script.kind.SCRIPT,
|
||||
title: 'Action'
|
||||
title: 'Action',
|
||||
Icon: Code
|
||||
},
|
||||
{
|
||||
value: Script.kind.TRIGGER,
|
||||
title: 'Trigger',
|
||||
desc: 'First module of flows to trigger them based on external changes. These kind of scripts are usually running on a schedule to periodically look for changes.',
|
||||
documentationLink: 'https://www.windmill.dev/docs/flows/flow_trigger'
|
||||
documentationLink: 'https://www.windmill.dev/docs/flows/flow_trigger',
|
||||
Icon: Rocket
|
||||
},
|
||||
{
|
||||
value: Script.kind.APPROVAL,
|
||||
title: 'Approval',
|
||||
desc: 'Send notifications externally to ask for approval to continue a flow.',
|
||||
documentationLink: 'https://www.windmill.dev/docs/flows/flow_approval'
|
||||
documentationLink: 'https://www.windmill.dev/docs/flows/flow_approval',
|
||||
Icon: CheckCircle
|
||||
},
|
||||
{
|
||||
value: Script.kind.FAILURE,
|
||||
title: 'Error Handler',
|
||||
desc: 'Handle errors in flows after all retry attempts have been exhausted.',
|
||||
documentationLink: 'https://www.windmill.dev/docs/flows/flow_error_handler'
|
||||
documentationLink: 'https://www.windmill.dev/docs/flows/flow_error_handler',
|
||||
Icon: Bug
|
||||
}
|
||||
]
|
||||
|
||||
@@ -277,339 +295,353 @@
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
<UnsavedConfirmationModal />
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
<Drawer placement="right" bind:open={metadataOpen} size="800px">
|
||||
<DrawerContent noPadding title="Settings" on:close={() => (metadataOpen = false)}>
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="metadata">Metadata</Tab>
|
||||
<Tab value="runtime">Runtime</Tab>
|
||||
<Tab value="ui"
|
||||
>Generated UI<Tooltip
|
||||
<Tab value="ui">
|
||||
Generated UI
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/json_schema_and_parsing"
|
||||
>The arguments are synced with the main signature but you may refine the parts that
|
||||
cannot be inferred from the type directly.</Tooltip
|
||||
></Tab
|
||||
>
|
||||
>
|
||||
The arguments are synced with the main signature but you may refine the parts that
|
||||
cannot be inferred from the type directly.
|
||||
</Tooltip>
|
||||
</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
<div class="px-4">
|
||||
<div class="p-4">
|
||||
<TabContent value="metadata">
|
||||
<div class="text-sm mt-4 text-secondary font-semibold">Summary</div>
|
||||
<div class="flex flex-col gap-8 my-1.5">
|
||||
<Section label="Metadata">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<span class="text-secondary text-sm leading-none">Summary</span>
|
||||
<input
|
||||
type="text"
|
||||
autofocus
|
||||
bind:value={script.summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
on:keyup={() => {
|
||||
if (initialPath == '' && script.summary?.length > 0 && !dirtyPath) {
|
||||
path?.setName(
|
||||
script.summary
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_]/g, '_')
|
||||
.replace(/-+/g, '_')
|
||||
.replace(/^-|-$/g, '')
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-secondary text-sm leading-none">Description</span>
|
||||
<textarea
|
||||
use:autosize
|
||||
bind:value={script.description}
|
||||
placeholder="Description displayed in the details page"
|
||||
class="text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
autofocus
|
||||
bind:value={script.summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
on:keyup={() => {
|
||||
if (initialPath == '' && script.summary?.length > 0 && !dirtyPath) {
|
||||
path?.setName(
|
||||
script.summary
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_]/g, '_')
|
||||
.replace(/-+/g, '_')
|
||||
.replace(/^-|-$/g, '')
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div class="text-sm mt-6 text-secondary font-semibold">Path</div>
|
||||
<Path
|
||||
bind:this={path}
|
||||
bind:error={pathError}
|
||||
bind:path={script.path}
|
||||
bind:dirty={dirtyPath}
|
||||
{initialPath}
|
||||
autofocus={false}
|
||||
namePlaceholder="script"
|
||||
kind="script"
|
||||
/>
|
||||
<Section label="Path">
|
||||
<Path
|
||||
bind:this={path}
|
||||
bind:error={pathError}
|
||||
bind:path={script.path}
|
||||
bind:dirty={dirtyPath}
|
||||
{initialPath}
|
||||
autofocus={false}
|
||||
namePlaceholder="script"
|
||||
kind="script"
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<div class="text-sm mt-6 text-secondary font-semibold">Description</div>
|
||||
<textarea
|
||||
use:autosize
|
||||
bind:value={script.description}
|
||||
placeholder="Description displayed in the details page"
|
||||
class="text-sm"
|
||||
/>
|
||||
|
||||
<div class="border-b pb-1 mb-4 mt-8 font-semibold">Language</div>
|
||||
|
||||
{#if lockedLanguage}
|
||||
<div class="text-sm text-tertiary italic mb-2">
|
||||
As a forked script, the language '{script.language}' cannot be modified.
|
||||
</div>
|
||||
{/if}
|
||||
<div class=" grid grid-cols-3 gap-2">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
<Popover disablePopup={!enterpriseLangs.includes(lang) || !!$enterpriseLicense}>
|
||||
<Section label="Language">
|
||||
{#if lockedLanguage}
|
||||
<div class="text-sm text-tertiary italic mb-2">
|
||||
As a forked script, the language '{script.language}' cannot be modified.
|
||||
</div>
|
||||
{/if}
|
||||
<div class=" grid grid-cols-3 gap-2">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
<Popover
|
||||
disablePopup={!enterpriseLangs.includes(lang) || !!$enterpriseLicense}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={isPicked ? 'blue' : 'light'}
|
||||
btnClasses={isPicked
|
||||
? '!border-2 !bg-blue-50/75 dark:!bg-frost-900/75'
|
||||
: 'm-[1px]'}
|
||||
on:click={() => {
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
script.language = lang
|
||||
}}
|
||||
disabled={lockedLanguage ||
|
||||
(enterpriseLangs.includes(lang) && !$enterpriseLicense)}
|
||||
>
|
||||
<LanguageIcon {lang} />
|
||||
<span class="ml-2 py-2 truncate">{label}</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="text"
|
||||
>{label} is only available with an enterprise license</svelte:fragment
|
||||
>
|
||||
</Popover>
|
||||
{/each}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={isPicked ? 'blue' : 'light'}
|
||||
btnClasses={isPicked
|
||||
color={template == 'docker' ? 'blue' : 'light'}
|
||||
btnClasses={template == 'docker'
|
||||
? '!border-2 !bg-blue-50/75 dark:!bg-frost-900/75'
|
||||
: 'm-[1px]'}
|
||||
disabled={lockedLanguage}
|
||||
on:click={() => {
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
script.language = lang
|
||||
if (isCloudHosted()) {
|
||||
sendUserToast(
|
||||
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
|
||||
true,
|
||||
[
|
||||
{
|
||||
label: 'Learn more',
|
||||
callback: () => {
|
||||
window.open(
|
||||
'https://www.windmill.dev/docs/advanced/docker',
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
template = 'docker'
|
||||
initContent(Script.language.BASH, script.kind, template)
|
||||
script.language = Script.language.BASH
|
||||
}}
|
||||
disabled={lockedLanguage ||
|
||||
(enterpriseLangs.includes(lang) && !$enterpriseLicense)}
|
||||
>
|
||||
<LanguageIcon {lang} />
|
||||
<span class="ml-2 py-2 truncate">{label}</span>
|
||||
<LanguageIcon lang="docker" /><span class="ml-2 py-2">Docker</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="text"
|
||||
>{label} is only available with an enterprise license</svelte:fragment
|
||||
>
|
||||
</Popover>
|
||||
{/each}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'docker' ? 'blue' : 'light'}
|
||||
btnClasses={template == 'docker'
|
||||
? '!border-2 !bg-blue-50/75 dark:!bg-frost-900/75'
|
||||
: 'm-[1px]'}
|
||||
disabled={lockedLanguage}
|
||||
on:click={() => {
|
||||
if (isCloudHosted()) {
|
||||
sendUserToast(
|
||||
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
|
||||
true,
|
||||
[
|
||||
{
|
||||
label: 'Learn more',
|
||||
callback: () => {
|
||||
window.open('https://www.windmill.dev/docs/advanced/docker', '_blank')
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
template = 'docker'
|
||||
initContent(Script.language.BASH, script.kind, template)
|
||||
script.language = Script.language.BASH
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="docker" /><span class="ml-2 py-2">Docker</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<!-- <Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'mysql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'mysql'
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-2">MySQL</span>
|
||||
</Button> -->
|
||||
</div>
|
||||
|
||||
<div class="border-b pb-1 mb-4 mt-6 font-semibold">
|
||||
Script kind
|
||||
<Tooltip>
|
||||
Tag this script's purpose within flows such that it is available as the
|
||||
corresponding action.
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 mb-8">
|
||||
{#each scriptKindOptions as { value, title, desc, documentationLink }}
|
||||
{@const isPicked = script.kind === value}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={isPicked ? 'blue' : 'light'}
|
||||
btnClasses="font-medium {isPicked
|
||||
? '!bg-blue-50/75 dark:!bg-frost-900/75'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
<Section label="Script kind">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip>
|
||||
Tag this script's purpose within flows such that it is available as the
|
||||
corresponding action.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
<ToggleButtonGroup
|
||||
class="h-10"
|
||||
selected={script.kind}
|
||||
on:selected={({ detail }) => {
|
||||
template = 'script'
|
||||
script.kind = value
|
||||
initContent(script.language, value, template)
|
||||
script.kind = detail
|
||||
initContent(script.language, detail, template)
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
{#if desc}
|
||||
<Tooltip {documentationLink}>
|
||||
{desc}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Button>
|
||||
{/each}
|
||||
{#each scriptKindOptions as { value, title, desc, documentationLink, Icon }}
|
||||
<ToggleButton
|
||||
label={title}
|
||||
{value}
|
||||
tooltip={desc}
|
||||
{documentationLink}
|
||||
icon={Icon}
|
||||
showTooltipIcon={Boolean(desc)}
|
||||
/>
|
||||
{/each}
|
||||
</ToggleButtonGroup>
|
||||
</Section>
|
||||
</div>
|
||||
</TabContent>
|
||||
<TabContent value="runtime">
|
||||
<h2 class="border-b pb-1 mt-4 mb-4">Concurrency limits</h2>
|
||||
<div class="flex gap-x-4 shrink">
|
||||
<label class="block shrink min-w-0">
|
||||
<span class="text-secondary text-sm">Maximum number of runs</span>
|
||||
<input class="!w-55" type="number" bind:value={script.concurrent_limit} />
|
||||
</label>
|
||||
<label class="block shrink min-w-0">
|
||||
<span class="text-secondary text-sm">Per time window (seconds)</span>
|
||||
<input
|
||||
class="!w-18"
|
||||
type="number"
|
||||
bind:value={script.concurrency_time_window_s}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h2 class="border-b pb-1 mt-10 mb-4"
|
||||
>Worker group tag <Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
>The script will be executed on a worker configured to accept its worker group
|
||||
tag. For instance, you could setup an "highmem", or "gpu" worker group.</Tooltip
|
||||
></h2
|
||||
>
|
||||
{#if $workerTags}
|
||||
{#if $workerTags?.length > 0}
|
||||
<div class="max-w-sm">
|
||||
<select
|
||||
bind:value={script.tag}
|
||||
on:change={(e) => {
|
||||
if (script.tag == '') {
|
||||
script.tag = undefined
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if script.tag}
|
||||
<option value="">reset to default</option>
|
||||
{:else}
|
||||
<option value="" disabled selected>Worker Group</option>
|
||||
{/if}
|
||||
{#each $workerTags ?? [] as tag (tag)}
|
||||
<option value={tag}>{tag}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class="flex flex-col gap-8 my-1.5">
|
||||
<Section label="Concurency limits">
|
||||
<div class="flex gap-x-4 shrink">
|
||||
<label class="block shrink min-w-0">
|
||||
<span class="text-secondary text-sm leading-none">
|
||||
Maximum number of runs
|
||||
</span>
|
||||
<input type="number" bind:value={script.concurrent_limit} />
|
||||
</label>
|
||||
<label class="block shrink min-w-0">
|
||||
<span class="text-secondary text-sm leading-none"
|
||||
>Per time window (seconds)</span
|
||||
>
|
||||
<input type="number" bind:value={script.concurrency_time_window_s} />
|
||||
</label>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-secondary italic mb-2">
|
||||
No custom worker group defined on this instance. See <a
|
||||
href="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
target="_blank">documentation</a
|
||||
</Section>
|
||||
<Section label="Worker group tag">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Loader2 class="animate-spin" />
|
||||
{/if}
|
||||
The script will be executed on a worker configured to accept its worker group
|
||||
tag. For instance, you could setup an "highmem", or "gpu" worker group.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
<h2 class="border-b pb-1 mt-10 mb-4 flex items-center gap-4"
|
||||
>Cache <Toggle
|
||||
size="xs"
|
||||
checked={Boolean(script.cache_ttl)}
|
||||
on:change={() => {
|
||||
if (script.cache_ttl && script.cache_ttl != undefined) {
|
||||
script.cache_ttl = undefined
|
||||
} else {
|
||||
script.cache_ttl = 300
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Cache the results for each possible inputs'
|
||||
}}
|
||||
/></h2
|
||||
>
|
||||
|
||||
<div class="flex gap-x-4 shrink flex-col">
|
||||
<div class="text-xs">How long to the keep cache valid</div>
|
||||
|
||||
{#if script.cache_ttl}
|
||||
<SecondsInput bind:seconds={script.cache_ttl} />
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<h2 class="border-b pb-1 mt-10 mb-4 items-center flex gap-1"
|
||||
>Dedicated Workers<Tooltip
|
||||
>In this mode, the script is meant to be run on dedicated workers that run the
|
||||
script at native speed. Can reach >1500rps per dedicated worker. Only available on
|
||||
enterprise edition and for the Bun language.</Tooltip
|
||||
>
|
||||
<Toggle
|
||||
disabled={!$enterpriseLicense ||
|
||||
isCloudHosted() ||
|
||||
script.language != Script.language.BUN}
|
||||
size="xs"
|
||||
checked={Boolean(script.dedicated_worker)}
|
||||
on:change={() => {
|
||||
if (script.dedicated_worker) {
|
||||
script.dedicated_worker = undefined
|
||||
} else {
|
||||
script.dedicated_worker = true
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Script is run on dedicated workers'
|
||||
}}
|
||||
/></h2
|
||||
>
|
||||
|
||||
{#if !isCloudHosted()}
|
||||
<h2 class="border-b pb-1 mt-10 mb-4">
|
||||
Custom env variables
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/reference#custom-environment-variables"
|
||||
>
|
||||
Additional static custom env variables to pass to the script.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<div class="w-full">
|
||||
<span class="text-tertiary text-xs pb-2">Format is: `{'<KEY>=<VALUE>'}`</span>
|
||||
{#if Array.isArray(script.envs ?? [])}
|
||||
{#each script.envs ?? [] as v, i}
|
||||
<div class="flex max-w-md mt-1 w-full items-center">
|
||||
<input type="text" bind:value={v} placeholder="<KEY>=<VALUE>" />
|
||||
<button
|
||||
transition:fade|local={{ duration: 50 }}
|
||||
class="rounded-full p-1 bg-surface/60 duration-200 hover:bg-gray-200"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
script.envs && script.envs.splice(i, 1)
|
||||
script.envs = script.envs
|
||||
{#if $workerTags}
|
||||
{#if $workerTags?.length > 0}
|
||||
<div class="max-w-sm">
|
||||
<select
|
||||
bind:value={script.tag}
|
||||
on:change={(e) => {
|
||||
if (script.tag == '') {
|
||||
script.tag = undefined
|
||||
}
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
{#if script.tag}
|
||||
<option value="">reset to default</option>
|
||||
{:else}
|
||||
<option value="" disabled selected>Worker Group</option>
|
||||
{/if}
|
||||
{#each $workerTags ?? [] as tag (tag)}
|
||||
<option value={tag}>{tag}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-secondary flex flex-row gap-2">
|
||||
No custom worker group defined on this instance.
|
||||
<a
|
||||
href="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
target="_blank"
|
||||
class="hover:underline"
|
||||
>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
See documentation
|
||||
<ExternalLink size="12" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{/each}
|
||||
{#if script.envs && script.envs.length > 0}
|
||||
<div class="pt-2" />
|
||||
<Alert type="warning" title="Not passed in previews"
|
||||
>Static envs variables are not passed in preview but solely on deployed
|
||||
scripts.</Alert
|
||||
>
|
||||
{/if}
|
||||
{:else}
|
||||
<Loader2 class="animate-spin" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex mt-2">
|
||||
<Button
|
||||
variant="border"
|
||||
color="dark"
|
||||
size="xs"
|
||||
btnClasses="mt-1"
|
||||
on:click={() => {
|
||||
if (script.envs == undefined || !Array.isArray(script.envs)) {
|
||||
script.envs = []
|
||||
</Section>
|
||||
<Section label="Cache">
|
||||
<div class="flex gap-2 shrink flex-col">
|
||||
<Toggle
|
||||
size="sm"
|
||||
checked={Boolean(script.cache_ttl)}
|
||||
on:change={() => {
|
||||
if (script.cache_ttl && script.cache_ttl != undefined) {
|
||||
script.cache_ttl = undefined
|
||||
} else {
|
||||
script.cache_ttl = 300
|
||||
}
|
||||
}}
|
||||
options={{
|
||||
right: 'Cache the results for each possible inputs'
|
||||
}}
|
||||
/>
|
||||
<span class="text-secondary text-sm leading-none">
|
||||
How long to the keep cache valid
|
||||
</span>
|
||||
{#if script.cache_ttl}
|
||||
<SecondsInput bind:seconds={script.cache_ttl} />
|
||||
{:else}
|
||||
<SecondsInput disabled />
|
||||
{/if}
|
||||
</div>
|
||||
</Section>
|
||||
<Section label="Dedicated Workers">
|
||||
<Toggle
|
||||
disabled={!$enterpriseLicense ||
|
||||
isCloudHosted() ||
|
||||
script.language != Script.language.BUN}
|
||||
size="sm"
|
||||
checked={Boolean(script.dedicated_worker)}
|
||||
on:change={() => {
|
||||
if (script.dedicated_worker) {
|
||||
script.dedicated_worker = undefined
|
||||
} else {
|
||||
script.dedicated_worker = true
|
||||
}
|
||||
script.envs = script.envs.concat('')
|
||||
}}
|
||||
>
|
||||
<Icon data={faPlus} class="mr-2" />
|
||||
Add item
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
options={{
|
||||
right: 'Script is run on dedicated workers'
|
||||
}}
|
||||
/>
|
||||
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip
|
||||
>In this mode, the script is meant to be run on dedicated workers that run the
|
||||
script at native speed. Can reach >1500rps per dedicated worker. Only
|
||||
available on enterprise edition and for the Bun language.</Tooltip
|
||||
>
|
||||
</svelte:fragment>
|
||||
</Section>
|
||||
{#if !isCloudHosted()}
|
||||
<Section label="Custom env variables">
|
||||
<svelte:fragment slot="header">
|
||||
<Tooltip
|
||||
documentationLink="https://www.windmill.dev/docs/reference#custom-environment-variables"
|
||||
>
|
||||
Additional static custom env variables to pass to the script.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
{#if script.envs && script.envs.length > 0}
|
||||
<Alert type="warning" title="Not passed in previews" size="xs">
|
||||
Static envs variables are not passed in preview but solely on deployed
|
||||
scripts.
|
||||
</Alert>
|
||||
{/if}
|
||||
<div class="w-full mt-2">
|
||||
<span class="text-tertiary text-xs pb-2">Format is: `{'<KEY>=<VALUE>'}`</span>
|
||||
{#if Array.isArray(script.envs ?? [])}
|
||||
{#each script.envs ?? [] as v, i}
|
||||
<div class="flex max-w-md mt-1 w-full items-center relative">
|
||||
<input type="text" bind:value={v} placeholder="<KEY>=<VALUE>" />
|
||||
<button
|
||||
transition:fade|local={{ duration: 50 }}
|
||||
class="rounded-full p-1 bg-surface/60 duration-200 hover:bg-gray-200 absolute right-2"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
script.envs && script.envs.splice(i, 1)
|
||||
script.envs = script.envs
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex mt-2">
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
if (script.envs == undefined || !Array.isArray(script.envs)) {
|
||||
script.envs = []
|
||||
}
|
||||
script.envs = script.envs.concat('')
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row gap-1">
|
||||
<Plus size="16" />
|
||||
Add item
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</Section>
|
||||
{/if}
|
||||
</div>
|
||||
</TabContent>
|
||||
<TabContent value="ui">
|
||||
<div class="mt-4" />
|
||||
@@ -624,14 +656,26 @@
|
||||
<div class="flex flex-col h-screen">
|
||||
<div class="flex h-full max-h-12 items-center pl-2.5 pr-6 border-b shadow-sm">
|
||||
<div class="justify-between flex gap-2 lg:gap-8 w-full items-center">
|
||||
<div class="min-w-64 w-full max-w-md">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Script summary"
|
||||
class="text-sm w-full font-semibold"
|
||||
bind:value={script.summary}
|
||||
/>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div class="center-center">
|
||||
<button
|
||||
on:click={async () => {
|
||||
metadataOpen = true
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang={script.language} height={20} />
|
||||
</button>
|
||||
</div>
|
||||
<div class="min-w-64 w-full max-w-md">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Script summary"
|
||||
class="text-sm w-full font-semibold"
|
||||
bind:value={script.summary}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gap-4 flex">
|
||||
<div class="flex justify-start w-full">
|
||||
<div>
|
||||
@@ -660,15 +704,7 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center-center">
|
||||
<button
|
||||
on:click={async () => {
|
||||
metadataOpen = true
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang={script.language} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if $enterpriseLicense && initialPath != ''}
|
||||
<Awareness />
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let label: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="flex flex-row justify-between">
|
||||
<h2 class="text-base font-semibold leading-non mb-2">
|
||||
{label}
|
||||
<slot name="header" />
|
||||
</h2>
|
||||
<slot name="action" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
? 'text-tertiary-inverse'
|
||||
: 'text-tertiary'} {$$props.class} relative"
|
||||
>
|
||||
<InfoIcon class="{small ? 'bottom-0' : '-bottom-0.5'} absolute" size={small ? 12 : 16} />
|
||||
<InfoIcon class="{small ? 'bottom-0' : '-bottom-0.5'} absolute" size={small ? 12 : 14} />
|
||||
</div>
|
||||
<svelte:fragment slot="text">
|
||||
<slot />
|
||||
|
||||
@@ -13,17 +13,16 @@
|
||||
export let fullScreen: boolean = true
|
||||
</script>
|
||||
|
||||
<div class={classNames('flex flex-col divide-y ', fullScreen ? 'h-screen max-h-screen' : 'h-full')}>
|
||||
<div class="flex justify-between w-full items-center px-2 py-2 gap-2">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<CloseButton on:close />
|
||||
|
||||
<span class="font-semibold truncate text-primary"
|
||||
<div class={classNames('flex flex-col divide-y', fullScreen ? 'h-screen max-h-screen' : 'h-full')}>
|
||||
<div class="flex justify-between w-full items-center px-4 py-2 gap-2">
|
||||
<div class="flex items-center gap-2 justify-between w-full">
|
||||
<span class="font-semibold truncate text-primary !text-lg"
|
||||
>{title ?? ''}
|
||||
{#if tooltip != '' || documentationLink}
|
||||
<Tooltip {documentationLink} scale={0.9}>{tooltip}</Tooltip>
|
||||
{/if}</span
|
||||
>
|
||||
<CloseButton on:close noBg />
|
||||
</div>
|
||||
{#if $$slots.actions}
|
||||
<div class="flex gap-2 items-center justify-end">
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
<button
|
||||
class={twMerge(
|
||||
'border-b-2 py-1 px-4 cursor-pointer transition-all z-10 ease-linear font-medium text-primary',
|
||||
'border-b-2 py-1 px-4 cursor-pointer transition-all z-10 ease-linear font-normal text-primary',
|
||||
$selected?.startsWith(value)
|
||||
? 'wm-tab-active'
|
||||
: 'border-gray-300 dark:border-gray-600 border-opacity-0 hover:border-opacity-100 ',
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
export let small: boolean = false
|
||||
export let iconProps: Record<string, any> = {}
|
||||
export let showTooltipIcon: boolean = false
|
||||
export let documentationLink: string | undefined = undefined
|
||||
|
||||
const { select, selected } = getContext<ToggleButtonContext>('ToggleButtonGroup')
|
||||
</script>
|
||||
@@ -26,6 +27,7 @@
|
||||
class={twMerge('flex', disabled ? 'cursor-not-allowed' : 'cursor-pointer')}
|
||||
disablePopup={tooltip === undefined}
|
||||
disappearTimeout={0}
|
||||
{documentationLink}
|
||||
>
|
||||
<Tab
|
||||
{disabled}
|
||||
|
||||
Reference in New Issue
Block a user