From 0d96cfb5a8a167c56c344d8851fb586bab8a18d4 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 8 Nov 2024 19:20:22 +0100 Subject: [PATCH 01/30] nit right panel flow props --- .../src/lib/components/flows/content/FlowInputsQuick.svelte | 2 +- .../lib/components/flows/propPicker/PropPickerWrapper.svelte | 1 + .../src/lib/components/propertyPicker/ObjectViewer.svelte | 5 ++++- frontend/src/lib/components/propertyPicker/PropPicker.svelte | 3 +++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte b/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte index bf61ab6433..4439009115 100644 --- a/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte +++ b/frontend/src/lib/components/flows/content/FlowInputsQuick.svelte @@ -307,7 +307,7 @@ {/if} {#if inlineScripts?.length > 0} -
+
New {selectedKind != 'script' ? selectedKind + ' ' : ''}script
diff --git a/frontend/src/lib/components/flows/propPicker/PropPickerWrapper.svelte b/frontend/src/lib/components/flows/propPicker/PropPickerWrapper.svelte index 47b8ab6032..41f05c1fb5 100644 --- a/frontend/src/lib/components/flows/propPicker/PropPickerWrapper.svelte +++ b/frontend/src/lib/components/flows/propPicker/PropPickerWrapper.svelte @@ -142,6 +142,7 @@ {alwaysOn} {displayContext} {error} + previousId={pickableProperties?.previousId} {pickableProperties} allowCopy={!notSelectable && !$propPickerConfig} on:select={({ detail }) => { diff --git a/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte b/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte index b9db54d62b..ffe0269b52 100644 --- a/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte +++ b/frontend/src/lib/components/propertyPicker/ObjectViewer.svelte @@ -20,6 +20,7 @@ export let allowCopy = true export let collapseLevel: number | undefined = undefined export let prefix = '' + export let expandedEvenOnLevel0: string | undefined = undefined let s3FileViewer: S3FilePicker @@ -124,7 +125,9 @@ {allowCopy} on:select {collapseLevel} - collapsed={collapseLevel !== undefined ? level + 1 >= collapseLevel : undefined} + collapsed={collapseLevel !== undefined + ? level + 1 >= collapseLevel && key != expandedEvenOnLevel0 + : undefined} /> {:else} diff --git a/frontend/src/lib/components/propertyPicker/PropPicker.svelte b/frontend/src/lib/components/propertyPicker/PropPicker.svelte index 62610d48fc..a83952578c 100644 --- a/frontend/src/lib/components/propertyPicker/PropPicker.svelte +++ b/frontend/src/lib/components/propertyPicker/PropPicker.svelte @@ -17,6 +17,7 @@ export let error: boolean = false export let allowCopy = false export let alwaysOn = false + export let previousId: string | undefined = undefined let variables: Record = {} let resources: Record = {} @@ -257,6 +258,7 @@ results
From 58194521b12f121b802428eb4e39b5d879f52f4a Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 8 Nov 2024 19:35:21 +0100 Subject: [PATCH 02/30] nit right panel flow props --- .../flows/content/FlowModuleComponent.svelte | 2 +- .../propertyPicker/PropPicker.svelte | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 35a8585af1..6eee352cc9 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -378,7 +378,7 @@ class={advancedSelected === 'runtime' ? 'h-[calc(100%-68px)]' : 'h-[calc(100%-34px)]'} > {#if selected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow')} -
+
-
-
+
+
-
+ + + @@ -399,5 +404,6 @@
{/if} {/if} -
+ +
From 2774d394ad2134eeb17a6d0c553602518881551e Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 8 Nov 2024 19:38:09 +0100 Subject: [PATCH 03/30] nit right panel flow props --- .../components/flows/content/FlowModuleComponent.svelte | 1 + .../src/lib/components/propertyPicker/PropPicker.svelte | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 6eee352cc9..790758ca0e 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -385,6 +385,7 @@ noPadding >
-
+
- + +
+
+ +
+ + +
+ + +
+
+ + Page {page} + +
+
+ +
+
+ + +
+ + + + + + + + + + + {#each alerts as { id, alert_type, message, created_at, acknowledged }} + {#if !hideAcknowledged || !acknowledged} + + + + + + + + {/if} + {/each} + +
TypeMessageCreated AtAcknowledge
+ {#if alert_type === 'recovered_critical_error'} + + + + {:else} + + + + {/if} + {message}{formatDate(created_at)} +
+ {#if !acknowledged} + + {:else} + + {/if} +
+
+
+ + {#if alerts.length === 0} +

No critical alerts available.

+ {/if} + diff --git a/frontend/src/lib/stores.ts b/frontend/src/lib/stores.ts index 18cf32b074..9d8af53bae 100644 --- a/frontend/src/lib/stores.ts +++ b/frontend/src/lib/stores.ts @@ -121,3 +121,5 @@ export type DBSchema = SQLSchema | GraphqlSchema export type DBSchemas = Partial> export const dbSchemas = writable({}) + +export const instanceSettingsSelectedTab = writable('Core') \ No newline at end of file diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index e5b0c3271d..99fc3c0a0c 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -14,7 +14,9 @@ import { classNames, getModifierKey } from '$lib/utils' import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte' import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte' + import CriticalAlertModal from '$lib/components/sidebar/CriticalAlertModal.svelte' import { + enterpriseLicense, copilotInfo, isPremiumStore, starStore, @@ -48,6 +50,7 @@ import MenuButton from '$lib/components/sidebar/MenuButton.svelte' import { setContext } from 'svelte' import { base } from '$app/paths' + import CriticalAlertButton from '$lib/components/sidebar/CriticalAlertButton.svelte' OpenAPI.WITH_CREDENTIALS = true let menuOpen = false @@ -275,6 +278,26 @@ } setContext('openSearchWithPrefilledText', openSearchModal) + + $: { + if ($enterpriseLicense && $superadmin) { + loadCriticalAlertsMuted() + } + } + + let numUnacknowledgedCriticalAlerts = 0 + let isCriticalAlertsModalOpen = false + let isCriticalAlertsUiMuted = false + + async function loadCriticalAlertsMuted() { + isCriticalAlertsUiMuted = (await SettingService.getGlobal({ + key: 'critical_alert_mute_ui' + })) as boolean + } + + function openCriticalAlertsModal(text?: string): void { + isCriticalAlertsModalOpen = true + } @@ -291,6 +314,12 @@ {:else if $userStore} {#if $superadmin} + {#if !isCriticalAlertsUiMuted && $enterpriseLicense} + + {/if} {/if}
@@ -356,7 +385,6 @@ Windmill
-
@@ -405,6 +433,17 @@ {/if}
+ {#if $superadmin && $enterpriseLicense} + openCriticalAlertsModal()} + {numUnacknowledgedCriticalAlerts} + {isCollapsed} + label="Critical Alerts" + class="!text-xs" + disabled={numUnacknowledgedCriticalAlerts === 0} + /> + {/if}
From de9a839af4c40a6c637ca564bf783ef2336a5074 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Fri, 8 Nov 2024 21:50:36 -0500 Subject: [PATCH 06/30] improve modal loading in critical alert ui (#4679) * critical alert ui * updating ui, backend logic * revert * type check fix npm * checking out cli files from main * moving alert icon * adding sqlx mock data * more sqlx changes * feat(frontend): nodes from flow can be connected directly in expr input through a plug icon (#4652) * Add flow prop picker # Conflicts: # frontend/src/lib/components/propertyPicker/PropPicker.svelte * fix unwanted copy * cleaning * Fix unset context * move button and always display input * fix unwanted proppicker display * update * update * clean all * clean all --------- Co-authored-by: Ruben Fiszel * replace hide/show with toggle * adding mutable setting and navigation to settings to configure channels * merge fix * ee non ee changees * auto-acknowledge when muted * pr comments * fix bad log * user inner modal component * update unaknowledge alerts after acknowledging from modal * aknowledge -> acknowledge * format * immediately check for alerts * immediately check for alerts * simplify loading of superadmin/ee * update modal logic --------- Co-authored-by: Guilhem Co-authored-by: Ruben Fiszel Co-authored-by: Ruben Fiszel --- .../sidebar/CriticalAlertModal.svelte | 5 ++++- .../sidebar/CriticalAlertModalInner.svelte | 18 ++++-------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/components/sidebar/CriticalAlertModal.svelte b/frontend/src/lib/components/sidebar/CriticalAlertModal.svelte index 9e306ea917..68d7bfbffb 100644 --- a/frontend/src/lib/components/sidebar/CriticalAlertModal.svelte +++ b/frontend/src/lib/components/sidebar/CriticalAlertModal.svelte @@ -3,6 +3,7 @@ import CriticalAlertModalInner from './CriticalAlertModalInner.svelte' import { SettingService } from '$lib/gen' import { sendUserToast } from '$lib/toast' + import Modal from '../common/modal/Modal.svelte' export let open: boolean = false export let numUnacknowledgedCriticalAlerts: number = 0 @@ -66,4 +67,6 @@ } - + + + diff --git a/frontend/src/lib/components/sidebar/CriticalAlertModalInner.svelte b/frontend/src/lib/components/sidebar/CriticalAlertModalInner.svelte index f1b6208962..34ff0ffa35 100644 --- a/frontend/src/lib/components/sidebar/CriticalAlertModalInner.svelte +++ b/frontend/src/lib/components/sidebar/CriticalAlertModalInner.svelte @@ -1,34 +1,24 @@ - +
{#if !hasCriticalAlertChannels}
@@ -210,4 +200,4 @@ {#if alerts.length === 0}

No critical alerts available.

{/if} - +
From 02170032af1195c1c6ef5c7d4d967a0588a11274 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 10 Nov 2024 00:05:57 +0100 Subject: [PATCH 07/30] fix python preprocessor indent --- frontend/src/lib/script_helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index 702e60310d..b3f43d2382 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -602,7 +602,7 @@ class Websocket(TypedDict): class WmTrigger(TypedDict): kind: Literal["http", "email", "webhook", "websocket"] http: Http | None - websocket: Websocket | None + websocket: Websocket | None def preprocessor( wm_trigger: WmTrigger, From d44976f35e45ade510d1ec220b5a1503e11f3db9 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 11 Nov 2024 10:05:18 +0100 Subject: [PATCH 08/30] feat: expandable subflows in flows (#4683) * all * nit right panel flow props * nits --- .../src/lib/components/FlowGraphViewer.svelte | 3 + .../components/FlowStatusViewerInner.svelte | 125 ++++++++++++++-- .../src/lib/components/FlowTimeline.svelte | 2 +- .../flows/content/FlowEditorPanel.svelte | 4 + .../flows/content/FlowModuleComponent.svelte | 8 +- .../flows/content/FlowModuleHeader.svelte | 28 ++++ .../flows/map/FlowModuleSchemaItem.svelte | 13 +- .../flows/map/FlowModuleSchemaMap.svelte | 4 +- .../lib/components/flows/map/MapItem.svelte | 1 - .../components/flows/map/VirtualItem.svelte | 2 +- .../lib/components/graph/FlowGraphV2.svelte | 85 +++-------- .../src/lib/components/graph/graphBuilder.ts | 137 ++++++++++++++---- .../graph/renderers/nodes/ModuleNode.svelte | 15 +- .../graph/renderers/nodes/SubflowBound.svelte | 45 ++++++ .../[job]/[resume]/[hmac]/+page.svelte | 1 + frontend/src/routes/view_graph/+page.svelte | 9 +- 16 files changed, 367 insertions(+), 115 deletions(-) create mode 100644 frontend/src/lib/components/graph/renderers/nodes/SubflowBound.svelte diff --git a/frontend/src/lib/components/FlowGraphViewer.svelte b/frontend/src/lib/components/FlowGraphViewer.svelte index 1ecef2943f..326e1275b6 100644 --- a/frontend/src/lib/components/FlowGraphViewer.svelte +++ b/frontend/src/lib/components/FlowGraphViewer.svelte @@ -7,6 +7,7 @@ import FlowGraphViewerStep from './FlowGraphViewerStep.svelte' import FlowGraphV2 from './graph/FlowGraphV2.svelte' import { dfs } from './flows/dfs' + import { workspaceStore } from '$lib/stores' export let flow: { summary: string @@ -22,6 +23,7 @@ export let noGraph = false export let triggerNode = false export let stepDetail: FlowModule | string | undefined = undefined + export let workspace: string | undefined = $workspaceStore const dispatch = createEventDispatcher() @@ -37,6 +39,7 @@ path={flow?.path} {download} minHeight={400} + {workspace} modules={flow?.value?.modules} failureModule={flow?.value?.failure_module} preprocessorModule={flow?.value?.preprocessor_module} diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte index ec6982a5df..2c9aa54461 100644 --- a/frontend/src/lib/components/FlowStatusViewerInner.svelte +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -29,6 +29,7 @@ import Alert from './common/alert/Alert.svelte' import FlowGraphViewerStep from './FlowGraphViewerStep.svelte' import FlowGraphV2 from './graph/FlowGraphV2.svelte' + import { buildPrefix } from './graph/graphBuilder' const dispatch = createEventDispatcher() @@ -74,6 +75,10 @@ export let wideResults = false export let hideFlowResult = false + export let workspace: string | undefined = $workspaceStore + export let prefix: string | undefined = undefined + export let subflowParentsGlobalModuleStates: Writable>[] = [] + export let subflowParentsDurationStatuses: Writable>[] = [] let jobResults: any[] = flowJobIds?.flowJobs?.map((x, id) => `iter #${id + 1} not loaded by frontend yet`) ?? [] @@ -83,6 +88,7 @@ let localModuleStates: Writable> = writable({}) let localDurationStatuses: Writable> = writable({}) + let expandedSubflows: Record = {} export let job: Job | undefined = undefined @@ -94,6 +100,24 @@ // } // } + function updateModuleStates( + moduleState: Writable>, + key: string, + newValue: GraphModuleState, + keepType: boolean | undefined + ) { + moduleState.update((x) => { + if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) { + newValue.type = x[key].type + } + x[key] = newValue + return x + }) + } + + function buildSubflowKey(key: string, prefix: string | undefined) { + return prefix ? 'subflow:' + prefix + key : key + } function setModuleState( key: string, value: Partial, @@ -102,15 +126,14 @@ ) { let newValue = { ...($localModuleStates[key] ?? {}), ...value } if (!deepEqual($localModuleStates[key], value) || force) { - ;[localModuleStates, ...globalModuleStates].forEach((s) => { - s.update((x) => { - if (keepType && (x[key]?.type == 'Success' || x[key]?.type == 'Failure')) { - newValue.type = x[key].type - } - x[key] = newValue - return x - }) - }) + ;[localModuleStates, ...globalModuleStates].forEach((s) => + updateModuleStates(s, key, newValue, keepType) + ) + if (prefix) { + subflowParentsGlobalModuleStates.forEach((s) => + updateModuleStates(s, buildSubflowKey(key, prefix), newValue, keepType) + ) + } } } @@ -124,6 +147,14 @@ return x }) }) + if (prefix) { + subflowParentsDurationStatuses.forEach((s) => { + s.update((x) => { + x[buildSubflowKey(key, prefix)].byJob[id] = value + return x + }) + }) + } } } @@ -131,15 +162,25 @@ if ($localDurationStatuses[modId] == undefined) { $localDurationStatuses[modId] = { byJob: {} } } - let prefixed = modId globalDurationStatuses.forEach((x) => x.update((x) => { - if (x[prefixed] == undefined) { - x[prefixed] = { byJob: {} } + if (x[modId] == undefined) { + x[modId] = { byJob: {} } } return x }) ) + if (prefix) { + subflowParentsDurationStatuses.forEach((x) => + x.update((x) => { + let key = buildSubflowKey(modId, prefix) + if (x[key] == undefined) { + x[key] = { byJob: {} } + } + return x + }) + ) + } } let innerModules: FlowStatusModule[] = [] @@ -368,7 +409,7 @@ if (globalRefreshes) { let modId = flowJobIds?.moduleId if (modId) { - globalRefreshes[modId] = async (loopJob) => { + globalRefreshes[buildSubflowKey(modId, prefix)] = async (loopJob) => { setIteration(loopJob.index, loopJob.job, false, modId ?? '') refresh(true, loopJob) } @@ -631,6 +672,36 @@ }) } } + + function allModulesForTimeline( + modules: FlowModule[], + expandedSubflows: Record + ): string[] { + const ids = dfs(modules, (x) => x.id) + + function rec(ids: string[], prefix: string | undefined): string[] { + return ids.concat( + ids.flatMap((id) => { + let fms = expandedSubflows[id] + let oid = id.split(':').pop() + if (!oid) { + return [] + } + let nprefix = buildPrefix(prefix, oid) + return fms + ? rec( + dfs(fms, (x) => + x.id.startsWith('subflow:') ? x.id : buildSubflowKey(x.id, nprefix) + ), + nprefix + ) + : [] + }) + ) + } + + return rec(ids, undefined) + } {#if notAnonynmous} @@ -803,6 +874,9 @@ job={storedListJobs[j]} globalModuleStates={[localModuleStates, ...globalModuleStates]} globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} render={forloop_selected == loopJobId && selected == 'sequence' && render} reducedPolling={flowJobIds?.flowJobs.length && flowJobIds?.flowJobs.length > 20} {workspaceId} @@ -880,6 +954,9 @@ {childFlow} globalModuleStates={[localModuleStates, ...globalModuleStates]} globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} render={failedRetry == retry_selected && render} reducedPolling={false} {workspaceId} @@ -895,6 +972,17 @@ bind:refresh={recursiveRefresh[mod.job ?? '']} globalModuleStates={[]} globalDurationStatuses={[]} + prefix={buildPrefix(prefix, mod.id ?? '')} + subflowParentsGlobalModuleStates={[ + localModuleStates, + ...globalModuleStates, + ...subflowParentsGlobalModuleStates + ]} + subflowParentsDurationStatuses={[ + localDurationStatuses, + ...globalDurationStatuses, + ...subflowParentsDurationStatuses + ]} render={selected == 'sequence' && render} {workspaceId} jobId={mod.job} @@ -915,6 +1003,9 @@ globalDurationStatuses={[localDurationStatuses, ...globalDurationStatuses]} render={selected == 'sequence' && render} {workspaceId} + {prefix} + {subflowParentsGlobalModuleStates} + {subflowParentsDurationStatuses} jobId={mod.job} innerModule={mod.flow_jobs ? job.raw_flow?.modules[i]?.value : undefined} flowJobIds={mod.flow_jobs @@ -973,6 +1064,7 @@ minHeight={wrapperHeight} success={jobId != undefined && isSuccess(job?.['success'])} flowModuleStates={$localModuleStates} + bind:expandedSubflows on:select={(e) => { if (rightColumnSelect != 'node_definition') { rightColumnSelect = 'node_status' @@ -1004,11 +1096,13 @@ selectedForloopIndex: detail.index }) globalRefreshes[detail.moduleId]?.({ job: detail.id, index: detail.index }) + // console.log('selectedIteration', prefix, detail.moduleId, globalRefreshes) }} modules={job.raw_flow?.modules ?? []} failureModule={job.raw_flow?.failure_module} preprocessorModule={job.raw_flow?.preprocessor_module} allowSimplifiedPoll={false} + {workspace} />
x.id)} + flowModules={allModulesForTimeline( + job?.raw_flow?.modules ?? [], + expandedSubflows ?? {} + )} durationStatuses={localDurationStatuses} /> {:else if rightColumnSelect == 'node_status'} diff --git a/frontend/src/lib/components/FlowTimeline.svelte b/frontend/src/lib/components/FlowTimeline.svelte index 3f918c8c6b..4027eca9c3 100644 --- a/frontend/src/lib/components/FlowTimeline.svelte +++ b/frontend/src/lib/components/FlowTimeline.svelte @@ -178,7 +178,7 @@ {/if}
-
{k}
+
{k.startsWith('subflow:') ? k.substring(8) : k}
{#if min && total}
diff --git a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte index 4993afa0f9..4da8380fa6 100644 --- a/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte +++ b/frontend/src/lib/components/flows/content/FlowEditorPanel.svelte @@ -78,6 +78,10 @@ newItem={newFlow} isFlow={true} /> +{:else if $selectedId.startsWith('subflow:')} +
Selected step is witin an expanded subflow and is not directly editable in the flow editor
{:else} {@const dup = checkDup($flowStore.value.modules)} {#if dup} diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 790758ca0e..97801d24ab 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -253,6 +253,10 @@ forceReload++ await reload(flowModule) } + if (flowModule.value.type == 'flow') { + forceReload++ + await reload(flowModule) + } }} on:createScriptFromInlineScript={async () => { const [module, state] = await createScriptFromInlineScript( @@ -361,7 +365,9 @@
{/if} {:else if flowModule.value.type === 'flow'} - + {#key forceReload} + + {/key} {/if} diff --git a/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte b/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte index 434cf2451f..a044fadada 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleHeader.svelte @@ -5,10 +5,12 @@ import { Bed, Database, + ExternalLink, Gauge, GitFork, Pen, PhoneIncoming, + RefreshCcw, Repeat, Save, Square, @@ -139,6 +141,32 @@ {/if} {/if} + {#if module.value.type === 'flow'} + + {/if} + {:else if id?.startsWith('subflow:')} + + {id.substring('subflow:'.length)} {/if}
diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index 28167493cf..220b597384 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -26,7 +26,7 @@ import { getDependentComponents } from '../flowExplorer' import type { FlowCopilotContext } from '$lib/components/copilot/flow' import { fade } from 'svelte/transition' - import { copilotInfo, tutorialsToDo } from '$lib/stores' + import { copilotInfo, tutorialsToDo, workspaceStore } from '$lib/stores' import FlowTutorials from '$lib/components/FlowTutorials.svelte' import { ignoredTutorials } from '$lib/components/tutorials/ignoredTutorials' @@ -44,6 +44,7 @@ export let disableSettings = false export let newFlow: boolean = false export let smallErrorHandler = false + export let workspace: string | undefined = $workspaceStore let flowTutorials: FlowTutorials | undefined = undefined @@ -345,6 +346,7 @@ preprocessorModule={$flowStore.value?.preprocessor_module} {selectedId} {flowInputsStore} + {workspace} on:delete={({ detail }) => { let e = detail.detail dependents = getDependentComponents(e.id, $flowStore) diff --git a/frontend/src/lib/components/flows/map/MapItem.svelte b/frontend/src/lib/components/flows/map/MapItem.svelte index ebd4ffdb9b..05cfc49d90 100644 --- a/frontend/src/lib/components/flows/map/MapItem.svelte +++ b/frontend/src/lib/components/flows/map/MapItem.svelte @@ -20,7 +20,6 @@ export let modules: FlowModule[] export let moving: string | undefined = undefined export let duration_ms: number | undefined = undefined - export let isTrigger: boolean = false export let retries: number | undefined = undefined export let flowJobs: diff --git a/frontend/src/lib/components/flows/map/VirtualItem.svelte b/frontend/src/lib/components/flows/map/VirtualItem.svelte index a2e022e9ef..3f5350b7ed 100644 --- a/frontend/src/lib/components/flows/map/VirtualItem.svelte +++ b/frontend/src/lib/components/flows/map/VirtualItem.svelte @@ -54,7 +54,7 @@
{preLabel}
{/if}
- {#if id && !hideId} + {#if id && !hideId && !id?.startsWith('subflow:')}
{id} diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index caf28e43ce..d09d22cac7 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -1,5 +1,5 @@ + {#if data.module.value.type == 'flow'} + + {/if} + import VirtualItem from '$lib/components/flows/map/VirtualItem.svelte' + import NodeWrapper from './NodeWrapper.svelte' + import { Minimize2 } from 'lucide-svelte' + import type { GraphModuleState } from '../../model' + import { getStateColor } from '../../util' + import type { FlowModule } from '$lib/gen' + import type { GraphEventHandlers } from '../../graphBuilder' + + export let data: { + label: string + preLabel: string | undefined + insertable: boolean + flowModuleStates: Record | undefined + subflowId: string + id: string + modules: FlowModule[] + selected: boolean + eventHandlers: GraphEventHandlers + offset: number + } + + + + { + data.eventHandlers.select(data.id) + }} + /> + + diff --git a/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte b/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte index 2175c75009..eedeca636c 100644 --- a/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte +++ b/frontend/src/routes/approve/[workspace]/[job]/[resume]/[hmac]/+page.svelte @@ -284,6 +284,7 @@

Flow details

{:else if itemsType?.enum != undefined && Array.isArray(itemsType?.enum) && Array.isArray(value)} @@ -428,6 +431,9 @@ bind:selected={value} options={itemsType?.enum ?? []} selectedOptionsDraggable={true} + on:open={() => { + dispatch('focus') + }} />
{:else} @@ -816,6 +822,12 @@ bind:value {enum_} {autofocus} + on:focus={() => { + dispatch('focus') + }} + on:blur={(e) => { + dispatch('blur') + }} enumLabels={extra['enumLabels']} />
diff --git a/frontend/src/lib/components/InputTransformForm.svelte b/frontend/src/lib/components/InputTransformForm.svelte index 07a7ee534d..c0429a8284 100644 --- a/frontend/src/lib/components/InputTransformForm.svelte +++ b/frontend/src/lib/components/InputTransformForm.svelte @@ -279,7 +279,7 @@ class={twMerge( 'pl-2 pt-2 pb-2 ml-2 relative hover:bg-surface hover:shadow-md transition-all duration-200', $propPickerConfig?.propName == argName - ? 'bg-surface border-l-4 border-blue-500 shadow-md rounded-l-md z-2000' + ? 'bg-surface border-l-4 border-blue-500 shadow-md rounded-l-md z-50 ' : 'hover:rounded-md', $$props.class )} @@ -565,10 +565,13 @@
{#if $propPickerConfig?.propName == argName} -
+
0} {#each keys as argName (argName)} {#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)} -
+
Date: Mon, 11 Nov 2024 10:37:17 +0100 Subject: [PATCH 10/30] chore(main): release 1.422.0 (#4678) * chore(main): release 1.422.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel --- CHANGELOG.md | 8 +++ backend/Cargo.lock | 72 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 61 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7faf7d54b4..467d6a40d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.422.0](https://github.com/windmill-labs/windmill/compare/v1.421.2...v1.422.0) (2024-11-11) + + +### Features + +* expandable subflows in flows ([#4683](https://github.com/windmill-labs/windmill/issues/4683)) ([d44976f](https://github.com/windmill-labs/windmill/commit/d44976f35e45ade510d1ec220b5a1503e11f3db9)) +* **frontend:** critical alerts UI ([#4653](https://github.com/windmill-labs/windmill/issues/4653)) ([d9148ea](https://github.com/windmill-labs/windmill/commit/d9148eaa78680a93d81d71847a7df67e01f3c110)) + ## [1.421.2](https://github.com/windmill-labs/windmill/compare/v1.421.1...v1.421.2) (2024-11-08) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 7fe1f09907..de8c9b6502 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f" [[package]] name = "android-tzdata" @@ -2080,9 +2080,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", @@ -7191,9 +7191,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno", @@ -7539,9 +7539,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -9139,18 +9139,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -10578,7 +10578,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "axum", @@ -10620,7 +10620,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "argon2", @@ -10705,7 +10705,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.421.2" +version = "1.422.0" dependencies = [ "base64 0.22.1", "chrono", @@ -10723,7 +10723,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.421.2" +version = "1.422.0" dependencies = [ "chrono", "serde", @@ -10736,7 +10736,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "rsmq_async", @@ -10751,7 +10751,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "async-stream", @@ -10797,7 +10797,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.421.2" +version = "1.422.0" dependencies = [ "regex", "rsmq_async", @@ -10812,7 +10812,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "bytes", @@ -10834,7 +10834,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.421.2" +version = "1.422.0" dependencies = [ "itertools 0.13.0", "lazy_static", @@ -10846,7 +10846,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.421.2" +version = "1.422.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -10855,7 +10855,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "lazy_static", @@ -10867,7 +10867,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "gosyn", @@ -10879,7 +10879,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "lazy_static", @@ -10891,7 +10891,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10902,7 +10902,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10913,7 +10913,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "async-recursion", @@ -10931,7 +10931,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -10948,7 +10948,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "lazy_static", @@ -10960,7 +10960,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "lazy_static", @@ -10978,7 +10978,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -10999,7 +10999,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "serde_json", @@ -11009,7 +11009,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "async-recursion", @@ -11042,7 +11042,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.421.2" +version = "1.422.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -11052,7 +11052,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.421.2" +version = "1.422.0" dependencies = [ "anyhow", "async-recursion", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 8663582c19..44a89a30bb 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.421.2" +version = "1.422.0" authors.workspace = true edition.workspace = true @@ -29,7 +29,7 @@ members = [ ] [workspace.package] -version = "1.421.2" +version = "1.422.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 964df7ee5f..bd9b54aafb 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.421.2 + version: 1.422.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 5c02fcf14d..b110930bdb 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.421.2"; +export const VERSION = "v1.422.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/main.ts b/cli/main.ts index 38a4087b01..b54f215ba9 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -60,7 +60,7 @@ export { // } // }); -export const VERSION = "1.421.2"; +export const VERSION = "1.422.0"; const command = new Command() .name("wmill") diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d3ff5df966..ed4e5b4252 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.421.2", + "version": "1.422.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.421.2", + "version": "1.422.0", "license": "AGPL-3.0", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", diff --git a/frontend/package.json b/frontend/package.json index 3b10b586cd..e37a9f5f3b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.421.2", + "version": "1.422.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 86a7e35c0c..26968a651a 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.421.2" -wmill_pg = ">=1.421.2" +wmill = ">=1.422.0" +wmill_pg = ">=1.422.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 6ce1e0202a..9be31150e8 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.421.2 + version: 1.422.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 9eed73c5d4..405ec6922d 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. -ModuleVersion = '1.421.2' +ModuleVersion = '1.422.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 0ec46457c8..523e9163b1 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.421.2" +version = "1.422.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 47bd0ed863..0a19fb3453 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.421.2" +version = "1.422.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index afef82391e..7bc1e27f97 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.421.2", + "version": "1.422.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 5cd31ed603..ab0ee64a89 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.421.2", + "version": "1.422.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 520cca8f24..a9886b01f1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.421.2 +1.422.0 From 1f09311a08fbb3aadb0313934644326b3288652e Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 11 Nov 2024 17:48:09 +0100 Subject: [PATCH 11/30] nit toast --- frontend/src/lib/components/toast.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/toast.ts b/frontend/src/lib/components/toast.ts index 8c1afb1a31..f1f1a84f19 100644 --- a/frontend/src/lib/components/toast.ts +++ b/frontend/src/lib/components/toast.ts @@ -1,8 +1,8 @@ //regex that match path starting with u/ or f/ and with at least 2 / const pathRegex = /\b(u|f)\/[^\/\s]+\/[^\/\s]+\b/g -export function processMessage(message: string): string { - return message.replaceAll(pathRegex, (path) => { +export function processMessage(message: string | undefined): string { + return (message ?? 'Error without message').replaceAll(pathRegex, (path) => { return `${path}` }) } From e5e174ae9516f4c6b94ceb6e258b467f5c9a1f1a Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 12 Nov 2024 10:15:09 +0100 Subject: [PATCH 12/30] fix: fix password inputs --- frontend/src/lib/components/ArgInput.svelte | 18 ++--- .../lib/components/PasswordArgInput.svelte | 65 +++++++++++-------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index a1b41f2d35..ef20ebdb35 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -896,14 +896,16 @@
{#if password || extra?.['password'] == true} - {#if value && typeof value == 'string' && value?.startsWith('$var:')} - - {:else if onlyMaskPassword} - + {#if onlyMaskPassword} + {#if value && typeof value == 'string' && value?.startsWith('$var:')} + + {:else} + + {/if} {:else} {/if} diff --git a/frontend/src/lib/components/PasswordArgInput.svelte b/frontend/src/lib/components/PasswordArgInput.svelte index 9b87e8843b..3be0daf60c 100644 --- a/frontend/src/lib/components/PasswordArgInput.svelte +++ b/frontend/src/lib/components/PasswordArgInput.svelte @@ -10,41 +10,54 @@ let path = '' let password = value && !value.startsWith('$var:') ? value : '' + let isGenerating = false async function generateValue() { - let npath = - 'u/' + - ($userStore?.username ?? $userStore?.email)?.split('@')[0] + - '/secret_arg/' + - generateRandomString(12) - let nvalue = '$var:' + npath - await VariableService.createVariable({ - workspace: $workspaceStore!, - requestBody: { - value: password, - is_secret: true, - path: npath, - description: '', - expires_at: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7).toISOString() - } - }) - path = npath - value = nvalue + if (isGenerating) return + isGenerating = true + try { + let npath = + 'u/' + + ($userStore?.username ?? $userStore?.email)?.split('@')[0] + + '/secret_arg/' + + generateRandomString(12) + let nvalue = '$var:' + npath + const passwordBefore = password + await VariableService.createVariable({ + workspace: $workspaceStore!, + requestBody: { + value: password, + is_secret: true, + path: npath, + description: 'Ephemeral secret variable', + expires_at: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7).toISOString() + } + }) + path = npath + value = nvalue + debouncedUpdate() + } finally { + isGenerating = false + } } async function updateValue() { - await VariableService.updateVariable({ - workspace: $workspaceStore!, - path: path, - requestBody: { - value: password - } - }) + try { + await VariableService.updateVariable({ + workspace: $workspaceStore!, + path: path, + requestBody: { + value: password + } + }) + } catch (e) { + generateValue() + } } let timeout: NodeJS.Timeout | undefined = undefined function debouncedUpdate() { timeout && clearTimeout(timeout) - setTimeout(updateValue, 500) + timeout = setTimeout(updateValue, 500) } $: password && debouncedUpdate() From 7784c147265d98bed5820a48266986bd876bb0d5 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 12 Nov 2024 10:44:41 +0100 Subject: [PATCH 13/30] chore(main): release 1.422.1 (#4688) * chore(main): release 1.422.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel --- CHANGELOG.md | 7 ++ backend/Cargo.lock | 70 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 59 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 467d6a40d3..81efd26ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.422.1](https://github.com/windmill-labs/windmill/compare/v1.422.0...v1.422.1) (2024-11-12) + + +### Bug Fixes + +* fix password inputs ([e5e174a](https://github.com/windmill-labs/windmill/commit/e5e174ae9516f4c6b94ceb6e258b467f5c9a1f1a)) + ## [1.422.0](https://github.com/windmill-labs/windmill/compare/v1.421.2...v1.422.0) (2024-11-11) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index de8c9b6502..b7e16db7b4 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -1943,9 +1943,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -6773,7 +6773,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -6788,9 +6788,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -7579,9 +7579,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -7620,9 +7620,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -10578,7 +10578,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "axum", @@ -10620,7 +10620,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "argon2", @@ -10705,7 +10705,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.422.0" +version = "1.422.1" dependencies = [ "base64 0.22.1", "chrono", @@ -10723,7 +10723,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.422.0" +version = "1.422.1" dependencies = [ "chrono", "serde", @@ -10736,7 +10736,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "rsmq_async", @@ -10751,7 +10751,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "async-stream", @@ -10797,7 +10797,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.422.0" +version = "1.422.1" dependencies = [ "regex", "rsmq_async", @@ -10812,7 +10812,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "bytes", @@ -10834,7 +10834,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.422.0" +version = "1.422.1" dependencies = [ "itertools 0.13.0", "lazy_static", @@ -10846,7 +10846,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.422.0" +version = "1.422.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -10855,7 +10855,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "lazy_static", @@ -10867,7 +10867,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "gosyn", @@ -10879,7 +10879,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "lazy_static", @@ -10891,7 +10891,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10902,7 +10902,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "itertools 0.13.0", @@ -10913,7 +10913,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "async-recursion", @@ -10931,7 +10931,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -10948,7 +10948,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "lazy_static", @@ -10960,7 +10960,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "lazy_static", @@ -10978,7 +10978,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "getrandom 0.2.15", @@ -10999,7 +10999,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "serde_json", @@ -11009,7 +11009,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "async-recursion", @@ -11042,7 +11042,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.422.0" +version = "1.422.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -11052,7 +11052,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.422.0" +version = "1.422.1" dependencies = [ "anyhow", "async-recursion", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 44a89a30bb..370f24763a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.422.0" +version = "1.422.1" authors.workspace = true edition.workspace = true @@ -29,7 +29,7 @@ members = [ ] [workspace.package] -version = "1.422.0" +version = "1.422.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index bd9b54aafb..e394d48eaf 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.422.0 + version: 1.422.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index b110930bdb..f5f236d2e2 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.422.0"; +export const VERSION = "v1.422.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/main.ts b/cli/main.ts index b54f215ba9..e042ac9689 100644 --- a/cli/main.ts +++ b/cli/main.ts @@ -60,7 +60,7 @@ export { // } // }); -export const VERSION = "1.422.0"; +export const VERSION = "1.422.1"; const command = new Command() .name("wmill") diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ed4e5b4252..accbb18fc9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.422.0", + "version": "1.422.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.422.0", + "version": "1.422.1", "license": "AGPL-3.0", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", diff --git a/frontend/package.json b/frontend/package.json index e37a9f5f3b..cdf580b29c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.422.0", + "version": "1.422.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 26968a651a..3df63a21c2 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.422.0" -wmill_pg = ">=1.422.0" +wmill = ">=1.422.1" +wmill_pg = ">=1.422.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 9be31150e8..cc05d20ab6 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.422.0 + version: 1.422.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 405ec6922d..78e1901cdf 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. -ModuleVersion = '1.422.0' +ModuleVersion = '1.422.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 523e9163b1..4e65d4314f 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.422.0" +version = "1.422.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 0a19fb3453..e6d46ce661 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.422.0" +version = "1.422.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 7bc1e27f97..97969f5f66 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.422.0", + "version": "1.422.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index ab0ee64a89..3163237574 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.422.0", + "version": "1.422.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index a9886b01f1..5b029f78bb 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.422.0 +1.422.1 From c3d49a352ef8d8f36f7464163ee1918f80b6b383 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 12 Nov 2024 10:46:59 +0100 Subject: [PATCH 14/30] small nit --- frontend/src/lib/components/PasswordArgInput.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/lib/components/PasswordArgInput.svelte b/frontend/src/lib/components/PasswordArgInput.svelte index 3be0daf60c..c9250eab6b 100644 --- a/frontend/src/lib/components/PasswordArgInput.svelte +++ b/frontend/src/lib/components/PasswordArgInput.svelte @@ -21,7 +21,6 @@ '/secret_arg/' + generateRandomString(12) let nvalue = '$var:' + npath - const passwordBefore = password await VariableService.createVariable({ workspace: $workspaceStore!, requestBody: { From 3d9ca62ab6d21dea7d5842ad13bea4a140548dfe Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Tue, 12 Nov 2024 16:04:36 +0100 Subject: [PATCH 15/30] ts client s3 upload add content type/disposition (#4690) --- typescript-client/client.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/typescript-client/client.ts b/typescript-client/client.ts index c5256024a6..1fa88fca1b 100644 --- a/typescript-client/client.ts +++ b/typescript-client/client.ts @@ -706,7 +706,9 @@ export async function loadS3FileStream( export async function writeS3File( s3object: S3Object | undefined, fileContent: string | Blob, - s3ResourcePath: string | undefined = undefined + s3ResourcePath: string | undefined = undefined, + contentType: string | undefined = undefined, + contentDisposition: string | undefined = undefined ): Promise { let fileContentBlob: Blob; if (typeof fileContent === "string") { @@ -724,6 +726,8 @@ export async function writeS3File( s3ResourcePath: s3ResourcePath, requestBody: fileContentBlob, storage: s3object?.storage, + contentType, + contentDisposition, }); return { s3: response.file_key, From 167100510032ab53cd609fb2c7629e67faceb093 Mon Sep 17 00:00:00 2001 From: HugoCasa Date: Tue, 12 Nov 2024 20:07:59 +0100 Subject: [PATCH 16/30] feat: s3 input available for public apps (#4685) --- backend/ee-repo-ref.txt | 2 +- backend/windmill-api/openapi.yaml | 4 + backend/windmill-api/src/apps.rs | 350 ++++++++++++++++-- backend/windmill-api/src/job_helpers_ee.rs | 38 ++ backend/windmill-api/src/users.rs | 12 +- .../lib/components/LightweightArgInput.svelte | 15 + .../components/LightweightSchemaForm.svelte | 15 + .../components/buttons/AppSchemaForm.svelte | 26 +- .../helpers/RunnableComponent.svelte | 12 + .../components/inputs/AppS3FileInput.svelte | 14 + .../apps/editor/AppEditorHeader.svelte | 45 ++- .../lib/components/apps/editor/appUtilsS3.ts | 82 ++++ .../settingsPanel/InputsSpecEditor.svelte | 2 +- .../common/fileUpload/FileUpload.svelte | 44 ++- 14 files changed, 614 insertions(+), 47 deletions(-) create mode 100644 frontend/src/lib/components/apps/editor/appUtilsS3.ts diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 6a33fdaadd..7a1f8f391e 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -6534b0f31fb4a75dd706fca2ce91e37e77e4ad02 \ No newline at end of file +8f45974252a7ce6fcf8f49482751ffa75b81bed7 \ No newline at end of file diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index e394d48eaf..154200bdcf 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -12186,6 +12186,10 @@ components: type: object additionalProperties: type: object + s3_inputs: + type: array + items: + type: object execution_mode: type: string enum: [viewer, publisher, anonymous] diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index cd65136a2a..4ec07393dc 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -7,6 +7,12 @@ use std::collections::HashMap; * Please see the included NOTICE for copyright information and * LICENSE-AGPL for a copy of the license. */ + +#[cfg(feature = "parquet")] +use crate::{job_helpers_ee::{ + get_random_file_name, get_s3_resource, get_workspace_s3_resource, upload_file_internal, + UploadFileResponse, +}, users::fetch_api_authed_from_permissioned_as}; use crate::{ db::{ApiAuthed, DB}, resources::get_resource_value_interpolated_internal, @@ -23,7 +29,13 @@ use axum::{ Router, }; use hyper::StatusCode; +#[cfg(feature = "parquet")] +use itertools::Itertools; use magic_crypt::MagicCryptTrait; +#[cfg(feature = "parquet")] +use object_store::{Attribute, Attributes}; +#[cfg(feature = "parquet")] +use regex::Regex; use serde::{Deserialize, Serialize}; use serde_json::{json, value::RawValue}; use sha2::{Digest, Sha256}; @@ -32,6 +44,8 @@ use sqlx::{types::Uuid, FromRow}; use std::str; use windmill_audit::audit_ee::audit_log; use windmill_audit::ActionKind; +#[cfg(feature = "parquet")] +use windmill_common::s3_helpers::build_object_store_client; use windmill_common::{ apps::ListAppQuery, db::UserDB, @@ -69,6 +83,7 @@ pub fn workspaced_service() -> Router { pub fn unauthed_service() -> Router { Router::new() .route("/execute_component/*path", post(execute_component)) + .route("/upload_s3_file/*path", post(upload_s3_file_from_app)) .route("/public_app/:secret", get(get_public_app_by_secret)) .route("/public_resource/*path", get(get_public_resource)) } @@ -179,6 +194,14 @@ pub struct PolicyTriggerableInputs { allow_user_resources: AllowUserResources, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct S3Input { + allowed_resources: Vec, + allow_user_resources: bool, + allow_workspace_resource: bool, + file_key_regex: String, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Policy { pub on_behalf_of: Option, @@ -192,6 +215,7 @@ pub struct Policy { #[serde(skip_serializing_if = "Option::is_none")] pub triggerables_v2: Option>, pub execution_mode: ExecutionMode, + pub s3_inputs: Option>, } #[derive(Deserialize)] @@ -432,9 +456,7 @@ async fn get_latest_version( authed: ApiAuthed, Extension(user_db): Extension, Path((w_id, path)): Path<(String, StripPath)>, - ) -> JsonResult> { - let mut tx = user_db.begin(&authed).await?; let row = sqlx::query!( "SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg @@ -457,7 +479,6 @@ async fn get_latest_version( } else { return Ok(Json(None)); } - } async fn update_app_history( @@ -1067,6 +1088,49 @@ fn digest(code: &str) -> String { format!("rawscript/{:x}", result) } +async fn get_on_behalf_details_from_policy_and_authed( + policy: &Policy, + opt_authed: &Option, +) -> Result<(String, String, String)> { + let (username, permissioned_as, email) = match policy.execution_mode { + ExecutionMode::Anonymous => { + let username = opt_authed + .as_ref() + .map(|a| a.username.clone()) + .unwrap_or_else(|| "anonymous".to_string()); + let (permissioned_as, email) = get_on_behalf_of(&policy)?; + (username, permissioned_as, email) + } + ExecutionMode::Publisher => { + let username = opt_authed + .as_ref() + .map(|a| a.username.clone()) + .ok_or_else(|| { + Error::BadRequest( + "publisher execution mode requires authentication".to_string(), + ) + })?; + let (permissioned_as, email) = get_on_behalf_of(&policy)?; + (username, permissioned_as, email) + } + ExecutionMode::Viewer => { + let (username, email) = opt_authed + .as_ref() + .map(|a| (a.username.clone(), a.email.clone())) + .ok_or_else(|| { + Error::BadRequest("Required to be authed in viewer mode".to_string()) + })?; + ( + username.clone(), + username_to_permissioned_as(&username), + email, + ) + } + }; + + Ok((username, permissioned_as, email)) +} + async fn execute_component( OptAuthed(opt_authed): OptAuthed, Extension(db): Extension, @@ -1129,6 +1193,7 @@ async fn execute_component( triggerables_v2: Some(hm), on_behalf_of: None, on_behalf_of_email: None, + s3_inputs: None, } } _ => { @@ -1146,41 +1211,8 @@ async fn execute_component( } }; - let (username, permissioned_as, email) = match policy.execution_mode { - ExecutionMode::Anonymous => { - let username = opt_authed - .as_ref() - .map(|a| a.username.clone()) - .unwrap_or_else(|| "anonymous".to_string()); - let (permissioned_as, email) = get_on_behalf_of(&policy)?; - (username, permissioned_as, email) - } - ExecutionMode::Publisher => { - let username = opt_authed - .as_ref() - .map(|a| a.username.clone()) - .ok_or_else(|| { - Error::BadRequest( - "publisher execution mode requires authentication".to_string(), - ) - })?; - let (permissioned_as, email) = get_on_behalf_of(&policy)?; - (username, permissioned_as, email) - } - ExecutionMode::Viewer => { - let (username, email) = opt_authed - .as_ref() - .map(|a| (a.username.clone(), a.email.clone())) - .ok_or_else(|| { - Error::BadRequest("Required to be authed in viewer mode".to_string()) - })?; - ( - username.clone(), - username_to_permissioned_as(&username), - email, - ) - } - }; + let (username, permissioned_as, email) = + get_on_behalf_details_from_policy_and_authed(&policy, &opt_authed).await?; let (job_payload, (args, job_id), tag) = match payload { ExecuteApp { args, component, raw_code: Some(raw_code), path: None, .. } => { @@ -1249,6 +1281,248 @@ async fn execute_component( Ok(uuid.to_string()) } +#[cfg(not(feature = "parquet"))] +async fn upload_s3_file_from_app() -> Result<()> { + return Err(Error::BadRequest( + "This endpoint requires the parquet feature to be enabled".to_string(), + )); +} + + +#[cfg(feature = "parquet")] +#[derive(Debug, Deserialize, Clone)] +struct UploadFileToS3Query { + file_key: Option, + file_extension: Option, + s3_resource_path: Option, + content_type: Option, + content_disposition: Option, + force_viewer_file_key_regex: Option, + force_viewer_allow_user_resources: Option, + force_viewer_allow_workspace_resource: Option, + force_viewer_allowed_resources: Option, +} + +#[cfg(feature = "parquet")] +async fn upload_s3_file_from_app( + OptAuthed(opt_authed): OptAuthed, + Extension(db): Extension, + Path((w_id, path)): Path<(String, StripPath)>, + Query(query): Query, + request: axum::extract::Request, +) -> JsonResult { + let policy = if let Some(file_key_regex) = query.force_viewer_file_key_regex { + Some(Policy { + execution_mode: ExecutionMode::Viewer, + triggerables: None, + triggerables_v2: None, + on_behalf_of: None, + on_behalf_of_email: None, + s3_inputs: Some(vec![S3Input { + file_key_regex: file_key_regex, + allow_user_resources: query.force_viewer_allow_user_resources.unwrap_or(false), + allow_workspace_resource: query + .force_viewer_allow_workspace_resource + .unwrap_or(false), + allowed_resources: query + .force_viewer_allowed_resources + .map(|s| s.split(',').map(|s| s.to_string()).collect()) + .unwrap_or_default(), + }]), + }) + } else { + let policy_o = sqlx::query_scalar!( + "SELECT policy from app WHERE path = $1 AND workspace_id = $2", + &path.0, + &w_id + ) + .fetch_optional(&db) + .await?; + + policy_o + .map(|p| serde_json::from_value::(p).map_err(to_anyhow)) + .transpose()? + }; + + let user_db = UserDB::new(db.clone()); + + let (s3_resource_opt, file_key) = if policy.as_ref().is_some_and(|p| p.s3_inputs.is_some()) { + let policy = policy.unwrap(); + let s3_inputs = policy.s3_inputs.as_ref().unwrap(); + + let (username, permissioned_as, email) = + get_on_behalf_details_from_policy_and_authed(&policy, &opt_authed).await?; + + let on_behalf_authed = + fetch_api_authed_from_permissioned_as(permissioned_as, email, &w_id, &db, username) + .await?; + + if let Some(file_key) = query.file_key { + // file key is provided => requires workspace, user or list policy and must match the regex + let matching_s3_inputs = if let Some(ref s3_resource_path) = query.s3_resource_path { + s3_inputs + .iter() + .filter(|s3_input| { + s3_input.allowed_resources.contains(s3_resource_path) + || s3_input.allow_user_resources + }) + .sorted_by_key(|i| i.allow_user_resources) // consider user resources last + .collect::>() + } else { + s3_inputs + .iter() + .filter(|s3_input| s3_input.allow_workspace_resource) + .collect::>() + }; + + let matched_input = matching_s3_inputs.iter().find(|s3_input| { + match Regex::new(&s3_input.file_key_regex) { + Ok(re) => re.is_match(&file_key), + Err(e) => { + tracing::error!("Error compiling regex: {}", e); + false + } + } + }); + + if let Some(matched_input) = matched_input { + if let Some(ref s3_resource_path) = query.s3_resource_path { + if matched_input.allow_user_resources { + if let Some(authed) = opt_authed { + ( + Some( + get_s3_resource( + &authed, + &db, + Some(user_db), + "", + &w_id, + s3_resource_path, + None, + None, + ) + .await?, + ), + file_key, + ) + } else { + return Err(Error::BadRequest( + "User resources are not allowed without being logged in" + .to_string(), + )); + } + } else { + ( + Some( + get_s3_resource( + &on_behalf_authed, + &db, + Some(user_db), + "", + &w_id, + s3_resource_path, + None, + None, + ) + .await?, + ), + file_key, + ) + } + } else { + let (_, s3_resource_opt) = + get_workspace_s3_resource(&on_behalf_authed, &db, None, "", &w_id, None) + .await?; + (s3_resource_opt, file_key) + } + } else { + return Err(Error::BadRequest( + "No matching s3 resource found for the given file key".to_string(), + )); + } + } else { + // no file key => requires unnamed upload policy => allow workspace resource and file_key_regex is empty + let has_unnamed_policy = s3_inputs.iter().any(|s3_input| { + s3_input.allow_workspace_resource && s3_input.file_key_regex.is_empty() + }); + + if !has_unnamed_policy { + return Err(Error::BadRequest( + "no policy found for unnamed s3 file uplooad".to_string(), + )); + } + + // for now, we place all files into `windmill_uploads` folder with a random name + // TODO: make the folder configurable via the workspace settings + let file_key = get_random_file_name(query.file_extension); + + let (_, s3_resource_opt) = + get_workspace_s3_resource(&on_behalf_authed, &db, None, "", &w_id, None).await?; + + (s3_resource_opt, file_key) + } + } else { + // backward compatibility (no policy) + // if no policy but logged in, use the user's auth to get the s3 resource + if let Some(authed) = opt_authed { + let file_key = query + .file_key + .unwrap_or_else(|| get_random_file_name(query.file_extension)); + + if let Some(ref s3_resource_path) = query.s3_resource_path { + ( + Some( + get_s3_resource( + &authed, + &db, + Some(user_db), + "", + &w_id, + s3_resource_path, + None, + None, + ) + .await?, + ), + file_key, + ) + } else { + let (_, s3_resource) = + get_workspace_s3_resource(&authed, &db, None, "", &w_id, None).await?; + + (s3_resource, file_key) + } + } else { + return Err(Error::BadRequest("Missing s3 policy".to_string())); + } + }; + + let s3_resource = s3_resource_opt.ok_or(Error::InternalErr( + "No files storage resource defined at the workspace level".to_string(), + ))?; + let s3_client = build_object_store_client(&s3_resource).await?; + + let options = Attributes::from_iter(vec![ + ( + Attribute::ContentType, + query.content_type.unwrap_or_else(|| { + mime_guess::from_path(&file_key) + .first_or_octet_stream() + .to_string() + }), + ), + ( + Attribute::ContentDisposition, + query.content_disposition.unwrap_or("inline".to_string()), + ), + ]) + .into(); + + upload_file_internal(s3_client, &file_key, request, options).await?; + + return Ok(Json(UploadFileResponse { file_key })); +} + fn get_on_behalf_of(policy: &Policy) -> Result<(String, String)> { let permissioned_as = policy .on_behalf_of diff --git a/backend/windmill-api/src/job_helpers_ee.rs b/backend/windmill-api/src/job_helpers_ee.rs index b64ccde92d..3033a2ec5a 100644 --- a/backend/windmill-api/src/job_helpers_ee.rs +++ b/backend/windmill-api/src/job_helpers_ee.rs @@ -1,9 +1,21 @@ use axum::Router; +use serde::Serialize; +use uuid::Uuid; +use windmill_common::s3_helpers::StorageResourceType; #[cfg(feature = "parquet")] use crate::db::{ApiAuthed, DB}; #[cfg(feature = "parquet")] +use object_store::{ObjectStore, PutMultipartOpts}; +#[cfg(feature = "parquet")] +use std::sync::Arc; +use windmill_common::error; +#[cfg(feature = "parquet")] use windmill_common::{db::UserDB, s3_helpers::ObjectStoreResource}; +#[derive(Serialize)] +pub struct UploadFileResponse { + pub file_key: String, +} pub fn workspaced_service() -> Router { Router::new() @@ -21,3 +33,29 @@ pub async fn get_workspace_s3_resource<'c>( // implementation is not open source Ok((None, None)) } + +pub fn get_random_file_name(_file_extension: Option) -> String { + todo!() +} + +pub async fn get_s3_resource<'c>( + _authed: &ApiAuthed, + _db: &DB, + _user_db: Option, + _token: &str, + _w_id: &str, + _resource_path: &str, + _resource_type: Option, + _job_id: Option, +) -> error::Result { + todo!() +} + +pub async fn upload_file_internal( + _s3_client: Arc, + _file_key: &str, + _request: axum::extract::Request, + _options: PutMultipartOpts, +) -> error::Result<()> { + todo!() +} diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index 005dfad342..380084d524 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -746,10 +746,20 @@ pub async fn fetch_api_authed( username_override: String, ) -> error::Result { let permissioned_as = username_to_permissioned_as(username.as_str()); + fetch_api_authed_from_permissioned_as(permissioned_as, email, w_id, db, username_override).await +} + +pub async fn fetch_api_authed_from_permissioned_as( + permissioned_as: String, + email: String, + w_id: &str, + db: &DB, + username_override: String, +) -> error::Result { let authed = fetch_authed_from_permissioned_as(permissioned_as, email.clone(), w_id, db).await?; Ok(ApiAuthed { - username: username, + username: authed.username, email: email, is_admin: authed.is_admin, is_operator: authed.is_operator, diff --git a/frontend/src/lib/components/LightweightArgInput.svelte b/frontend/src/lib/components/LightweightArgInput.svelte index bc15a41926..f9c4a04ed0 100644 --- a/frontend/src/lib/components/LightweightArgInput.svelte +++ b/frontend/src/lib/components/LightweightArgInput.svelte @@ -61,6 +61,18 @@ export let render = true export let title: string | undefined = undefined export let placeholder: string | undefined = undefined + export let appPath: string | undefined = undefined + export let computeS3ForceViewerPolicies: + | (() => + | { + allowed_resources: string[] + allow_user_resources: boolean + allow_workspace_resource: boolean + file_key_regex: string + } + | undefined) + | undefined = undefined + export let workspace: string | undefined = undefined let oneOfSelected: string | undefined = undefined async function updateOneOfSelected(oneOf: SchemaProperty[] | undefined) { @@ -428,6 +440,9 @@ .toLowerCase() == 's3object'}
{ diff --git a/frontend/src/lib/components/LightweightSchemaForm.svelte b/frontend/src/lib/components/LightweightSchemaForm.svelte index d4da597066..55b1373b4d 100644 --- a/frontend/src/lib/components/LightweightSchemaForm.svelte +++ b/frontend/src/lib/components/LightweightSchemaForm.svelte @@ -16,6 +16,18 @@ export let defaultValues: Record = {} export let dynamicEnums: Record = {} export let disabled: boolean = false + export let appPath: string | undefined = undefined + export let computeS3ForceViewerPolicies: + | (() => + | { + allowed_resources: string[] + allow_user_resources: boolean + allow_workspace_resource: boolean + file_key_regex: string + } + | undefined) + | undefined = undefined + export let workspace: string | undefined = undefined let inputCheck: { [id: string]: boolean } = {} let errors: { [id: string]: string } = {} @@ -104,6 +116,9 @@ {displayType} {css} disabled={disabled || schema.properties[argName].disabled} + {appPath} + {computeS3ForceViewerPolicies} + {workspace} /> {/if} {/each} diff --git a/frontend/src/lib/components/apps/components/buttons/AppSchemaForm.svelte b/frontend/src/lib/components/apps/components/buttons/AppSchemaForm.svelte index fcbd4e789a..a77d708e13 100644 --- a/frontend/src/lib/components/apps/components/buttons/AppSchemaForm.svelte +++ b/frontend/src/lib/components/apps/components/buttons/AppSchemaForm.svelte @@ -18,6 +18,9 @@ import ResolveConfig from '../helpers/ResolveConfig.svelte' import ResolveStyle from '../helpers/ResolveStyle.svelte' import { deepEqual } from 'fast-equals' + import { computeWorkspaceS3FileInputPolicy } from '../../editor/appUtilsS3' + import { defaultIfEmptyString } from '$lib/utils' + import { userStore } from '$lib/stores' export let id: string export let componentInput: AppInput | undefined @@ -26,8 +29,16 @@ export let configuration: RichConfigurations export let customCss: ComponentCustomCSS<'schemaformcomponent'> | undefined = undefined - const { worldStore, connectingInput, app, selectedComponent, componentControl } = - getContext('AppViewerContext') + const { + worldStore, + connectingInput, + app, + selectedComponent, + componentControl, + appPath, + isEditor, + workspace + } = getContext('AppViewerContext') const iterContext = getContext('ListWrapperContext') const listInputs: ListInputs | undefined = getContext('ListInputs') @@ -100,6 +111,14 @@ previousDefault = structuredClone(resolvedConfig.defaultValues) args = previousDefault ?? {} } + + function computeS3ForceViewerPolicies() { + if (!isEditor) { + return undefined + } + const policy = computeWorkspaceS3FileInputPolicy() + return policy + } {#each Object.keys(components['schemaformcomponent'].initialData.configuration) as key (key)} @@ -140,6 +159,9 @@ bind:this={schemaForm} displayType={Boolean(resolvedConfig.displayType)} largeGap={Boolean(resolvedConfig.largeGap)} + appPath={defaultIfEmptyString(appPath, `u/${$userStore?.username ?? 'unknown'}/newapp`)} + {computeS3ForceViewerPolicies} + {workspace} {css} />
diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte index 66fd746285..286c5ac573 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte @@ -26,6 +26,7 @@ import { get } from 'svelte/store' import RefreshButton from '$lib/components/apps/components/helpers/RefreshButton.svelte' import { ctxRegex } from '../../utils' + import { computeWorkspaceS3FileInputPolicy } from '../../editor/appUtilsS3' // Component props export let id: string @@ -671,6 +672,14 @@ return undefined } } + + function computeS3ForceViewerPolicies() { + if (!isEditor) { + return undefined + } + const policy = computeWorkspaceS3FileInputPolicy() + return policy + } {#each Object.entries(fields ?? {}) as [key, v] (key)} @@ -754,6 +763,9 @@
('AppViewerContext') + function computeForceViewerPolicies() { + if (!isEditor) { + return undefined + } + const policy = computeS3FileInputPolicy((configuration as any)?.type?.configuration?.s3, $app) + return policy + } @@ -139,5 +151,7 @@ outputs.result.set(value) }} {forceDisplayUploads} + appPath={defaultIfEmptyString(appPath, `u/${$userStore?.username ?? 'unknown'}/newapp`)} + {computeForceViewerPolicies} /> {/if} diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte index af2f4563df..662631f752 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte @@ -87,6 +87,7 @@ import ToggleEnable from '$lib/components/common/toggleButton-v2/ToggleEnable.svelte' import HideButton from './settingsPanel/HideButton.svelte' import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte' + import { computeS3FileInputPolicy, computeWorkspaceS3FileInputPolicy } from './appUtilsS3' async function hash(message) { try { @@ -195,8 +196,12 @@ } async function computeTriggerables() { + const items = allItems($app.grid, $app.subgrids) + + console.log('items', items) + const allTriggers: ([string, TriggerableV2] | undefined)[] = (await Promise.all( - allItems($app.grid, $app.subgrids) + items .flatMap((x) => { let c = x.data as AppComponent let r: { input: AppInput | undefined; id: string }[] = [ @@ -296,6 +301,44 @@ allTriggers.filter(Boolean) as [string, TriggerableV2][] ) policy.triggerables_v2 = ntriggerables + + const s3_inputs = items + .filter((x) => (x.data as AppComponent).type === 's3fileinputcomponent') + .map((x) => { + const c = x.data as AppComponent + const config = c.configuration as any + return computeS3FileInputPolicy(config?.type?.configuration?.s3, $app) + }) + .filter(Boolean) as { + allowed_resources: string[] + allow_user_resources: boolean + file_key_regex: string + }[] + + if ( + items.findIndex((x) => { + const c = x.data as AppComponent + if (c.type === 'schemaformcomponent') { + return ( + Object.values((c.componentInput as any)?.value?.properties ?? {}).findIndex( + (p: any) => p?.type === 'object' && p?.format === 'resource-s3_object' + ) !== -1 + ) + } else if (c.type === 'formbuttoncomponent' || c.type === 'formcomponent') { + return ( + Object.values((c.componentInput as any)?.fields ?? {}).findIndex( + (p: any) => p?.fieldType === 'object' && p?.format === 'resource-s3_object' + ) !== -1 + ) + } else { + return false + } + }) !== -1 + ) { + s3_inputs.push(computeWorkspaceS3FileInputPolicy()) + } + + policy.s3_inputs = s3_inputs } async function processRunnable( diff --git a/frontend/src/lib/components/apps/editor/appUtilsS3.ts b/frontend/src/lib/components/apps/editor/appUtilsS3.ts new file mode 100644 index 0000000000..2256331cc4 --- /dev/null +++ b/frontend/src/lib/components/apps/editor/appUtilsS3.ts @@ -0,0 +1,82 @@ +import type { AppInput, EvalInputV2 } from '../inputType' +import type { App } from '../types' +import { collectOneOfFields } from './appUtils' + +function filenameExprToRegex(template: string) { + const filenameEscaped = template.replaceAll('${file.name}', '') // replace filename with placeholder + const escapedTemplate = filenameEscaped + .slice(1, -1) // remove quotes + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // escape regex special characters + const regexPattern = escapedTemplate.replaceAll('', '[^/]+') // replace filename placeholder with regex pattern + return `^${regexPattern}$` +} + +function staticToRegex(str: string) { + return `^${str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$` +} + +function checkIfExprIsString(input: string) { + return /^(['"`])[^'"`]*\1$/g.test(input) +} + +function checkIfEvalIsStringWithFilename(input: EvalInputV2) { + if (input.connections.length > 0) { + return false + } else { + return checkIfExprIsString(input.expr.replaceAll('${file.name}', '')) + } +} + +function removeResourcePrefix(resource: string) { + return resource.replace(/^\$res:/, '') +} + +export function computeWorkspaceS3FileInputPolicy() { + return { + allow_workspace_resource: true, + allowed_resources: [], + allow_user_resources: false, + file_key_regex: '' + } +} + +export function computeS3FileInputPolicy(s3Config: any, app: App) { + const resourceInput = s3Config?.resource as AppInput | undefined + const pathTemplateInput = s3Config?.pathTemplate as AppInput | undefined + + const allow_workspace_resource = + !resourceInput || (resourceInput.type === 'static' && !resourceInput.value) + const allowed_resources: string[] = resourceInput + ? resourceInput.type === 'static' + ? resourceInput.value + ? [removeResourcePrefix(resourceInput.value)] + : [] + : collectOneOfFields( + { + s3_resource: resourceInput + }, + app + ).s3_resource?.map((s) => removeResourcePrefix(s)) ?? [] + : [] + + const allow_user_resources = + (resourceInput?.type === 'evalv2' && resourceInput?.allowUserResources) ?? false + let file_key_regex = '^.*$' + if (pathTemplateInput) { + if (pathTemplateInput.type === 'static') { + file_key_regex = staticToRegex(pathTemplateInput.value) + } else if ( + pathTemplateInput.type === 'evalv2' && + checkIfEvalIsStringWithFilename(pathTemplateInput) + ) { + file_key_regex = filenameExprToRegex(pathTemplateInput.expr) + } + } + + return { + allow_workspace_resource, + allowed_resources, + allow_user_resources, + file_key_regex + } +} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte index 14ecb4855c..02cfce4ea4 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte @@ -193,7 +193,7 @@ {:else if componentInput?.type === 'user'} Field's value is set by the user {/if} - {#if (componentInput?.type === 'evalv2' || componentInput?.type === 'connected' || componentInput?.type === 'user') && fieldType == 'object' && format?.startsWith('resource-')} + {#if (componentInput?.type === 'evalv2' || componentInput?.type === 'connected' || componentInput?.type === 'user') && ((fieldType == 'object' && format?.startsWith('resource-') && format !== 'resource-s3_object') || fieldType == 'resource')}
= writable([]) + export let appPath: string | undefined = undefined + export let computeForceViewerPolicies: + | (() => + | { + allowed_resources: string[] + allow_user_resources: boolean + allow_workspace_resource: boolean + file_key_regex: string + } + | undefined) + | undefined = undefined const dispatch = createEventDispatcher() @@ -115,6 +126,26 @@ params.append('content_type', fileToUpload.type) } + if (computeForceViewerPolicies !== undefined) { + const forceViewerPolicies = computeForceViewerPolicies() + + if (forceViewerPolicies) { + params.append( + 'force_viewer_allowed_resources', + forceViewerPolicies.allowed_resources.join(',') + ) + params.append( + 'force_viewer_allow_user_resources', + JSON.stringify(forceViewerPolicies.allow_user_resources) + ) + params.append( + 'force_viewer_allow_workspace_resource', + JSON.stringify(forceViewerPolicies.allow_workspace_resource) + ) + params.append('force_viewer_file_key_regex', forceViewerPolicies.file_key_regex) + } + } + // let response = await fetch( // `/api/w/${$workspaceStore}/job_helpers/multipart_upload_s3_file?${params.toString()}`, // { @@ -158,9 +189,16 @@ } } }) + xhr?.open( 'POST', - `/api/w/${workspace ?? $workspaceStore}/job_helpers/upload_s3_file?${params.toString()}`, + appPath + ? `/api/w/${ + workspace ?? $workspaceStore + }/apps_u/upload_s3_file/${appPath}?${params.toString()}` + : `/api/w/${ + workspace ?? $workspaceStore + }/job_helpers/upload_s3_file?${params.toString()}`, true ) xhr?.setRequestHeader('Content-Type', 'application/octet-stream') @@ -309,7 +347,7 @@ {/if} - {#if fileUpload.progress === 100 && !fileUpload.cancelled} + {#if fileUpload.progress === 100 && !fileUpload.cancelled && $userStore}