mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat(frontend): add flowstatus and log component for apps
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { JobService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { onDestroy, tick } from 'svelte'
|
||||
import type { Preview } from '$lib/gen/models/Preview'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
if (lastStartedAt < startedAt) {
|
||||
lastStartedAt = startedAt
|
||||
if (testId) {
|
||||
dispatch('started', testId)
|
||||
try {
|
||||
await watchJob(testId)
|
||||
} catch {
|
||||
@@ -149,6 +150,7 @@
|
||||
isCompleted = true
|
||||
if (currentId === id) {
|
||||
job = { ...maybe_job, id }
|
||||
await tick()
|
||||
dispatch('done', job)
|
||||
currentId = undefined
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Job, JobService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { onDestroy, tick } from 'svelte'
|
||||
import type { Preview } from '$lib/gen/models/Preview'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
//only CompletedJob has success property
|
||||
isCompleted = true
|
||||
if (currentId === id) {
|
||||
await tick()
|
||||
dispatch('done', job)
|
||||
currentId = undefined
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
export let preclickAction: (() => Promise<void>) | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'buttoncomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let initializing: boolean | undefined = false
|
||||
export let errorHandledByComponent: boolean | undefined = false
|
||||
export let extraKey: string | undefined = undefined
|
||||
|
||||
@@ -38,7 +37,6 @@
|
||||
configuration
|
||||
)
|
||||
|
||||
$: initializing = resolvedConfig?.label == undefined
|
||||
$: errorHandledByComponent = resolvedConfig?.onError?.selected !== 'errorOverlay'
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import FlowStatusViewer from '$lib/components/FlowStatusViewer.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'flowstatuscomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
|
||||
$: css = concatCustomCss($app.css?.flowstatuscomponent, customCss)
|
||||
|
||||
let jobId: string | undefined
|
||||
</script>
|
||||
|
||||
<RunnableWrapper
|
||||
on:started={(e) => {
|
||||
jobId = e.detail
|
||||
}}
|
||||
{outputs}
|
||||
{render}
|
||||
{componentInput}
|
||||
{id}
|
||||
bind:initializing
|
||||
>
|
||||
<div class="flex flex-col w-full h-full">
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
|
||||
css?.header?.class
|
||||
)}
|
||||
style={css?.header?.style}
|
||||
>
|
||||
Flow Status
|
||||
</div>
|
||||
<div
|
||||
style={twMerge(
|
||||
$app.css?.['flowstatuscomponent']?.['container']?.style,
|
||||
customCss?.container?.style
|
||||
)}
|
||||
class={twMerge(
|
||||
'p-2 grow overflow-auto',
|
||||
$app.css?.['flowstatuscomponent']?.['container']?.class,
|
||||
customCss?.container?.class
|
||||
)}
|
||||
>
|
||||
{#if jobId}
|
||||
<FlowStatusViewer {jobId} />
|
||||
{:else}
|
||||
<span class="text-gray-600 text-xs">No flow</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import LogViewer from '$lib/components/LogViewer.svelte'
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import type { Job } from '$lib/gen'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'logcomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
|
||||
$: css = concatCustomCss($app.css?.logcomponent, customCss)
|
||||
|
||||
let testJobLoader: TestJobLoader | undefined = undefined
|
||||
let testIsLoading: boolean = false
|
||||
let testJob: Job | undefined = undefined
|
||||
</script>
|
||||
|
||||
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job={testJob} />
|
||||
|
||||
<RunnableWrapper
|
||||
on:started={(e) => {
|
||||
testJobLoader?.watchJob(e.detail)
|
||||
}}
|
||||
{outputs}
|
||||
{render}
|
||||
{componentInput}
|
||||
{id}
|
||||
bind:initializing
|
||||
>
|
||||
<div class="flex flex-col w-full h-full">
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
|
||||
css?.header?.class
|
||||
)}
|
||||
style={css?.header?.style}
|
||||
>
|
||||
Logs
|
||||
</div>
|
||||
<div
|
||||
style={twMerge($app.css?.['logcomponent']?.['container']?.style, customCss?.container?.style)}
|
||||
class={twMerge(
|
||||
'p-2 grow overflow-auto',
|
||||
$app.css?.['logcomponent']?.['container']?.class,
|
||||
customCss?.container?.class
|
||||
)}
|
||||
>
|
||||
<LogViewer content={testJob?.logs} isLoading={testIsLoading} />
|
||||
</div>
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -210,8 +210,6 @@
|
||||
return
|
||||
}
|
||||
|
||||
loading = true
|
||||
|
||||
try {
|
||||
let njob = await resultJobLoader?.abstractRun(() => {
|
||||
const nonStaticRunnableInputs = {}
|
||||
@@ -281,6 +279,7 @@
|
||||
}
|
||||
|
||||
async function setResult(res: any, jobId: string | undefined) {
|
||||
dispatch('done')
|
||||
const hasRes = res !== undefined && res !== null
|
||||
|
||||
if (transformer) {
|
||||
@@ -410,6 +409,10 @@
|
||||
{/if}
|
||||
|
||||
<ResultJobLoader
|
||||
on:started={(e) => {
|
||||
loading = true
|
||||
dispatch('started', e.detail)
|
||||
}}
|
||||
workspaceOverride={workspace}
|
||||
on:done={(e) => {
|
||||
lastJobId = e.detail.id
|
||||
|
||||
@@ -53,10 +53,6 @@
|
||||
const { staticExporter, noBackend, componentControl, runnableComponents } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: if (initializing && result != undefined) {
|
||||
initializing = false
|
||||
}
|
||||
|
||||
if (noBackend && componentInput?.type == 'runnable') {
|
||||
result = componentInput?.['value']
|
||||
}
|
||||
@@ -156,6 +152,8 @@
|
||||
wrapperClass={runnableClass}
|
||||
wrapperStyle={runnableStyle}
|
||||
{render}
|
||||
on:started
|
||||
on:done={() => (initializing = false)}
|
||||
on:success={() => handleSideEffect(true)}
|
||||
on:handleError={(e) => handleSideEffect(false, e.detail)}
|
||||
{outputs}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'rangecomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let initializing: boolean = true
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
let min = 0
|
||||
@@ -48,12 +47,7 @@
|
||||
<InputValue {id} input={configuration.min} bind:value={min} />
|
||||
<InputValue {id} input={configuration.max} bind:value={max} />
|
||||
<InputValue {id} input={configuration.defaultLow} bind:value={values[0]} />
|
||||
<InputValue
|
||||
on:done={() => (initializing = false)}
|
||||
{id}
|
||||
input={configuration.defaultHigh}
|
||||
bind:value={values[1]}
|
||||
/>
|
||||
<InputValue {id} input={configuration.defaultHigh} bind:value={values[1]} />
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'slidercomponent'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let initializing: boolean = true
|
||||
|
||||
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
let min = 0
|
||||
@@ -79,12 +78,7 @@
|
||||
<InputValue {id} input={configuration.step} bind:value={step} />
|
||||
<InputValue {id} input={configuration.min} bind:value={min} />
|
||||
<InputValue {id} input={configuration.max} bind:value={max} />
|
||||
<InputValue
|
||||
on:done={() => (initializing = false)}
|
||||
{id}
|
||||
input={configuration.defaultValue}
|
||||
bind:value={values[0]}
|
||||
/>
|
||||
<InputValue {id} input={configuration.defaultValue} bind:value={values[0]} />
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<AlignWrapper {render} {verticalAlignment}>
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
import AppConditionalWrapper from '../../components/layout/AppConditionalWrapper.svelte'
|
||||
import AppSelectStep from '../../components/inputs/AppSelectStep.svelte'
|
||||
import AppDownload from '../../components/display/AppDownload.svelte'
|
||||
import AppLogsComponent from '../../components/display/AppLogsComponent.svelte'
|
||||
import AppFlowStatusComponent from '../../components/display/AppFlowStatusComponent.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
@@ -138,6 +140,22 @@
|
||||
componentInput={component.componentInput}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'logcomponent'}
|
||||
<AppLogsComponent
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'flowstatuscomponent'}
|
||||
<AppFlowStatusComponent
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'barchartcomponent'}
|
||||
<AppBarChart
|
||||
configuration={component.configuration}
|
||||
@@ -237,7 +255,6 @@
|
||||
customCss={component.customCss}
|
||||
componentInput={component.componentInput}
|
||||
recomputeIds={component.recomputeIds}
|
||||
bind:initializing
|
||||
bind:errorHandledByComponent
|
||||
{render}
|
||||
/>
|
||||
@@ -368,7 +385,6 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'horizontaldividercomponent'}
|
||||
@@ -398,7 +414,6 @@
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
{render}
|
||||
bind:initializing
|
||||
/>
|
||||
{:else if component.type === 'tabscomponent' && component.tabs}
|
||||
<AppTabs
|
||||
|
||||
@@ -88,6 +88,8 @@ export type TableComponent = BaseComponent<'tablecomponent'> & {
|
||||
}
|
||||
export type AggridComponent = BaseComponent<'aggridcomponent'>
|
||||
export type DisplayComponent = BaseComponent<'displaycomponent'>
|
||||
export type LogComponent = BaseComponent<'logcomponent'>
|
||||
export type FlowStatusComponent = BaseComponent<'flowstatuscomponent'>
|
||||
export type ImageComponent = BaseComponent<'imagecomponent'>
|
||||
export type InputComponent = BaseComponent<'inputcomponent'>
|
||||
export type SelectComponent = BaseComponent<'selectcomponent'> & RecomputeOthersSource
|
||||
@@ -127,6 +129,8 @@ export type SelectStepComponent = BaseComponent<'selectstepcomponent'>
|
||||
|
||||
export type TypedComponent =
|
||||
| DisplayComponent
|
||||
| LogComponent
|
||||
| FlowStatusComponent
|
||||
| TextInputComponent
|
||||
| TextareaInputComponent
|
||||
| PasswordInputComponent
|
||||
@@ -298,7 +302,8 @@ const onSuccessClick = {
|
||||
fieldType: 'text',
|
||||
type: 'static',
|
||||
value: '',
|
||||
placeholder: 'Hello there'
|
||||
placeholder: 'Hello there',
|
||||
noVariablePicker: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,6 +404,42 @@ export const components = {
|
||||
configuration: {}
|
||||
}
|
||||
},
|
||||
logcomponent: {
|
||||
name: 'Log',
|
||||
icon: Monitor,
|
||||
dims: '2:8-6:8' as AppComponentDimensions,
|
||||
customCss: {
|
||||
header: { class: '', style: '' },
|
||||
container: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
configuration: {},
|
||||
componentInput: {
|
||||
type: 'runnable',
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
flowstatuscomponent: {
|
||||
name: 'Flow Status',
|
||||
icon: Monitor,
|
||||
dims: '2:8-6:8' as AppComponentDimensions,
|
||||
customCss: {
|
||||
header: { class: '', style: '' },
|
||||
container: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
configuration: {},
|
||||
componentInput: {
|
||||
type: 'runnable',
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
containercomponent: {
|
||||
name: 'Container',
|
||||
icon: BoxSelect,
|
||||
|
||||
@@ -57,7 +57,9 @@ const display: ComponentSet = {
|
||||
'mapcomponent',
|
||||
'htmlcomponent',
|
||||
'pdfcomponent',
|
||||
'displaycomponent'
|
||||
'displaycomponent',
|
||||
'logcomponent',
|
||||
'flowstatuscomponent'
|
||||
]
|
||||
} as const
|
||||
|
||||
|
||||
@@ -632,6 +632,14 @@ export const quickStyleProperties: Record<
|
||||
header: [...containerDefaultProps, typographyGrouping],
|
||||
container: containerDefaultProps
|
||||
},
|
||||
logcomponent: {
|
||||
header: [...containerDefaultProps, typographyGrouping],
|
||||
container: containerDefaultProps
|
||||
},
|
||||
flowstatuscomponent: {
|
||||
header: [...containerDefaultProps, typographyGrouping],
|
||||
container: containerDefaultProps
|
||||
},
|
||||
barchartcomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
|
||||
+3
-2
@@ -3,7 +3,7 @@
|
||||
import FlowModuleScript from '$lib/components/flows/content/FlowModuleScript.svelte'
|
||||
import FlowPathViewer from '$lib/components/flows/content/FlowPathViewer.svelte'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import { getContext, tick } from 'svelte'
|
||||
import type {
|
||||
ConnectedAppInput,
|
||||
RowAppInput,
|
||||
@@ -99,10 +99,11 @@
|
||||
variant="border"
|
||||
size="xs"
|
||||
startIcon={{ icon: faRefresh }}
|
||||
on:click={() => {
|
||||
on:click={async () => {
|
||||
sendUserToast('Refreshing inputs')
|
||||
refresh()
|
||||
$stateId = $stateId + 1
|
||||
await tick()
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
<div class="flex min-h-full flex-col min-w-[150px] w-full divide-y">
|
||||
{#if component.componentInput}
|
||||
<PanelSection
|
||||
title={componentSettings?.item.data.type
|
||||
title={componentSettings?.item.data.type == 'steppercomponent'
|
||||
? 'Validations'
|
||||
: hasInteraction
|
||||
? 'Event handler'
|
||||
@@ -205,8 +205,8 @@
|
||||
>
|
||||
<svelte:fragment slot="action">
|
||||
<Tooltip>
|
||||
The runnable inputs of a button component are not settable by the user. They
|
||||
must be defined statically or connected.
|
||||
The runnable inputs are inferred from the inputs of the flow or script
|
||||
parameters this component is attached to.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
placeholder={condition?.['placeholder']}
|
||||
customTitle={condition?.['customTitle']}
|
||||
displayType={false}
|
||||
noVariablePicker={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
export let placeholder: string | undefined
|
||||
export let customTitle: string | undefined = undefined
|
||||
export let displayType: boolean = false
|
||||
export let noVariablePicker: boolean | undefined = undefined
|
||||
|
||||
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -129,6 +130,7 @@
|
||||
{selectOptions}
|
||||
{format}
|
||||
{placeholder}
|
||||
noVariablePicker={noVariablePicker ?? false}
|
||||
bind:componentInput
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
fileUpload={meta?.['fileUpload']}
|
||||
placeholder={meta?.['placeholder']}
|
||||
customTitle={meta?.['customTitle']}
|
||||
noVariablePicker={meta?.['noVariablePicker']}
|
||||
{displayType}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
placeholder={config?.['placeholder']}
|
||||
onlyStatic={config?.['onlyStatic']}
|
||||
customTitle={config?.['customTitle']}
|
||||
noVariablePicker={config?.['noVariablePicker']}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
+6
-1
@@ -32,5 +32,10 @@
|
||||
{#if isScriptByPathDefined(appInput) || isScriptByNameDefined(appInput)}
|
||||
<SelectedRunnable {appComponent} bind:appInput />
|
||||
{:else if appInput !== undefined}
|
||||
<RunnableSelector {defaultUserInput} on:pick={(e) => onPick(e.detail)} />
|
||||
<RunnableSelector
|
||||
hideCreateScript={appComponent.type === 'flowstatuscomponent'}
|
||||
onlyFlow={appComponent.type === 'flowstatuscomponent'}
|
||||
{defaultUserInput}
|
||||
on:pick={(e) => onPick(e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
+25
-20
@@ -17,8 +17,9 @@
|
||||
|
||||
export let defaultUserInput = false
|
||||
export let hideCreateScript = false
|
||||
export let onlyFlow = false
|
||||
|
||||
let tab: Tab = 'inlinescripts'
|
||||
let tab: Tab = onlyFlow ? 'workspaceflows' : 'inlinescripts'
|
||||
let filter: string = ''
|
||||
let picker: Drawer
|
||||
|
||||
@@ -132,30 +133,34 @@
|
||||
<div>
|
||||
<div class="max-w-6xl">
|
||||
<Tabs bind:selected={tab}>
|
||||
<Tab size="sm" value="inlinescripts">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
Detached Inline Scripts
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="sm" value="workspacescripts">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
Workspace Scripts
|
||||
</div>
|
||||
</Tab>
|
||||
{#if !onlyFlow}
|
||||
<Tab size="sm" value="inlinescripts">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
Detached Inline Scripts
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="sm" value="workspacescripts">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
Workspace Scripts
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
<Tab size="sm" value="workspaceflows">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
Workspace Flows
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="sm" value="hubscripts">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Globe2 size={18} />
|
||||
Hub Scripts
|
||||
</div>
|
||||
</Tab>
|
||||
{#if !onlyFlow}
|
||||
<Tab size="sm" value="hubscripts">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Globe2 size={18} />
|
||||
Hub Scripts
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
</Tabs>
|
||||
<div class="my-2" />
|
||||
<div class="flex flex-col gap-y-16">
|
||||
@@ -202,6 +207,6 @@
|
||||
startIcon={{ icon: faMousePointer }}
|
||||
btnClasses="truncate w-full"
|
||||
>
|
||||
Select a script or flow
|
||||
{#if onlyFlow}Select a flow{:else}Select a script or flow{/if}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user