mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-12 00:06:14 +00:00
Flow: Input connect (#325)
* Fix EditorBar in the script editor * Rework ArgInput * Add a button to link a property * Adapt style * Clean up * Clean up Toggle * Clean up Toggleclear * Fix editor * Fix login test * Fix login test * Done * Fix toggling issues
This commit is contained in:
@@ -1,23 +1,30 @@
|
||||
<script lang="ts">
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faArrowRotateLeft,
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faMinus,
|
||||
faPlus
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { setInputCat as computeInputCat, type InputCat } from '$lib/utils'
|
||||
import { Button, Tooltip } from 'flowbite-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Editor from './Editor.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
|
||||
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
|
||||
export let defaultValue: any = undefined
|
||||
|
||||
export let description: string = ''
|
||||
export let format: string = ''
|
||||
export let contentEncoding: 'base64' | 'binary' | undefined = undefined
|
||||
@@ -141,17 +148,15 @@
|
||||
on:click={() => {
|
||||
seeEditable = !seeEditable
|
||||
}}
|
||||
>Customize argument<Icon
|
||||
class="ml-2"
|
||||
data={seeEditable ? faChevronUp : faChevronDown}
|
||||
scale={0.7}
|
||||
/></span
|
||||
>
|
||||
Customize argument
|
||||
<Icon class="ml-2" data={seeEditable ? faChevronUp : faChevronDown} scale={0.7} />
|
||||
</span>
|
||||
|
||||
{#if seeEditable}
|
||||
<div transition:slide class="mt-2">
|
||||
<label class="text-gray-700"
|
||||
>Description
|
||||
<label class="text-gray-700">
|
||||
Description
|
||||
<textarea rows="1" bind:value={description} placeholder="Edit description" />
|
||||
{#if type == 'string' && !contentEncoding && format != 'date-time'}
|
||||
<StringTypeNarrowing bind:format bind:pattern bind:enum_ bind:contentEncoding />
|
||||
@@ -182,7 +187,7 @@
|
||||
{error === '' ? '...' : error}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row gap-1">
|
||||
<div class="flex space-x-1">
|
||||
{#if inputCat == 'number' && !numberAsString}
|
||||
<input
|
||||
{disabled}
|
||||
@@ -230,8 +235,10 @@
|
||||
if (value.length == 0) {
|
||||
value = undefined
|
||||
}
|
||||
}}><Icon data={faMinus} class="mb-1" /></button
|
||||
}}
|
||||
>
|
||||
<Icon data={faMinus} class="mb-1" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -242,9 +249,14 @@
|
||||
value = []
|
||||
}
|
||||
value = value.concat('')
|
||||
}}>Add item <Icon data={faPlus} class="mb-1" /></button
|
||||
><span class="ml-2">{(value ?? []).length} item{(value ?? []).length > 1 ? 's' : ''}</span
|
||||
}}
|
||||
>
|
||||
<Icon data={faPlus} class="mr-2" />
|
||||
Add item
|
||||
</button>
|
||||
<span class="ml-2">
|
||||
{(value ?? []).length} item{(value ?? []).length > 1 ? 's' : ''}
|
||||
</span>
|
||||
</div>
|
||||
{:else if inputCat == 'resource-object'}
|
||||
<ObjectResourceInput {format} bind:value />
|
||||
@@ -306,15 +318,19 @@
|
||||
/>
|
||||
{/if}
|
||||
{#if !required && inputCat != 'resource-object'}
|
||||
<div class="flex flex-row-reverse">
|
||||
<button
|
||||
{disabled}
|
||||
class="default-button-secondary items-center leading-4 py-0 my-px px-1 float-right"
|
||||
<Tooltip placement="bottom" content="Reset to default value">
|
||||
<Button
|
||||
on:click={() => (value = undefined)}
|
||||
>Reset <Tooltip>Reset to default value</Tooltip></button
|
||||
{disabled}
|
||||
color="alternative"
|
||||
size="sm"
|
||||
class="h-8"
|
||||
>
|
||||
</div>
|
||||
<Icon data={faArrowRotateLeft} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -132,14 +132,14 @@
|
||||
<div class="justify-between flex flex-row w-full">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbItem>
|
||||
<button on:click={() => changeStep(1)} class={step === 1 ? 'font-bold' : null}
|
||||
>Flow Editor</button
|
||||
>
|
||||
<button on:click={() => changeStep(1)} class={step === 1 ? 'font-bold' : null}>
|
||||
Flow Editor
|
||||
</button>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem>
|
||||
<button on:click={() => changeStep(2)} class={step === 2 ? 'font-bold' : null}
|
||||
>UI customisation</button
|
||||
>
|
||||
<button on:click={() => changeStep(2)} class={step === 2 ? 'font-bold' : null}>
|
||||
UI customisation
|
||||
</button>
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
<div class="flex flex-row-reverse ml-2">
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { InputTransform } from '$lib/gen'
|
||||
import type { InputCat } from '$lib/utils'
|
||||
import { faChain } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Button, Tooltip } from 'flowbite-svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import Editor from './Editor.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import DynamicInputHelpBox from './flows/DynamicInputHelpBox.svelte'
|
||||
import { codeToStaticTemplate } from './flows/flowStore'
|
||||
import { getCodeInjectionExpr, getDefaultExpr, isCodeInjection } from './flows/utils'
|
||||
import ClickablePropertyPicker from './propertyPicker/ClickablePropertyPicker.svelte'
|
||||
import OverlayPropertyPicker from './propertyPicker/OverlayPropertyPicker.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
|
||||
export let schema: Schema
|
||||
export let arg: InputTransform | any
|
||||
export let argName: string
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let inputCheck: { [id: string]: boolean }
|
||||
export let i: number | undefined = undefined
|
||||
export let pickableProperties: Object | undefined = undefined
|
||||
|
||||
let overlays: { [id: string]: OverlayPropertyPicker } = {}
|
||||
let monacos: { [id: string]: Editor } = {}
|
||||
|
||||
let inputCats: { [id: string]: InputCat } = {}
|
||||
let propertyType = getPropertyType(arg)
|
||||
|
||||
function getPropertyType(arg: InputTransform | any): 'static' | 'javascript' {
|
||||
let type: 'static' | 'javascript' = arg.type
|
||||
if (type == 'javascript') {
|
||||
if (codeToStaticTemplate(arg.expr)) {
|
||||
type = 'static'
|
||||
}
|
||||
}
|
||||
return type
|
||||
}
|
||||
|
||||
function setPropertyType(id: string, rawValue: string, isRaw: boolean) {
|
||||
if (!arg) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isCodeInjection(rawValue)) {
|
||||
arg.expr = getCodeInjectionExpr(rawValue, isRaw)
|
||||
arg.type = 'javascript'
|
||||
propertyType = 'static'
|
||||
} else {
|
||||
if (arg.type === 'javascript' && propertyType === 'static') {
|
||||
arg.type = 'static'
|
||||
if (inputCats[id] == 'number') {
|
||||
arg.value = Number(arg.value)
|
||||
}
|
||||
}
|
||||
if (arg.type) {
|
||||
propertyType = arg.type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isStaticTemplate(inputCat: InputCat) {
|
||||
return inputCat === 'string' || inputCat === 'number' || inputCat === 'sql'
|
||||
}
|
||||
|
||||
function focusProp(argName: string) {
|
||||
Object.keys(overlays).forEach((k) => {
|
||||
if (k == argName) {
|
||||
overlays[k].focus()
|
||||
} else {
|
||||
overlays[k].unfocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onPropertyLink(argName: string, rawValue: string) {
|
||||
if (isStaticTemplate(inputCats[argName])) {
|
||||
arg.value = `\$\{${rawValue}}`
|
||||
setPropertyType(argName, arg.value, false)
|
||||
} else {
|
||||
arg.expr = getDefaultExpr(i ?? -1, undefined, rawValue)
|
||||
|
||||
arg.type = 'javascript'
|
||||
|
||||
propertyType = 'javascript'
|
||||
}
|
||||
|
||||
if (monacos[argName]) {
|
||||
monacos[argName].setCode(arg.value)
|
||||
}
|
||||
}
|
||||
|
||||
$: checked = propertyType == 'javascript'
|
||||
</script>
|
||||
|
||||
{#if arg != undefined}
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center">
|
||||
<FieldHeader
|
||||
label={argName}
|
||||
format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
required={schema.required.includes(argName)}
|
||||
type={schema.properties[argName].type}
|
||||
itemsType={schema.properties[argName].items}
|
||||
/>
|
||||
|
||||
{#if !checked && arg.type === 'javascript'}
|
||||
<span class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded ml-2">
|
||||
{'${...}'}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<Toggle
|
||||
bind:checked
|
||||
options={{
|
||||
right: 'Raw Javascript Editor'
|
||||
}}
|
||||
on:change={(e) => {
|
||||
const type = e.detail ? 'javascript' : 'static'
|
||||
const staticTemplate = isStaticTemplate(inputCats[argName])
|
||||
if (type === 'javascript') {
|
||||
arg.expr = getDefaultExpr(
|
||||
i ?? -1,
|
||||
argName,
|
||||
staticTemplate ? `\`${arg.value ?? ''}\`` : undefined
|
||||
)
|
||||
arg.value = undefined
|
||||
propertyType = 'javascript'
|
||||
} else {
|
||||
arg.value = staticTemplate ? codeToStaticTemplate(arg.expr) : undefined
|
||||
|
||||
arg.expr = undefined
|
||||
propertyType = 'static'
|
||||
}
|
||||
|
||||
arg.type = type
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="max-w-xs" />
|
||||
|
||||
{#if propertyType === undefined || !checked}
|
||||
<OverlayPropertyPicker
|
||||
bind:this={overlays[argName]}
|
||||
{pickableProperties}
|
||||
disabled={!isStaticTemplate(inputCats[argName])}
|
||||
on:select={(event) => {
|
||||
const toAppend = `\$\{${event.detail}}`
|
||||
arg.value = `${arg.value ?? ''}${toAppend}`
|
||||
if (monacos[argName]) {
|
||||
monacos[argName].setCode(arg.value)
|
||||
}
|
||||
setPropertyType(argName, arg.value, false)
|
||||
}}
|
||||
>
|
||||
<ArgInput
|
||||
on:focus={() => focusProp(argName)}
|
||||
label={argName}
|
||||
bind:editor={monacos[argName]}
|
||||
bind:description={schema.properties[argName].description}
|
||||
bind:value={arg.value}
|
||||
type={schema.properties[argName].type}
|
||||
required={schema.required.includes(argName)}
|
||||
bind:pattern={schema.properties[argName].pattern}
|
||||
bind:valid={inputCheck[argName]}
|
||||
defaultValue={schema.properties[argName].default}
|
||||
bind:enum_={schema.properties[argName].enum}
|
||||
bind:format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
bind:itemsType={schema.properties[argName].items}
|
||||
displayHeader={false}
|
||||
bind:inputCat={inputCats[argName]}
|
||||
numberAsString={true}
|
||||
on:input={(e) => {
|
||||
if (isStaticTemplate(inputCats[argName])) {
|
||||
setPropertyType(argName, e.detail.rawValue, e.detail.isRaw)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div slot="actions">
|
||||
<ClickablePropertyPicker
|
||||
bind:pickableProperties
|
||||
on:select={(event) => onPropertyLink(argName, event.detail)}
|
||||
>
|
||||
<Tooltip placement="bottom" content="Input connect">
|
||||
<Button color="blue" size="sm" class="h-8">
|
||||
<Icon data={faChain} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</ClickablePropertyPicker>
|
||||
</div>
|
||||
</ArgInput>
|
||||
</OverlayPropertyPicker>
|
||||
{:else if checked}
|
||||
{#if arg.expr != undefined}
|
||||
<OverlayPropertyPicker
|
||||
bind:this={overlays[argName]}
|
||||
{pickableProperties}
|
||||
on:select={(event) => {
|
||||
monacos[argName].insertAtCursor(event.detail)
|
||||
}}
|
||||
>
|
||||
<div class="border rounded p-2 mt-2 border-gray-300">
|
||||
<Editor
|
||||
bind:this={monacos[argName]}
|
||||
on:focus={() => focusProp(argName)}
|
||||
bind:code={arg.expr}
|
||||
lang="javascript"
|
||||
class="few-lines-editor"
|
||||
{extraLib}
|
||||
extraLibPath="file:///node_modules/@types/windmill@{i}/index.d.ts"
|
||||
/>
|
||||
</div>
|
||||
</OverlayPropertyPicker>
|
||||
<DynamicInputHelpBox />
|
||||
{/if}
|
||||
{:else}
|
||||
<p>Not recognized arg type {arg.type}</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<p>Arg at {argName} is undefined</p>
|
||||
{/if}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { type Resource, ResourceService } from '$lib/gen'
|
||||
import { ResourceService, type Resource } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ResourceEditor from './ResourceEditor.svelte'
|
||||
@@ -27,7 +27,7 @@
|
||||
</script>
|
||||
|
||||
<ResourceEditor bind:this={resourceEditor} on:refresh={() => loadResources(resourceType)} />
|
||||
<select class="mt-1" bind:value placeholder="Pick a resource {resourceType}">
|
||||
<select bind:value placeholder="Pick a resource {resourceType}">
|
||||
<option value={undefined} />
|
||||
{#each resources as r}
|
||||
<option value={r.path}>{r.path}{r.description ? ' | ' + r.description : ''}</option>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import type { Script, Flow } from '$lib/gen'
|
||||
import type { Flow, Script } from '$lib/gen'
|
||||
import { decodeState, getToday } from '$lib/utils'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let runnable: Script | Flow | undefined
|
||||
export let runAction: (scheduledForStr: string | undefined, args: Record<string, any>) => void
|
||||
@@ -60,17 +60,16 @@
|
||||
{#if runnable?.schema}
|
||||
{#if detailed}
|
||||
<h2>
|
||||
Arguments <Tooltip
|
||||
>The optional fields, if left blank, will use the placeholder value as default.</Tooltip
|
||||
>
|
||||
Arguments
|
||||
<Tooltip>
|
||||
The optional fields, if left blank, will use the placeholder value as default.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
{/if}
|
||||
{#if !runnable.schema.properties || Object.keys(runnable.schema.properties).length === 0}
|
||||
<div class="text-sm p-4">No arguments</div>
|
||||
{:else}
|
||||
<div
|
||||
class="bg-gray-50 border border shadow-md shadow-blue-100 shadow-inner rounded border-gray-300 p-6"
|
||||
>
|
||||
<div class="bg-gray-50 border shadow-blue-100 shadow-inner rounded border-gray-300 p-6">
|
||||
<SchemaForm schema={runnable.schema} bind:isValid bind:args />
|
||||
</div>
|
||||
{/if}
|
||||
@@ -97,8 +96,10 @@
|
||||
class="default-button-secondary mx-2 mb-1"
|
||||
on:click={() => {
|
||||
scheduledForStr = undefined
|
||||
}}>clear</button
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,10 +114,8 @@
|
||||
>
|
||||
{#if schedulable}
|
||||
<div>
|
||||
schedule to run later <Icon
|
||||
data={viewOptions ? faChevronUp : faChevronDown}
|
||||
scale={0.5}
|
||||
/>
|
||||
Schedule to run later
|
||||
<Icon data={viewOptions ? faChevronUp : faChevronDown} scale={0.5} />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
import type { Schema, SchemaProperty } from '$lib/common'
|
||||
import { emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { faPen, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Badge, Button } from 'flowbite-svelte'
|
||||
import { Button } from 'flowbite-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Editor from './Editor.svelte'
|
||||
import SchemaEditorProperty from './SchemaEditorProperty.svelte'
|
||||
import type { ModalSchemaProperty } from './SchemaModal.svelte'
|
||||
import SchemaModal, { DEFAULT_PROPERTY, schemaToModal } from './SchemaModal.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
@@ -153,8 +154,7 @@
|
||||
<Toggle
|
||||
on:change={() => switchTab()}
|
||||
options={{
|
||||
left: { label: '', value: false },
|
||||
right: { label: 'Json Schema Editor', value: true }
|
||||
right: 'Json Schema Editor'
|
||||
}}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
@@ -185,11 +185,7 @@
|
||||
<tr>
|
||||
<td class="font-bold">{name}</td>
|
||||
<td>
|
||||
{#if !property.type}
|
||||
<Badge color="red">ANY</Badge>
|
||||
{:else}
|
||||
<Badge color="blue">{property.type.toLocaleUpperCase()}</Badge>
|
||||
{/if}
|
||||
<SchemaEditorProperty {property} />
|
||||
</td>
|
||||
<td>{property.description}</td>
|
||||
<td>{JSON.stringify(property.default) ?? ''}</td>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
import { Badge } from 'flowbite-svelte'
|
||||
|
||||
export let property: SchemaProperty
|
||||
</script>
|
||||
|
||||
<Badge color="blue">{property.type?.toUpperCase() ?? 'ANY'}</Badge>
|
||||
{#if property.format}
|
||||
<Badge color="green">{property.format?.toUpperCase()}</Badge>
|
||||
{/if}
|
||||
{#if property.contentEncoding}
|
||||
<Badge color="indigo">{property.contentEncoding?.toUpperCase()}</Badge>
|
||||
{/if}
|
||||
@@ -1,206 +1,36 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { InputTransform } from '$lib/gen'
|
||||
import { logout } from '$lib/logout'
|
||||
import { allTrue, type InputCat } from '$lib/utils'
|
||||
import { allTrue } from '$lib/utils'
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
import Editor from './Editor.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import DynamicInputHelpBox from './flows/DynamicInputHelpBox.svelte'
|
||||
import { codeToStaticTemplate } from './flows/flowStore'
|
||||
import { getCodeInjectionExpr, getDefaultExpr, isCodeInjection } from './flows/utils'
|
||||
import OverlayPropertyPicker from './propertyPicker/OverlayPropertyPicker.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import InputTransformForm from './InputTransformForm.svelte'
|
||||
|
||||
export let inputTransform = false
|
||||
export let schema: Schema
|
||||
export let args: Record<string, InputTransform | any> = {}
|
||||
export let editableSchema = false
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let isValid: boolean = true
|
||||
|
||||
export let i: number | undefined = undefined
|
||||
export let pickableProperties: Object | undefined = undefined
|
||||
export let extraLib: string = 'missing extraLib'
|
||||
export let i: number | undefined = undefined
|
||||
|
||||
let inputCheck: { [id: string]: boolean } = {}
|
||||
let overlays: { [id: string]: OverlayPropertyPicker } = {}
|
||||
let monacos: { [id: string]: Editor } = {}
|
||||
|
||||
$: isValid = allTrue(inputCheck) ?? false
|
||||
|
||||
let propertiesTypes: { [id: string]: 'javascript' | 'static' } = inputTransform
|
||||
? Object.keys(args).reduce((acc, key) => {
|
||||
let type = args[key].type
|
||||
if (type == 'javascript') {
|
||||
if (codeToStaticTemplate(args[key].expr)) {
|
||||
type = 'static'
|
||||
}
|
||||
}
|
||||
acc[key] = type
|
||||
return acc
|
||||
}, {})
|
||||
: {}
|
||||
|
||||
let inputCats: { [id: string]: InputCat } = {}
|
||||
|
||||
function setPropertyType(id: string, rawValue: string, isRaw: boolean) {
|
||||
const arg = args[id]
|
||||
if (!arg) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isCodeInjection(rawValue)) {
|
||||
args[id].expr = getCodeInjectionExpr(rawValue, isRaw)
|
||||
args[id].type = 'javascript'
|
||||
propertiesTypes[id] = 'static'
|
||||
} else {
|
||||
if (args[id].type === 'javascript' && propertiesTypes[id] === 'static') {
|
||||
args[id].type = 'static'
|
||||
if (inputCats[id] == 'number') {
|
||||
args[id].value = Number(args[id].value)
|
||||
}
|
||||
}
|
||||
if (arg.type) {
|
||||
propertiesTypes[id] = arg.type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isStaticTemplate(inputCat: InputCat) {
|
||||
return inputCat === 'string' || inputCat === 'number' || inputCat === 'sql'
|
||||
}
|
||||
|
||||
function focusProp(argName: string) {
|
||||
Object.keys(overlays).forEach((k) => {
|
||||
if (k == argName) {
|
||||
overlays[k].focus()
|
||||
} else {
|
||||
overlays[k].unfocus()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
{#if Object.keys(schema?.properties ?? {}).length > 0}
|
||||
{#each Object.keys(schema?.properties ?? {}) as argName, index}
|
||||
{#each Object.keys(schema?.properties ?? {}) as argName}
|
||||
{#if inputTransform}
|
||||
{#if args[argName] != undefined}
|
||||
<div class={index > 0 ? 'mt-8' : ''} />
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center">
|
||||
<FieldHeader
|
||||
label={argName}
|
||||
format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
required={schema.required.includes(argName)}
|
||||
type={schema.properties[argName].type}
|
||||
itemsType={schema.properties[argName].items}
|
||||
/>
|
||||
{#if propertiesTypes[argName] === 'static' && args[argName].type === 'javascript'}
|
||||
<span
|
||||
class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 py-0.5 rounded ml-2"
|
||||
>
|
||||
{'${...}'}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<Toggle
|
||||
options={{
|
||||
left: { label: '', value: 'static' },
|
||||
right: { label: 'Raw Javascript Editor', value: 'javascript' }
|
||||
}}
|
||||
bind:value={propertiesTypes[argName]}
|
||||
on:change={(e) => {
|
||||
const staticTemplate = isStaticTemplate(inputCats[argName])
|
||||
if (e.detail === 'javascript') {
|
||||
args[argName].expr = getDefaultExpr(
|
||||
i ?? -1,
|
||||
argName,
|
||||
staticTemplate ? args[argName].value : undefined
|
||||
)
|
||||
args[argName].value = undefined
|
||||
} else {
|
||||
args[argName].value = staticTemplate
|
||||
? codeToStaticTemplate(args[argName].expr)
|
||||
: undefined
|
||||
args[argName].expr = undefined
|
||||
}
|
||||
|
||||
args[argName].type = e.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="max-w-xs" />
|
||||
|
||||
{#if propertiesTypes[argName] === undefined || propertiesTypes[argName] === 'static'}
|
||||
<OverlayPropertyPicker
|
||||
bind:this={overlays[argName]}
|
||||
{pickableProperties}
|
||||
disabled={!isStaticTemplate(inputCats[argName])}
|
||||
on:select={(event) => {
|
||||
const toAppend = `\$\{${event.detail}}`
|
||||
args[argName].value = `${args[argName].value ?? ''}${toAppend}`
|
||||
if (monacos[argName]) {
|
||||
monacos[argName].setCode(args[argName].value)
|
||||
}
|
||||
setPropertyType(argName, args[argName].value, false)
|
||||
}}
|
||||
>
|
||||
<ArgInput
|
||||
on:focus={() => focusProp(argName)}
|
||||
label={argName}
|
||||
bind:editor={monacos[argName]}
|
||||
bind:description={schema.properties[argName].description}
|
||||
bind:value={args[argName].value}
|
||||
type={schema.properties[argName].type}
|
||||
required={schema.required.includes(argName)}
|
||||
bind:pattern={schema.properties[argName].pattern}
|
||||
bind:valid={inputCheck[argName]}
|
||||
defaultValue={schema.properties[argName].default}
|
||||
bind:enum_={schema.properties[argName].enum}
|
||||
bind:format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
bind:itemsType={schema.properties[argName].items}
|
||||
displayHeader={false}
|
||||
bind:inputCat={inputCats[argName]}
|
||||
numberAsString={true}
|
||||
on:input={(e) => {
|
||||
if (isStaticTemplate(inputCats[argName])) {
|
||||
setPropertyType(argName, e.detail.rawValue, e.detail.isRaw)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</OverlayPropertyPicker>
|
||||
{:else if propertiesTypes[argName] === 'javascript'}
|
||||
{#if args[argName].expr != undefined}
|
||||
<OverlayPropertyPicker
|
||||
bind:this={overlays[argName]}
|
||||
{pickableProperties}
|
||||
on:select={(event) => {
|
||||
monacos[argName].insertAtCursor(event.detail)
|
||||
}}
|
||||
>
|
||||
<div class="border rounded p-2 mt-2 border-gray-300">
|
||||
<Editor
|
||||
bind:this={monacos[argName]}
|
||||
on:focus={() => focusProp(argName)}
|
||||
bind:code={args[argName].expr}
|
||||
lang="javascript"
|
||||
class="few-lines-editor"
|
||||
{extraLib}
|
||||
extraLibPath="file:///node_modules/@types/windmill@{i}/index.d.ts"
|
||||
/>
|
||||
</div>
|
||||
</OverlayPropertyPicker>
|
||||
<DynamicInputHelpBox />
|
||||
{/if}
|
||||
{:else}
|
||||
<p>Not recognized arg type {args[argName].type}</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<p>Arg at {argName} is undefined</p>
|
||||
{/if}
|
||||
<InputTransformForm
|
||||
bind:arg={args[argName]}
|
||||
bind:schema
|
||||
bind:argName
|
||||
bind:inputCheck
|
||||
bind:pickableProperties
|
||||
bind:extraLib
|
||||
bind:i
|
||||
/>
|
||||
{:else}
|
||||
<ArgInput
|
||||
label={argName}
|
||||
|
||||
@@ -433,9 +433,9 @@
|
||||
<tbody slot="body">
|
||||
{#each pastPreviews as { id, created_at, success, result }}
|
||||
<tr class="">
|
||||
<td class="text-xs"
|
||||
><a class="pr-3" href="/run/{id}" target="_blank">{id.substring(30)}</a></td
|
||||
>
|
||||
<td class="text-xs">
|
||||
<a class="pr-3" href="/run/{id}" target="_blank">{id.substring(30)}</a>
|
||||
</td>
|
||||
<td class="text-xs">{displayDate(created_at)}</td>
|
||||
<td class="text-xs">
|
||||
{#if success}
|
||||
@@ -452,9 +452,10 @@
|
||||
modalViewerContent = result
|
||||
modalViewerMode = 'result'
|
||||
modalViewer.openModal()
|
||||
}}>{JSON.stringify(result).substring(0, 30)}...</a
|
||||
></td
|
||||
>
|
||||
}}
|
||||
>{JSON.stringify(result).substring(0, 30)}...
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-xs"
|
||||
><a
|
||||
href="#code"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</script>
|
||||
|
||||
<div class="mt-8 flex flex-col {$$props.class}">
|
||||
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
|
||||
<div class="inline-block min-w-full align-middle">
|
||||
<table class="min-w-full divide-y divide-gray-300 table-auto">
|
||||
<thead>
|
||||
<tr class={twTextSize}>
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
export let value: any = undefined
|
||||
export let options:
|
||||
| {
|
||||
left: { label?: string; value: any }
|
||||
right: { label?: string; value: any }
|
||||
}
|
||||
| undefined = undefined
|
||||
export let checked = options?.right.value === value
|
||||
|
||||
export let options: {
|
||||
left?: string
|
||||
right?: string
|
||||
} = {}
|
||||
export let checked: boolean = false
|
||||
|
||||
const id = (Math.random() + 1).toString(36).substring(10)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<span>
|
||||
<label for={id} class="inline-flex items-center cursor-pointer mt-2">
|
||||
{#if Boolean(options?.left.label)}
|
||||
<span class="mr-2 text-sm font-medium text-gray-900">{options?.left.label}</span>
|
||||
{#if Boolean(options?.left)}
|
||||
<span class="mr-2 text-sm font-medium text-gray-900">{options?.left}</span>
|
||||
{/if}
|
||||
<div class="relative">
|
||||
<input
|
||||
@@ -27,16 +24,15 @@
|
||||
class="sr-only peer"
|
||||
bind:checked
|
||||
on:change={() => {
|
||||
value = options ? (checked ? options?.right.value : options?.left.value) : checked
|
||||
dispatch('change', value ?? checked)
|
||||
dispatch('change', checked)
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class="w-11 h-6 bg-gray-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-blue-300 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"
|
||||
/>
|
||||
</div>
|
||||
{#if Boolean(options?.right.label)}
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">{options?.right.label}</span>
|
||||
{#if Boolean(options?.right)}
|
||||
<span class="ml-2 text-sm font-medium text-gray-900">{options?.right}</span>
|
||||
{/if}
|
||||
</label>
|
||||
</span>
|
||||
|
||||
@@ -165,8 +165,8 @@
|
||||
<Toggle
|
||||
bind:checked={scheduleEnabled}
|
||||
options={{
|
||||
left: { label: 'disabled', value: false },
|
||||
right: { label: 'enabled', value: true }
|
||||
left: 'disabled',
|
||||
right: 'enabled'
|
||||
}}
|
||||
/>
|
||||
<div class="p-2 mt-2 rounded" class:bg-gray-300={!scheduleEnabled}>
|
||||
|
||||
@@ -55,6 +55,7 @@ export function codeToStaticTemplate(code?: string): string | undefined {
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function flattenForloopFlows(flow: Flow): Flow {
|
||||
let newFlow: Flow = JSON.parse(JSON.stringify(flow))
|
||||
const mod = newFlow.value.modules[1]?.value
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { FlowModuleValue, InputTransform, Flow, FlowModule, RawScript } from '$lib/gen'
|
||||
import type { Flow, FlowModule, FlowModuleValue, InputTransform, RawScript } from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { loadSchema } from '$lib/scripts'
|
||||
import { emptySchema, getScriptByPath, schemaToObject } from '$lib/utils'
|
||||
@@ -22,7 +22,6 @@ export function flowToMode(flow: Flow | any, mode: FlowMode): Flow {
|
||||
})
|
||||
|
||||
if (mode == 'pull') {
|
||||
|
||||
const triggerModule = newFlow.value.modules[0]
|
||||
const oldModules = newFlow.value.modules.slice(1)
|
||||
|
||||
@@ -49,7 +48,6 @@ export function flowToMode(flow: Flow | any, mode: FlowMode): Flow {
|
||||
return newFlow
|
||||
}
|
||||
|
||||
|
||||
export function getTypeAsString(arg: any): string {
|
||||
if (arg === null) {
|
||||
return 'null'
|
||||
@@ -60,7 +58,6 @@ export function getTypeAsString(arg: any): string {
|
||||
return typeof arg
|
||||
}
|
||||
|
||||
|
||||
export async function getFirstStepSchema(flow: Flow): Promise<Schema> {
|
||||
const [firstModule] = flow.value.modules
|
||||
if (firstModule.value.type === 'rawscript') {
|
||||
@@ -116,8 +113,7 @@ export async function loadSchemaFromModule(module: FlowModule): Promise<{
|
||||
let input_transform = module.input_transform
|
||||
|
||||
if (
|
||||
JSON.stringify(keys.sort()) !==
|
||||
JSON.stringify(Object.keys(module.input_transform).sort())
|
||||
JSON.stringify(keys.sort()) !== JSON.stringify(Object.keys(module.input_transform).sort())
|
||||
) {
|
||||
input_transform = keys.reduce((accu, key) => {
|
||||
let nv = module.input_transform[key] ?? {
|
||||
@@ -128,6 +124,7 @@ export async function loadSchemaFromModule(module: FlowModule): Promise<{
|
||||
return accu
|
||||
}, {})
|
||||
}
|
||||
|
||||
return {
|
||||
input_transform: input_transform,
|
||||
schema: schema ?? emptySchema()
|
||||
@@ -162,11 +159,12 @@ export function getCodeInjectionExpr(code: string, isRaw: boolean): string {
|
||||
expr = `JSON.parse(${expr})`
|
||||
}
|
||||
return `import { previous_result, flow_input, step, variable, resource, params } from 'windmill'
|
||||
|
||||
${expr}`
|
||||
}
|
||||
|
||||
export function getDefaultExpr(i: number, key: string = 'myfield', previousExpr?: string) {
|
||||
const expr = previousExpr ? `\`${previousExpr}\`` : `previous_result.${key}`
|
||||
const expr = previousExpr ?? `previous_result.${key}`
|
||||
return `import { previous_result, flow_input, step, variable, resource, params } from 'windmill@${i}'
|
||||
|
||||
${expr}`
|
||||
@@ -182,16 +180,18 @@ export function getPickableProperties(
|
||||
const flowInputAsObject = schemaToObject(schema, args)
|
||||
const flowInput =
|
||||
mode === 'pull' && i >= 1
|
||||
? Object.assign(Object.assign(
|
||||
{
|
||||
_value: 'The current value of the iteration as an object',
|
||||
_index: 'The current index of the iteration as a number'
|
||||
},
|
||||
flowInputAsObject), previewResults[0])
|
||||
|
||||
? Object.assign(
|
||||
Object.assign(
|
||||
{
|
||||
_value: 'The current value of the iteration as an object',
|
||||
_index: 'The current index of the iteration as a number'
|
||||
},
|
||||
flowInputAsObject
|
||||
),
|
||||
previewResults[0]
|
||||
)
|
||||
: flowInputAsObject
|
||||
|
||||
|
||||
let previous_result
|
||||
if (i === 0 || (i == 1 && mode == 'pull')) {
|
||||
previous_result = flowInput
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createPopperActions } from 'svelte-popperjs'
|
||||
import PropPicker from './PropPicker.svelte'
|
||||
|
||||
const [popperRef, popperContent] = createPopperActions({
|
||||
placement: 'bottom-end',
|
||||
strategy: 'fixed'
|
||||
})
|
||||
|
||||
export let pickableProperties: Object | undefined
|
||||
export let disabled = false
|
||||
let isOpen = false
|
||||
|
||||
function toggle() {
|
||||
isOpen = !isOpen
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
{#if !disabled}
|
||||
<div class="w-full">
|
||||
<div use:popperRef on:click={toggle}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="content" use:popperContent>
|
||||
<PropPicker
|
||||
bind:pickableProperties
|
||||
on:select={(event) => {
|
||||
isOpen = false
|
||||
dispatch('select', event.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.content {
|
||||
@apply drop-shadow-xl;
|
||||
@apply w-full;
|
||||
@apply max-w-4xl;
|
||||
@apply px-6;
|
||||
@apply z-50;
|
||||
}
|
||||
</style>
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
{#if !disabled}
|
||||
<div class="w-full">
|
||||
<div use:popperRef on:mouseenter={open} on:mouseleave={close}>
|
||||
<div use:popperRef on:mouseleave={close}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Schema, SchemaProperty } from './common.js'
|
||||
import { ScriptService, type MainArgSignature } from '$lib/gen'
|
||||
import type { Schema, SchemaProperty } from './common.js'
|
||||
|
||||
export async function inferArgs(
|
||||
language: 'python3' | 'deno',
|
||||
@@ -38,7 +38,10 @@ export async function inferArgs(
|
||||
}
|
||||
}
|
||||
|
||||
function argSigToJsonSchemaType(t: string | { resource: string } | { list: string }, s: SchemaProperty): void {
|
||||
function argSigToJsonSchemaType(
|
||||
t: string | { resource: string } | { list: string },
|
||||
s: SchemaProperty
|
||||
): void {
|
||||
if (t === 'int') {
|
||||
s.type = 'integer'
|
||||
} else if (t === 'float') {
|
||||
|
||||
@@ -7,20 +7,21 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { canWrite } from '$lib/utils'
|
||||
import { GroupService } from '$lib/gen'
|
||||
import type { Group } from '$lib/gen'
|
||||
import { GroupService } from '$lib/gen'
|
||||
import { canWrite } from '$lib/utils'
|
||||
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import TableCustom from '$lib/components/TableCustom.svelte'
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import { faEdit, faPlus, faShare } from '@fortawesome/free-solid-svg-icons'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import GroupModal from '$lib/components/GroupModal.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import SharedBadge from '$lib/components/SharedBadge.svelte'
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
import TableCustom from '$lib/components/TableCustom.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { faEdit, faPlus, faShare } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Alert } from 'flowbite-svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
type GroupW = Group & { canWrite: boolean }
|
||||
|
||||
@@ -84,13 +85,14 @@
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div class="bg-blue-100 border-l-4 border-blue-600 text-blue-700 p-4 m-4" role="alert">
|
||||
<p class="font-bold">Groups are a team or enterprise feature - Unlimited during beta</p>
|
||||
<Alert accent rounded={false}>
|
||||
<p class="font-bold text-lg">Groups are a team or enterprise feature - Unlimited during beta</p>
|
||||
<p>
|
||||
Groups are a team or enterprise feature and the feature might be significantly different after
|
||||
beta in the community edition
|
||||
</p>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<div class="relative">
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { WorkerService, type WorkerPing } from '$lib/gen'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { displayDate, elapsedSinceSecs, groupBy, sendUserToast } from '$lib/utils'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import TableCustom from '$lib/components/TableCustom.svelte'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import { WorkerService, type WorkerPing } from '$lib/gen'
|
||||
import { displayDate, elapsedSinceSecs, groupBy, sendUserToast } from '$lib/utils'
|
||||
import { Badge } from 'flowbite-svelte'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
let workers: WorkerPing[] = []
|
||||
let filteredWorkers: WorkerPing[] = []
|
||||
@@ -53,9 +54,9 @@
|
||||
<p>No workers seems to be available</p>
|
||||
{/if}
|
||||
{#each groupedWorkers as [section, workers]}
|
||||
<h2 class="m-5">
|
||||
Instance: {section} | Ip:
|
||||
<span class="text-gray-500 bg-gray-100 font-mono">{workers[0].ip}</span>
|
||||
<h2 class="my-5">
|
||||
Instance: {section} | IP:
|
||||
<Badge large color="gray">{workers[0].ip}</Badge>
|
||||
</h2>
|
||||
|
||||
<TableCustom>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { chromium, FullConfig } from '@playwright/test'
|
||||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
console.log('start global setup!')
|
||||
const browser = await chromium.launch()
|
||||
const page = await browser.newPage()
|
||||
await page.goto('http://localhost:8000/user/login')
|
||||
await page.locator('text=login without third-party').click()
|
||||
await page.locator('#email').fill('admin@windmill.dev')
|
||||
await page.locator('input[type="password"]').fill('changeme')
|
||||
await page.locator('text="Login"').click()
|
||||
await page.waitForResponse('http://localhost:8000/api/auth/login')
|
||||
await page.context().storageState({ path: 'storageState.json' })
|
||||
await browser.close()
|
||||
console.log('end global setup!')
|
||||
console.log('start global setup!')
|
||||
const browser = await chromium.launch()
|
||||
const page = await browser.newPage()
|
||||
await page.goto('http://localhost:8000/user/login')
|
||||
|
||||
if (!(await page.locator('#email').isVisible())) {
|
||||
page.locator('text=login without third-party').click()
|
||||
}
|
||||
|
||||
await page.locator('#email').fill('admin@windmill.dev')
|
||||
await page.locator('input[type="password"]').fill('changeme')
|
||||
await page.locator('text="Login"').click()
|
||||
await page.waitForResponse('http://localhost:8000/api/auth/login')
|
||||
await page.context().storageState({ path: 'storageState.json' })
|
||||
await browser.close()
|
||||
console.log('end global setup!')
|
||||
}
|
||||
|
||||
export default globalSetup
|
||||
|
||||
Reference in New Issue
Block a user