improve modularity for whitelabelling

This commit is contained in:
Ruben Fiszel
2024-08-21 15:37:26 +02:00
parent 22d7a2d2b6
commit d1c2e1f40b
17 changed files with 275 additions and 139 deletions
+1 -1
View File
@@ -1 +1 @@
export const base: string = import.meta.env.VITE_BASE_URL ?? ''
export let base: string = import.meta.env.VITE_BASE_URL ?? ''
+49 -38
View File
@@ -45,6 +45,7 @@
import CodeCompletionStatus from './copilot/CodeCompletionStatus.svelte'
import Popover from './Popover.svelte'
import ResourceEditorDrawer from './ResourceEditorDrawer.svelte'
import type { EditorBarUi } from './custom_ui'
export let lang: SupportedLanguage | 'bunnative' | undefined
export let editor: Editor | undefined
@@ -68,6 +69,7 @@
export let args: Record<string, any>
export let noHistory = false
export let saveToWorkspace = false
export let customUi: EditorBarUi = {}
let contextualVariablePicker: ItemPicker
let variablePicker: ItemPicker
@@ -313,6 +315,7 @@
{/if}
</DrawerContent>
</Drawer>
<ItemPicker
bind:this={contextualVariablePicker}
pickCallback={(path, name) => {
@@ -513,7 +516,7 @@ $res = json_decode(curl_exec($ch));`)
class="rounded-full w-2 h-2 mx-2 {validCode ? 'bg-green-300' : 'bg-red-300'}"
/>
<div class="flex items-center gap-0.5">
{#if showContextVarPicker}
{#if showContextVarPicker && customUi?.contextVar != false}
<Button
title="Add context variable"
color="light"
@@ -526,7 +529,7 @@ $res = json_decode(curl_exec($ch));`)
>+Context Var
</Button>
{/if}
{#if showVarPicker}
{#if showVarPicker && customUi?.variable != false}
<Button
title="Add variable"
color="light"
@@ -556,7 +559,7 @@ $res = json_decode(curl_exec($ch));`)
</Button>
{/if}
{#if showResourceTypePicker}
{#if showResourceTypePicker && customUi?.type != false}
<Button
title="Add resource type"
btnClasses="!font-medium text-tertiary"
@@ -584,37 +587,41 @@ $res = json_decode(curl_exec($ch));`)
Reset
</Button>
{#if lang == 'deno' || lang == 'python3' || lang == 'go' || lang == 'bash'}
<Button
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
on:click={() => editor?.reloadWebsocket()}
startIcon={{
icon: RotateCw,
classes: websocketAlive[lang] == false ? 'animate-spin' : ''
}}
title="Reload assistants"
>
{#if !iconOnly}
Assistants
{/if}
<span class="-my-1">
{#if lang == 'deno'}
(<span class={websocketAlive.deno ? 'green' : 'text-red-700'}>Deno</span>)
{:else if lang == 'go'}
(<span class={websocketAlive.go ? 'green' : 'text-red-700'}>Go</span>)
{:else if lang == 'python3'}
(<span class={websocketAlive.pyright ? 'green' : 'text-red-700'}>Pyright</span>
<span class={websocketAlive.ruff ? 'green' : 'text-red-700'}>Ruff</span>)
{:else if lang == 'bash'}
(<span class={websocketAlive.shellcheck ? 'green' : 'text-red-700'}>Shellcheck</span>)
{#if customUi?.assistants != false}
{#if lang == 'deno' || lang == 'python3' || lang == 'go' || lang == 'bash'}
<Button
btnClasses="!font-medium text-tertiary"
size="xs"
spacingSize="md"
color="light"
on:click={() => editor?.reloadWebsocket()}
startIcon={{
icon: RotateCw,
classes: websocketAlive[lang] == false ? 'animate-spin' : ''
}}
title="Reload assistants"
>
{#if !iconOnly}
Assistants
{/if}
</span>
</Button>
<span class="-my-1">
{#if lang == 'deno'}
(<span class={websocketAlive.deno ? 'green' : 'text-red-700'}>Deno</span>)
{:else if lang == 'go'}
(<span class={websocketAlive.go ? 'green' : 'text-red-700'}>Go</span>)
{:else if lang == 'python3'}
(<span class={websocketAlive.pyright ? 'green' : 'text-red-700'}>Pyright</span>
<span class={websocketAlive.ruff ? 'green' : 'text-red-700'}>Ruff</span>)
{:else if lang == 'bash'}
(<span class={websocketAlive.shellcheck ? 'green' : 'text-red-700'}>Shellcheck</span
>)
{/if}
</span>
</Button>
{/if}
{/if}
{#if collabMode}
{#if collabMode && customUi?.multiplayer != false}
<div class="flex items-center px-3">
<Toggle
options={{ right: '' }}
@@ -648,11 +655,15 @@ $res = json_decode(curl_exec($ch));`)
</div>
{/if}
<FormatOnSave />
<ScriptGen {editor} {diffEditor} {lang} {iconOnly} {args} />
<CodeCompletionStatus />
{#if customUi?.autoformatting != false}
<FormatOnSave />
{/if}
{#if customUi?.aiGen != false}
<ScriptGen {editor} {diffEditor} {lang} {iconOnly} {args} />
{/if}
{#if customUi?.aiFix != false}
<CodeCompletionStatus />
{/if}
</div>
</div>
@@ -671,7 +682,7 @@ $res = json_decode(curl_exec($ch));`)
History
</Button>
{/if}
{#if SCRIPT_EDITOR_SHOW_EXPLORE_OTHER_SCRIPTS}
{#if SCRIPT_EDITOR_SHOW_EXPLORE_OTHER_SCRIPTS && customUi?.library != false}
<Button
btnClasses="!font-medium text-tertiary"
size="xs"
+18 -15
View File
@@ -80,7 +80,7 @@
import { twMerge } from 'tailwind-merge'
import CustomPopover from './CustomPopover.svelte'
import Summary from './Summary.svelte'
import type { WhitelabelCustomUi } from './custom_ui'
import type { FlowBuilderWhitelabelCustomUi } from './custom_ui'
export let initialPath: string = ''
export let pathStoreInit: string | undefined = undefined
@@ -96,7 +96,7 @@
})
| undefined = undefined
export let diffDrawer: DiffDrawer | undefined = undefined
export let customUi: WhitelabelCustomUi = {}
export let customUi: FlowBuilderWhitelabelCustomUi = {}
$: setContext('customUi', customUi)
@@ -403,7 +403,8 @@
testStepStore,
saveDraft,
initialPath,
flowInputsStore: writable<FlowInput>({})
flowInputsStore: writable<FlowInput>({}),
customUi
})
async function loadSchedule() {
@@ -489,18 +490,20 @@
onClick: () => void
}> = []
if (savedFlow?.draft_only === false || savedFlow?.draft_only === undefined) {
dropdownItems.push({
label: 'Exit & see details',
onClick: () => dispatch('details', $pathStore)
})
}
if (customUi.topBar?.extraDeployOptions != false) {
if (savedFlow?.draft_only === false || savedFlow?.draft_only === undefined) {
dropdownItems.push({
label: 'Exit & see details',
onClick: () => dispatch('details', $pathStore)
})
}
if (!newFlow) {
dropdownItems.push({
label: 'Fork',
onClick: () => window.open(`/flows/add?template=${initialPath}`)
})
if (!newFlow) {
dropdownItems.push({
label: 'Fork',
onClick: () => window.open(`/flows/add?template=${initialPath}`)
})
}
}
let flowCopilotContext: FlowCopilotContext = {
@@ -1026,7 +1029,7 @@
$: onCustomUiChange(customUi)
function onCustomUiChange(customUi: WhitelabelCustomUi | undefined) {
function onCustomUiChange(customUi: FlowBuilderWhitelabelCustomUi | undefined) {
moreItems = [
...(customUi?.topBar?.history != false
? [
@@ -12,6 +12,9 @@
export let flowStateStore: Writable<FlowState> = writable({})
export let selectedJobStep: string | undefined = undefined
export let hideFlowResult = false
export let hideTimeline = false
export let hideDownloadInGraph = false
export let hideNodeDefinition = false
export let isOwner = false
export let wideResults = false
@@ -23,7 +26,10 @@
setContext<FlowStatusViewerContext>('FlowStatusViewer', {
flowStateStore,
suspendStatus,
retryStatus
retryStatus,
hideDownloadInGraph,
hideNodeDefinition,
hideTimeline
})
function loadOwner(path: string) {
@@ -32,8 +32,14 @@
const dispatch = createEventDispatcher()
let { flowStateStore, retryStatus, suspendStatus } =
getContext<FlowStatusViewerContext>('FlowStatusViewer')
let {
flowStateStore,
retryStatus,
suspendStatus,
hideDownloadInGraph,
hideTimeline,
hideNodeDefinition
} = getContext<FlowStatusViewerContext>('FlowStatusViewer')
export let jobId: string
export let workspaceId: string | undefined = undefined
@@ -924,7 +930,7 @@
</div>
<FlowGraphV2
download
download={!hideDownloadInGraph}
minHeight={wrapperHeight}
success={jobId != undefined && isSuccess(job?.['success'])}
flowModuleStates={$localModuleStates}
@@ -964,9 +970,14 @@
class="border-l border-tertiary-inverse pt-1 overflow-auto min-h-[700px] flex flex-col z-0 h-full"
>
<Tabs bind:selected={rightColumnSelect}>
<Tab value="timeline"><span class="font-semibold text-md">Timeline</span></Tab>
{#if !hideTimeline}
<Tab value="timeline"><span class="font-semibold text-md">Timeline</span></Tab>
{/if}
<Tab value="node_status"><span class="font-semibold">Node status</span></Tab>
<Tab value="node_definition"><span class="font-semibold">Node definition</span></Tab>
{#if !hideNodeDefinition}
<Tab value="node_definition"><span class="font-semibold">Node definition</span></Tab
>
{/if}
{#if Object.keys(job?.flow_status?.user_states ?? {}).length > 0}
<Tab value="user_states"><span class="font-semibold">User States</span></Tab>
{/if}
@@ -33,6 +33,7 @@
export let tab: 'ui' | 'raw' | 'schema' = noGraph ? 'schema' : 'ui'
export let noSummary = false
export let noGraphDownload = false
let rawType: 'json' | 'yaml' = 'yaml'
@@ -88,7 +89,7 @@
<div class="text-secondary text-xs italic mb-4">No inputs</div>
{/if}
<FlowGraphViewer download {noSide} {flow} overflowAuto />
<FlowGraphViewer download={!noGraphDownload} {noSide} {flow} overflowAuto />
</div>
</TabContent>
<TabContent value="raw"
@@ -67,6 +67,7 @@
import { createEventDispatcher } from 'svelte'
import CustomPopover from './CustomPopover.svelte'
import Summary from './Summary.svelte'
import type { ScriptBuilderWhitelabelCustomUi } from './custom_ui'
export let script: NewScript
export let fullyLoaded: boolean = true
@@ -81,6 +82,7 @@
export let disableHistoryChange = false
export let replaceStateFn: (url: string) => void = (url) =>
window.history.replaceState(null, '', url)
export let customUi: ScriptBuilderWhitelabelCustomUi = {}
let metadataOpen =
showMeta ||
@@ -423,7 +425,7 @@
function computeDropdownItems(initialPath: string) {
let dropdownItems: { label: string; onClick: () => void }[] =
initialPath != ''
initialPath != '' && customUi?.topBar?.extraDeployOptions != false
? [
{
label: 'Deploy & Stay here',
@@ -1050,32 +1052,34 @@
{$scheduleStore.cron ?? ''}
</Button>
{/if}
<div class="flex justify-start w-full border rounded-md overflow-hidden">
<div>
<button
on:click={async () => {
metadataOpen = true
}}
>
<Badge
color="gray"
class="center-center !bg-surface-secondary !text-tertiary !h-[28px] !w-[70px] rounded-none hover:!bg-surface-hover transition-all"
{#if customUi?.topBar?.path != false}
<div class="flex justify-start w-full border rounded-md overflow-hidden">
<div>
<button
on:click={async () => {
metadataOpen = true
}}
>
<Pen size={12} class="mr-2" /> Path
</Badge>
</button>
<Badge
color="gray"
class="center-center !bg-surface-secondary !text-tertiary !h-[28px] !w-[70px] rounded-none hover:!bg-surface-hover transition-all"
>
<Pen size={12} class="mr-2" /> Path
</Badge>
</button>
</div>
<input
type="text"
readonly
value={script.path}
size={script.path?.length || 50}
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none !border-0 !shadow-none"
on:focus={({ currentTarget }) => {
currentTarget.select()
}}
/>
</div>
<input
type="text"
readonly
value={script.path}
size={script.path?.length || 50}
class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none !border-0 !shadow-none"
on:focus={({ currentTarget }) => {
currentTarget.select()
}}
/>
</div>
{/if}
</div>
{#if $enterpriseLicense && initialPath != ''}
@@ -1106,17 +1110,19 @@
<span class="hidden lg:flex"> Diff </span>
</div>
</Button>
<Button
color="light"
variant="border"
size="xs"
on:click={() => {
metadataOpen = true
}}
startIcon={{ icon: Settings }}
>
<span class="hidden lg:flex"> Settings </span>
</Button>
{#if customUi?.topBar?.settings != false}
<Button
color="light"
variant="border"
size="xs"
on:click={() => {
metadataOpen = true
}}
startIcon={{ icon: Settings }}
>
<span class="hidden lg:flex"> Settings </span>
</Button>
{/if}
<Button
loading={loadingDraft}
size="xs"
@@ -1170,6 +1176,7 @@
</div>
<ScriptEditor
{customUi}
collabMode
edit={initialPath != ''}
on:format={() => {
@@ -23,6 +23,7 @@
import DiffEditor from './DiffEditor.svelte'
import { Clipboard, CornerDownLeft, Github, Play } from 'lucide-svelte'
import { setLicense } from '$lib/enterpriseUtils'
import type { ScriptEditorWhitelabelCustomUi } from './custom_ui'
// Exported
export let schema: Schema | any = emptySchema()
@@ -43,6 +44,7 @@
export let noHistory = false
export let saveToWorkspace = false
export let watchChanges = false
export let customUi: ScriptEditorWhitelabelCustomUi = {}
let websocketAlive = {
pyright: false,
@@ -243,6 +245,7 @@
setCollaborationMode()
}
}}
customUi={customUi?.editorBar}
collabLive={wsProvider?.shouldConnect}
{collabMode}
{validCode}
@@ -260,7 +263,7 @@
{noHistory}
{saveToWorkspace}
/>
{#if !noSyncFromGithub}
{#if !noSyncFromGithub && customUi?.editorBar?.useVsCode != false}
<div class="py-1">
<Button
target="_blank"
+38 -1
View File
@@ -1,6 +1,6 @@
import type { SupportedLanguage } from '$lib/common'
export type WhitelabelCustomUi = {
export type FlowBuilderWhitelabelCustomUi = {
topBar?: {
path?: boolean
export?: boolean
@@ -8,8 +8,18 @@ export type WhitelabelCustomUi = {
aiBuilder?: boolean
tutorials?: boolean
diff?: boolean
extraDeployOptions?: boolean
}
settingsPanel?: boolean
settingsTabs?: {
schedule?: boolean
sharedDiretory?: boolean
earlyStop?: boolean
earlyReturn?: boolean
workerGroup?: boolean
concurrency?: boolean
cache?: boolean
}
triggers?: boolean
flowNode?: boolean
hub?: boolean
@@ -18,4 +28,31 @@ export type WhitelabelCustomUi = {
stepAdvancedSettings?: boolean
languages?: (SupportedLanguage | 'docker' | 'bunnative')[]
scriptFork?: boolean
editorBar?: EditorBarUi
}
export type EditorBarUi = {
contextVar?: boolean
variable?: boolean
type?: boolean
assistants?: boolean
multiplayer?: boolean
autoformatting?: boolean
aiGen?: boolean
aiFix?: boolean
library?: boolean
useVsCode?: boolean
}
export type ScriptEditorWhitelabelCustomUi = {
editorBar?: EditorBarUi
}
export type ScriptBuilderWhitelabelCustomUi = {
topBar?: {
path?: boolean
settings?: boolean
extraDeployOptions?: boolean
}
editorBar?: EditorBarUi
}
@@ -17,7 +17,7 @@
import { defaultScriptLanguages, processLangs } from '$lib/scripts'
import type { SupportedLanguage } from '$lib/common'
import DefaultScripts from '$lib/components/DefaultScripts.svelte'
import type { WhitelabelCustomUi } from '$lib/components/custom_ui'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
export let failureModule: boolean
export let shouldDisableTriggerScripts: boolean = false
@@ -55,7 +55,7 @@
return kind == 'script' && !failureModule
}
let customUi: undefined | WhitelabelCustomUi = getContext('customUi')
let customUi: undefined | FlowBuilderWhitelabelCustomUi = getContext('customUi')
</script>
<div class="p-4 h-full flex flex-col" id="flow-editor-flow-inputs">
@@ -55,7 +55,8 @@
flowStore,
pathStore,
saveDraft,
flowInputsStore
flowInputsStore,
customUi
} = getContext<FlowEditorContext>('FlowEditorContext')
export let flowModule: FlowModule
@@ -265,6 +266,7 @@
{#if flowModule.value.type === 'rawscript' && !noEditor}
<div class="border-b-2 shadow-sm px-1">
<EditorBar
customUi={customUi?.editorBar}
{validCode}
{editor}
{diffEditor}
@@ -19,14 +19,14 @@
import { sendUserToast } from '$lib/utils'
import { workerTags } from '$lib/stores'
import { getLatestHashForScript } from '$lib/scripts'
import type { WhitelabelCustomUi } from '$lib/components/custom_ui'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
export let module: FlowModule
const { scriptEditorDrawer, flowStore, selectedId } =
getContext<FlowEditorContext>('FlowEditorContext')
const dispatch = createEventDispatcher()
let customUi: undefined | WhitelabelCustomUi = getContext('customUi')
let customUi: undefined | FlowBuilderWhitelabelCustomUi = getContext('customUi')
loadWorkerGroups()
@@ -29,7 +29,7 @@
export let noEditor: boolean
const { selectedId, flowStore, initialPath, previewArgs, pathStore, schedule } =
const { selectedId, flowStore, initialPath, previewArgs, pathStore, schedule, customUi } =
getContext<FlowEditorContext>('FlowEditorContext')
let hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
@@ -52,22 +52,33 @@
<div class="h-full flex-1">
<Tabs bind:selected={$selectedId}>
<Tab value="settings-metadata">Metadata</Tab>
{#if !noEditor}
{#if !noEditor && customUi?.settingsTabs?.schedule != false}
<Tab value="settings-schedule" active={$schedule.enabled}>Schedule</Tab>
{/if}
<Tab value="settings-same-worker" active={$flowStore.value.same_worker}>
Shared Directory
</Tab>
<Tab value="settings-early-stop" active={Boolean($flowStore.value.skip_expr)}>
Early Stop
</Tab>
<Tab value="settings-early-return" active={Boolean($flowStore.value.early_return)}>
Early Return
</Tab>
<Tab value="settings-worker-group">Worker Group</Tab>
<Tab value="settings-concurrency">Concurrency</Tab>
<Tab value="settings-cache" active={Boolean($flowStore.value.cache_ttl)}>Cache</Tab>
{#if customUi?.settingsTabs?.sharedDiretory != false}
<Tab value="settings-same-worker" active={$flowStore.value.same_worker}>
Shared Directory
</Tab>
{/if}
{#if customUi?.settingsTabs?.earlyStop != false}
<Tab value="settings-early-stop" active={Boolean($flowStore.value.skip_expr)}>
Early Stop
</Tab>
{/if}
{#if customUi?.settingsTabs?.earlyReturn != false}
<Tab value="settings-early-return" active={Boolean($flowStore.value.early_return)}>
Early Return
</Tab>
{/if}
{#if customUi?.settingsTabs?.workerGroup != false}
<Tab value="settings-worker-group">Worker Group</Tab>
{/if}
{#if customUi?.settingsTabs?.concurrency != false}
<Tab value="settings-concurrency">Concurrency</Tab>
{/if}
{#if customUi?.settingsTabs?.cache != false}
<Tab value="settings-cache" active={Boolean($flowStore.value.cache_ttl)}>Cache</Tab>
{/if}
<svelte:fragment slot="content">
<TabContent value="settings-metadata" class="p-4 h-full overflow-auto">
<Section label="Metadata">
@@ -5,7 +5,7 @@
import StepGen from '$lib/components/copilot/StepGen.svelte'
import type { FlowModule } from '$lib/gen'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import type { WhitelabelCustomUi } from '$lib/components/custom_ui'
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
import { twMerge } from 'tailwind-merge'
const dispatch = createEventDispatcher()
@@ -18,7 +18,7 @@
export let disableAi = false
$: !open && (funcDesc = '')
let customUi: undefined | WhitelabelCustomUi = getContext('customUi')
let customUi: undefined | FlowBuilderWhitelabelCustomUi = getContext('customUi')
</script>
<Menu
@@ -4,6 +4,7 @@ import type { Writable } from 'svelte/store'
import type ScriptEditorDrawer from './content/ScriptEditorDrawer.svelte'
import type { FlowState } from './flowState'
import type { Schedule } from './scheduleUtils'
import type { FlowBuilderWhitelabelCustomUi } from '../custom_ui'
export type FlowInput = Record<
string,
@@ -39,4 +40,5 @@ export type FlowEditorContext = {
saveDraft: () => void
initialPath: string
flowInputsStore: Writable<FlowInput>
customUi: FlowBuilderWhitelabelCustomUi
}
@@ -33,6 +33,9 @@ export type FlowStatusViewerContext = {
flowStateStore?: Writable<FlowState>
retryStatus: Writable<Record<string, number | undefined>>
suspendStatus: Writable<Record<string, { nb: number; job: Job }>>
hideDownloadInGraph?: boolean
hideTimeline?: boolean
hideNodeDefinition?: boolean
}
export type GraphModuleState = {
type: FlowStatusModule['type']
+57 -18
View File
@@ -1,22 +1,61 @@
<script lang="ts">
import EditableSchemaWrapper from '$lib/components/schema/EditableSchemaWrapper.svelte'
// import EditableSchemaWrapper from '$lib/components/schema/EditableSchemaWrapper.svelte'
let schema = {
type: 'object',
properties: {
foo: {
type: 'string',
description: '',
required: [],
nullable: false,
default: '',
disableVariablePicker: false,
disableCreate: false,
password: false
}
},
order: ['foo']
}
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
import ScriptBuilder from '$lib/components/ScriptBuilder.svelte'
import type { OpenFlow } from '$lib/gen'
import { writable, type Writable } from 'svelte/store'
// let schema = {
// type: 'object',
// properties: {
// foo: {
// type: 'string',
// description: '',
// required: [],
// nullable: false,
// default: '',
// disableVariablePicker: false,
// disableCreate: false,
// password: false
// }
// },
// order: ['foo']
// }
// let flowStore: Writable<OpenFlow> = writable({
// summary: 'foo',
// value: {
// modules: []
// }
// })
// let flowStateStore: Writable<Record<string, any>> = writable({})
</script>
<EditableSchemaWrapper on:change={() => console.log('FOO')} {schema} />
<!-- <EditableSchemaWrapper on:change={() => console.log('FOO')} {schema} /> -->
<!-- <FlowBuilder
newFlow={false}
initialPath="u/admin/foo"
selectedId={undefined}
{flowStore}
{flowStateStore}
customUi={{
topBar: {
extraDeployOptions: false
},
settingsTabs: {
workerGroup: false
},
settingsPanel: false
}}
/> -->
<ScriptBuilder
script={{
summary: 'foo',
path: 'u/admin/foo',
description: 'foo',
language: 'python3',
content: 'print("foo")'
}}
/>