mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
feat(frontend): flow UX overhaul II + go + python support for trigger scripts (#928)
* all * improve all * improve all * fix frontend * fix input picker nits * fix(frontend): Fix Menu display * all * fix all * fix all Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import type { Script } from "./gen"
|
||||
|
||||
export type OwnerKind = 'group' | 'user'
|
||||
|
||||
export type ActionKind = 'Create' | 'Update' | 'Delete' | 'Execute'
|
||||
|
||||
export type SupportedLanguage = 'deno' | 'python3' | 'go' | 'bash'
|
||||
export type SupportedLanguage = Script.language
|
||||
|
||||
export interface SchemaProperty {
|
||||
type: string | undefined
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
|
||||
<UnsavedConfirmationModal />
|
||||
|
||||
<Drawer bind:this={flowViewer} size="900px">
|
||||
<Drawer bind:this={flowViewer} size="1200px">
|
||||
<DrawerContent title="View Graph" on:close={flowViewer.closeDrawer} noPadding>
|
||||
<FlowGraphViewer flow={$flowStore} overflowAuto />
|
||||
</DrawerContent>
|
||||
@@ -241,9 +241,11 @@
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex flex-row-reverse ml-2 space-x-reverse space-x-2">
|
||||
<Button disabled={pathError != ''} size="sm" on:click={saveFlow}>Save</Button>
|
||||
<div class="flex flex-row space-x-2">
|
||||
<FlowPreviewButtons />
|
||||
<div>
|
||||
<Button disabled={pathError != ''} size="sm" on:click={saveFlow}>Save</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
arg.expr = getDefaultExpr(undefined, previousModuleId, rawValue)
|
||||
arg.type = 'javascript'
|
||||
propertyType = 'javascript'
|
||||
monaco?.setCode(arg.expr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,8 +93,8 @@
|
||||
</script>
|
||||
|
||||
{#if arg != undefined}
|
||||
<div class="flex flex-row justify-between gap-4 mb-1">
|
||||
<div class="flex items-center grow">
|
||||
<div class="flex flex-row justify-between gap-1 mb-1">
|
||||
<div class="flex items-center flex-wrap grow">
|
||||
<FieldHeader
|
||||
label={argName}
|
||||
format={schema.properties[argName].format}
|
||||
@@ -114,55 +115,63 @@
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
bind:selected={propertyType}
|
||||
on:selected={(e) => {
|
||||
const staticTemplate = isStaticTemplate(inputCat)
|
||||
if (e.detail === 'javascript') {
|
||||
arg.expr = getDefaultExpr(
|
||||
argName,
|
||||
previousModuleId,
|
||||
staticTemplate ? `\`${arg.value ?? ''}\`` : arg.value
|
||||
)
|
||||
<div class="flex flex-row gap-1 flex-wrap">
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
bind:selected={propertyType}
|
||||
on:selected={(e) => {
|
||||
const staticTemplate = isStaticTemplate(inputCat)
|
||||
if (e.detail === 'javascript') {
|
||||
arg.expr = getDefaultExpr(
|
||||
argName,
|
||||
previousModuleId,
|
||||
staticTemplate
|
||||
? `\`${arg.value ?? ''}\``
|
||||
: arg.value
|
||||
? JSON.stringify(arg.value, null, 4)
|
||||
: ''
|
||||
)
|
||||
|
||||
arg.value = undefined
|
||||
propertyType = 'javascript'
|
||||
arg.type = 'javascript'
|
||||
} else {
|
||||
arg.value = staticTemplate ? codeToStaticTemplate(arg.expr) : undefined
|
||||
arg.expr = undefined
|
||||
propertyType = 'static'
|
||||
if (!isStaticTemplate) {
|
||||
arg.type = 'static'
|
||||
arg.value = undefined
|
||||
propertyType = 'javascript'
|
||||
arg.type = 'javascript'
|
||||
} else {
|
||||
setPropertyType(arg.value)
|
||||
arg.value = staticTemplate ? codeToStaticTemplate(arg.expr) : undefined
|
||||
arg.expr = undefined
|
||||
propertyType = 'static'
|
||||
if (!isStaticTemplate) {
|
||||
arg.type = 'static'
|
||||
} else {
|
||||
setPropertyType(arg.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton position="left" value="static" size="xs">Template</ToggleButton>
|
||||
{:else}
|
||||
<ToggleButton position="left" value="static" size="xs">Static</ToggleButton>
|
||||
{/if}
|
||||
}}
|
||||
>
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton position="left" value="static" size="xs">Template</ToggleButton>
|
||||
{:else}
|
||||
<ToggleButton position="left" value="static" size="xs">Static</ToggleButton>
|
||||
{/if}
|
||||
|
||||
<ToggleButton position="right" value="javascript" startIcon={{ icon: faCode }} size="xs">
|
||||
Dynamic (JS)
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<ToggleButton position="right" value="javascript" startIcon={{ icon: faCode }} size="xs">
|
||||
Dynamic (JS)
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="blue"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
focusProp(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
return false
|
||||
})
|
||||
}}>Connect →</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="blue"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
focusProp(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
return false
|
||||
})
|
||||
}}>Connect →</Button
|
||||
>
|
||||
</div>
|
||||
<div class="max-w-xs" />
|
||||
|
||||
|
||||
@@ -135,12 +135,17 @@
|
||||
}
|
||||
|
||||
function changePosition(i: number, up: boolean): any {
|
||||
isAnimated = true
|
||||
setTimeout(() => {
|
||||
isAnimated = false
|
||||
}, 500)
|
||||
const entries = Object.entries(schema.properties)
|
||||
var element = entries[i]
|
||||
entries.splice(i, 1)
|
||||
entries.splice(up ? i - 1 : i + 1, 0, element)
|
||||
schema.properties = Object.fromEntries(entries)
|
||||
}
|
||||
let isAnimated = false
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
@@ -148,8 +153,8 @@
|
||||
<div>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="blue"
|
||||
size="md"
|
||||
color="dark"
|
||||
size="sm"
|
||||
startIcon={{ icon: faPlus }}
|
||||
on:click={() => {
|
||||
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
|
||||
@@ -164,7 +169,7 @@
|
||||
<Toggle
|
||||
on:change={() => switchTab()}
|
||||
options={{
|
||||
right: 'Json Schema Editor'
|
||||
right: 'As JSON'
|
||||
}}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
@@ -192,7 +197,7 @@
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#each Object.entries(schema.properties) as [name, property], i (name)}
|
||||
<tr animate:flip={{ duration: 50 }}>
|
||||
<tr animate:flip>
|
||||
<td class="font-bold">{name}</td>
|
||||
<td>
|
||||
<SchemaEditorProperty {property} />
|
||||
@@ -206,13 +211,17 @@
|
||||
>
|
||||
<td class="justify-end flex">
|
||||
{#if i > 0}
|
||||
<button on:click={() => changePosition(i, true)} class="text-lg mr-2"
|
||||
>↑</button
|
||||
<button
|
||||
on:click={() => changePosition(i, true)}
|
||||
class="text-lg mr-2 {isAnimated ? 'invisible' : ''}"
|
||||
>
|
||||
↑</button
|
||||
>
|
||||
{/if}
|
||||
{#if i < Object.keys(schema.properties).length - 1}
|
||||
<button on:click={() => changePosition(i, false)} class="text-lg mr-2"
|
||||
>↓</button
|
||||
<button
|
||||
on:click={() => changePosition(i, false)}
|
||||
class="text-lg mr-2 {isAnimated ? 'invisible' : ''}">↓</button
|
||||
>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -19,14 +19,21 @@
|
||||
import { faChevronDown, faChevronUp, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import Breadcrumb from './common/breadcrumb/Breadcrumb.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
|
||||
export let script: Script
|
||||
export let initialPath: string = ''
|
||||
export let template: 'pgsql' | 'script' = 'script'
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
|
||||
const langs: [string, SupportedLanguage][] = [
|
||||
['Typescript (Deno)', Script.language.DENO],
|
||||
['Python 3.11', Script.language.PYTHON3],
|
||||
['Go', Script.language.GO],
|
||||
['Bash', Script.language.BASH]
|
||||
]
|
||||
let viewScriptKind = script.kind !== Script.kind.SCRIPT
|
||||
let viewTemplate = script.kind !== Script.kind.SCRIPT && script.language == Script.language.DENO
|
||||
|
||||
let pathError = ''
|
||||
|
||||
@@ -185,18 +192,36 @@
|
||||
/>
|
||||
</label>
|
||||
<h2 class="border-b pb-1 mt-4">Language</h2>
|
||||
<div class="max-w-md">
|
||||
<RadioButton
|
||||
label="Language"
|
||||
options={[
|
||||
['Typescript (Deno)', 'deno'],
|
||||
['Python 3.11', 'python3'],
|
||||
['Go', 'go'],
|
||||
['Bash', 'bash']
|
||||
]}
|
||||
on:change={(e) => initContent(e.detail, script.kind, template)}
|
||||
bind:value={script.language}
|
||||
/>
|
||||
<div class="max-w-md flex flex-row gap-x-2">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={isPicked ? 'blue' : 'dark'}
|
||||
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = lang
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon {lang} /><span class="ml-2">{label}</span>
|
||||
</Button>
|
||||
{/each}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'pgsql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'pgsql'
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2">PostgreSQL</span>
|
||||
</Button>
|
||||
</div>
|
||||
<h2 class="border-b pb-1 mt-4">Advanced</h2>
|
||||
<div>
|
||||
@@ -206,7 +231,7 @@
|
||||
endIcon={{ icon: viewScriptKind ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewScriptKind = !viewScriptKind)}
|
||||
>
|
||||
Specialize the script as a specific module kind for flows
|
||||
Tag this script as having a specific purpose inside flows
|
||||
</Button>
|
||||
</div>
|
||||
{#if viewScriptKind}
|
||||
@@ -249,31 +274,6 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if script.language == 'deno' && script.kind == Script.kind.SCRIPT}
|
||||
<div
|
||||
><Button
|
||||
color="light"
|
||||
size="sm"
|
||||
endIcon={{ icon: viewTemplate ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewTemplate = !viewTemplate)}
|
||||
>
|
||||
Use a predefined template specific to this language and script kind
|
||||
</Button>
|
||||
</div>
|
||||
{#if viewTemplate}
|
||||
<div class="max-w-lg">
|
||||
<RadioButton
|
||||
label="Template"
|
||||
options={[
|
||||
['Standard', 'script'],
|
||||
['PostgreSQL Prepared Statement', 'pgsql']
|
||||
]}
|
||||
on:change={(e) => initContent(script.language, script.kind, e.detail)}
|
||||
bind:value={template}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<div class="ml-3">
|
||||
<Toggle
|
||||
bind:checked={script.is_template}
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
const handleHide = () => (hidden = !hidden)
|
||||
</script>
|
||||
|
||||
<span class="inline-flex justify-center items-center whitespace-nowrap">
|
||||
<span on:click on:keydown class="inline-flex justify-center items-center whitespace-nowrap">
|
||||
<svelte:element
|
||||
this={href ? 'a' : 'span'}
|
||||
{href}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<script lang="ts">
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import PostgresIcon from '$lib/components/icons/PostgresIcon.svelte'
|
||||
import type { SvelteComponent } from 'svelte'
|
||||
import { BashIcon, GoIcon, PythonIcon, TypeScriptIcon } from './'
|
||||
|
||||
export let lang: SupportedLanguage
|
||||
export let lang: SupportedLanguage | 'pgsql'
|
||||
export let width = 16
|
||||
export let height = 16
|
||||
export let scale = 1
|
||||
const langToComponent: Record<SupportedLanguage, typeof SvelteComponent> = {
|
||||
const langToComponent: Record<SupportedLanguage | 'pgsql', typeof SvelteComponent> = {
|
||||
go: GoIcon,
|
||||
python3: PythonIcon,
|
||||
deno: TypeScriptIcon,
|
||||
bash: BashIcon
|
||||
bash: BashIcon,
|
||||
pgsql: PostgresIcon
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { onMount } from 'svelte'
|
||||
import { scale } from 'svelte/transition'
|
||||
|
||||
export let noMinW = false
|
||||
let show = false
|
||||
let menu: HTMLDivElement
|
||||
|
||||
@@ -47,15 +47,17 @@
|
||||
</script>
|
||||
|
||||
<div class="relative" bind:this={menu}>
|
||||
<div on:click={() => (show = !show)} on:click>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div on:click={() => (show = !show)}>
|
||||
<slot name="trigger" />
|
||||
</div>
|
||||
|
||||
{#if show}
|
||||
<div
|
||||
class={classNames(
|
||||
'z-50 absolute mt-2 w-60 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none',
|
||||
placementsClasses[placement]
|
||||
'z-50 absolute mt-2 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none',
|
||||
placementsClasses[placement],
|
||||
noMinW ? 'min-w-0' : 'w-60'
|
||||
)}
|
||||
role="menu"
|
||||
tabindex="-1"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
class={classNames(
|
||||
$selected?.startsWith(value)
|
||||
? 'border-b-2 border-gray-600 text-gray-800 '
|
||||
: 'hover:border-b-2 hover:border-gray-300 text-gray-500',
|
||||
: 'hover:border-b-2 hover:border-gray-300 text-gray-600',
|
||||
'py-1 px-4 cursor-pointer transition-all ease-linear font-medium',
|
||||
fontSizeClasses[size],
|
||||
$$props.class
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<div class="h-full overflow-hidden border-t">
|
||||
<Splitpanes>
|
||||
<Pane size={25} minSize={20} class="h-full">
|
||||
<Pane size={20} minSize={15} class="h-full">
|
||||
<div class="grow overflow-auto bg-gray h-full bg-gray-50 relative">
|
||||
{#if loading}
|
||||
<div class="p-2 pt-10">
|
||||
@@ -25,7 +25,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={75} minSize={40}>
|
||||
<Pane size={80} minSize={40}>
|
||||
{#if loading}
|
||||
<div class="w-full h-full ">
|
||||
<div class="block m-auto mt-40 w-10">
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
{branch.summary && branch.summary != '' ? branch.summary : 'No summary'}</div
|
||||
>
|
||||
<div>
|
||||
<Button size="sm" on:click={() => ($selectedId = branch.id)}>
|
||||
<Button color="dark" size="sm" on:click={() => ($selectedId = branch.id)}>
|
||||
Set branch {i} predicate</Button
|
||||
>
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@
|
||||
{branch.summary && branch.summary != '' ? branch.summary : 'No summary'}
|
||||
</div>
|
||||
<div>
|
||||
<Button size="sm" on:click={() => ($selectedId = branch.id)}>
|
||||
<Button color="dark" size="sm" on:click={() => ($selectedId = branch.id)}>
|
||||
Set branch {i} summary</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -13,33 +13,29 @@
|
||||
|
||||
<CapturePayload bind:this={capturePayload} />
|
||||
<FlowCard title="Flow Input">
|
||||
<div slot="header">
|
||||
<div class="flex flex-row space-x-4">
|
||||
<div class="p-6">
|
||||
<div class="flex flex-row items-center space-x-4 pb-2 border-b border-gray-400">
|
||||
<div>Copy input's schema from</div>
|
||||
<Button
|
||||
color="light"
|
||||
color="dark"
|
||||
size="sm"
|
||||
on:click={() => {
|
||||
capturePayload.openDrawer()
|
||||
}}
|
||||
variant="border"
|
||||
>
|
||||
Capture from a request to seed inputs
|
||||
A request
|
||||
</Button>
|
||||
<Button
|
||||
color="light"
|
||||
color="dark"
|
||||
size="sm"
|
||||
disabled={$flowStore.value.modules.length === 0 ||
|
||||
isEmptyFlowModule($flowStore.value.modules[0])}
|
||||
on:click={copyFirstStepSchema}
|
||||
variant="border"
|
||||
>
|
||||
Copy from first step schema {#if $flowStore.value.modules.length === 0 || isEmptyFlowModule($flowStore.value.modules[0])}
|
||||
(no steps to copy from!){/if}
|
||||
First step's inputs
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="p-6">
|
||||
<div class="pt-6">
|
||||
<SchemaEditor
|
||||
bind:schema={$flowStore.schema}
|
||||
on:change={() => {
|
||||
@@ -49,6 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<h2 class="mb-4">Customize Inputs</h2>
|
||||
<SchemaForm bind:schema={$flowStore.schema} editableSchema={true} />
|
||||
</div>
|
||||
</FlowCard>
|
||||
|
||||
@@ -1,163 +1,131 @@
|
||||
<script lang="ts">
|
||||
import { ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import WindmillIcon from '$lib/components/icons/WindmillIcon.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { RawScript, Script } from '$lib/gen'
|
||||
|
||||
import { faCode, faCodeBranch, faRepeat } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faBolt, faBuilding, faCheck, faCode } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import FlowScriptPicker from '../pickers/FlowScriptPicker.svelte'
|
||||
import PickHubScript from '../pickers/PickHubScript.svelte'
|
||||
import PickScript from '../pickers/PickScript.svelte'
|
||||
import WorkspaceScriptPicker from '../pickers/WorkspaceScriptPicker.svelte'
|
||||
|
||||
export let failureModule: boolean
|
||||
export let shouldDisableTriggerScripts: boolean = false
|
||||
const dispatch = createEventDispatcher()
|
||||
let kind: 'script' | 'failure' | 'approval' | 'trigger' = failureModule ? 'failure' : 'script'
|
||||
let pick_existing: 'workspace' | 'hub' = 'hub'
|
||||
</script>
|
||||
|
||||
<div class="space-y-4 p-4">
|
||||
<div class="text-sm font-bold">{failureModule ? 'Error handler' : 'Common script'}</div>
|
||||
<div class="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div class="space-y-4 p-4 h-full flex flex-col">
|
||||
{#if !failureModule}
|
||||
<div class="center-center">
|
||||
<div class="max-w-min">
|
||||
<ToggleButtonGroup bind:selected={kind}>
|
||||
<ToggleButton position="left" value="script" size="sm" startIcon={{ icon: faCode }}>
|
||||
Common
|
||||
</ToggleButton>
|
||||
{#if !shouldDisableTriggerScripts}
|
||||
<ToggleButton position="center" value="trigger" size="sm" startIcon={{ icon: faBolt }}>
|
||||
Trigger <Tooltip>
|
||||
Used as a first step most commonly with a state and a schedule to watch for changes
|
||||
on an external system, compute the diff since last time, set the new state. The
|
||||
diffs are then treated one by one with a for-loop.
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
{/if}
|
||||
<ToggleButton position="right" value="approval" size="sm" startIcon={{ icon: faCheck }}>
|
||||
Approval <Tooltip>
|
||||
An approval step will suspend the execution of a flow until it has been approved
|
||||
through the resume endpoints or the approval page by and solely by the recipients of
|
||||
those secret urls. Use getResumeEndpoints from the wmill client to generate those
|
||||
URLs.
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<h3>Inline new</h3>
|
||||
<div class="flex flex-row flex-wrap gap-x-4 gap-y-2">
|
||||
<FlowScriptPicker
|
||||
label="Inline Python (3.10)"
|
||||
icon={faCode}
|
||||
iconColor="text-green-500"
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.PYTHON3,
|
||||
kind: failureModule ? 'failure' : 'script',
|
||||
subkind: 'flow'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
label="Inline Typescript (Deno)"
|
||||
icon={faCode}
|
||||
iconColor="text-blue-800"
|
||||
label="Typescript (Deno)"
|
||||
lang={Script.language.DENO}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.DENO,
|
||||
kind: failureModule ? 'failure' : 'script',
|
||||
kind,
|
||||
subkind: 'flow'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
label="Inline Go"
|
||||
icon={faCode}
|
||||
iconColor="text-blue-700"
|
||||
label="Python (3.11)"
|
||||
lang={Script.language.PYTHON3}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.GO,
|
||||
kind: failureModule ? 'failure' : 'script',
|
||||
subkind: 'flow'
|
||||
language: RawScript.language.PYTHON3,
|
||||
kind,
|
||||
subkind: kind
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
label="Inline Bash"
|
||||
icon={faCode}
|
||||
iconColor="text-green-700"
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.BASH,
|
||||
kind: failureModule ? 'failure' : 'script',
|
||||
subkind: 'flow'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if !failureModule}
|
||||
{#if kind != 'approval'}
|
||||
<FlowScriptPicker
|
||||
label={`Inline PostgreSQL`}
|
||||
icon={faCode}
|
||||
iconColor="text-blue-800"
|
||||
on:click={() =>
|
||||
dispatch('new', { language: RawScript.language.DENO, kind: 'script', subkind: 'pgsql' })}
|
||||
label="Go"
|
||||
lang={Script.language.GO}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.GO,
|
||||
kind,
|
||||
subkind: 'flow'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<PickScript
|
||||
customText={failureModule ? 'Error Handler from workspace' : undefined}
|
||||
kind={failureModule ? Script.kind.FAILURE : Script.kind.SCRIPT}
|
||||
on:pick
|
||||
/>
|
||||
<PickHubScript
|
||||
customText={failureModule ? 'Error Handler from Hub' : undefined}
|
||||
kind={failureModule ? Script.kind.FAILURE : Script.kind.SCRIPT}
|
||||
on:pick
|
||||
/>
|
||||
{#if kind == 'script'}
|
||||
<FlowScriptPicker
|
||||
label="Bash"
|
||||
lang={Script.language.PYTHON3}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.BASH,
|
||||
kind,
|
||||
subkind: 'flow'
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if !failureModule}
|
||||
<FlowScriptPicker
|
||||
label={`PostgreSQL`}
|
||||
lang="pgsql"
|
||||
on:click={() =>
|
||||
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'pgsql' })}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !shouldDisableTriggerScripts}
|
||||
<div class="text-sm font-bold pt-8">
|
||||
Trigger script
|
||||
<Tooltip>
|
||||
Used as a first step most commonly with an internal state and a schedule to watch for
|
||||
changes on an external system, compute the diff since last time, set the new state. The
|
||||
diffs are then treated one by one with a for-loop.
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<PickScript customText="Trigger script from workspace" kind={Script.kind.TRIGGER} on:pick />
|
||||
<PickHubScript customText="Trigger script from Hub" kind={Script.kind.TRIGGER} on:pick />
|
||||
<FlowScriptPicker
|
||||
label="Inline Typescript (Deno)"
|
||||
icon={faCode}
|
||||
iconColor="text-blue-800"
|
||||
on:click={() => dispatch('new', { language: RawScript.language.DENO, kind: 'trigger' })}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if !failureModule}
|
||||
<div class="text-sm font-bold pt-8">
|
||||
Approval step
|
||||
<Tooltip>
|
||||
Inlined common scripts can be turned into approval step by changing their suspend settings.
|
||||
An approval step will suspend the execution of a flow until it has been approved through the
|
||||
resume endpoints or the approval page by and solely by the recipients of those secret urls.
|
||||
Use getResumeEndpoints from the wmill client to generate those URLs.
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<PickScript customText="Approval step from workspace" kind={Script.kind.APPROVAL} on:pick />
|
||||
<PickHubScript
|
||||
customText={'Approval step from the Hub'}
|
||||
kind={Script.kind.APPROVAL}
|
||||
on:pick
|
||||
/>
|
||||
<FlowScriptPicker
|
||||
label="Inline Typescript (Deno)"
|
||||
icon={faCode}
|
||||
iconColor="text-blue-800"
|
||||
on:click={() => dispatch('new', { language: RawScript.language.DENO, kind: 'approval' })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="text-sm font-bold pt-8">Flow primitive</div>
|
||||
|
||||
<div class="grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<FlowScriptPicker
|
||||
label={`Branches and switch to one`}
|
||||
icon={faCodeBranch}
|
||||
iconColor="text-blue-500"
|
||||
on:click={() => dispatch('branchone')}
|
||||
/>
|
||||
<FlowScriptPicker
|
||||
label={`Branches and run them all`}
|
||||
icon={faCodeBranch}
|
||||
iconColor="text-blue-500"
|
||||
on:click={() => dispatch('branchall')}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
label={`For loop`}
|
||||
icon={faRepeat}
|
||||
iconColor="text-blue-500"
|
||||
on:click={() => dispatch('loop')}
|
||||
/>
|
||||
</div>
|
||||
<h3>Use pre-made</h3>
|
||||
<div class="max-w-min">
|
||||
<ToggleButtonGroup bind:selected={pick_existing}>
|
||||
<ToggleButton position="left" value="hub" size="xs">
|
||||
<WindmillIcon white={pick_existing == 'hub'} height="16px" width="16px" /><div
|
||||
class="mr-1"
|
||||
/>Hub
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="workspace" size="xs" startIcon={{ icon: faBuilding }}>
|
||||
Workspace
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{#if pick_existing == 'hub'}
|
||||
<PickHubScript {kind} on:pick />
|
||||
{:else}
|
||||
<WorkspaceScriptPicker {kind} on:pick />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={50} minSize={20}>
|
||||
{#if flowModule.value.type === 'rawscript'}
|
||||
<div on:mouseleave={() => reload(flowModule)} class="h-full">
|
||||
<div class="h-full">
|
||||
<Editor
|
||||
bind:websocketAlive
|
||||
bind:this={editor}
|
||||
|
||||
@@ -46,34 +46,12 @@
|
||||
Steps are retried until they succeed, or until the maximum number of retries defined for that
|
||||
spec is reached, at which point the error handler is called.
|
||||
</Alert>
|
||||
{:else}
|
||||
<h1 class="p-4"
|
||||
>Select a step kind <Tooltip
|
||||
>Until being defined, this step acts as an identify function, returning as result its
|
||||
input and assigning it a key 'previous_result' if the input is not a json object</Tooltip
|
||||
></h1
|
||||
>
|
||||
{/if}
|
||||
|
||||
<FlowInputs
|
||||
shouldDisableTriggerScripts={parentModule !== undefined ||
|
||||
previousModule !== undefined ||
|
||||
$selectedId == 'failure'}
|
||||
on:loop={async () => {
|
||||
const [module, state] = await createLoop(flowModule.id)
|
||||
flowModule = module
|
||||
$flowStateStore[module.id] = state
|
||||
}}
|
||||
on:branchone={async () => {
|
||||
const [module, state] = await createBranches(flowModule.id)
|
||||
flowModule = module
|
||||
$flowStateStore[module.id] = state
|
||||
}}
|
||||
on:branchall={async () => {
|
||||
const [module, state] = await createBranchAll(flowModule.id)
|
||||
flowModule = module
|
||||
$flowStateStore[module.id] = state
|
||||
}}
|
||||
on:pick={async ({ detail }) => {
|
||||
const { path, summary, kind } = detail
|
||||
const [module, state] = await pickScript(path, summary, flowModule.id)
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Drawer bind:open={previewOpen} size="800px">
|
||||
<Drawer bind:open={previewOpen} size="1200px">
|
||||
<FlowPreviewContent
|
||||
open={previewOpen}
|
||||
bind:previewMode
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
import { classNames } from '$lib/utils'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { deleteFlowStateById, emptyModule, idMutex } from '../flowStateUtils'
|
||||
import { emptyFlowModuleState } from '../utils'
|
||||
import { flowStateStore } from '../flowState'
|
||||
|
||||
let selectedBranch = 0
|
||||
|
||||
export let module: FlowModule
|
||||
|
||||
let selectedBranch = 0
|
||||
const { select, selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
async function addBranch() {
|
||||
@@ -24,11 +23,9 @@
|
||||
module.value.branches.splice(module.value.branches.length, 0, {
|
||||
summary: '',
|
||||
skip_failure: false,
|
||||
modules: [newModule]
|
||||
modules: []
|
||||
})
|
||||
module = module
|
||||
|
||||
$flowStateStore[newModule.id] = emptyFlowModuleState()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -52,43 +49,44 @@
|
||||
>
|
||||
{#each module.value.branches ?? [] as branch, branchIndex (branchIndex)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
transition:slide|local
|
||||
on:click={() => {
|
||||
selectedBranch = branchIndex
|
||||
select(`${module.id}-branch-${branchIndex}`)
|
||||
}}
|
||||
class={classNames(
|
||||
`border-b ${
|
||||
branchIndex > 0 ? 'border-t' : ''
|
||||
} w-full p-2 bg-white border-gray-500 text-sm cursor-pointer flex items-center relative module`,
|
||||
$selectedId === `${module.id}-branch-${branchIndex}`
|
||||
? 'outline outline-2 outline-slate-900'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
<Icon data={faCodeBranch} class="mr-2" />
|
||||
<span
|
||||
class="text-xs flex flex-row justify-between w-full flex-wrap gap-2 items-center truncate"
|
||||
<div transition:slide|local>
|
||||
<div
|
||||
on:click={() => {
|
||||
selectedBranch = branchIndex
|
||||
select(`${module.id}-branch-${branchIndex}`)
|
||||
}}
|
||||
class={classNames(
|
||||
`border-b ${
|
||||
branchIndex > 0 ? 'border-t' : ''
|
||||
} w-full p-2 bg-white border-gray-500 text-sm cursor-pointer flex items-center relative module`,
|
||||
$selectedId === `${module.id}-branch-${branchIndex}`
|
||||
? 'outline outline-2 outline-slate-900'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
{branch.summary || `Branch ${branchIndex}`}
|
||||
<button
|
||||
class="absolute -top-2 -right-1 rounded-full h-4 w-4 bg-white center-center {$selectedId ===
|
||||
`${module.id}-branch-${branchIndex}`
|
||||
? ''
|
||||
: '!hidden'} trash"
|
||||
on:click={() => removeBranch(branchIndex)}
|
||||
><Icon
|
||||
data={faTimesCircle}
|
||||
class="text-gray-600 hover:text-red-600"
|
||||
scale={0.9}
|
||||
/></button
|
||||
<Icon data={faCodeBranch} class="mr-2" />
|
||||
<span
|
||||
class="text-xs flex flex-row justify-between w-full flex-wrap gap-2 items-center truncate"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
{branch.summary || `Branch ${branchIndex}`}
|
||||
<button
|
||||
class="absolute -top-2 -right-1 rounded-full h-4 w-4 bg-white center-center {$selectedId ===
|
||||
`${module.id}-branch-${branchIndex}`
|
||||
? ''
|
||||
: '!hidden'} trash"
|
||||
on:click={() => removeBranch(branchIndex)}
|
||||
><Icon
|
||||
data={faTimesCircle}
|
||||
class="text-gray-600 hover:text-red-600"
|
||||
scale={0.9}
|
||||
/></button
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FlowModuleSchemaMap bind:modules={branch.modules} />
|
||||
<div>
|
||||
<FlowModuleSchemaMap bind:modules={branch.modules} />
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="overflow-clip ml-2 mt-2">
|
||||
|
||||
@@ -21,15 +21,12 @@
|
||||
async function addBranch() {
|
||||
await idMutex.runExclusive(async () => {
|
||||
if (module.value.type === 'branchone') {
|
||||
const newModule = emptyModule()
|
||||
module.value.branches.splice(module.value.branches.length, 0, {
|
||||
summary: '',
|
||||
expr: '',
|
||||
modules: [newModule]
|
||||
expr: 'false',
|
||||
modules: []
|
||||
})
|
||||
module = module
|
||||
|
||||
$flowStateStore[newModule.id] = emptyFlowModuleState()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -77,41 +74,42 @@
|
||||
|
||||
{#each module.value.branches ?? [] as branch, branchIndex (branchIndex)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
transition:slide|local
|
||||
on:click={() => {
|
||||
selectedBranch = branchIndex + 1
|
||||
select(`${module.id}-branch-${branchIndex}`)
|
||||
}}
|
||||
class={classNames(
|
||||
`border-b border-t w-full p-2 bg-white border-gray-500 text-sm cursor-pointer flex items-center relative module`,
|
||||
$selectedId === `${module.id}-branch-${branchIndex}`
|
||||
? 'outline outline-2 outline-slate-900'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
<Icon data={faCodeBranch} class="mr-2" />
|
||||
<span
|
||||
class="text-xs flex flex-row justify-between w-full flex-wrap gap-2 items-center truncate"
|
||||
<div transition:slide|local>
|
||||
<div
|
||||
on:click={() => {
|
||||
selectedBranch = branchIndex + 1
|
||||
select(`${module.id}-branch-${branchIndex}`)
|
||||
}}
|
||||
class={classNames(
|
||||
`border-b border-t w-full p-2 bg-white border-gray-500 text-sm cursor-pointer flex items-center relative module`,
|
||||
$selectedId === `${module.id}-branch-${branchIndex}`
|
||||
? 'outline outline-2 outline-slate-900'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
{branch.summary || `Branch ${branchIndex}`}
|
||||
<button
|
||||
class="absolute -top-2 -right-2 rounded-full bg-white h-4 w-4 center-center trash {$selectedId ===
|
||||
`${module.id}-branch-${branchIndex}`
|
||||
? ''
|
||||
: '!hidden'}"
|
||||
on:click={() => removeBranch(branchIndex)}
|
||||
><Icon
|
||||
data={faTimesCircle}
|
||||
class="text-gray-600 hover:text-red-600"
|
||||
scale={0.9}
|
||||
/></button
|
||||
<Icon data={faCodeBranch} class="mr-2" />
|
||||
<span
|
||||
class="text-xs flex flex-row justify-between w-full flex-wrap gap-2 items-center truncate"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
{branch.summary || `Branch ${branchIndex}`}
|
||||
<button
|
||||
class="absolute -top-2 -right-2 rounded-full bg-white h-4 w-4 center-center trash {$selectedId ===
|
||||
`${module.id}-branch-${branchIndex}`
|
||||
? ''
|
||||
: '!hidden'}"
|
||||
on:click={() => removeBranch(branchIndex)}
|
||||
><Icon
|
||||
data={faTimesCircle}
|
||||
class="text-gray-600 hover:text-red-600"
|
||||
scale={0.9}
|
||||
/></button
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FlowModuleSchemaMap bind:modules={branch.modules} />
|
||||
<div>
|
||||
<FlowModuleSchemaMap bind:modules={branch.modules} />
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="overflow-clip ml-2">
|
||||
|
||||
@@ -41,16 +41,18 @@
|
||||
}
|
||||
}}
|
||||
class={classNames(
|
||||
'border rounded-md p-2 bg-white text-sm border-gray-400 cursor-pointer flex flex-col overflow-x-hidden ',
|
||||
'border rounded-sm p-2 bg-white text-sm border-gray-400 cursor-pointer flex flex-col overflow-x-hidden ',
|
||||
$selectedId.includes('failure') ? 'outline outline-offset-1 outline-2 outline-slate-900' : ''
|
||||
)}
|
||||
>
|
||||
<div class=" flex justify-between items-center flex-wrap">
|
||||
<div>
|
||||
<Icon data={faBug} class="mr-2" />
|
||||
<span class="font-bold">Error handler</span>
|
||||
<span class="font-bold text-xs">Error handler</span>
|
||||
</div>
|
||||
<div class="-my-1">
|
||||
<Toggle checked={Boolean($flowStore.value.failure_module)} on:change={onToggle} />
|
||||
</div>
|
||||
<Toggle checked={Boolean($flowStore.value.failure_module)} on:change={onToggle} />
|
||||
</div>
|
||||
|
||||
<div class="w-full truncate block">
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import { getContext } from 'svelte'
|
||||
import { deleteFlowStateById, emptyModule, idMutex } from '$lib/components/flows/flowStateUtils'
|
||||
import { flowStateStore } from '../flowState'
|
||||
import {
|
||||
createBranchAll,
|
||||
createBranches,
|
||||
createLoop,
|
||||
deleteFlowStateById,
|
||||
emptyModule,
|
||||
idMutex
|
||||
} from '$lib/components/flows/flowStateUtils'
|
||||
import { flowStateStore, type FlowModuleState } from '../flowState'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import FlowErrorHandlerItem from './FlowErrorHandlerItem.svelte'
|
||||
import RemoveStepConfirmationModal from '../content/RemoveStepConfirmationModal.svelte'
|
||||
@@ -19,12 +26,24 @@
|
||||
let indexToRemove: number | undefined = undefined
|
||||
const { select } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
async function insertNewModuleAtIndex(index: number): Promise<void> {
|
||||
async function insertNewModuleAtIndex(
|
||||
index: number,
|
||||
kind: 'script' | 'forloop' | 'branchone' | 'branchall'
|
||||
): Promise<void> {
|
||||
await idMutex.runExclusive(async () => {
|
||||
const flowModule = emptyModule()
|
||||
var module = emptyModule()
|
||||
var state = emptyFlowModuleState()
|
||||
if (kind == 'forloop') {
|
||||
;[module, state] = await createLoop(module.id)
|
||||
} else if (kind == 'branchone') {
|
||||
;[module, state] = await createBranches(module.id)
|
||||
} else if (kind == 'branchall') {
|
||||
;[module, state] = await createBranchAll(module.id)
|
||||
}
|
||||
const flowModule = module
|
||||
modules.splice(index, 0, flowModule)
|
||||
modules = modules
|
||||
$flowStateStore[flowModule.id] = emptyFlowModuleState()
|
||||
$flowStateStore[flowModule.id] = state
|
||||
select(flowModule.id)
|
||||
})
|
||||
}
|
||||
@@ -75,11 +94,15 @@
|
||||
|
||||
<div class="flex flex-col h-full">
|
||||
{#if root}
|
||||
<div class="flex-initial p-4 border-b">
|
||||
<div class="flex-initial px-3 py-2 border-b">
|
||||
<FlowSettingsItem />
|
||||
</div>
|
||||
{/if}
|
||||
<ul class="w-full flex-auto relative overflow-y-auto overflow-x-hidden {root ? 'px-2' : ''} py-1">
|
||||
<ul
|
||||
class="w-full flex-auto relative {root
|
||||
? ' overflow-y-auto overflow-x-hidden px-2 my-2'
|
||||
: ''} py-1"
|
||||
>
|
||||
{#if root}
|
||||
<li>
|
||||
<FlowInputsItem />
|
||||
@@ -97,17 +120,17 @@
|
||||
indexToRemove = index
|
||||
}
|
||||
}}
|
||||
on:insert={() => {
|
||||
insertNewModuleAtIndex(index)
|
||||
on:insert={(e) => {
|
||||
insertNewModuleAtIndex(index, e.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<InsertModuleButton on:click={() => insertNewModuleAtIndex(modules.length)} />
|
||||
<InsertModuleButton on:new={(e) => insertNewModuleAtIndex(modules.length, e.detail)} />
|
||||
</ul>
|
||||
{#if root}
|
||||
<div class="flex-none p-4 border-t">
|
||||
<div class="flex-none px-4 py-2 border-t">
|
||||
<FlowErrorHandlerItem />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
const { select, selectedId, schedule } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
$: settingsClass = classNames(
|
||||
'border w-full rounded-md p-2 bg-white border-gray-400 text-sm cursor-pointer flex items-center',
|
||||
'border w-full rounded-sm p-2 bg-white border-gray-400 text-sm cursor-pointer flex items-center',
|
||||
$selectedId === 'settings' ? 'outline outline-offset-1 outline-2 outline-slate-900' : ''
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,67 @@
|
||||
<script lang="ts">
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Menu } from '$lib/components/common'
|
||||
import { faCode, faCodeBranch, faPlus, faRepeat } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click
|
||||
type="button"
|
||||
class="text-gray-900 bg-white border mx-0.5 border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-6 h-6 flex items-center justify-center"
|
||||
>
|
||||
<Icon data={faPlus} scale={0.8} />
|
||||
</button>
|
||||
<Menu noMinW placement="bottom-start" let:close>
|
||||
<button
|
||||
slot="trigger"
|
||||
type="button"
|
||||
class="text-gray-900 bg-white border mx-0.5 border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-6 h-6 flex items-center justify-center"
|
||||
>
|
||||
<Icon data={faPlus} scale={0.8} />
|
||||
</button>
|
||||
<div class="divide-y divide-gray-100 text-xs w-40">
|
||||
<button
|
||||
class="w-full text-left p-2"
|
||||
on:click={() => {
|
||||
close()
|
||||
dispatch('new', 'script')
|
||||
}}
|
||||
role="menuitem"
|
||||
tabindex="-1"
|
||||
>
|
||||
<Icon data={faCode} scale={0.8} class="mr-1" />
|
||||
Script
|
||||
</button>
|
||||
<button
|
||||
class="w-full text-left p-2"
|
||||
on:click={() => {
|
||||
close()
|
||||
dispatch('new', 'forloop')
|
||||
}}
|
||||
role="menuitem"
|
||||
>
|
||||
<Icon data={faRepeat} scale={0.8} class="mr-1" />
|
||||
For Loop
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="w-full text-left p-2"
|
||||
on:click={() => {
|
||||
close()
|
||||
dispatch('new', 'branchone')
|
||||
}}
|
||||
role="menuitem"
|
||||
>
|
||||
<Icon data={faCodeBranch} scale={0.8} class="mr-1" />
|
||||
Branch to one
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="w-full text-left p-2"
|
||||
on:click={() => {
|
||||
close()
|
||||
dispatch('new', 'branchall')
|
||||
}}
|
||||
role="menuitem"
|
||||
>
|
||||
<Icon data={faCodeBranch} scale={0.8} class="mr-1" />
|
||||
Branch to all
|
||||
</button>
|
||||
</div>
|
||||
</Menu>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
faCode,
|
||||
faCodeBranch,
|
||||
faLongArrowDown,
|
||||
faQuestion,
|
||||
faRepeat
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
@@ -20,7 +19,10 @@
|
||||
export let mod: FlowModule
|
||||
|
||||
const { select, selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const dispatch = createEventDispatcher<{ delete: CustomEvent<MouseEvent>; insert: void }>()
|
||||
const dispatch = createEventDispatcher<{
|
||||
delete: CustomEvent<MouseEvent>
|
||||
insert: 'script' | 'forloop' | 'branchone' | 'branchall'
|
||||
}>()
|
||||
|
||||
$: itemProps = {
|
||||
selected: $selectedId === mod.id,
|
||||
@@ -35,7 +37,7 @@
|
||||
</script>
|
||||
|
||||
{#if mod}
|
||||
<InsertModuleButton on:click={() => dispatch('insert')} />
|
||||
<InsertModuleButton on:new={(e) => dispatch('insert', e.detail)} />
|
||||
{#if mod.value.type === 'forloopflow'}
|
||||
<li class="w-full">
|
||||
<FlowModuleSchemaItem
|
||||
@@ -52,7 +54,7 @@
|
||||
</FlowModuleSchemaItem>
|
||||
<div class="flex flex-row w-full">
|
||||
<div class="w-7 shrink-0 line" />
|
||||
<div class="grow my-1 overflow-auto">
|
||||
<div class="grow my-1">
|
||||
<div class="w-full">
|
||||
<FlowModuleSchemaMap bind:modules={mod.value.modules} />
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import type { IconDefinition } from '@fortawesome/free-brands-svg-icons'
|
||||
import LanguageIcon from '$lib/components/common/languageIcons/LanguageIcon.svelte'
|
||||
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
export let disabled: boolean = false
|
||||
export let icon: IconDefinition
|
||||
export let label: string
|
||||
export let iconColor: string
|
||||
export let lang: SupportedLanguage | 'pgsql' | undefined = undefined
|
||||
export let icon: IconDefinition | undefined = undefined
|
||||
export let iconColor: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<Button
|
||||
@@ -21,5 +24,7 @@
|
||||
classes: iconColor
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
{#if lang}
|
||||
<LanguageIcon {lang} />
|
||||
{/if}<span class="ml-2">{label}</span>
|
||||
</Button>
|
||||
|
||||
@@ -1,39 +1,88 @@
|
||||
<script lang="ts">
|
||||
import { faUserGroup } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import FlowScriptPicker from '$lib/components/flows/pickers/FlowScriptPicker.svelte'
|
||||
import ItemPicker from '$lib/components/ItemPicker.svelte'
|
||||
import { hubScripts } from '$lib/stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Script } from '$lib/gen'
|
||||
import type { HubItem } from './model'
|
||||
import Fuse from 'fuse.js'
|
||||
import IconedResourceType from '$lib/components/IconedResourceType.svelte'
|
||||
import { Badge } from '$lib/components/common'
|
||||
|
||||
export let kind: Script.kind
|
||||
export let customText: string | undefined = undefined
|
||||
export let kind: 'script' | 'trigger' | 'approval' | 'failure' = 'script'
|
||||
|
||||
let items: HubItem[]
|
||||
$: items = $hubScripts?.filter((x) => x.kind == kind) ?? []
|
||||
let itemPicker: ItemPicker
|
||||
let items: HubItem[] = []
|
||||
|
||||
let filteredItems: Item[] | undefined = []
|
||||
let itemsFilter = ''
|
||||
let appFilter: string | undefined = undefined
|
||||
|
||||
const fuseOptions = {
|
||||
includeScore: false,
|
||||
keys: ['path', 'summay']
|
||||
}
|
||||
const fuse: Fuse<Item> = new Fuse(items, fuseOptions)
|
||||
|
||||
$: {
|
||||
items =
|
||||
$hubScripts?.filter(
|
||||
(x) => x.kind == kind && (appFilter == undefined || x.app == appFilter)
|
||||
) ?? []
|
||||
fuse.setCollection(items)
|
||||
}
|
||||
|
||||
$: filteredItems =
|
||||
itemsFilter.length > 0 && items ? fuse.search(itemsFilter).map((value) => value.item) : items
|
||||
|
||||
$: apps = Array.from(new Set(filteredItems?.map((x) => x.app) ?? []))
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<ItemPicker
|
||||
bind:this={itemPicker}
|
||||
pickCallback={(path, summary) => {
|
||||
dispatch('pick', { path, summary, kind })
|
||||
}}
|
||||
itemName={'Script'}
|
||||
extraField="summary"
|
||||
loadItems={async () => {
|
||||
return items
|
||||
}}
|
||||
noItemMessage="Hub not reachable. If your environment is air gapped, contact sales@windmill.dev to setup a local mirror."
|
||||
/>
|
||||
<div class="flex flex-col min-h-0">
|
||||
<div class="w-12/12 pb-4">
|
||||
<input type="text" placeholder="Search script" bind:value={itemsFilter} class="search-item" />
|
||||
</div>
|
||||
|
||||
<FlowScriptPicker
|
||||
label={customText ?? `${kind == Script.kind.SCRIPT ? 'Script' : `${kind} script`} from the Hub`}
|
||||
icon={faUserGroup}
|
||||
iconColor="text-blue-500"
|
||||
on:click={() => itemPicker.openDrawer()}
|
||||
/>
|
||||
<div class="gap-2 w-full flex flex-wrap pb-4">
|
||||
{#each apps as app}
|
||||
<Badge
|
||||
class="cursor-pointer"
|
||||
on:click={() => {
|
||||
appFilter = appFilter == app ? undefined : app
|
||||
}}
|
||||
capitalize
|
||||
color={app === appFilter ? 'blue' : 'gray'}
|
||||
>
|
||||
{app}
|
||||
{#if app === appFilter}✗{/if}
|
||||
</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
{#if filteredItems}
|
||||
<div class="overflow-auto">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{#each filteredItems as obj}
|
||||
<li class="flex flex-row w-full">
|
||||
<button
|
||||
class="py-4 px-1 gap-1 flex flex-row grow hover:bg-white hover:border text-black"
|
||||
on:click={() => {
|
||||
dispatch('pick', obj)
|
||||
}}
|
||||
>
|
||||
<div class="mr-2 text-sm text-left truncate w-24">
|
||||
<IconedResourceType after={true} silent={false} name={obj['app']} />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-sm font-semibold flex flex-col">
|
||||
<span class="mr-2 text-left">{obj['summary'] ?? ''}</span>
|
||||
<span class="font-normal text-xs text-left italic overflow-hidden"
|
||||
>{obj['path'] ?? ''}</span
|
||||
>
|
||||
</div>
|
||||
<div class="text-xs font-light italic text-left">{obj['description'] ?? ''}</div>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import ItemPicker from '$lib/components/ItemPicker.svelte'
|
||||
import { faUserGroup } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faBuilding, faUserGroup } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Script, ScriptService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<FlowScriptPicker
|
||||
label={customText ?? `${kind == Script.kind.SCRIPT ? 'Script' : `${kind} script`} from workspace`}
|
||||
icon={faUserGroup}
|
||||
icon={faBuilding}
|
||||
iconColor="text-blue-500"
|
||||
on:click={() => itemPicker.openDrawer()}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { HubItem } from './model'
|
||||
import Fuse from 'fuse.js'
|
||||
import { ScriptService } from '$lib/gen'
|
||||
|
||||
export let kind: 'script' | 'trigger' | 'approval' | 'failure' = 'script'
|
||||
|
||||
let items: HubItem[] = []
|
||||
|
||||
let filteredItems: Item[] | undefined = []
|
||||
let itemsFilter = ''
|
||||
|
||||
const fuseOptions = {
|
||||
includeScore: false,
|
||||
keys: ['path', 'summay']
|
||||
}
|
||||
const fuse: Fuse<Item> = new Fuse(items, fuseOptions)
|
||||
|
||||
$: $workspaceStore && loadItems()
|
||||
|
||||
$: filteredItems =
|
||||
itemsFilter.length > 0 && items ? fuse.search(itemsFilter).map((value) => value.item) : items
|
||||
|
||||
async function loadItems(): Promise<void> {
|
||||
items = await ScriptService.listScripts({ workspace: $workspaceStore!, kind })
|
||||
fuse.setCollection(items)
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col min-h-0">
|
||||
<div class="w-12/12 pb-4">
|
||||
<input type="text" placeholder="Search script" bind:value={itemsFilter} class="search-item" />
|
||||
</div>
|
||||
|
||||
{#if filteredItems}
|
||||
<ul class="divide-y divide-gray-200 overflow-auto">
|
||||
{#each filteredItems as obj}
|
||||
<li class="flex flex-row w-full">
|
||||
<button
|
||||
class="py-4 px-1 gap-1 flex flex-row grow hover:bg-white hover:border text-black"
|
||||
on:click={() => {
|
||||
dispatch('pick', obj)
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-sm font-semibold flex flex-col">
|
||||
<span class="mr-2 text-left">{obj['summary'] ?? ''}</span>
|
||||
<span class="font-normal text-xs text-left italic overflow-hidden"
|
||||
>{obj['path'] ?? ''}</span
|
||||
>
|
||||
</div>
|
||||
<div class="text-xs font-light italic text-left">{obj['description'] ?? ''}</div>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -49,10 +49,10 @@
|
||||
</script>
|
||||
|
||||
<Splitpanes>
|
||||
<Pane minSize={20} size={66} class="relative p-4 !transition-none">
|
||||
<Pane minSize={20} size={60} class="relative p-4 !transition-none">
|
||||
<slot />
|
||||
</Pane>
|
||||
<Pane minSize={20} size={34} class="px-2 py-2 h-full !transition-none">
|
||||
<Pane minSize={20} size={40} class="py-2 relative !transition-none">
|
||||
{#if result}
|
||||
<PropPickerResult {result} />
|
||||
{:else if pickableProperties}
|
||||
|
||||
@@ -152,7 +152,7 @@ export async function runFlowPreview(args: Record<string, any>, flow: Flow) {
|
||||
}
|
||||
|
||||
export function codeToStaticTemplate(code?: string): string | undefined {
|
||||
if (!code) return undefined
|
||||
if (!code || typeof code != 'string') return undefined
|
||||
|
||||
const lines = code
|
||||
.split('\n')
|
||||
|
||||
@@ -59,135 +59,139 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !notSelectable}
|
||||
<div class="flex flex-row space-x-1">
|
||||
{#if $propPickerConfig}
|
||||
<Badge color="blue">
|
||||
{`Selected: ${$propPickerConfig?.propName}`}
|
||||
</Badge>
|
||||
<Badge color="blue">
|
||||
{`Mode: ${$propPickerConfig?.insertionMode}`}
|
||||
</Badge>
|
||||
{:else}
|
||||
<Badge color="blue">← Select a step input</Badge>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="px-2">
|
||||
{#if !notSelectable}
|
||||
<div class="flex flex-row space-x-1">
|
||||
{#if $propPickerConfig}
|
||||
<Badge large color="blue">
|
||||
{`Selected: ${$propPickerConfig?.propName}`}
|
||||
</Badge>
|
||||
<Badge large color="blue">
|
||||
{`Mode: ${$propPickerConfig?.insertionMode}`}
|
||||
</Badge>
|
||||
{:else}
|
||||
<Badge large color="blue">← Select a step input</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={search}
|
||||
class="bg-gray-50 mt-1 border border-gray-300 text-gray-900 text-sm rounded-lg block px-2 mb-2 w-full"
|
||||
placeholder="Search prop..."
|
||||
/>
|
||||
<div class:bg-gray-100={!$propPickerConfig && !notSelectable}>
|
||||
<div class="flex justify-between items-center space-x-1">
|
||||
<span class="font-bold text-sm">Flow Input</span>
|
||||
<div class="flex space-x-2 items-center" />
|
||||
</div>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
<ObjectViewer
|
||||
pureViewer={!$propPickerConfig}
|
||||
json={flowInputsFiltered}
|
||||
on:select={(e) => {
|
||||
dispatch('select', `flow_input.${e.detail}`)
|
||||
}}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={search}
|
||||
class="bg-gray-50 mt-2 border border-gray-300 text-gray-900 text-sm rounded-lg block mb-2 w-full"
|
||||
placeholder="Search prop..."
|
||||
/>
|
||||
</div>
|
||||
{#if error}
|
||||
<span class="font-bold text-sm">Error</span>
|
||||
<div class="overflow-y-auto px-2 grow" class:bg-gray-100={!$propPickerConfig && !notSelectable}>
|
||||
<div class="flex justify-between items-center space-x-1">
|
||||
<span class="font-bold text-sm">Flow Input</span>
|
||||
<div class="flex space-x-2 items-center" />
|
||||
</div>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
<ObjectViewer
|
||||
pureViewer={!$propPickerConfig}
|
||||
json={{ previous_result: { error: 'The error to handle' } }}
|
||||
on:select
|
||||
json={flowInputsFiltered}
|
||||
on:select={(e) => {
|
||||
dispatch('select', `flow_input.${e.detail}`)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
{#if previousId}
|
||||
<span class="font-bold text-sm">Previous Result</span>
|
||||
{#if error}
|
||||
<span class="font-bold text-sm">Error</span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
<ObjectViewer
|
||||
topLevelNode
|
||||
pureViewer={!$propPickerConfig}
|
||||
json={Object.fromEntries(
|
||||
Object.entries(resultByIdFiltered).filter(([k, v]) => k == previousId)
|
||||
)}
|
||||
on:select={(e) => {
|
||||
dispatch('select', `results.${e.detail}`)
|
||||
}}
|
||||
json={{ previous_result: { error: 'The error to handle' } }}
|
||||
on:select
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
{#if previousId}
|
||||
<span class="font-bold text-sm">Previous Result</span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
<ObjectViewer
|
||||
topLevelNode
|
||||
pureViewer={!$propPickerConfig}
|
||||
json={Object.fromEntries(
|
||||
Object.entries(resultByIdFiltered).filter(([k, v]) => k == previousId)
|
||||
)}
|
||||
on:select={(e) => {
|
||||
dispatch('select', `results.${e.detail}`)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if Object.keys(pickableProperties.priorIds).length > 0}
|
||||
<span class="font-bold text-sm">All Results</span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
<ObjectViewer
|
||||
topLevelNode
|
||||
pureViewer={!$propPickerConfig}
|
||||
collapsed={true}
|
||||
json={resultByIdFiltered}
|
||||
on:select={(e) => {
|
||||
dispatch('select', `results.${e.detail}`)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if Object.keys(pickableProperties.priorIds).length > 0}
|
||||
<span class="font-bold text-sm">All Results</span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
<ObjectViewer
|
||||
topLevelNode
|
||||
pureViewer={!$propPickerConfig}
|
||||
collapsed={true}
|
||||
json={resultByIdFiltered}
|
||||
on:select={(e) => {
|
||||
dispatch('select', `results.${e.detail}`)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if displayContext}
|
||||
<span class="font-bold text-sm">Variables </span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
{#if displayVariable}
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
displayVariable = false
|
||||
}}>-</Button
|
||||
>
|
||||
<ObjectViewer
|
||||
pureViewer={!$propPickerConfig}
|
||||
rawKey={true}
|
||||
json={variables}
|
||||
on:select={(e) => dispatch('select', `variable('${e.detail}')`)}
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="border border-blue-600 key font-normal rounded hover:bg-blue-100 px-1"
|
||||
on:click={async () => {
|
||||
await loadVariables()
|
||||
displayVariable = true
|
||||
}}>{'{...}'}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="font-bold text-sm">Resources</span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
{#if displayResources}
|
||||
<Button
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
displayResources = false
|
||||
}}>-</Button
|
||||
>
|
||||
<ObjectViewer
|
||||
pureViewer={!$propPickerConfig}
|
||||
rawKey={true}
|
||||
json={resources}
|
||||
on:select={(e) => dispatch('select', `resource('${e.detail}')`)}
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="border border-blue-600 px-1 key font-normal rounded hover:bg-blue-100"
|
||||
on:click={async () => {
|
||||
await loadResources()
|
||||
displayResources = true
|
||||
}}>{'{...}'}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if displayContext}
|
||||
<span class="font-bold text-sm">Variables </span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
{#if displayVariable}
|
||||
<Button
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
displayVariable = false
|
||||
}}>-</Button
|
||||
>
|
||||
<ObjectViewer
|
||||
pureViewer={!$propPickerConfig}
|
||||
rawKey={true}
|
||||
json={variables}
|
||||
on:select={(e) => dispatch('select', `variable('${e.detail}')`)}
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="border border-blue-600 key font-normal rounded hover:bg-blue-100 px-1"
|
||||
on:click={async () => {
|
||||
await loadVariables()
|
||||
displayVariable = true
|
||||
}}>{'{...}'}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="font-bold text-sm">Resources</span>
|
||||
<div class="overflow-y-auto mb-2">
|
||||
{#if displayResources}
|
||||
<Button
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
displayResources = false
|
||||
}}>-</Button
|
||||
>
|
||||
<ObjectViewer
|
||||
pureViewer={!$propPickerConfig}
|
||||
rawKey={true}
|
||||
json={resources}
|
||||
on:select={(e) => dispatch('select', `resource('${e.detail}')`)}
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="border border-blue-600 px-1 key font-normal rounded hover:bg-blue-100"
|
||||
on:click={async () => {
|
||||
await loadResources()
|
||||
displayResources = true
|
||||
}}>{'{...}'}</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -161,20 +161,60 @@ export const DENO_INIT_CODE_TRIGGER = `import * as wmill from "https://deno.land
|
||||
|
||||
export async function main() {
|
||||
|
||||
// A common trigger script would follow this pattern:
|
||||
// 1. Get the last saved state
|
||||
// const state = await wmill.getState()
|
||||
// 2. Get the actual state from the external service
|
||||
// const newState = await (await fetch('https://hacker-news.firebaseio.com/v0/topstories.json')).json()
|
||||
// 3. Compare the two states and update the internal state
|
||||
// await wmill.setState(newState)
|
||||
// 4. Return the new rows
|
||||
// return range from (state to newState)
|
||||
// A common trigger script would follow this pattern:
|
||||
// 1. Get the last saved state
|
||||
// const state = await wmill.getState()
|
||||
// 2. Get the actual state from the external service
|
||||
// const newState = await (await fetch('https://hacker-news.firebaseio.com/v0/topstories.json')).json()
|
||||
// 3. Compare the two states and update the internal state
|
||||
// await wmill.setState(newState)
|
||||
// 4. Return the new rows
|
||||
// return range from (state to newState)
|
||||
|
||||
return [1,2,3]
|
||||
return [1,2,3]
|
||||
|
||||
// In subsequent scripts, you may refer to each row/value returned by the trigger script using
|
||||
// 'flow_input._value'
|
||||
// In subsequent scripts, you may refer to each row/value returned by the trigger script using
|
||||
// 'flow_input.iter.value'
|
||||
}
|
||||
`
|
||||
|
||||
export const PYTHON_INIT_CODE_TRIGGER = `import wmill
|
||||
|
||||
def main():
|
||||
# A common trigger script would follow this pattern:
|
||||
# 1. Get the last saved state
|
||||
# const state = wnmill.get_state()
|
||||
# 2. Get the actual state from the external service
|
||||
# const newState = ...
|
||||
# 3. Compare the two states and update the internal state
|
||||
# wmill.setState(newState)
|
||||
# 4. Return the new rows
|
||||
# return range from (state to newState)
|
||||
return [1, 2, 3]
|
||||
`
|
||||
|
||||
|
||||
export const GO_INIT_CODE_TRIGGER = `package inner
|
||||
|
||||
import (
|
||||
wmill "github.com/windmill-labs/windmill-go-client"
|
||||
)
|
||||
|
||||
func main() (interface{}, error) {
|
||||
|
||||
// A common trigger script would follow this pattern:
|
||||
// 1. Get the last saved state
|
||||
state, _ := wmill.GetState()
|
||||
// 2. Get the actual state from the external service
|
||||
// newState := ...
|
||||
// 3. Compare the two states and update the internal state
|
||||
wmill.SetState(4)
|
||||
// 4. Return the new rows
|
||||
|
||||
return state, nil
|
||||
|
||||
// In subsequent scripts, you may refer to each row/value returned by the trigger script using
|
||||
// 'flow_input.iter.value'
|
||||
}
|
||||
`
|
||||
|
||||
@@ -184,7 +224,7 @@ export async function main(approver?: string) {
|
||||
return wmill.getResumeEndpoints(approver)
|
||||
}`
|
||||
|
||||
const ALL_INITIAL_CODE = [PYTHON_INIT_CODE, DENO_INIT_CODE, POSTGRES_INIT_CODE, DENO_INIT_CODE_TRIGGER, DENO_INIT_CODE_CLEAR, PYTHON_INIT_CODE_CLEAR, DENO_INIT_CODE_APPROVAL, DENO_FAILURE_MODULE_CODE]
|
||||
const ALL_INITIAL_CODE = [PYTHON_INIT_CODE, PYTHON_INIT_CODE_TRIGGER, DENO_INIT_CODE, POSTGRES_INIT_CODE, DENO_INIT_CODE_TRIGGER, DENO_INIT_CODE_CLEAR, PYTHON_INIT_CODE_CLEAR, DENO_INIT_CODE_APPROVAL, DENO_FAILURE_MODULE_CODE]
|
||||
|
||||
export function isInitialCode(content: string): boolean {
|
||||
for (const code of ALL_INITIAL_CODE) {
|
||||
@@ -221,6 +261,8 @@ export function initialCode(language: 'deno' | 'python3' | 'go' | 'bash', kind:
|
||||
return PYTHON_INIT_CODE_CLEAR
|
||||
} else if (kind === 'failure') {
|
||||
return PYTHON_FAILURE_MODULE_CODE
|
||||
} else if (kind === 'trigger') {
|
||||
return PYTHON_INIT_CODE_TRIGGER
|
||||
} else {
|
||||
return PYTHON_INIT_CODE
|
||||
}
|
||||
@@ -229,6 +271,8 @@ export function initialCode(language: 'deno' | 'python3' | 'go' | 'bash', kind:
|
||||
} else {
|
||||
if (kind === 'failure') {
|
||||
return GO_FAILURE_MODULE_CODE
|
||||
} else if (kind === 'trigger') {
|
||||
return GO_INIT_CODE_TRIGGER
|
||||
} else {
|
||||
return GO_INIT_CODE
|
||||
}
|
||||
|
||||
@@ -59,9 +59,8 @@ export function displayDate(dateString: string | undefined): string {
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
return ''
|
||||
} else {
|
||||
return `${date.getFullYear()}/${
|
||||
date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
return `${date.getFullYear()}/${date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,7 +440,7 @@ export async function getScriptByPath(path: string): Promise<{
|
||||
|
||||
return {
|
||||
content,
|
||||
language,
|
||||
language: language as SupportedLanguage,
|
||||
schema
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
path: templatePath
|
||||
})
|
||||
Object.assign(flow, template)
|
||||
flow.path = templatePath
|
||||
const oldPath = flow.path.split('/')
|
||||
flow.path = `u/${$userStore?.username}/${oldPath[oldPath.length - 1]}`
|
||||
flow = flow
|
||||
$page.url.searchParams.delete('template')
|
||||
selectedId = 'settings-graph'
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import RunForm from '$lib/components/RunForm.svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { Button, Skeleton } from '$lib/components/common'
|
||||
import { faPlay, faScroll } from '@fortawesome/free-solid-svg-icons'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
@@ -131,13 +131,15 @@
|
||||
<SvelteMarkdown source={defaultIfEmptyString(flow.description, 'No description')} />
|
||||
</div>
|
||||
</div>
|
||||
<RunForm
|
||||
autofocus
|
||||
bind:this={runForm}
|
||||
bind:isValid
|
||||
detailed={false}
|
||||
runnable={flow}
|
||||
runAction={runFlow}
|
||||
/>
|
||||
{:else}
|
||||
<Skeleton layout={[2, [3], 1, [2], 4, [4], 3, [8]]} />
|
||||
{/if}
|
||||
<RunForm
|
||||
autofocus
|
||||
bind:this={runForm}
|
||||
bind:isValid
|
||||
detailed={false}
|
||||
runnable={flow}
|
||||
runAction={runFlow}
|
||||
/>
|
||||
</CenteredPage>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import RunForm from '$lib/components/RunForm.svelte'
|
||||
import { Alert, Badge, Button } from '$lib/components/common'
|
||||
import { Alert, Badge, Button, Skeleton } from '$lib/components/common'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import { faPlay, faScroll } from '@fortawesome/free-solid-svg-icons'
|
||||
@@ -167,30 +167,32 @@
|
||||
<SvelteMarkdown source={defaultIfEmptyString(script.description, 'No description')} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if script?.lock_error_logs}
|
||||
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4" role="alert">
|
||||
<p class="font-bold">Not deployed properly</p>
|
||||
<p>
|
||||
This version of this script is unable to be run because because the deployment had the
|
||||
following errors:
|
||||
</p>
|
||||
<pre class="w-full text-xs mt-2 whitespace-pre-wrap">{script.lock_error_logs}</pre>
|
||||
</div>
|
||||
{:else if script && script?.lock == undefined}
|
||||
<div class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4" role="alert">
|
||||
<p class="font-bold">Deployment in progress</p>
|
||||
<p>Refresh this page in a few seconds.</p>
|
||||
</div>
|
||||
{#if script?.lock_error_logs}
|
||||
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4" role="alert">
|
||||
<p class="font-bold">Not deployed properly</p>
|
||||
<p>
|
||||
This version of this script is unable to be run because because the deployment had the
|
||||
following errors:
|
||||
</p>
|
||||
<pre class="w-full text-xs mt-2 whitespace-pre-wrap">{script.lock_error_logs}</pre>
|
||||
</div>
|
||||
{:else if script && script?.lock == undefined}
|
||||
<div class="bg-orange-100 border-l-4 border-orange-500 text-orange-700 p-4" role="alert">
|
||||
<p class="font-bold">Deployment in progress</p>
|
||||
<p>Refresh this page in a few seconds.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<RunForm
|
||||
autofocus
|
||||
detailed={false}
|
||||
bind:isValid
|
||||
bind:this={runForm}
|
||||
runnable={script}
|
||||
runAction={runScript}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<RunForm
|
||||
autofocus
|
||||
detailed={false}
|
||||
bind:isValid
|
||||
bind:this={runForm}
|
||||
runnable={script}
|
||||
runAction={runScript}
|
||||
/>
|
||||
<Skeleton layout={[2, [3], 1, [2], 4, [4], 3, [8]]} />
|
||||
{/if}
|
||||
</CenteredPage>
|
||||
|
||||
Reference in New Issue
Block a user