mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 16:02:24 +00:00
@@ -300,6 +300,14 @@
|
||||
"types": "./package/utils.d.ts",
|
||||
"default": "./package/utils.js"
|
||||
},
|
||||
"./icons/store": {
|
||||
"types": "./package/icons/store.d.ts",
|
||||
"default": "./package/icons/store.js"
|
||||
},
|
||||
"./script_helpers": {
|
||||
"types": "./package/script_helpers.d.ts",
|
||||
"default": "./package/script_helpers.js"
|
||||
},
|
||||
"./infer": {
|
||||
"types": "./package/infer.d.ts",
|
||||
"default": "./package/infer.js"
|
||||
@@ -341,6 +349,11 @@
|
||||
"types": "./package/components/DropdownV2.svelte.d.ts",
|
||||
"svelte": "./package/components/DropdownV2.svelte",
|
||||
"default": "./package/components/DropdownV2.svelte"
|
||||
},
|
||||
"./components/flows/FlowHistoryInner.svelte": {
|
||||
"types": "./package/components/flows/FlowHistoryInner.svelte.d.ts",
|
||||
"svelte": "./package/components/flows/FlowHistoryInner.svelte",
|
||||
"default": "./package/components/flows/FlowHistoryInner.svelte"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
@@ -426,6 +439,9 @@
|
||||
"components/EditableSchemaWrapper.svelte": [
|
||||
"./package/components/schema/EditableSchemaWrapper.svelte.d.ts"
|
||||
],
|
||||
"components/flows/FlowHistoryInner.svelte": [
|
||||
"./package/components/flows/FlowHistoryInner.svelte.d.ts"
|
||||
],
|
||||
"utils": [
|
||||
"./package/utils.d.ts"
|
||||
],
|
||||
@@ -461,6 +477,12 @@
|
||||
],
|
||||
"components/DropdownV2.svelte": [
|
||||
"./package/components/DropdownV2.svelte.d.ts"
|
||||
],
|
||||
"script_helpers": [
|
||||
"./package/script_helpers.d.ts"
|
||||
],
|
||||
"icons/store": [
|
||||
"./package/icons/store.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
+6
-10
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import { base } from '$app/paths'
|
||||
|
||||
import AppConnectInner from '$lib/components/AppConnectInner.svelte'
|
||||
@@ -8,7 +7,10 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
let resourceType = $page.url.searchParams.get('resource_type') ?? undefined
|
||||
export let resourceType: string | undefined = undefined
|
||||
export let workspace: string
|
||||
export let express = false
|
||||
|
||||
let step = 1
|
||||
let disabled = false
|
||||
let isGoogleSignin = false
|
||||
@@ -17,8 +19,6 @@
|
||||
let appConnect: AppConnectInner | undefined = undefined
|
||||
|
||||
let darkMode: boolean = false
|
||||
const workspace = $page.url.searchParams.get('workspace')
|
||||
const express = $page.url.searchParams.get('express') == 'true'
|
||||
|
||||
if (workspace) {
|
||||
$workspaceStore = workspace
|
||||
@@ -68,11 +68,7 @@
|
||||
bind:isGoogleSignin
|
||||
bind:disabled
|
||||
bind:manual
|
||||
on:error={(e) => {
|
||||
window?.parent?.postMessage({ type: 'error', error: e.detail }, '*')
|
||||
}}
|
||||
on:refresh={(e) => {
|
||||
window?.parent?.postMessage({ type: 'refresh', detail: e.detail }, '*')
|
||||
}}
|
||||
on:error
|
||||
on:refresh
|
||||
/>
|
||||
</div>
|
||||
@@ -89,6 +89,7 @@
|
||||
export let editor: SimpleEditor | undefined = undefined
|
||||
export let orderEditable = false
|
||||
export let shouldDispatchChanges: boolean = false
|
||||
export let noDefaultOnSelectFirst: boolean = false
|
||||
export let helperScript:
|
||||
| { type: 'inline'; path?: string; lang: Script['language']; code: string }
|
||||
| { type: 'hash'; hash: string }
|
||||
@@ -537,11 +538,14 @@
|
||||
{:else if inputCat == 'resource-object' && (resourceTypes == undefined || (format.split('-').length > 1 && resourceTypes.includes(format.substring('resource-'.length))))}
|
||||
<ObjectResourceInput
|
||||
{defaultValue}
|
||||
selectFirst
|
||||
selectFirst={!noDefaultOnSelectFirst}
|
||||
{disablePortal}
|
||||
{format}
|
||||
bind:value
|
||||
bind:editor
|
||||
on:clear={() => {
|
||||
defaultValue = null
|
||||
}}
|
||||
{showSchemaExplorer}
|
||||
/>
|
||||
{:else if inputCat == 'resource-object' && format.split('-').length > 1 && format
|
||||
@@ -807,7 +811,7 @@
|
||||
</div>
|
||||
{:else if inputCat == 'resource-string'}
|
||||
<ResourcePicker
|
||||
selectFirst
|
||||
selectFirst={noDefaultOnSelectFirst}
|
||||
{disablePortal}
|
||||
bind:value
|
||||
initialValue={defaultValue}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
{#if showTooltip && !disablePopup}
|
||||
<Portal>
|
||||
<Portal name="custom-popover">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
use:popperContent={popperOptions}
|
||||
|
||||
@@ -854,7 +854,7 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Portal>
|
||||
<Portal name="s3filepicker">
|
||||
<S3FilePicker bind:this={s3FileViewer} readOnlyMode={true} />
|
||||
</Portal>
|
||||
{/if}
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
export let diffDrawer: DiffDrawer | undefined = undefined
|
||||
export let customUi: FlowBuilderWhitelabelCustomUi = {}
|
||||
export let disableAi: boolean = false
|
||||
export let disabledFlowInputs = false
|
||||
|
||||
$: setContext('customUi', customUi)
|
||||
|
||||
@@ -1326,6 +1327,7 @@
|
||||
<!-- metadata -->
|
||||
{#if $flowStateStore}
|
||||
<FlowEditor
|
||||
{disabledFlowInputs}
|
||||
disableAi={disableAi || customUi?.stepInputs?.ai == false}
|
||||
disableSettings={customUi?.settingsPanel === false}
|
||||
{loading}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
|
||||
export let schema: Schema | { [key: string]: unknown } | undefined
|
||||
</script>
|
||||
|
||||
<ul class="my-2">
|
||||
{#if schema == undefined}
|
||||
<li class="text-secondary text-xs italic mb-4">No inputs</li>
|
||||
{:else}
|
||||
{#each Object.entries(schema.properties ?? {}) as [inp, v]}
|
||||
<li class="list-disc flex flex-row items-center">
|
||||
<FieldHeader
|
||||
label={inp}
|
||||
required={Array.isArray(schema.required) && schema.required?.includes(inp)}
|
||||
type={v?.type}
|
||||
contentEncoding={v?.contentEncoding}
|
||||
format={v?.format}
|
||||
/><span class="ml-4 mt-1 text-xs"
|
||||
>{v?.default != undefined && v?.default != ''
|
||||
? 'default: ' + JSON.stringify(v?.default)
|
||||
: ''}</span
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
@@ -26,7 +26,7 @@
|
||||
{#if job['success'] != undefined}
|
||||
Received job: {displayDate(job.created_at ?? '')}
|
||||
{:else}
|
||||
Received job <TimeAgo withDate date={job.created_at ?? ''} />
|
||||
Received job <TimeAgo date={job.created_at ?? ''} />
|
||||
{/if}
|
||||
<Tooltip small>{job?.created_at}</Tooltip>
|
||||
</span>
|
||||
@@ -35,7 +35,7 @@
|
||||
<div class="flex flex-row gap-2 items-center text-sm">
|
||||
<Clock size={SMALL_ICON_SIZE} class="text-secondary min-w-3.5" />
|
||||
<span class="whitespace-nowrap">
|
||||
Started <TimeAgo withDate agoOnlyIfRecent date={job.started_at ?? ''} />
|
||||
Started <TimeAgo agoOnlyIfRecent date={job.started_at ?? ''} />
|
||||
<Tooltip small>{job?.started_at}</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
import type { FlowValue } from '$lib/gen'
|
||||
import { Tab, Tabs, TabContent } from './common'
|
||||
import SchemaViewer from './SchemaViewer.svelte'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import FlowGraphViewer from './FlowGraphViewer.svelte'
|
||||
|
||||
import HighlightTheme from './HighlightTheme.svelte'
|
||||
import FlowViewerInner from './FlowViewerInner.svelte'
|
||||
import FlowInputViewer from './FlowInputViewer.svelte'
|
||||
|
||||
export let flow: {
|
||||
summary: string
|
||||
@@ -27,10 +27,6 @@
|
||||
if (initialOpen) {
|
||||
open[initialOpen] = true
|
||||
}
|
||||
|
||||
function toAny(x: unknown): any {
|
||||
return x as any
|
||||
}
|
||||
</script>
|
||||
|
||||
<HighlightTheme />
|
||||
@@ -54,23 +50,7 @@
|
||||
<span>Flow Input</span>
|
||||
</p>
|
||||
{#if flow.schema && flow.schema.properties && Object.keys(flow.schema.properties).length > 0 && flow.schema}
|
||||
<ul class="my-2">
|
||||
{#each Object.entries(flow.schema.properties) as [inp, v]}
|
||||
<li class="list-disc flex flex-row">
|
||||
<FieldHeader
|
||||
label={inp}
|
||||
required={flow.schema.required?.includes(inp)}
|
||||
type={toAny(v)?.type}
|
||||
contentEncoding={toAny(v)?.contentEncoding}
|
||||
format={toAny(v)?.format}
|
||||
/><span class="ml-4 mt-2 text-xs"
|
||||
>{toAny(v)?.default != undefined
|
||||
? 'default: ' + JSON.stringify(toAny(v)?.default)
|
||||
: ''}</span
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<FlowInputViewer schema={flow.schema} />
|
||||
{:else}
|
||||
<div class="text-secondary text-xs italic mb-4">No inputs</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ResourceService } from '$lib/gen'
|
||||
import { base } from '$lib/base'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import Select from './apps/svelte-select/lib/index'
|
||||
@@ -8,7 +7,7 @@
|
||||
|
||||
import DarkModeObserver from './DarkModeObserver.svelte'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import { Plus } from 'lucide-svelte'
|
||||
import { Plus, Loader2 } from 'lucide-svelte'
|
||||
import type { AppViewerContext } from './apps/types'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
|
||||
@@ -65,23 +64,6 @@
|
||||
refreshCount += 1
|
||||
open = true
|
||||
}
|
||||
|
||||
function processEvent(event: MessageEvent) {
|
||||
if (event.origin !== window.location.origin) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.data.type === 'error') {
|
||||
sendUserToast(event.data.error, true)
|
||||
}
|
||||
if (event.data.type === 'refresh') {
|
||||
window.removeEventListener('message', processEvent)
|
||||
value = event.data.detail
|
||||
valueSelect = { value, label: value }
|
||||
drawer?.closeDrawer?.()
|
||||
open = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DarkModeObserver bind:darkMode />
|
||||
@@ -89,12 +71,15 @@
|
||||
{#if expressOAuthSetup}
|
||||
{#if open}
|
||||
{#key refreshCount}
|
||||
<iframe
|
||||
title="App connection"
|
||||
class="w-full h-full hidden"
|
||||
src="{base}/embed_connect?resource_type={resourceType}&workspace={appViewerContext?.workspace ??
|
||||
$workspaceStore}&express=true"
|
||||
/>
|
||||
{#await import('./AppConnectLightweightResourcePicker.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
workspace={appViewerContext?.workspace ?? $workspaceStore}
|
||||
{resourceType}
|
||||
express={true}
|
||||
/>
|
||||
{/await}
|
||||
{/key}
|
||||
{/if}
|
||||
{:else}
|
||||
@@ -105,12 +90,31 @@
|
||||
tooltip="Resources represent connections to third party systems. Learn more on how to integrate external APIs."
|
||||
documentationLink="https://www.windmill.dev/docs/integrations/integrations_on_windmill"
|
||||
>
|
||||
<iframe
|
||||
{#await import('./AppConnectLightweightResourcePicker.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
workspace={appViewerContext?.workspace ?? $workspaceStore}
|
||||
{resourceType}
|
||||
express={false}
|
||||
on:error={(e) => {
|
||||
sendUserToast(e.detail, true)
|
||||
}}
|
||||
on:refresh={(e) => {
|
||||
value = e.detail
|
||||
valueSelect = { value, label: value }
|
||||
drawer?.closeDrawer?.()
|
||||
open = false
|
||||
}}
|
||||
/>
|
||||
{/await}
|
||||
|
||||
<!-- <iframe
|
||||
title="App connection"
|
||||
class="w-full h-full"
|
||||
src="{base}/embed_connect?resource_type={resourceType}&workspace={appViewerContext?.workspace ??
|
||||
$workspaceStore}&express=false"
|
||||
/>
|
||||
/> -->
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
{/if}
|
||||
@@ -142,13 +146,11 @@
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
variant="contained"
|
||||
btnClasses="w-8 px-0.5 py-1.5"
|
||||
size="sm"
|
||||
on:click={() => {
|
||||
open = true
|
||||
window.removeEventListener('message', processEvent)
|
||||
window.addEventListener('message', processEvent)
|
||||
|
||||
drawer?.openDrawer?.()
|
||||
}}
|
||||
startIcon={{ icon: Plus }}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<slot />
|
||||
</fragment>
|
||||
{#if showTooltip}
|
||||
<Portal>
|
||||
<Portal name="manual-popover">
|
||||
<div
|
||||
use:popperContent
|
||||
class={twMerge('z-[901] rounded-lg shadow-md border p-4 bg-surface', $$props.class)}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
export let selectFirst = false
|
||||
export let defaultValue: any
|
||||
export let editor: SimpleEditor | undefined = undefined
|
||||
|
||||
function isString(value: any) {
|
||||
return typeof value === 'string' || value instanceof String
|
||||
}
|
||||
@@ -19,7 +18,7 @@
|
||||
export let path: string = ''
|
||||
|
||||
function resourceToValue() {
|
||||
if (path) {
|
||||
if (path && path != '') {
|
||||
value = `$res:${path}`
|
||||
} else {
|
||||
value = undefined
|
||||
@@ -50,8 +49,11 @@
|
||||
path = e.detail
|
||||
resourceToValue()
|
||||
}}
|
||||
on:clear
|
||||
bind:value={path}
|
||||
initialValue={defaultValue}
|
||||
initialValue={typeof defaultValue == 'string' && defaultValue.startsWith('$res:')
|
||||
? defaultValue.substr('$res:'.length)
|
||||
: defaultValue}
|
||||
resourceType={format.split('-').length > 1 ? format.substring('resource-'.length) : undefined}
|
||||
{showSchemaExplorer}
|
||||
/>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
{#if showTooltip && !disablePopup}
|
||||
<Portal>
|
||||
<Portal name="popover">
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
use:popperContent={popperOptions}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
<script context="module">
|
||||
import { tick } from 'svelte'
|
||||
|
||||
/**
|
||||
* Usage: <div use:portal={'css selector'}> or <div use:portal={document.body}>
|
||||
*
|
||||
* @param {HTMLElement} el
|
||||
* @param {HTMLElement|string} target DOM Element or CSS Selector
|
||||
*/
|
||||
export function portal(el, target = 'body') {
|
||||
export function portal(el, options) {
|
||||
let { target, name } = options
|
||||
let targetEl
|
||||
async function update(newTarget) {
|
||||
target = newTarget
|
||||
@@ -32,6 +27,9 @@
|
||||
if (!el.classList.contains('windmill-app')) {
|
||||
el.classList.add('windmill-app')
|
||||
}
|
||||
if (name && !el.classList.contains(name)) {
|
||||
el.classList.add(name)
|
||||
}
|
||||
targetEl.appendChild(el)
|
||||
el.hidden = false
|
||||
}
|
||||
@@ -56,8 +54,9 @@
|
||||
* @type { HTMLElement|string}
|
||||
*/
|
||||
export let target = 'body'
|
||||
export let name = undefined
|
||||
</script>
|
||||
|
||||
<div use:portal={target} hidden>
|
||||
<div use:portal={{ target, name }} hidden>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
sendUserToast(`Updated resource at ${path}`)
|
||||
dispatch('refresh', path)
|
||||
} else {
|
||||
throw Error('Cannot edit undefined resourceToEdit')
|
||||
throw Error('Cannot edit undefined resource')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,11 +231,10 @@
|
||||
<Skeleton layout={[[4]]} />
|
||||
{:else if !viewJsonSchema && resourceSchema && resourceSchema?.properties}
|
||||
{#if resourceTypeInfo?.format_extension}
|
||||
<h5 class="mt-4 inline-flex items-center gap-4 pb-2">
|
||||
File content ({resourceTypeInfo.format_extension})
|
||||
</h5>
|
||||
<h5 class="mt-4 inline-flex items-center gap-4 pb-2">
|
||||
File content ({resourceTypeInfo.format_extension})
|
||||
</h5>
|
||||
<div class="h-full w-full border p-1 rounded">
|
||||
|
||||
<SimpleEditor
|
||||
autoHeight
|
||||
class="editor"
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
/>
|
||||
|
||||
<div class="flex flex-col w-full items-start">
|
||||
<div class="flex flex-row gap-x-1 w-full items-center">
|
||||
<div class="flex flex-row w-full items-center">
|
||||
{#if collection?.length > 0}
|
||||
<Select
|
||||
{disabled}
|
||||
@@ -128,9 +128,11 @@
|
||||
valueSelect = e.detail
|
||||
}}
|
||||
on:clear={() => {
|
||||
initialValue = undefined
|
||||
value = undefined
|
||||
valueType = undefined
|
||||
valueSelect = undefined
|
||||
dispatch('clear')
|
||||
}}
|
||||
items={collection}
|
||||
class="text-clip grow min-w-0"
|
||||
@@ -143,14 +145,15 @@
|
||||
{:else if !loading}
|
||||
<div class="text-2xs text-tertiary mr-2">0 found</div>
|
||||
{/if}
|
||||
|
||||
{#if !loading}
|
||||
<div class="mx-0.5" />
|
||||
{#if value && value != ''}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
variant="contained"
|
||||
size="sm"
|
||||
btnClasses="w-8 px-0.5 py-1.5"
|
||||
on:click={() => resourceEditor?.initEdit?.(value ?? '')}
|
||||
startIcon={{ icon: Pen }}
|
||||
iconOnly
|
||||
@@ -162,8 +165,9 @@
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
variant="contained"
|
||||
size="sm"
|
||||
btnClasses="w-8 px-0.5 py-1.5"
|
||||
on:click={() => appConnect?.open?.(rt)}
|
||||
startIcon={{ icon: Plus }}>{rt}</Button
|
||||
>
|
||||
@@ -172,8 +176,9 @@
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
variant="contained"
|
||||
size="sm"
|
||||
btnClasses="w-8 px-0.5 py-1.5"
|
||||
on:click={() => appConnect?.open?.(resourceType, expressOAuthSetup)}
|
||||
startIcon={{ icon: Plus }}
|
||||
iconOnly={collection?.length > 0}
|
||||
@@ -185,9 +190,10 @@
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
variant="border"
|
||||
variant="contained"
|
||||
color="light"
|
||||
size="xs"
|
||||
btnClasses="w-8 px-0.5 py-1.5"
|
||||
size="sm"
|
||||
on:click={() => {
|
||||
loadResources(resourceType)
|
||||
}}
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-tertiary">
|
||||
{#if runnable}
|
||||
Edited <TimeAgo withDate agoOnlyIfRecent date={runnable.created_at || ''} /> by {runnable.created_by ||
|
||||
Edited <TimeAgo agoOnlyIfRecent date={runnable.created_at || ''} /> by {runnable.created_by ||
|
||||
'unknown'}
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
@@ -364,7 +364,7 @@
|
||||
<div
|
||||
class="whitespace-nowrap col-span-3 !text-tertiary !text-2xs overflow-hidden text-ellipsis flex-shrink text-center"
|
||||
>
|
||||
<TimeAgo date={i.started_at ?? ''} />
|
||||
<TimeAgo noDate date={i.started_at ?? ''} />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<a
|
||||
@@ -425,7 +425,7 @@
|
||||
<div
|
||||
class="whitespace-nowrap col-span-2 !text-tertiary !text-2xs overflow-hidden text-ellipsis flex-shrink text-center"
|
||||
>
|
||||
<TimeAgo date={i.created_at ?? ''} />
|
||||
<TimeAgo noDate date={i.created_at ?? ''} />
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<a
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import PanelSection from './apps/editor/settingsPanel/common/PanelSection.svelte'
|
||||
import { classNames, emptyString } from '$lib/utils'
|
||||
import { ScriptService, type ScriptHistory } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
@@ -63,70 +62,66 @@
|
||||
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane size={20}>
|
||||
<PanelSection title="Past Versions">
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if !loading}
|
||||
{#if versions && versions.length > 0}
|
||||
<div class="flex gap-2 flex-col">
|
||||
{#each versions ?? [] as version, versionIndex}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class={classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer ',
|
||||
selectedVersion?.script_hash == version.script_hash
|
||||
? 'bg-surface-selected'
|
||||
: '',
|
||||
'hover:bg-surface-hover'
|
||||
)}
|
||||
on:click={() => {
|
||||
selectedVersion = version
|
||||
selectedVersionIndex = versionIndex
|
||||
<div class="flex flex-col gap-2 px-2 pt-2 w-full">
|
||||
{#if !loading}
|
||||
{#if versions && versions.length > 0}
|
||||
<div class="flex gap-2 flex-col">
|
||||
{#each versions ?? [] as version, versionIndex}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class={classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer ',
|
||||
selectedVersion?.script_hash == version.script_hash ? 'bg-surface-selected' : '',
|
||||
'hover:bg-surface-hover'
|
||||
)}
|
||||
on:click={() => {
|
||||
selectedVersion = version
|
||||
selectedVersionIndex = versionIndex
|
||||
|
||||
if (showDiff && versions && selectedVersionIndex === versions.length - 1) {
|
||||
showDiff = false
|
||||
}
|
||||
if (showDiff && versions && selectedVersionIndex === versions.length - 1) {
|
||||
showDiff = false
|
||||
}
|
||||
|
||||
const availableVersions = versions?.slice(selectedVersionIndex + 1)
|
||||
const availableVersions = versions?.slice(selectedVersionIndex + 1)
|
||||
|
||||
if (
|
||||
previousHash &&
|
||||
!availableVersions?.find((v) => v.script_hash === previousHash)
|
||||
) {
|
||||
previousHash = availableVersions?.[0]?.script_hash
|
||||
}
|
||||
if (
|
||||
previousHash &&
|
||||
!availableVersions?.find((v) => v.script_hash === previousHash)
|
||||
) {
|
||||
previousHash = availableVersions?.[0]?.script_hash
|
||||
}
|
||||
|
||||
deploymentMsgUpdate = undefined
|
||||
deploymentMsgUpdateMode = false
|
||||
}}
|
||||
>
|
||||
<span class="text-xs truncate">
|
||||
{#if emptyString(version.deployment_msg)}Version {version.script_hash}{:else}{version.deployment_msg}{/if}
|
||||
</span>
|
||||
{#if openDetails}
|
||||
<Button
|
||||
on:click={() => {
|
||||
dispatch('openDetails', { version: version.script_hash })
|
||||
}}
|
||||
class="ml-2 inline-flex gap-1 text-xs items-center"
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
>
|
||||
Run page<ExternalLink size={14} />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-tertiary">No items</div>
|
||||
{/if}
|
||||
deploymentMsgUpdate = undefined
|
||||
deploymentMsgUpdateMode = false
|
||||
}}
|
||||
>
|
||||
<span class="text-xs truncate">
|
||||
{#if emptyString(version.deployment_msg)}Version {version.script_hash}{:else}{version.deployment_msg}{/if}
|
||||
</span>
|
||||
{#if openDetails}
|
||||
<Button
|
||||
on:click={() => {
|
||||
dispatch('openDetails', { version: version.script_hash })
|
||||
}}
|
||||
class="ml-2 inline-flex gap-1 text-xs items-center"
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
>
|
||||
Run page<ExternalLink size={14} />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<Skeleton layout={[[40], [40], [40], [40], [40]]} />
|
||||
<div class="text-sm text-tertiary">No items</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
{:else}
|
||||
<Skeleton layout={[[40], [40], [40], [40], [40]]} />
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={80}>
|
||||
<div class="h-full w-full overflow-auto">
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
|
||||
export let date: string
|
||||
export let withDate: boolean = false
|
||||
export let agoOnlyIfRecent: boolean = false
|
||||
export let noDate = false
|
||||
|
||||
let computedTimeAgo: string | undefined = undefined
|
||||
|
||||
@@ -63,21 +63,20 @@
|
||||
let dAgo = daysAgo(date)
|
||||
if (dAgo == 0) {
|
||||
return `yesterday at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
} else if (dAgo > 7 && withDate) {
|
||||
} else if (dAgo > 7 && !noDate) {
|
||||
return `${dAgo + 1} days ago at ${date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}`
|
||||
} else {
|
||||
return !withDate ? displayDate(dateString, false, withDate) : ''
|
||||
return displayDate(dateString, false, !noDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if withDate}
|
||||
{displayDate(date)}
|
||||
{/if}
|
||||
{#if computedTimeAgo && (!agoOnlyIfRecent || isRecent)}
|
||||
{computedTimeAgo}
|
||||
{:else}
|
||||
{displayDate(date)}
|
||||
{/if}
|
||||
|
||||
@@ -244,7 +244,7 @@
|
||||
</RunnableWrapper>
|
||||
|
||||
{#if resolvedConfig?.confirmationModal?.selected === 'confirmationModal'}
|
||||
<Portal target="#app-editor-top-level-drawer">
|
||||
<Portal name="app-button" target="#app-editor-top-level-drawer">
|
||||
<ConfirmationModal
|
||||
open={Boolean(confirmedCallback)}
|
||||
title={resolvedConfig?.confirmationModal?.configuration?.confirmationModal?.title ?? ''}
|
||||
|
||||
@@ -695,7 +695,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
<Portal>
|
||||
<Portal name="db-explorer">
|
||||
<Drawer bind:this={insertDrawer} size="800px">
|
||||
<DrawerContent title="Insert row" on:close={insertDrawer.closeDrawer}>
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
{option}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
<Portal>
|
||||
<Portal name="app-multiselect">
|
||||
<div use:floatingContent class="z5000" hidden={!open}>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
{option}
|
||||
</div>
|
||||
</MultiSelect>
|
||||
<Portal>
|
||||
<Portal name="app-multiselect-v2">
|
||||
<div use:floatingContent class="z5000" hidden={!open}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
</AlignWrapper>
|
||||
</div>
|
||||
|
||||
<Portal target="#app-editor-top-level-drawer">
|
||||
<Portal target="#app-editor-top-level-drawer" name="app-drawer">
|
||||
<Drawer
|
||||
let:open
|
||||
bind:this={appDrawer}
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
</AlignWrapper>
|
||||
</div>
|
||||
{/if}
|
||||
<Portal target="#app-editor-top-level-drawer">
|
||||
<Portal target="#app-editor-top-level-drawer" name="app-modal">
|
||||
<Disposable
|
||||
{id}
|
||||
let:handleClickAway
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
<slot />
|
||||
|
||||
{#if contextMenuVisible}
|
||||
<Portal>
|
||||
<Portal name="grid-editor">
|
||||
<div style="position: fixed; top: {menuY}px; left: {menuX}px; z-index:6000;">
|
||||
<div class="rounded-md bg-surface border shadow-md divide-y w-64">
|
||||
<div class="p-1" use:clickOutside={false}>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</script>
|
||||
|
||||
{#if condition}
|
||||
<Portal {target}><slot /></Portal>
|
||||
<Portal name="conditional-portal-select" {target}><slot /></Portal>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</script>
|
||||
|
||||
{#if condition}
|
||||
<Portal><slot /></Portal>
|
||||
<Portal name="conditional-portal-global"><slot /></Portal>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</MenuButton>
|
||||
</span>
|
||||
|
||||
<Portal {target}>
|
||||
<Portal name="button-dropdown" {target}>
|
||||
<div use:popperContent={popperOptions} class="z-[6000]">
|
||||
<Transition
|
||||
show={open}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
{#if condition}
|
||||
<Portal {target}><slot /></Portal>
|
||||
<Portal name="conditional-portal" {target}><slot /></Portal>
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<slot name="trigger" />
|
||||
</MenuButton>
|
||||
</div>
|
||||
<Portal>
|
||||
<Portal name="menu-v2">
|
||||
<div use:floatingContent class="z-[6000]">
|
||||
<Transition
|
||||
{open}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Portal target="#app-editor-top-level-drawer">
|
||||
<Portal name="always-mounted" target="#app-editor-top-level-drawer">
|
||||
<div
|
||||
class={twMerge(
|
||||
`${
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<slot {pointerup} {pointerdown} name="button" />
|
||||
</div>
|
||||
|
||||
<Portal {target}>
|
||||
<Portal name="popup-v2" {target}>
|
||||
{#if open}
|
||||
<div
|
||||
class="border rounded-lg shadow-lg bg-surface z5000"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</MenuButton>
|
||||
</span>
|
||||
|
||||
<Portal>
|
||||
<Portal name="menu">
|
||||
<div use:popperContent={popperOptions} class="z-[2000]">
|
||||
<Transition
|
||||
show={open}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
export let disableTutorials = false
|
||||
export let disableAi = false
|
||||
export let disableSettings = false
|
||||
export let disabledFlowInputs = false
|
||||
export let smallErrorHandler = false
|
||||
|
||||
let size = 50
|
||||
@@ -61,7 +62,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<FlowEditorPanel enableAi={!disableAi} />
|
||||
<FlowEditorPanel {disabledFlowInputs} enableAi={!disableAi} />
|
||||
{/if}
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
|
||||
@@ -1,88 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import PanelSection from '../apps/editor/settingsPanel/common/PanelSection.svelte'
|
||||
import { classNames, displayDate, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { type Flow, FlowService, type FlowVersion } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { Skeleton } from '$lib/components/common'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Drawer from '../common/drawer/Drawer.svelte'
|
||||
import DrawerContent from '../common/drawer/DrawerContent.svelte'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { ArrowRight, Loader2, Pencil, X } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import FlowHistoryInner from './FlowHistoryInner.svelte'
|
||||
|
||||
export let path: string
|
||||
let drawer: Drawer
|
||||
let loading: boolean = false
|
||||
|
||||
let versions: FlowVersion[] = []
|
||||
|
||||
let selectedVersion: FlowVersion | undefined = undefined
|
||||
let selected: Flow | undefined = undefined
|
||||
let deploymentMsgUpdateMode = false
|
||||
let deploymentMsgUpdate: string | undefined = undefined
|
||||
|
||||
export function open() {
|
||||
loadVersions()
|
||||
drawer.openDrawer()
|
||||
}
|
||||
|
||||
async function loadFlow(version: number) {
|
||||
selected = await FlowService.getFlowVersion({
|
||||
workspace: $workspaceStore!,
|
||||
version,
|
||||
path
|
||||
})
|
||||
}
|
||||
|
||||
async function loadVersions() {
|
||||
loading = true
|
||||
versions = await FlowService.getFlowHistory({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
})
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function updateDeploymentMsg(version: number | undefined) {
|
||||
if (
|
||||
selectedVersion === undefined ||
|
||||
version === undefined ||
|
||||
emptyString(deploymentMsgUpdate)
|
||||
) {
|
||||
return
|
||||
}
|
||||
await FlowService.updateFlowHistory({
|
||||
workspace: $workspaceStore!,
|
||||
version,
|
||||
path,
|
||||
requestBody: {
|
||||
deployment_msg: deploymentMsgUpdate!
|
||||
}
|
||||
})
|
||||
selectedVersion.deployment_msg = deploymentMsgUpdate
|
||||
deploymentMsgUpdateMode = false
|
||||
loadVersions()
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function restoreVersion(flow: Flow | undefined) {
|
||||
if (!flow) return
|
||||
await FlowService.updateFlow({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
...flow,
|
||||
path
|
||||
},
|
||||
path
|
||||
})
|
||||
dispatch('historyRestore')
|
||||
drawer?.closeDrawer()
|
||||
sendUserToast('Flow restored from previous deployment')
|
||||
}
|
||||
|
||||
$: selectedVersion !== undefined && loadFlow(selectedVersion.id)
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={drawer} size="1200px">
|
||||
@@ -91,136 +21,14 @@
|
||||
drawer?.closeDrawer()
|
||||
}}
|
||||
noPadding
|
||||
title="Deployment History"
|
||||
>
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane size={20}>
|
||||
<PanelSection title="Past Deployments">
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if !loading}
|
||||
{#if versions.length > 0}
|
||||
<div class="flex gap-2 flex-col">
|
||||
{#each versions ?? [] as version}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedVersion?.id == version.id ? 'bg-blue-100 text-blue-600' : ''
|
||||
)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:click={() => {
|
||||
selectedVersion = version
|
||||
}}
|
||||
>
|
||||
<span class="text-xs truncate">
|
||||
{#if emptyString(version.deployment_msg)}Version {version.id}{:else}{version.deployment_msg}{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-tertiary">No items</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Skeleton layout={[[40], [40], [40], [40], [40]]} />
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
</Pane>
|
||||
<Pane size={80}>
|
||||
<div class="h-full w-full overflow-auto">
|
||||
{#if selectedVersion}
|
||||
{#if selected}
|
||||
<div class="px-2 flex flex-col gap-2">
|
||||
<span class="flex flex-row text-sm p-1 text-tertiary">
|
||||
{#if deploymentMsgUpdateMode}
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={deploymentMsgUpdate}
|
||||
class="!w-auto grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
on:keydown|stopPropagation
|
||||
on:keypress|stopPropagation={({ key }) => {
|
||||
if (key === 'Enter') updateDeploymentMsg(selectedVersion?.id)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Save Deployment Message"
|
||||
on:click={() => {
|
||||
updateDeploymentMsg(selectedVersion?.id)
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Abort"
|
||||
on:click={() => {
|
||||
deploymentMsgUpdateMode = false
|
||||
deploymentMsgUpdate = undefined
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
{#if selectedVersion.deployment_msg}
|
||||
{selectedVersion.deployment_msg}
|
||||
{:else}
|
||||
Deployed {displayDate(selected.edited_at)} by {selected.edited_by}
|
||||
{/if}
|
||||
<button
|
||||
on:click={() => {
|
||||
deploymentMsgUpdate = selectedVersion?.deployment_msg
|
||||
deploymentMsgUpdateMode = true
|
||||
}}
|
||||
title="Update commit message"
|
||||
class="flex items-center px-1 rounded-sm hover:text-primary text-secondary h-5"
|
||||
aria-label="Update commit message"
|
||||
>
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
<div class="flex p-1 gap-2">
|
||||
<Button
|
||||
size="xs"
|
||||
on:click={() =>
|
||||
window.open(
|
||||
`/flows/add?template_id=${selectedVersion?.id}&template=${path}`,
|
||||
'_blank'
|
||||
)}
|
||||
>
|
||||
Restore as fork
|
||||
</Button>
|
||||
<Button size="xs" on:click={() => restoreVersion(selected)}
|
||||
>Redeploy with that version
|
||||
</Button>
|
||||
</div>
|
||||
{#await import('$lib/components/FlowViewer.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default flow={selected} />
|
||||
{/await}
|
||||
</div>
|
||||
{:else}
|
||||
<Skeleton layout={[[40]]} />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-sm p-2 text-tertiary"
|
||||
>Select a deployment version to see its details</div
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
<FlowHistoryInner
|
||||
on:historyRestore={() => {
|
||||
drawer.closeDrawer()
|
||||
dispatch('historyRestore')
|
||||
}}
|
||||
{path}
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
<script lang="ts">
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { classNames, displayDate, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { type Flow, FlowService, type FlowVersion } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { Skeleton } from '$lib/components/common'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import { ArrowRight, Loader2, Pencil, X } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let path: string
|
||||
let loading: boolean = false
|
||||
|
||||
let versions: FlowVersion[] = []
|
||||
|
||||
let selectedVersion: FlowVersion | undefined = undefined
|
||||
let selected: Flow | undefined = undefined
|
||||
let deploymentMsgUpdateMode = false
|
||||
let deploymentMsgUpdate: string | undefined = undefined
|
||||
|
||||
async function loadFlow(version: number) {
|
||||
selected = await FlowService.getFlowVersion({
|
||||
workspace: $workspaceStore!,
|
||||
version,
|
||||
path
|
||||
})
|
||||
}
|
||||
|
||||
async function loadVersions() {
|
||||
loading = true
|
||||
versions = await FlowService.getFlowHistory({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
})
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function updateDeploymentMsg(version: number | undefined) {
|
||||
if (
|
||||
selectedVersion === undefined ||
|
||||
version === undefined ||
|
||||
emptyString(deploymentMsgUpdate)
|
||||
) {
|
||||
return
|
||||
}
|
||||
await FlowService.updateFlowHistory({
|
||||
workspace: $workspaceStore!,
|
||||
version,
|
||||
path,
|
||||
requestBody: {
|
||||
deployment_msg: deploymentMsgUpdate!
|
||||
}
|
||||
})
|
||||
selectedVersion.deployment_msg = deploymentMsgUpdate
|
||||
deploymentMsgUpdateMode = false
|
||||
loadVersions()
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function restoreVersion(flow: Flow | undefined) {
|
||||
if (!flow) return
|
||||
await FlowService.updateFlow({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
...flow,
|
||||
path
|
||||
},
|
||||
path
|
||||
})
|
||||
dispatch('historyRestore')
|
||||
sendUserToast('Flow restored from previous deployment')
|
||||
}
|
||||
|
||||
loadVersions()
|
||||
|
||||
$: selectedVersion !== undefined && loadFlow(selectedVersion.id)
|
||||
</script>
|
||||
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane size={20}>
|
||||
<div class="flex flex-col gap-2 w-full px-2 py-2">
|
||||
{#if !loading}
|
||||
{#if versions.length > 0}
|
||||
<div class="flex gap-2 flex-col">
|
||||
{#each versions ?? [] as version}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-surface-hover hover:text-primary',
|
||||
selectedVersion?.id == version.id ? 'bg-surface-selected text-primary' : ''
|
||||
)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:click={() => {
|
||||
selectedVersion = version
|
||||
}}
|
||||
>
|
||||
<span class="text-xs truncate">
|
||||
{#if emptyString(version.deployment_msg)}Version {version.id}{:else}{version.deployment_msg}{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-tertiary">No items</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Skeleton layout={[[40], [40], [40], [40], [40]]} />
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={80}>
|
||||
<div class="h-full w-full overflow-auto pt-2">
|
||||
{#if selectedVersion}
|
||||
{#if selected}
|
||||
<div class="px-2 flex flex-col gap-2">
|
||||
<div class="flex justify-between">
|
||||
<span class="flex flex-row text-sm p-1 text-tertiary">
|
||||
{#if deploymentMsgUpdateMode}
|
||||
<div class="flex w-full">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={deploymentMsgUpdate}
|
||||
class="!w-auto grow"
|
||||
on:click|stopPropagation={() => {}}
|
||||
on:keydown|stopPropagation
|
||||
on:keypress|stopPropagation={({ key }) => {
|
||||
if (key === 'Enter') updateDeploymentMsg(selectedVersion?.id)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Save Deployment Message"
|
||||
on:click={() => {
|
||||
updateDeploymentMsg(selectedVersion?.id)
|
||||
}}
|
||||
>
|
||||
<ArrowRight size={14} />
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
buttonType="button"
|
||||
btnClasses="!p-1 !w-[34px] !ml-1"
|
||||
aria-label="Abort"
|
||||
on:click={() => {
|
||||
deploymentMsgUpdateMode = false
|
||||
deploymentMsgUpdate = undefined
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
{#if selectedVersion.deployment_msg}
|
||||
{selectedVersion.deployment_msg}
|
||||
{:else}
|
||||
Deployed {displayDate(selected.edited_at)} by {selected.edited_by}
|
||||
{/if}
|
||||
<button
|
||||
on:click={() => {
|
||||
deploymentMsgUpdate = selectedVersion?.deployment_msg
|
||||
deploymentMsgUpdateMode = true
|
||||
}}
|
||||
title="Update commit message"
|
||||
class="flex items-center px-1 rounded-sm hover:text-primary text-secondary h-5"
|
||||
aria-label="Update commit message"
|
||||
>
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
<div class="flex p-1 gap-2">
|
||||
<div class="flex">
|
||||
<Button
|
||||
size="sm"
|
||||
color="dark"
|
||||
on:click={() =>
|
||||
window.open(
|
||||
`/flows/add?template_id=${selectedVersion?.id}&template=${path}`,
|
||||
'_blank'
|
||||
)}
|
||||
>
|
||||
Restore as fork
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<Button size="sm" color="dark" on:click={() => restoreVersion(selected)}
|
||||
>Redeploy with that version
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#await import('$lib/components/FlowViewer.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default flow={selected} />
|
||||
{/await}
|
||||
</div>
|
||||
{:else}
|
||||
<Skeleton layout={[[40]]} />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-sm p-2 text-tertiary">Select a deployment version to see its details</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
export let noEditor = false
|
||||
export let enableAi = false
|
||||
export let disabledFlowInputs = false
|
||||
|
||||
const { selectedId, flowStore, flowStateStore, flowInputsStore } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
@@ -57,7 +58,7 @@
|
||||
{#if $selectedId?.startsWith('settings')}
|
||||
<FlowSettings {noEditor} />
|
||||
{:else if $selectedId === 'Input'}
|
||||
<FlowInput {noEditor} />
|
||||
<FlowInput {noEditor} disabled={disabledFlowInputs} />
|
||||
{:else if $selectedId === 'Result'}
|
||||
<p class="p-4 text-secondary">Nothing to show about the result node. Happy flow building!</p>
|
||||
{:else if $selectedId === 'constants'}
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
import SavedInputs from '$lib/components/SavedInputs.svelte'
|
||||
import EditableSchemaForm from '$lib/components/EditableSchemaForm.svelte'
|
||||
import AddProperty from '$lib/components/schema/AddProperty.svelte'
|
||||
import FlowInputViewer from '$lib/components/FlowInputViewer.svelte'
|
||||
|
||||
export let noEditor: boolean
|
||||
export let disabled: boolean
|
||||
|
||||
const { flowStore, flowStateStore, previewArgs, initialPath } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
@@ -42,67 +44,73 @@
|
||||
<CapturePayload bind:this={capturePayload} />
|
||||
|
||||
<FlowCard {noEditor} title="Flow Input">
|
||||
<div class="flex flex-row items-center gap-2 px-4 py-2 border-b">
|
||||
<div>Copy input's schema from</div>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
capturePayload.openDrawer()
|
||||
}}
|
||||
>
|
||||
A request
|
||||
</Button>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
jsonPayload.openDrawer()
|
||||
}}
|
||||
>
|
||||
A JSON
|
||||
</Button>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
inputLibraryDrawer.openDrawer()
|
||||
}}
|
||||
>
|
||||
Past Runs/Input library
|
||||
</Button>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
disabled={$flowStore.value.modules.length === 0 ||
|
||||
$flowStore.value.modules[0].value.type == 'identity'}
|
||||
on:click={() => copyFirstStepSchema($flowStateStore, flowStore)}
|
||||
>
|
||||
First step's inputs
|
||||
</Button>
|
||||
</div>
|
||||
<div class="p-4 border-b">
|
||||
<AddProperty
|
||||
bind:schema={$flowStore.schema}
|
||||
bind:this={addProperty}
|
||||
on:change={() => {
|
||||
$flowStore = $flowStore
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if !disabled}
|
||||
<div class="flex flex-row items-center gap-2 px-4 py-2 border-b">
|
||||
<div>Copy input's schema from</div>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
capturePayload.openDrawer()
|
||||
}}
|
||||
>
|
||||
A request
|
||||
</Button>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
jsonPayload.openDrawer()
|
||||
}}
|
||||
>
|
||||
A JSON
|
||||
</Button>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
inputLibraryDrawer.openDrawer()
|
||||
}}
|
||||
>
|
||||
Past Runs/Input library
|
||||
</Button>
|
||||
<Button
|
||||
color="dark"
|
||||
size="xs"
|
||||
disabled={$flowStore.value.modules.length === 0 ||
|
||||
$flowStore.value.modules[0].value.type == 'identity'}
|
||||
on:click={() => copyFirstStepSchema($flowStateStore, flowStore)}
|
||||
>
|
||||
First step's inputs
|
||||
</Button>
|
||||
</div>
|
||||
<div class="p-4 border-b">
|
||||
<AddProperty
|
||||
bind:schema={$flowStore.schema}
|
||||
bind:this={addProperty}
|
||||
on:change={() => {
|
||||
$flowStore = $flowStore
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<EditableSchemaForm
|
||||
bind:schema={$flowStore.schema}
|
||||
isFlowInput
|
||||
on:edit={(e) => {
|
||||
addProperty?.openDrawer(e.detail)
|
||||
}}
|
||||
on:delete={(e) => {
|
||||
addProperty?.handleDeleteArgument([e.detail])
|
||||
}}
|
||||
offset={yOffset}
|
||||
displayWebhookWarning
|
||||
/>
|
||||
<EditableSchemaForm
|
||||
bind:schema={$flowStore.schema}
|
||||
isFlowInput
|
||||
on:edit={(e) => {
|
||||
addProperty?.openDrawer(e.detail)
|
||||
}}
|
||||
on:delete={(e) => {
|
||||
addProperty?.handleDeleteArgument([e.detail])
|
||||
}}
|
||||
offset={yOffset}
|
||||
displayWebhookWarning
|
||||
/>
|
||||
{:else}
|
||||
<div class="p-4 border-b">
|
||||
<FlowInputViewer schema={$flowStore.schema} />
|
||||
</div>
|
||||
{/if}
|
||||
</FlowCard>
|
||||
|
||||
<Drawer bind:this={jsonPayload} size="800px">
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
<div class="flex flex-col flex-1 h-full overflow-auto p-2">
|
||||
{#if showDate && date}
|
||||
<span class="text-xs text-tertiary mb-4"><TimeAgo agoOnlyIfRecent withDate {date} /></span>
|
||||
<span class="text-xs text-tertiary mb-4"><TimeAgo agoOnlyIfRecent {date} /></span>
|
||||
{/if}
|
||||
{#if notFound}
|
||||
<div class="text-red-400">script not found at {path} in workspace {$workspaceStore}</div>
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Portal>
|
||||
<Portal name="flow-module">
|
||||
<ConfirmationModal
|
||||
title="Confirm deleting step with dependents"
|
||||
confirmationText="Delete step"
|
||||
|
||||
@@ -1,114 +1,129 @@
|
||||
<script lang="ts">
|
||||
import { customIcon } from './store'
|
||||
|
||||
export let height = '24px'
|
||||
export let width = '24px'
|
||||
export let white = false
|
||||
export let spin: 'slow' | 'medium' | 'fast' | 'veryfast' | undefined = undefined
|
||||
</script>
|
||||
|
||||
<!-- Generator: Adobe Illustrator 26.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg
|
||||
class={$$props.class}
|
||||
class:animate-[spin_2s_linear_infinite]={spin === 'veryfast'}
|
||||
class:animate-[spin_5s_linear_infinite]={spin === 'fast'}
|
||||
class:animate-[spin_15s_linear_infinite]={spin === 'medium'}
|
||||
class:animate-[spin_50s_linear_infinite]={spin === 'slow'}
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
{width}
|
||||
{height}
|
||||
viewBox="0 0 256 256"
|
||||
style="enable-background:new 0 0 256 256;"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
fill: #ffffff;
|
||||
}
|
||||
.st1 {
|
||||
opacity: 0.4;
|
||||
fill: #ffffff;
|
||||
}
|
||||
.st2 {
|
||||
fill: #bcd4fc;
|
||||
}
|
||||
.st2-gray {
|
||||
fill: #cccccc;
|
||||
}
|
||||
.st3 {
|
||||
fill: #3b82f6;
|
||||
}
|
||||
.st4 {
|
||||
fill: #b3b3b3;
|
||||
}
|
||||
.st5 {
|
||||
fill: url(#SVGID_1_);
|
||||
}
|
||||
.st6 {
|
||||
fill: url(#SVGID_00000021089067129159788970000008246765442136188072_);
|
||||
}
|
||||
.st7 {
|
||||
fill: url(#SVGID_00000117639240116366130650000015074833605515028638_);
|
||||
}
|
||||
.st8 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000101781798616409025840000016567063639337360777_);
|
||||
}
|
||||
.st9 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000052086836598721292040000002033117744178971046_);
|
||||
}
|
||||
.st10 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000159460939004760751800000002448009281983951536_);
|
||||
}
|
||||
.st11 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000013177830667419993080000017721442101626521532_);
|
||||
}
|
||||
.st12 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000152235521444854938490000006526001119318383285_);
|
||||
}
|
||||
.st13 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000119823135212293698520000012774889010992664993_);
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<polygon
|
||||
class:st2={!white}
|
||||
class:st2-gray={white}
|
||||
points="134.78,14.22 114.31,48.21 101.33,69.75 158.22,69.75 177.97,36.95 191.67,14.22 "
|
||||
{#if customIcon}
|
||||
{#if white}
|
||||
<img src={customIcon.white} alt="Windmill Custom icon" {width} {height} class={$$props.class} />
|
||||
{:else}
|
||||
<img
|
||||
src={customIcon.normal}
|
||||
alt="Windmill Custom icon"
|
||||
{width}
|
||||
{height}
|
||||
class={$$props.class}
|
||||
/>
|
||||
<polygon
|
||||
class:st3={!white}
|
||||
class:st0={white}
|
||||
points="227.55,69.75 186.61,69.75 101.33,69.75 129.78,119.02 158.16,119.02 228.61,119.02 256,119.02 "
|
||||
/>
|
||||
<polygon
|
||||
class:st3={!white}
|
||||
class:st0={white}
|
||||
points="136.93,132.47 116.46,167.93 73.82,241.78 130.71,241.78 144.9,217.2 180.13,156.18 193.82,132.46
|
||||
{/if}
|
||||
{:else}
|
||||
<svg
|
||||
class={$$props.class}
|
||||
class:animate-[spin_2s_linear_infinite]={spin === 'veryfast'}
|
||||
class:animate-[spin_5s_linear_infinite]={spin === 'fast'}
|
||||
class:animate-[spin_15s_linear_infinite]={spin === 'medium'}
|
||||
class:animate-[spin_50s_linear_infinite]={spin === 'slow'}
|
||||
version="1.1"
|
||||
id="Calque_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
{width}
|
||||
{height}
|
||||
viewBox="0 0 256 256"
|
||||
style="enable-background:new 0 0 256 256;"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<style type="text/css">
|
||||
.st0 {
|
||||
fill: #ffffff;
|
||||
}
|
||||
.st1 {
|
||||
opacity: 0.4;
|
||||
fill: #ffffff;
|
||||
}
|
||||
.st2 {
|
||||
fill: #bcd4fc;
|
||||
}
|
||||
.st2-gray {
|
||||
fill: #cccccc;
|
||||
}
|
||||
.st3 {
|
||||
fill: #3b82f6;
|
||||
}
|
||||
.st4 {
|
||||
fill: #b3b3b3;
|
||||
}
|
||||
.st5 {
|
||||
fill: url(#SVGID_1_);
|
||||
}
|
||||
.st6 {
|
||||
fill: url(#SVGID_00000021089067129159788970000008246765442136188072_);
|
||||
}
|
||||
.st7 {
|
||||
fill: url(#SVGID_00000117639240116366130650000015074833605515028638_);
|
||||
}
|
||||
.st8 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000101781798616409025840000016567063639337360777_);
|
||||
}
|
||||
.st9 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000052086836598721292040000002033117744178971046_);
|
||||
}
|
||||
.st10 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000159460939004760751800000002448009281983951536_);
|
||||
}
|
||||
.st11 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000013177830667419993080000017721442101626521532_);
|
||||
}
|
||||
.st12 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000152235521444854938490000006526001119318383285_);
|
||||
}
|
||||
.st13 {
|
||||
opacity: 0.4;
|
||||
fill: url(#SVGID_00000119823135212293698520000012774889010992664993_);
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<polygon
|
||||
class:st2={!white}
|
||||
class:st2-gray={white}
|
||||
points="134.78,14.22 114.31,48.21 101.33,69.75 158.22,69.75 177.97,36.95 191.67,14.22 "
|
||||
/>
|
||||
<polygon
|
||||
class:st3={!white}
|
||||
class:st0={white}
|
||||
points="227.55,69.75 186.61,69.75 101.33,69.75 129.78,119.02 158.16,119.02 228.61,119.02 256,119.02 "
|
||||
/>
|
||||
<polygon
|
||||
class:st3={!white}
|
||||
class:st0={white}
|
||||
points="136.93,132.47 116.46,167.93 73.82,241.78 130.71,241.78 144.9,217.2 180.13,156.18 193.82,132.46
|
||||
"
|
||||
/>
|
||||
<polygon
|
||||
class:st3={!white}
|
||||
class:st0={white}
|
||||
points="121.7,131.95 101.23,96.49 58.59,22.63 30.15,71.91 44.34,96.49 79.57,157.5 93.26,181.22 "
|
||||
/>
|
||||
<polygon
|
||||
class:st2={!white}
|
||||
class:st2-gray={white}
|
||||
points="64.81,131.95 25.15,131.21 0,130.74 28.44,180.01 66.73,180.72 93.26,181.21 "
|
||||
/>
|
||||
<polygon
|
||||
class:st2={!white}
|
||||
class:st2-gray={white}
|
||||
points="165.38,181.74 184.58,216.46 196.75,238.47 225.19,189.2 206.66,155.69 193.83,132.46 "
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
/>
|
||||
<polygon
|
||||
class:st3={!white}
|
||||
class:st0={white}
|
||||
points="121.7,131.95 101.23,96.49 58.59,22.63 30.15,71.91 44.34,96.49 79.57,157.5 93.26,181.22 "
|
||||
/>
|
||||
<polygon
|
||||
class:st2={!white}
|
||||
class:st2-gray={white}
|
||||
points="64.81,131.95 25.15,131.21 0,130.74 28.44,180.01 66.73,180.72 93.26,181.21 "
|
||||
/>
|
||||
<polygon
|
||||
class:st2={!white}
|
||||
class:st2-gray={white}
|
||||
points="165.38,181.74 184.58,216.46 196.75,238.47 225.19,189.2 206.66,155.69 193.83,132.46 "
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export let customIcon: { normal: string; white: string } | undefined = undefined
|
||||
@@ -80,7 +80,7 @@
|
||||
</div>
|
||||
</MultiSelect>
|
||||
</div>
|
||||
<Portal>
|
||||
<Portal name="multi-select">
|
||||
<div use:floatingContent class="z5000" hidden={!open}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
$: fullyCollapsed = keys.length > 1 && collapsed
|
||||
</script>
|
||||
|
||||
<Portal>
|
||||
<Portal name="object-viewer">
|
||||
<S3FilePicker bind:this={s3FileViewer} readOnlyMode={true} />
|
||||
</Portal>
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Portal>
|
||||
<Portal name="run-row">
|
||||
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
|
||||
</Portal>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
@@ -105,7 +105,7 @@
|
||||
<div class="flex flex-row items-center gap-1 text-gray-500 dark:text-gray-300 text-2xs">
|
||||
{#if job}
|
||||
{#if 'started_at' in job && job.started_at}
|
||||
Started <TimeAgo withDate agoOnlyIfRecent date={job.started_at ?? ''} />
|
||||
Started <TimeAgo agoOnlyIfRecent date={job.started_at ?? ''} />
|
||||
{#if job && 'duration_ms' in job && job.duration_ms != undefined}
|
||||
(Ran in {msToReadableTime(
|
||||
job.duration_ms
|
||||
@@ -121,11 +121,7 @@
|
||||
{:else if `scheduled_for` in job && job.scheduled_for && forLater(job.scheduled_for)}
|
||||
Scheduled for {displayDate(job.scheduled_for)}
|
||||
{:else}
|
||||
Waiting for executor (created <TimeAgo
|
||||
withDate
|
||||
agoOnlyIfRecent
|
||||
date={job.created_at || ''}
|
||||
/>)
|
||||
Waiting for executor (created <TimeAgo agoOnlyIfRecent date={job.created_at || ''} />)
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
/>
|
||||
{:else}
|
||||
<ArgInput
|
||||
noDefaultOnSelectFirst
|
||||
{itemPicker}
|
||||
resourceTypes={getResourceTypesFromFormat(format)}
|
||||
bind:value={defaultValue}
|
||||
|
||||
@@ -499,7 +499,7 @@
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<Portal>
|
||||
<Portal name="global-search">
|
||||
<div
|
||||
class={twMerge(
|
||||
`fixed top-0 bottom-0 left-0 right-0 transition-all duration-50 flex items-start justify-center`,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export default `import os
|
||||
|
||||
def main(message: str, name: str, step_id: str):
|
||||
flow_id = os.environ.get("WM_ROOT_FLOW_JOB_ID")
|
||||
print("message", message)
|
||||
print("name", name)
|
||||
print("step_id", step_id)
|
||||
return { "message": message, "flow_id": flow_id, "step_id": step_id, "recover": False }`
|
||||
@@ -1,40 +0,0 @@
|
||||
export default `import os
|
||||
import wmill
|
||||
|
||||
# You can import any PyPi package.
|
||||
# See here for more info: https://www.windmill.dev/docs/advanced/dependencies_in_python
|
||||
|
||||
# you can use typed resources by doing a type alias to dict
|
||||
#postgresql = dict
|
||||
|
||||
def main(
|
||||
no_default: str,
|
||||
#db: postgresql,
|
||||
name="Nicolas Bourbaki",
|
||||
age=42,
|
||||
obj: dict = {"even": "dicts"},
|
||||
l: list = ["or", "lists!"],
|
||||
file_: bytes = bytes(0),
|
||||
):
|
||||
|
||||
print(f"Hello World and a warm welcome especially to {name}")
|
||||
print("and its acolytes..", age, obj, l, len(file_))
|
||||
|
||||
# retrieve variables, resources, states using the wmill client
|
||||
try:
|
||||
secret = wmill.get_variable("f/examples/secret")
|
||||
except:
|
||||
secret = "No secret yet at f/examples/secret !"
|
||||
print(f"The variable at \`f/examples/secret\`: {secret}")
|
||||
|
||||
# Get last state of this script execution by the same trigger/user
|
||||
last_state = wmill.get_state()
|
||||
new_state = {"foo": 42} if last_state is None else last_state
|
||||
new_state["foo"] += 1
|
||||
wmill.set_state(new_state)
|
||||
|
||||
# fetch context variables
|
||||
user = os.environ.get("WM_USERNAME")
|
||||
|
||||
# return value is converted to JSON
|
||||
return {"splitted": name.split(), "user": user, "state": new_state}`
|
||||
@@ -1,5 +0,0 @@
|
||||
export default `# import wmill
|
||||
|
||||
|
||||
def main(x: str):
|
||||
return x`
|
||||
@@ -1,14 +0,0 @@
|
||||
export default `import wmill
|
||||
|
||||
|
||||
def main():
|
||||
# A common trigger script would follow this pattern:
|
||||
# 1. Get the last saved state
|
||||
# state = wmill.get_state()
|
||||
# 2. Get the actual state from the external service
|
||||
# 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]`
|
||||
@@ -1,19 +1,79 @@
|
||||
import { type Script } from './gen'
|
||||
|
||||
import PYTHON_INIT_CODE from '$lib/init_scripts/python_init_code'
|
||||
import PYTHON_INIT_CODE_CLEAR from '$lib/init_scripts/python_init_code_clear'
|
||||
import PYTHON_INIT_CODE_TRIGGER from '$lib/init_scripts/python_init_code_trigger'
|
||||
import PYTHON_FAILURE_MODULE_CODE from '$lib/init_scripts/python_failure_module'
|
||||
import type { SupportedLanguage } from './common'
|
||||
|
||||
export {
|
||||
PYTHON_INIT_CODE,
|
||||
PYTHON_INIT_CODE_CLEAR,
|
||||
PYTHON_INIT_CODE_TRIGGER,
|
||||
PYTHON_FAILURE_MODULE_CODE
|
||||
}
|
||||
export let PYTHON_FAILURE_MODULE_CODE = `import os
|
||||
|
||||
export const NATIVETS_INIT_CODE = `// Fetch-only script, no imports allowed (except windmill) but benefits from a dedicated highly efficient runtime
|
||||
def main(message: str, name: str, step_id: str):
|
||||
flow_id = os.environ.get("WM_ROOT_FLOW_JOB_ID")
|
||||
print("message", message)
|
||||
print("name", name)
|
||||
print("step_id", step_id)
|
||||
return { "message": message, "flow_id": flow_id, "step_id": step_id, "recover": False }`
|
||||
|
||||
export let PYTHON_INIT_CODE_CLEAR = `# import wmill
|
||||
|
||||
|
||||
def main(x: str):
|
||||
return x`
|
||||
|
||||
export let PYTHON_INIT_CODE_TRIGGER = `import wmill
|
||||
|
||||
|
||||
def main():
|
||||
# A common trigger script would follow this pattern:
|
||||
# 1. Get the last saved state
|
||||
# state = wmill.get_state()
|
||||
# 2. Get the actual state from the external service
|
||||
# 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 let PYTHON_INIT_CODE = `import os
|
||||
import wmill
|
||||
|
||||
# You can import any PyPi package.
|
||||
# See here for more info: https://www.windmill.dev/docs/advanced/dependencies_in_python
|
||||
|
||||
# you can use typed resources by doing a type alias to dict
|
||||
#postgresql = dict
|
||||
|
||||
def main(
|
||||
no_default: str,
|
||||
#db: postgresql,
|
||||
name="Nicolas Bourbaki",
|
||||
age=42,
|
||||
obj: dict = {"even": "dicts"},
|
||||
l: list = ["or", "lists!"],
|
||||
file_: bytes = bytes(0),
|
||||
):
|
||||
|
||||
print(f"Hello World and a warm welcome especially to {name}")
|
||||
print("and its acolytes..", age, obj, l, len(file_))
|
||||
|
||||
# retrieve variables, resources, states using the wmill client
|
||||
try:
|
||||
secret = wmill.get_variable("f/examples/secret")
|
||||
except:
|
||||
secret = "No secret yet at f/examples/secret !"
|
||||
print(f"The variable at \`f/examples/secret\`: {secret}")
|
||||
|
||||
# Get last state of this script execution by the same trigger/user
|
||||
last_state = wmill.get_state()
|
||||
new_state = {"foo": 42} if last_state is None else last_state
|
||||
new_state["foo"] += 1
|
||||
wmill.set_state(new_state)
|
||||
|
||||
# fetch context variables
|
||||
user = os.environ.get("WM_USERNAME")
|
||||
|
||||
# return value is converted to JSON
|
||||
return {"splitted": name.split(), "user": user, "state": new_state}`
|
||||
|
||||
export let NATIVETS_INIT_CODE = `// Fetch-only script, no imports allowed (except windmill) but benefits from a dedicated highly efficient runtime
|
||||
//import * as wmill from './windmill.ts'
|
||||
|
||||
export async function main(example_input: number = 3) {
|
||||
@@ -25,7 +85,7 @@ export async function main(example_input: number = 3) {
|
||||
}
|
||||
`
|
||||
|
||||
export const BUNNATIVE_INIT_CODE = `//native
|
||||
export let BUNNATIVE_INIT_CODE = `//native
|
||||
//you can add proxy support using //proxy http(s)://host:port
|
||||
|
||||
// native scripts are bun scripts that are executed on native workers and can be parallelized
|
||||
@@ -42,7 +102,7 @@ export async function main(example_input: number = 3) {
|
||||
}
|
||||
`
|
||||
|
||||
export const NATIVETS_INIT_CODE_CLEAR = `// Fetch-only script, no imports allowed (except windmill) but benefits from a dedicated highly efficient runtime
|
||||
export let NATIVETS_INIT_CODE_CLEAR = `// Fetch-only script, no imports allowed (except windmill) but benefits from a dedicated highly efficient runtime
|
||||
//import * as wmill from './windmill.ts'
|
||||
|
||||
export async function main() {
|
||||
@@ -53,7 +113,7 @@ export async function main() {
|
||||
}
|
||||
`
|
||||
|
||||
export const DENO_INIT_CODE = `// Ctrl/CMD+. to cache dependencies on imports hover.
|
||||
export let DENO_INIT_CODE = `// Ctrl/CMD+. to cache dependencies on imports hover.
|
||||
|
||||
// Deno uses "npm:" prefix to import from npm (https://deno.land/manual@v1.36.3/node/npm_specifiers)
|
||||
// import * as wmill from "npm:windmill-client@${__pkg__.version}"
|
||||
@@ -74,7 +134,7 @@ export async function main(
|
||||
}
|
||||
`
|
||||
|
||||
export const BUN_INIT_CODE = `// there are multiple modes to add as header: //nobundling //native //npm //nodejs
|
||||
export let BUN_INIT_CODE = `// there are multiple modes to add as header: //nobundling //native //npm //nodejs
|
||||
// https://www.windmill.dev/docs/getting_started/scripts_quickstart/typescript#modes
|
||||
|
||||
// import { toWords } from "number-to-words@1"
|
||||
@@ -105,7 +165,7 @@ export async function main(
|
||||
}
|
||||
`
|
||||
|
||||
export const GO_INIT_CODE = `package inner
|
||||
export let GO_INIT_CODE = `package inner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -129,7 +189,7 @@ func main(x string, nested struct {
|
||||
}
|
||||
`
|
||||
|
||||
export const GO_FAILURE_MODULE_CODE = `package inner
|
||||
export let GO_FAILURE_MODULE_CODE = `package inner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -146,21 +206,21 @@ func main(message string, name string) (interface{}, error) {
|
||||
}
|
||||
`
|
||||
|
||||
export const DENO_INIT_CODE_CLEAR = `// import * as wmill from "npm:windmill-client@${__pkg__.version}"
|
||||
export let DENO_INIT_CODE_CLEAR = `// import * as wmill from "npm:windmill-client@${__pkg__.version}"
|
||||
|
||||
export async function main(x: string) {
|
||||
return x
|
||||
}
|
||||
`
|
||||
|
||||
export const BUN_INIT_CODE_CLEAR = `// import * as wmill from "windmill-client"
|
||||
export let BUN_INIT_CODE_CLEAR = `// import * as wmill from "windmill-client"
|
||||
|
||||
export async function main(x: string) {
|
||||
return x
|
||||
}
|
||||
`
|
||||
|
||||
export const DENO_FAILURE_MODULE_CODE = `
|
||||
export let DENO_FAILURE_MODULE_CODE = `
|
||||
export async function main(message: string, name: string, step_id: string) {
|
||||
const flow_id = Deno.env.get("WM_ROOT_FLOW_JOB_ID")
|
||||
console.log("message", message)
|
||||
@@ -170,7 +230,7 @@ export async function main(message: string, name: string, step_id: string) {
|
||||
}
|
||||
`
|
||||
|
||||
export const BUN_FAILURE_MODULE_CODE = `
|
||||
export let BUN_FAILURE_MODULE_CODE = `
|
||||
export async function main(message: string, name: string, step_id: string) {
|
||||
const flow_id = process.env.WM_ROOT_FLOW_JOB_ID
|
||||
console.log("message", message)
|
||||
@@ -180,7 +240,7 @@ export async function main(message: string, name: string, step_id: string) {
|
||||
}
|
||||
`
|
||||
|
||||
export const POSTGRES_INIT_CODE = `-- to pin the database use '-- database f/your/path'
|
||||
export let POSTGRES_INIT_CODE = `-- to pin the database use '-- database f/your/path'
|
||||
-- $1 name1 = default arg
|
||||
-- $2 name2
|
||||
-- $3 name3
|
||||
@@ -189,7 +249,7 @@ INSERT INTO demo VALUES (\$1::TEXT, \$2::INT, \$3::TEXT[]) RETURNING *;
|
||||
UPDATE demo SET col2 = \$4::INT WHERE col2 = \$2::INT;
|
||||
`
|
||||
|
||||
export const MYSQL_INIT_CODE = `-- to pin the database use '-- database f/your/path'
|
||||
export let MYSQL_INIT_CODE = `-- to pin the database use '-- database f/your/path'
|
||||
-- :name1 (text) = default arg
|
||||
-- :name2 (int)
|
||||
-- :name3 (int)
|
||||
@@ -197,7 +257,7 @@ INSERT INTO demo VALUES (:name1, :name2);
|
||||
UPDATE demo SET col2 = :name3 WHERE col2 = :name2;
|
||||
`
|
||||
|
||||
export const BIGQUERY_INIT_CODE = `-- to pin the database use '-- database f/your/path'
|
||||
export let BIGQUERY_INIT_CODE = `-- to pin the database use '-- database f/your/path'
|
||||
-- @name1 (string) = default arg
|
||||
-- @name2 (integer)
|
||||
-- @name3 (string[])
|
||||
@@ -206,7 +266,7 @@ INSERT INTO \`demodb.demo\` VALUES (@name1, @name2, @name3);
|
||||
UPDATE \`demodb.demo\` SET col2 = @name4 WHERE col2 = @name2;
|
||||
`
|
||||
|
||||
export const SNOWFLAKE_INIT_CODE = `-- to pin the database use '-- database f/your/path'
|
||||
export let SNOWFLAKE_INIT_CODE = `-- to pin the database use '-- database f/your/path'
|
||||
-- ? name1 (varchar) = default arg
|
||||
-- ? name2 (int)
|
||||
INSERT INTO demo VALUES (?, ?);
|
||||
@@ -215,7 +275,7 @@ INSERT INTO demo VALUES (?, ?);
|
||||
UPDATE demo SET col2 = ? WHERE col2 = ?;
|
||||
`
|
||||
|
||||
export const MSSQL_INIT_CODE = `-- return_last_result
|
||||
export let MSSQL_INIT_CODE = `-- return_last_result
|
||||
-- to pin the database use '-- database f/your/path'
|
||||
-- @p1 name1 (varchar) = default arg
|
||||
-- @p2 name2 (int)
|
||||
@@ -224,7 +284,7 @@ INSERT INTO demo VALUES (@p1, @p2);
|
||||
UPDATE demo SET col2 = @p3 WHERE col2 = @p2;
|
||||
`
|
||||
|
||||
export const GRAPHQL_INIT_CODE = `query($name4: String, $name2: Int, $name3: [String]) {
|
||||
export let GRAPHQL_INIT_CODE = `query($name4: String, $name2: Int, $name3: [String]) {
|
||||
demo(name1: $name1, name2: $name2, name3: $name3) {
|
||||
name1,
|
||||
name2,
|
||||
@@ -233,7 +293,7 @@ export const GRAPHQL_INIT_CODE = `query($name4: String, $name2: Int, $name3: [St
|
||||
}
|
||||
`
|
||||
|
||||
export const PHP_INIT_CODE = `<?php
|
||||
export let PHP_INIT_CODE = `<?php
|
||||
|
||||
// remove the first // of the following lines to specify packages to install using composer
|
||||
// // require:
|
||||
@@ -253,7 +313,7 @@ function main(
|
||||
}
|
||||
`
|
||||
|
||||
export const RUST_INIT_CODE = `//! Add dependencies in the following partial Cargo.toml manifest
|
||||
export let RUST_INIT_CODE = `//! Add dependencies in the following partial Cargo.toml manifest
|
||||
//!
|
||||
//! \`\`\`cargo
|
||||
//! [dependencies]
|
||||
@@ -288,7 +348,7 @@ fn main(who_to_greet: String, numbers: Vec<i8>) -> anyhow::Result<Ret> {
|
||||
}
|
||||
`
|
||||
|
||||
export const FETCH_INIT_CODE = `export async function main(
|
||||
export let FETCH_INIT_CODE = `export async function main(
|
||||
url: string | undefined,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' = 'GET',
|
||||
body: Object = {},
|
||||
@@ -319,7 +379,7 @@ export const FETCH_INIT_CODE = `export async function main(
|
||||
})
|
||||
}`
|
||||
|
||||
export const BASH_INIT_CODE = `# shellcheck shell=bash
|
||||
export let BASH_INIT_CODE = `# shellcheck shell=bash
|
||||
# arguments of the form X="$I" are parsed as parameters X of type string
|
||||
msg="$1"
|
||||
dflt="\${2:-default value}"
|
||||
@@ -329,7 +389,7 @@ dflt="\${2:-default value}"
|
||||
echo "Hello $msg"
|
||||
`
|
||||
|
||||
export const DENO_INIT_CODE_TRIGGER = `import * as wmill from "npm:windmill-client@${__pkg__.version}"
|
||||
export let DENO_INIT_CODE_TRIGGER = `import * as wmill from "npm:windmill-client@${__pkg__.version}"
|
||||
|
||||
export async function main() {
|
||||
|
||||
@@ -350,7 +410,7 @@ export async function main() {
|
||||
}
|
||||
`
|
||||
|
||||
export const BUN_INIT_CODE_TRIGGER = `import * as wmill from "windmill-client"
|
||||
export let BUN_INIT_CODE_TRIGGER = `import * as wmill from "windmill-client"
|
||||
|
||||
export async function main() {
|
||||
|
||||
@@ -371,7 +431,7 @@ export async function main() {
|
||||
}
|
||||
`
|
||||
|
||||
export const GO_INIT_CODE_TRIGGER = `package inner
|
||||
export let GO_INIT_CODE_TRIGGER = `package inner
|
||||
|
||||
import (
|
||||
wmill "github.com/windmill-labs/windmill-go-client"
|
||||
@@ -395,7 +455,7 @@ func main() (interface{}, error) {
|
||||
}
|
||||
`
|
||||
|
||||
export const DENO_INIT_CODE_APPROVAL = `import * as wmill from "npm:windmill-client@^1.158.2"
|
||||
export let DENO_INIT_CODE_APPROVAL = `import * as wmill from "npm:windmill-client@^1.158.2"
|
||||
|
||||
export async function main(approver?: string) {
|
||||
const urls = await wmill.getResumeUrls(approver)
|
||||
@@ -423,7 +483,7 @@ export async function main(approver?: string) {
|
||||
// add a form in Advanced - Suspend
|
||||
// all on approval steps: https://www.windmill.dev/docs/flows/flow_approval`
|
||||
|
||||
export const BUN_INIT_CODE_APPROVAL = `import * as wmill from "windmill-client@^1.158.2"
|
||||
export let BUN_INIT_CODE_APPROVAL = `import * as wmill from "windmill-client@^1.158.2"
|
||||
|
||||
export async function main(approver?: string) {
|
||||
const urls = await wmill.getResumeUrls(approver)
|
||||
@@ -451,7 +511,7 @@ export async function main(approver?: string) {
|
||||
// add a form in Advanced - Suspend
|
||||
// all on approval steps: https://www.windmill.dev/docs/flows/flow_approval`
|
||||
|
||||
export const BUN_PREPROCESSOR_MODULE_CODE = `
|
||||
export let BUN_PREPROCESSOR_MODULE_CODE = `
|
||||
export async function preprocessor(
|
||||
wm_trigger: {
|
||||
kind: 'http' | 'email' | 'webhook',
|
||||
@@ -472,7 +532,7 @@ export async function preprocessor(
|
||||
}
|
||||
`
|
||||
|
||||
export const DENO_PREPROCESSOR_MODULE_CODE = `
|
||||
export let DENO_PREPROCESSOR_MODULE_CODE = `
|
||||
export async function preprocessor(
|
||||
wm_trigger: {
|
||||
kind: 'http' | 'email' | 'wehbook',
|
||||
@@ -493,7 +553,7 @@ export async function preprocessor(
|
||||
}
|
||||
`
|
||||
|
||||
export const PYTHON_INIT_CODE_APPROVAL = `import wmill
|
||||
export let PYTHON_INIT_CODE_APPROVAL = `import wmill
|
||||
|
||||
def main():
|
||||
urls = wmill.get_resume_urls()
|
||||
@@ -520,7 +580,7 @@ def main():
|
||||
# add a form in Advanced - Suspend
|
||||
# all on approval steps: https://www.windmill.dev/docs/flows/flow_approval`
|
||||
|
||||
export const PYTHON_PREPROCESSOR_MODULE_CODE = `from typing import TypedDict, Literal
|
||||
export let PYTHON_PREPROCESSOR_MODULE_CODE = `from typing import TypedDict, Literal
|
||||
|
||||
class Http(TypedDict):
|
||||
route: str # The route path, e.g. "/users/:id"
|
||||
@@ -543,7 +603,7 @@ def preprocessor(
|
||||
}
|
||||
`
|
||||
|
||||
export const DOCKER_INIT_CODE = `# shellcheck shell=bash
|
||||
export let DOCKER_INIT_CODE = `# shellcheck shell=bash
|
||||
# Bash script that calls docker as a client to the host daemon
|
||||
# See documentation: https://www.windmill.dev/docs/advanced/docker
|
||||
msg="\${1:-world}"
|
||||
@@ -556,7 +616,7 @@ docker pull $IMAGE
|
||||
docker run --rm $IMAGE $COMMAND
|
||||
`
|
||||
|
||||
export const POWERSHELL_INIT_CODE = `param($Msg, $Dflt = "default value", [int]$Nb = 3)
|
||||
export let POWERSHELL_INIT_CODE = `param($Msg, $Dflt = "default value", [int]$Nb = 3)
|
||||
|
||||
# Import-Module MyModule
|
||||
|
||||
@@ -567,7 +627,7 @@ export const POWERSHELL_INIT_CODE = `param($Msg, $Dflt = "default value", [int]$
|
||||
# the last line of the stdout is the return value
|
||||
Write-Output "Hello $Msg"`
|
||||
|
||||
export const ANSIBLE_PLAYBOOK_INIT_CODE = `---
|
||||
export let ANSIBLE_PLAYBOOK_INIT_CODE = `---
|
||||
inventory:
|
||||
- resource_type: ansible_inventory
|
||||
# You can pin an inventory to this script by hardcoding the resource path:
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
Badge,
|
||||
Loader2,
|
||||
GitFork,
|
||||
Play,
|
||||
History,
|
||||
Columns,
|
||||
Pen,
|
||||
@@ -182,6 +183,19 @@
|
||||
label: `View runs`,
|
||||
buttonProps: {
|
||||
href: `${base}/runs/${flow.path}`,
|
||||
size: 'xs',
|
||||
color: 'light',
|
||||
startIcon: Play
|
||||
}
|
||||
})
|
||||
|
||||
buttons.push({
|
||||
label: `History`,
|
||||
buttonProps: {
|
||||
onClick: () => {
|
||||
flowHistory?.open()
|
||||
},
|
||||
|
||||
size: 'xs',
|
||||
color: 'light',
|
||||
startIcon: History
|
||||
@@ -442,7 +456,7 @@
|
||||
{/if}
|
||||
<div class="flex flex-row gap-x-2 flex-wrap items-center">
|
||||
<span class="text-sm text-tertiary">
|
||||
Edited <TimeAgo withDate date={flow.edited_at ?? ''} /> by {flow.edited_by}
|
||||
Edited <TimeAgo date={flow.edited_at ?? ''} /> by {flow.edited_by}
|
||||
</span>
|
||||
|
||||
{#if flow.archived}
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
type WorkflowStatus,
|
||||
type NewScript,
|
||||
ConcurrencyGroupsService,
|
||||
|
||||
MetricsService
|
||||
|
||||
} from '$lib/gen'
|
||||
import {
|
||||
canWrite,
|
||||
@@ -97,7 +95,7 @@
|
||||
let job: Job | undefined
|
||||
let jobUpdateLastFetch: Date | undefined
|
||||
|
||||
let scriptProgress: number | undefined = undefined;
|
||||
let scriptProgress: number | undefined = undefined
|
||||
let currentJobIsLongRunning: boolean = false
|
||||
|
||||
let viewTab: 'result' | 'logs' | 'code' | 'stats' = 'result'
|
||||
@@ -203,20 +201,20 @@
|
||||
async function onJobLoaded() {
|
||||
// We want to set up scriptProgress once job is loaded
|
||||
// We need this to show progress bar if job has progress and is finished
|
||||
if (job && job.type == "CompletedJob"){
|
||||
if (job && job.type == 'CompletedJob') {
|
||||
// If error occured and job is completed
|
||||
// than we fetch progress from server to display on what progress did it fail
|
||||
// Could be displayed after run or as a historical page
|
||||
// If opening page without running job (e.g. reloading page after run) progress will be displayed instantly
|
||||
MetricsService.getJobProgress({
|
||||
workspace: job.workspace_id ?? "NO_WORKSPACE",
|
||||
id: job.id,
|
||||
}).then(progress => {
|
||||
// Returned progress is not always 100%, could be 65%, 33%, anything
|
||||
// Its ok if its a failure and we want to keep that value
|
||||
// But we want progress to be 100% if job has been succeeded
|
||||
scriptProgress = progress;
|
||||
});
|
||||
workspace: job.workspace_id ?? 'NO_WORKSPACE',
|
||||
id: job.id
|
||||
}).then((progress) => {
|
||||
// Returned progress is not always 100%, could be 65%, 33%, anything
|
||||
// Its ok if its a failure and we want to keep that value
|
||||
// But we want progress to be 100% if job has been succeeded
|
||||
scriptProgress = progress
|
||||
})
|
||||
}
|
||||
|
||||
if (job === undefined || job.job_kind !== 'script' || job.script_hash === undefined) {
|
||||
@@ -229,7 +227,6 @@
|
||||
if (script.restart_unless_cancelled ?? false) {
|
||||
persistentScriptDefinition = script
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$: {
|
||||
@@ -349,7 +346,7 @@
|
||||
|
||||
<TestJobLoader
|
||||
lazyLogs
|
||||
bind:scriptProgress
|
||||
bind:scriptProgress
|
||||
on:done={() => job?.['result'] != undefined && (viewTab = 'result')}
|
||||
bind:this={testJobLoader}
|
||||
bind:getLogs
|
||||
@@ -360,7 +357,7 @@
|
||||
bind:notfound
|
||||
/>
|
||||
|
||||
<Portal>
|
||||
<Portal name="persistent-run">
|
||||
<PersistentScriptDrawer bind:this={persistentScriptDrawer} />
|
||||
</Portal>
|
||||
|
||||
@@ -613,11 +610,7 @@
|
||||
<Button
|
||||
on:click|once={() => {
|
||||
$initialArgsStore = job?.args
|
||||
goto(
|
||||
`${stem}/edit/${
|
||||
job?.script_path
|
||||
}${isScript ? `` : `?nodraft=true`}`
|
||||
)
|
||||
goto(`${stem}/edit/${job?.script_path}${isScript ? `` : `?nodraft=true`}`)
|
||||
}}
|
||||
color="blue"
|
||||
size="sm"
|
||||
@@ -753,7 +746,7 @@
|
||||
<Skeleton loading={!job} layout={[[9.5]]} />
|
||||
{#if job}
|
||||
<FlowMetadata {job} {scheduleEditor} />
|
||||
{#if currentJobIsLongRunning && showExplicitProgressTip && !scriptProgress && 'running' in job}
|
||||
{#if currentJobIsLongRunning && showExplicitProgressTip && !scriptProgress && 'running' in job}
|
||||
<Alert
|
||||
class="mt-4 p-1 flex flex-row relative text-center"
|
||||
size="xs"
|
||||
@@ -801,7 +794,7 @@
|
||||
/>
|
||||
{/if}
|
||||
{#if scriptProgress}
|
||||
<JobProgressBar {job} {scriptProgress} class="py-4" hideStepTitle={true}/>
|
||||
<JobProgressBar {job} {scriptProgress} class="py-4" hideStepTitle={true} />
|
||||
{/if}
|
||||
<!-- Logs and outputs-->
|
||||
<div class="mr-2 sm:mr-0 mt-12">
|
||||
|
||||
@@ -660,8 +660,7 @@
|
||||
{/if}
|
||||
<div class="flex flex-row gap-x-2 flex-wrap items-center">
|
||||
<span class="text-sm text-tertiary">
|
||||
Edited <TimeAgo withDate date={script.created_at || ''} /> by {script.created_by ||
|
||||
'unknown'}
|
||||
Edited <TimeAgo date={script.created_at || ''} /> by {script.created_by || 'unknown'}
|
||||
</span>
|
||||
<Badge small color="gray">
|
||||
{truncateHash(script?.hash ?? '')}
|
||||
|
||||
@@ -629,7 +629,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Portal>
|
||||
<Portal name="workspace-settings">
|
||||
<S3FilePicker bind:this={s3FileViewer} readOnlyMode={false} fromWorkspaceSettings={true} />
|
||||
</Portal>
|
||||
|
||||
@@ -1053,9 +1053,12 @@
|
||||
{:else if tab == 'windmill_lfs'}
|
||||
<div class="flex flex-col gap-4 my-8">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class=" text-primary text-lg font-semibold">Workspace object storage (S3/Azure Blob)</div>
|
||||
<div class=" text-primary text-lg font-semibold"
|
||||
>Workspace object storage (S3/Azure Blob)</div
|
||||
>
|
||||
<div class="text-tertiary text-xs">
|
||||
Connect your Windmill workspace to your S3 bucket or your Azure Blob storage to enable users to read and write from S3 without having to have access to the credentials.
|
||||
Connect your Windmill workspace to your S3 bucket or your Azure Blob storage to enable
|
||||
users to read and write from S3 without having to have access to the credentials.
|
||||
<a
|
||||
href="https://www.windmill.dev/docs/core_concepts/object_storage_in_windmill#workspace-object-storage"
|
||||
target="_blank"
|
||||
@@ -1074,8 +1077,11 @@
|
||||
<Alert type="info" title="Logs storage is set at the instance level">
|
||||
This setting is only for storage of large files allowing to upload files directly to
|
||||
object storage using S3Object and use the wmill sdk to read and write large files backed
|
||||
by an object storage. Large-scale log management and distributed dependency caching is under <a href="https://www.windmill.dev/docs/core_concepts/object_storage_in_windmill#instance-object-storage" class="text-blue-500">Instance object storage</a>, set by the superadmins in the
|
||||
instance settings UI.
|
||||
by an object storage. Large-scale log management and distributed dependency caching is
|
||||
under <a
|
||||
href="https://www.windmill.dev/docs/core_concepts/object_storage_in_windmill#instance-object-storage"
|
||||
class="text-blue-500">Instance object storage</a
|
||||
>, set by the superadmins in the instance settings UI.
|
||||
</Alert>
|
||||
{/if}
|
||||
{#if s3ResourceSettings}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export function load() {
|
||||
return {
|
||||
stuff: { title: 'App Connection' }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user