windmill-components 1.698.0 → 1.699.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/components/DisplayResult.svelte +39 -19
- package/package/components/FlowStatusViewerInner.svelte +23 -9
- package/package/components/HistoricInputs.svelte +2 -1
- package/package/components/InstanceSetting.svelte +47 -5
- package/package/components/LogViewer.svelte +62 -23
- package/package/components/ParqetCsvTableRenderer.svelte +9 -4
- package/package/components/Path.svelte +10 -0
- package/package/components/S3FilePickerInner.svelte +22 -8
- package/package/components/ScriptEditor.svelte +34 -4
- package/package/components/ShareModal.svelte.d.ts +1 -1
- package/package/components/VariableForm.svelte +1 -1
- package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +1 -0
- package/package/components/common/fileDownload/FileDownload.svelte +16 -6
- package/package/components/flows/idUtils.js +4 -1
- package/package/components/flows/stepsInputArgs.svelte.js +6 -1
- package/package/components/instanceSettings/SecretBackendConfig.svelte +36 -11
- package/package/components/propertyPicker/ObjectViewer.svelte +10 -4
- package/package/components/runs/runsFilter.d.ts +1 -1
- package/package/components/runs/useJobsLoader.svelte.d.ts +1 -0
- package/package/components/runs/useJobsLoader.svelte.js +3 -1
- package/package/components/scriptEditor/LogPanel.svelte +4 -1
- package/package/components/scriptEditor/LogPanel.svelte.d.ts +1 -0
- package/package/components/settings/WorkspaceOperatorSettings.svelte +1 -1
- package/package/components/sidebar/SidebarContent.svelte +40 -2
- package/package/gen/core/OpenAPI.js +1 -1
- package/package/gen/schemas.gen.d.ts +33 -4
- package/package/gen/schemas.gen.js +33 -4
- package/package/gen/services.gen.d.ts +20 -1
- package/package/gen/services.gen.js +40 -0
- package/package/gen/types.gen.d.ts +182 -3
- package/package/system_prompts/prompts.d.ts +1 -1
- package/package/system_prompts/prompts.js +1 -1
- package/package/utils/downloadFile.d.ts +11 -0
- package/package/utils/downloadFile.js +48 -0
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { Highlight } from 'svelte-highlight';
|
|
|
3
3
|
import { json } from 'svelte-highlight/languages';
|
|
4
4
|
import { copyToClipboard, parseS3Object, roughSizeOfObject } from '../utils';
|
|
5
5
|
import { base } from '../base';
|
|
6
|
+
import { downloadViaClient, shouldDownloadViaClient } from '../utils/downloadFile';
|
|
6
7
|
import { Button, Drawer, DrawerContent } from './common';
|
|
7
8
|
import { ClipboardCopy, Download, PanelRightOpen, Table2, Braces, Highlighter, ArrowDownFromLine, Loader2 } from 'lucide-svelte';
|
|
8
9
|
import Portal from './Portal.svelte';
|
|
@@ -72,6 +73,21 @@ function isTableRowObjectInner(json, hasHeaders) {
|
|
|
72
73
|
}));
|
|
73
74
|
}
|
|
74
75
|
let largeObject = $state(undefined);
|
|
76
|
+
let resultApiPath = $derived(workspaceId && jobId
|
|
77
|
+
? nodeId
|
|
78
|
+
? `/w/${workspaceId}/jobs/result_by_id/${jobId}/${nodeId}`
|
|
79
|
+
: `/w/${workspaceId}/jobs_u/completed/get_result/${jobId}`
|
|
80
|
+
: undefined);
|
|
81
|
+
let resultDownloadHref = $derived(resultApiPath
|
|
82
|
+
? `${base}/api${resultApiPath}`
|
|
83
|
+
: `data:text/json;charset=utf-8,${encodeURIComponent(toJsonStr(result))}`);
|
|
84
|
+
let resultDownloadName = $derived(`${filename ?? 'result'}.json`);
|
|
85
|
+
async function onResultDownload(e) {
|
|
86
|
+
if (!resultApiPath || !shouldDownloadViaClient())
|
|
87
|
+
return;
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
await downloadViaClient(resultApiPath, resultDownloadName);
|
|
90
|
+
}
|
|
75
91
|
function checkIfS3(result, keys) {
|
|
76
92
|
return keys.includes('s3') && typeof result.s3 === 'string';
|
|
77
93
|
}
|
|
@@ -860,12 +876,9 @@ $effect(() => {
|
|
|
860
876
|
{#if largeObject}
|
|
861
877
|
<div class="text-xs text-emphasis"
|
|
862
878
|
><a
|
|
863
|
-
download=
|
|
864
|
-
href={
|
|
865
|
-
|
|
866
|
-
? `${base}/api/w/${workspaceId}/jobs/result_by_id/${jobId}/${nodeId}`
|
|
867
|
-
: `${base}/api/w/${workspaceId}/jobs_u/completed/get_result/${jobId}`
|
|
868
|
-
: `data:text/json;charset=utf-8,${encodeURIComponent(toJsonStr(result))}`}
|
|
879
|
+
download={resultDownloadName}
|
|
880
|
+
href={resultDownloadHref}
|
|
881
|
+
onclick={onResultDownload}
|
|
869
882
|
>
|
|
870
883
|
Download {filename ? '' : 'as JSON'}
|
|
871
884
|
</a>
|
|
@@ -933,19 +946,26 @@ $effect(() => {
|
|
|
933
946
|
<DrawerContent title="Expanded Result" on:close={jsonViewer.closeDrawer}>
|
|
934
947
|
{#snippet actions()}
|
|
935
948
|
{#if customUi?.disableDownload !== true}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
+
{#if resultApiPath && shouldDownloadViaClient()}
|
|
950
|
+
<Button
|
|
951
|
+
on:click={() => downloadViaClient(resultApiPath!, resultDownloadName)}
|
|
952
|
+
startIcon={{ icon: Download }}
|
|
953
|
+
variant="subtle"
|
|
954
|
+
unifiedSize="md"
|
|
955
|
+
>
|
|
956
|
+
Download
|
|
957
|
+
</Button>
|
|
958
|
+
{:else}
|
|
959
|
+
<Button
|
|
960
|
+
download={resultDownloadName}
|
|
961
|
+
href={resultDownloadHref}
|
|
962
|
+
startIcon={{ icon: Download }}
|
|
963
|
+
variant="subtle"
|
|
964
|
+
unifiedSize="md"
|
|
965
|
+
>
|
|
966
|
+
Download
|
|
967
|
+
</Button>
|
|
968
|
+
{/if}
|
|
949
969
|
{/if}
|
|
950
970
|
<Button
|
|
951
971
|
on:click={() => copyToClipboard(toJsonStr(result))}
|
|
@@ -12,6 +12,7 @@ import Tabs from './common/tabs/Tabs.svelte';
|
|
|
12
12
|
import {} from './graph';
|
|
13
13
|
import ModuleStatus from './ModuleStatus.svelte';
|
|
14
14
|
import { clone, isScriptPreview, msToSec, readFieldsRecursively, truncateRev } from '../utils';
|
|
15
|
+
import { downloadViaClient, shouldDownloadViaClient } from '../utils/downloadFile';
|
|
15
16
|
import JobArgs from './JobArgs.svelte';
|
|
16
17
|
import { ChevronDown, Download, ExternalLink, Hourglass } from 'lucide-svelte';
|
|
17
18
|
import { deepEqual } from 'fast-equals';
|
|
@@ -1481,16 +1482,29 @@ let totalEventsWaiting = $derived(Object.values(suspendStatus?.val ?? {}).reduce
|
|
|
1481
1482
|
style="min-height: {minTabHeight}px"
|
|
1482
1483
|
>
|
|
1483
1484
|
{#if !hideDownloadLogs && !isReplay && job?.id}
|
|
1485
|
+
{@const logsApiPath = `/w/${workspace}/jobs_u/get_flow_all_logs/${job.id}`}
|
|
1486
|
+
{@const logsName = `windmill_flow_logs_${job.id}.txt`}
|
|
1484
1487
|
<div class="flex justify-end p-1">
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1488
|
+
{#if shouldDownloadViaClient()}
|
|
1489
|
+
<Button
|
|
1490
|
+
on:click={() => downloadViaClient(logsApiPath, logsName)}
|
|
1491
|
+
color="light"
|
|
1492
|
+
size="xs"
|
|
1493
|
+
startIcon={{ icon: Download }}
|
|
1494
|
+
>
|
|
1495
|
+
Download all logs
|
|
1496
|
+
</Button>
|
|
1497
|
+
{:else}
|
|
1498
|
+
<Button
|
|
1499
|
+
href="{base}/api{logsApiPath}"
|
|
1500
|
+
download={logsName}
|
|
1501
|
+
color="light"
|
|
1502
|
+
size="xs"
|
|
1503
|
+
startIcon={{ icon: Download }}
|
|
1504
|
+
>
|
|
1505
|
+
Download all logs
|
|
1506
|
+
</Button>
|
|
1507
|
+
{/if}
|
|
1494
1508
|
</div>
|
|
1495
1509
|
{/if}
|
|
1496
1510
|
<FlowLogViewerWrapper
|
|
@@ -81,7 +81,8 @@ let jobsLoader = useJobsLoader(() => ({
|
|
|
81
81
|
syncQueuedRunsCount: false,
|
|
82
82
|
refreshRate: 10000,
|
|
83
83
|
currentWorkspace: $workspaceStore ?? '',
|
|
84
|
-
skip: !runnableId
|
|
84
|
+
skip: !runnableId,
|
|
85
|
+
excludesEntrypointOverride: true
|
|
85
86
|
}));
|
|
86
87
|
let jobs = $derived(jobsLoader?.jobs ?? []);
|
|
87
88
|
</script>
|
|
@@ -29,6 +29,7 @@ import SettingCard from './instanceSettings/SettingCard.svelte';
|
|
|
29
29
|
let { setting, version, values, loading = true, openSmtpSettings, oauths, warning } = $props();
|
|
30
30
|
const dispatch = createEventDispatcher();
|
|
31
31
|
let latestKeyRenewalAttempt = $state(null);
|
|
32
|
+
let offlineCapStatus = $state(null);
|
|
32
33
|
function showSetting(setting, values) {
|
|
33
34
|
if (setting == 'dev_instance') {
|
|
34
35
|
if (values['license_key'] == undefined) {
|
|
@@ -42,6 +43,14 @@ let opening = $state(false);
|
|
|
42
43
|
async function reloadKeyrenewalAttemptInfo() {
|
|
43
44
|
latestKeyRenewalAttempt = await SettingService.getLatestKeyRenewalAttempt();
|
|
44
45
|
}
|
|
46
|
+
async function reloadLicenseStatus() {
|
|
47
|
+
try {
|
|
48
|
+
offlineCapStatus = (await SettingService.getOfflineLicenseStatus());
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
offlineCapStatus = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
45
54
|
async function reloadLicenseKey() {
|
|
46
55
|
$values['license_key'] = await SettingService.getGlobal({
|
|
47
56
|
key: 'license_key'
|
|
@@ -49,7 +58,10 @@ async function reloadLicenseKey() {
|
|
|
49
58
|
}
|
|
50
59
|
$effect(() => {
|
|
51
60
|
if (setting.key == 'license_key') {
|
|
52
|
-
untrack(() =>
|
|
61
|
+
untrack(() => {
|
|
62
|
+
reloadKeyrenewalAttemptInfo();
|
|
63
|
+
reloadLicenseStatus();
|
|
64
|
+
});
|
|
53
65
|
}
|
|
54
66
|
});
|
|
55
67
|
export async function renewLicenseKey() {
|
|
@@ -393,7 +405,7 @@ $effect(() => {
|
|
|
393
405
|
</div>
|
|
394
406
|
{/if}
|
|
395
407
|
{/if}
|
|
396
|
-
{#if latestKeyRenewalAttempt}
|
|
408
|
+
{#if latestKeyRenewalAttempt && !offlineCapStatus}
|
|
397
409
|
{@const attemptedAt = new Date(latestKeyRenewalAttempt.attempted_at).toLocaleString()}
|
|
398
410
|
{@const isTrial = latestKeyRenewalAttempt.result.startsWith('error: trial:')}
|
|
399
411
|
<div class="relative">
|
|
@@ -463,11 +475,41 @@ $effect(() => {
|
|
|
463
475
|
</div>
|
|
464
476
|
{/if}
|
|
465
477
|
|
|
478
|
+
{#if offlineCapStatus}
|
|
479
|
+
{@const cap = offlineCapStatus}
|
|
480
|
+
{@const seatsOver = cap.seats_used > cap.seats_cap}
|
|
481
|
+
{@const cuOver = cap.cu_over_cap}
|
|
482
|
+
<div class="mt-1 flex flex-row items-center gap-2 text-xs">
|
|
483
|
+
<div class="flex flex-row items-center gap-1">
|
|
484
|
+
{#if seatsOver}
|
|
485
|
+
<BadgeX class="text-red-600" size={12} />
|
|
486
|
+
{:else}
|
|
487
|
+
<BadgeCheck class="text-green-600" size={12} />
|
|
488
|
+
{/if}
|
|
489
|
+
<span class={seatsOver ? 'text-red-600' : 'text-green-600'}>
|
|
490
|
+
Seats: {cap.seats_used.toFixed(1)} / {cap.seats_cap}
|
|
491
|
+
</span>
|
|
492
|
+
</div>
|
|
493
|
+
<div class="flex flex-row items-center gap-1">
|
|
494
|
+
{#if cuOver}
|
|
495
|
+
<BadgeX class="text-red-600" size={12} />
|
|
496
|
+
{:else}
|
|
497
|
+
<BadgeCheck class="text-green-600" size={12} />
|
|
498
|
+
{/if}
|
|
499
|
+
<span class={cuOver ? 'text-red-600' : 'text-green-600'}>
|
|
500
|
+
CUs: {cap.current_cu.toFixed(2)} / {cap.cu_cap.toFixed(2)}
|
|
501
|
+
</span>
|
|
502
|
+
</div>
|
|
503
|
+
</div>
|
|
504
|
+
{/if}
|
|
505
|
+
|
|
466
506
|
{#if valid || expiration}
|
|
467
507
|
<div class="flex flex-row gap-2 mt-1">
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
508
|
+
{#if !offlineCapStatus}
|
|
509
|
+
<Button on:click={renewLicenseKey} loading={renewing} size="xs" variant="accent"
|
|
510
|
+
>Renew key
|
|
511
|
+
</Button>
|
|
512
|
+
{/if}
|
|
471
513
|
<Button variant="accent" size="xs" loading={opening} on:click={openCustomerPortal}>
|
|
472
514
|
Open customer portal
|
|
473
515
|
</Button>
|
|
@@ -8,11 +8,12 @@ const s3LogPrefixes = [
|
|
|
8
8
|
const S3_LOG_SEARCH_LIMIT = 2000;
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
|
-
<script lang="ts">import { ClipboardCopy, Download, Expand, Loader2 } from 'lucide-svelte';
|
|
11
|
+
<script lang="ts">import { ClipboardCopy, Download, Expand, Loader2, Timer, Cpu } from 'lucide-svelte';
|
|
12
12
|
import { Button, Drawer, DrawerContent } from './common';
|
|
13
13
|
import { copyToClipboard } from '../utils';
|
|
14
14
|
import { base } from '../base';
|
|
15
15
|
import { withExternalDomain } from '../externalDomain';
|
|
16
|
+
import { downloadViaClient, shouldDownloadViaClient } from '../utils/downloadFile';
|
|
16
17
|
import { workspaceStore } from '../stores';
|
|
17
18
|
import { AnsiUp } from 'ansi_up';
|
|
18
19
|
import NoWorkerWithTagWarning from './runs/NoWorkerWithTagWarning.svelte';
|
|
@@ -96,8 +97,7 @@ function truncateContent(jobContent, loadedFromObjectStore, limit) {
|
|
|
96
97
|
return content;
|
|
97
98
|
}
|
|
98
99
|
export function scrollToBottom() {
|
|
99
|
-
scroll &&
|
|
100
|
-
setTimeout(() => preEl?.scroll({ top: preEl?.scrollHeight, behavior: 'smooth' }), 100);
|
|
100
|
+
scroll && setTimeout(() => preEl?.scroll({ top: preEl?.scrollHeight, behavior: 'smooth' }), 100);
|
|
101
101
|
}
|
|
102
102
|
let logViewer = $state();
|
|
103
103
|
async function getStoreLogs() {
|
|
@@ -133,7 +133,15 @@ $effect.pre(() => {
|
|
|
133
133
|
scroll = true;
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
|
-
let
|
|
136
|
+
let logsApiPath = $derived(`/w/${$workspaceStore}/jobs_u/get_logs/${jobId}`);
|
|
137
|
+
let downloadHref = $derived(withExternalDomain(`${base}/api${logsApiPath}`));
|
|
138
|
+
let downloadName = $derived(`windmill_logs_${jobId}.txt`);
|
|
139
|
+
async function onDownloadClick(e) {
|
|
140
|
+
if (!shouldDownloadViaClient())
|
|
141
|
+
return;
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
await downloadViaClient(logsApiPath, downloadName);
|
|
144
|
+
}
|
|
137
145
|
let truncatedContent = $derived(truncateContent(content, loadedFromObjectStore, LOG_LIMIT));
|
|
138
146
|
let prefixInfo = $derived(findPrefixInfo(truncatedContent));
|
|
139
147
|
let downloadStartUrl = $derived(findStartUrl(truncatedContent, prefixInfo));
|
|
@@ -171,17 +179,30 @@ let html = $derived.by(() => {
|
|
|
171
179
|
<DrawerContent title="Expanded Logs" on:close={logViewer.closeDrawer}>
|
|
172
180
|
{#snippet actions()}
|
|
173
181
|
{#if jobId && download}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
{#if shouldDownloadViaClient()}
|
|
183
|
+
<Button
|
|
184
|
+
on:click={() => downloadViaClient(logsApiPath, downloadName)}
|
|
185
|
+
color="light"
|
|
186
|
+
size="xs"
|
|
187
|
+
startIcon={{
|
|
188
|
+
icon: Download
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
Download
|
|
192
|
+
</Button>
|
|
193
|
+
{:else}
|
|
194
|
+
<Button
|
|
195
|
+
href={downloadHref}
|
|
196
|
+
download={downloadName}
|
|
197
|
+
color="light"
|
|
198
|
+
size="xs"
|
|
199
|
+
startIcon={{
|
|
200
|
+
icon: Download
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
Download
|
|
204
|
+
</Button>
|
|
205
|
+
{/if}
|
|
185
206
|
{/if}
|
|
186
207
|
|
|
187
208
|
<Button
|
|
@@ -220,7 +241,9 @@ let html = $derived.by(() => {
|
|
|
220
241
|
class="w-full h-full bg-surface-secondary flex flex-col {noMaxH ? '' : 'max-h-screen'}"
|
|
221
242
|
data-nav-id={navigationId}
|
|
222
243
|
>
|
|
223
|
-
<div
|
|
244
|
+
<div
|
|
245
|
+
class="flex gap-2 ml-2 {small ? 'py-1' : 'py-2'} border-b overflow-x-auto overflow-y-hidden"
|
|
246
|
+
>
|
|
224
247
|
{#if isLoading}
|
|
225
248
|
<div class="flex gap-2 items-center">
|
|
226
249
|
<Loader2 class="animate-spin" />
|
|
@@ -236,14 +259,27 @@ let html = $derived.by(() => {
|
|
|
236
259
|
</div>
|
|
237
260
|
{:else if duration}
|
|
238
261
|
<span
|
|
239
|
-
class={twMerge(
|
|
240
|
-
|
|
262
|
+
class={twMerge(
|
|
263
|
+
'flex items-center gap-1 text-secondary dark:text-gray-400',
|
|
264
|
+
small ? '!text-2xs' : '!text-xs'
|
|
265
|
+
)}
|
|
266
|
+
title="Duration"
|
|
241
267
|
>
|
|
268
|
+
<Timer size={small ? 10 : 12} />
|
|
269
|
+
{duration}ms
|
|
270
|
+
</span>
|
|
242
271
|
{/if}
|
|
243
272
|
{#if mem}
|
|
244
|
-
<span
|
|
245
|
-
|
|
273
|
+
<span
|
|
274
|
+
class={twMerge(
|
|
275
|
+
'flex items-center gap-1 text-secondary dark:text-gray-400',
|
|
276
|
+
small ? '!text-2xs' : '!text-xs'
|
|
277
|
+
)}
|
|
278
|
+
title="Memory peak"
|
|
246
279
|
>
|
|
280
|
+
<Cpu size={small ? 10 : 12} />
|
|
281
|
+
{(mem / 1024).toPrecision(4)}MB
|
|
282
|
+
</span>
|
|
247
283
|
{/if}
|
|
248
284
|
<div class="flex gap-2 justify-end flex-1">
|
|
249
285
|
{#if jobId && download}
|
|
@@ -252,15 +288,18 @@ let html = $derived.by(() => {
|
|
|
252
288
|
class="text-primary pb-0.5"
|
|
253
289
|
target="_blank"
|
|
254
290
|
href={downloadHref}
|
|
255
|
-
download=
|
|
291
|
+
download={downloadName}
|
|
292
|
+
onclick={onDownloadClick}
|
|
256
293
|
><Download size="14" />
|
|
257
294
|
</a>
|
|
258
295
|
</div>
|
|
259
296
|
{/if}
|
|
260
297
|
<button onclick={logViewer.openDrawer}><Expand size="12" /></button>
|
|
261
298
|
{#if !noAutoScroll}
|
|
262
|
-
<label
|
|
263
|
-
|
|
299
|
+
<label
|
|
300
|
+
class="pr-2 text-2xs flex gap-2 font-normal text-primary items-center whitespace-nowrap"
|
|
301
|
+
>
|
|
302
|
+
auto-scroll
|
|
264
303
|
<input class="windmillapp" type="checkbox" bind:checked={scroll} />
|
|
265
304
|
</label>
|
|
266
305
|
{/if}
|
|
@@ -6,6 +6,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
6
6
|
import DarkModeObserver from './DarkModeObserver.svelte';
|
|
7
7
|
import { HelpersService } from '../gen';
|
|
8
8
|
import { base } from '../base';
|
|
9
|
+
import { downloadViaClient, shouldDownloadViaClient } from '../utils/downloadFile';
|
|
9
10
|
import { enterpriseLicense, workspaceStore } from '../stores';
|
|
10
11
|
import { Download } from 'lucide-svelte';
|
|
11
12
|
import { Loader2 } from 'lucide-svelte';
|
|
@@ -171,13 +172,17 @@ run(() => {
|
|
|
171
172
|
</div>
|
|
172
173
|
{/if}
|
|
173
174
|
{#if !disable_download && !s3resource.endsWith('.csv')}
|
|
175
|
+
{@const csvApiPath = `/w/${workspaceId}/job_helpers/download_s3_parquet_file_as_csv?file_key=${encodeURIComponent(s3resource)}${storage ? `&storage=${storage}` : ''}`}
|
|
176
|
+
{@const csvName = (s3resource.split('/').pop() ?? 'download') + '.csv'}
|
|
174
177
|
<a
|
|
175
178
|
target="_blank"
|
|
176
|
-
href="{base}/api
|
|
177
|
-
s3resource
|
|
178
|
-
)}{storage ? `&storage=${storage}` : ''}"
|
|
179
|
+
href="{base}/api{csvApiPath}"
|
|
179
180
|
class="text-secondary w-full text-right underline text-2xs whitespace-nowrap"
|
|
180
|
-
|
|
181
|
+
onclick={async (e) => {
|
|
182
|
+
if (!shouldDownloadViaClient()) return
|
|
183
|
+
e.preventDefault()
|
|
184
|
+
await downloadViaClient(csvApiPath, csvName)
|
|
185
|
+
}}><div class="flex flex-row-reverse gap-2 items-center"><Download size={12} /> CSV</div></a
|
|
181
186
|
>
|
|
182
187
|
{/if}
|
|
183
188
|
|
|
@@ -282,6 +282,16 @@ async function initPath() {
|
|
|
282
282
|
function setDirty() {
|
|
283
283
|
!dirty && (dirty = true);
|
|
284
284
|
}
|
|
285
|
+
$effect(() => {
|
|
286
|
+
if (path !== undefined &&
|
|
287
|
+
path !== '' &&
|
|
288
|
+
initialPath &&
|
|
289
|
+
!initialPath.startsWith('tmp/') &&
|
|
290
|
+
path !== initialPath &&
|
|
291
|
+
!dirty) {
|
|
292
|
+
dirty = true;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
285
295
|
const openSearchWithPrefilledText = getContext('openSearchWithPrefilledText');
|
|
286
296
|
$effect.pre(() => {
|
|
287
297
|
;
|
|
@@ -5,6 +5,7 @@ import { workspaceStore } from '../stores';
|
|
|
5
5
|
import { CancelablePromise, HelpersService } from '../gen';
|
|
6
6
|
import { base } from '../base';
|
|
7
7
|
import { displayDate, displaySize, emptyString, parseS3Object, sendUserToast } from '../utils';
|
|
8
|
+
import { downloadViaClient, shouldDownloadViaClient } from '../utils/downloadFile';
|
|
8
9
|
import { Alert, Button } from './common';
|
|
9
10
|
import Section from './Section.svelte';
|
|
10
11
|
import { createEventDispatcher, untrack } from 'svelte';
|
|
@@ -577,14 +578,27 @@ $effect.pre(() => {
|
|
|
577
578
|
{#if filePreview !== undefined && (!hideS3SpecificDetails || !readOnlyMode || allowDelete)}
|
|
578
579
|
<div class="flex gap-2 shrink-0">
|
|
579
580
|
{#if !hideS3SpecificDetails}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
581
|
+
{@const downloadApiPath = `/w/${$workspaceStore}/job_helpers/download_s3_file?file_key=${encodeURIComponent(fileMetadata?.fileKey ?? '')}${storage ? `&storage=${storage}` : ''}`}
|
|
582
|
+
{@const downloadName =
|
|
583
|
+
fileMetadata?.fileKey.split('/').pop() ?? 'unnamed_download.file'}
|
|
584
|
+
{#if shouldDownloadViaClient()}
|
|
585
|
+
<Button
|
|
586
|
+
title="Download file from S3"
|
|
587
|
+
variant="default"
|
|
588
|
+
on:click={() => downloadViaClient(downloadApiPath, downloadName)}
|
|
589
|
+
startIcon={{ icon: Download }}
|
|
590
|
+
iconOnly={true}
|
|
591
|
+
/>
|
|
592
|
+
{:else}
|
|
593
|
+
<Button
|
|
594
|
+
title="Download file from S3"
|
|
595
|
+
variant="default"
|
|
596
|
+
href={`${base}/api${downloadApiPath}`}
|
|
597
|
+
download={downloadName}
|
|
598
|
+
startIcon={{ icon: Download }}
|
|
599
|
+
iconOnly={true}
|
|
600
|
+
/>
|
|
601
|
+
{/if}
|
|
588
602
|
{/if}
|
|
589
603
|
{#if !readOnlyMode}
|
|
590
604
|
<Button
|
|
@@ -408,6 +408,8 @@ let logPanel = $state(undefined);
|
|
|
408
408
|
let testIsLoading = $state(false);
|
|
409
409
|
let testJob = $state();
|
|
410
410
|
let pastPreviews = $state([]);
|
|
411
|
+
let historyTabActive = false;
|
|
412
|
+
let pastPreviewsRequest;
|
|
411
413
|
let validCode = $state(true);
|
|
412
414
|
// Recording
|
|
413
415
|
let scriptRecording = createScriptRecording();
|
|
@@ -511,7 +513,9 @@ export async function runTest() {
|
|
|
511
513
|
lastRecording = scriptRecording.stop();
|
|
512
514
|
setActiveRecording(undefined);
|
|
513
515
|
}
|
|
514
|
-
|
|
516
|
+
if (historyTabActive) {
|
|
517
|
+
loadPastTests();
|
|
518
|
+
}
|
|
515
519
|
},
|
|
516
520
|
doneError({ error }) {
|
|
517
521
|
if (scriptRecording.active) {
|
|
@@ -536,12 +540,31 @@ function downloadRecording() {
|
|
|
536
540
|
}
|
|
537
541
|
}
|
|
538
542
|
async function loadPastTests() {
|
|
539
|
-
|
|
543
|
+
pastPreviewsRequest?.cancel();
|
|
544
|
+
const req = JobService.listCompletedJobs({
|
|
540
545
|
workspace: $workspaceStore,
|
|
541
546
|
jobKinds: 'preview',
|
|
542
547
|
createdBy: $userStore?.username,
|
|
543
|
-
scriptPathExact: path
|
|
548
|
+
scriptPathExact: path,
|
|
549
|
+
hasNullParent: true
|
|
544
550
|
});
|
|
551
|
+
pastPreviewsRequest = req;
|
|
552
|
+
try {
|
|
553
|
+
const result = await req;
|
|
554
|
+
if (pastPreviewsRequest === req) {
|
|
555
|
+
pastPreviews = result;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
if (!(err instanceof Error) || err.name !== 'CancelError') {
|
|
560
|
+
throw err;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
finally {
|
|
564
|
+
if (pastPreviewsRequest === req) {
|
|
565
|
+
pastPreviewsRequest = undefined;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
545
568
|
}
|
|
546
569
|
export async function inferSchema(code, { nlang, resetArgs = false, applyInitialArgs = false } = {}) {
|
|
547
570
|
let nschema = schema ?? emptySchema();
|
|
@@ -897,7 +920,6 @@ onMount(async () => {
|
|
|
897
920
|
if (!validCode && code && lang) {
|
|
898
921
|
await inferSchema(code, { applyInitialArgs: true });
|
|
899
922
|
}
|
|
900
|
-
loadPastTests();
|
|
901
923
|
aiChatManager.saveAndClear();
|
|
902
924
|
aiChatManager.changeMode(AIMode.SCRIPT);
|
|
903
925
|
});
|
|
@@ -964,6 +986,8 @@ export function disableCollaboration() {
|
|
|
964
986
|
wsProvider = undefined;
|
|
965
987
|
}
|
|
966
988
|
onDestroy(() => {
|
|
989
|
+
pastPreviewsRequest?.cancel();
|
|
990
|
+
pastPreviewsRequest = undefined;
|
|
967
991
|
disableCollaboration();
|
|
968
992
|
aiChatManager.scriptEditorApplyCode = undefined;
|
|
969
993
|
aiChatManager.scriptEditorShowDiffMode = undefined;
|
|
@@ -1413,6 +1437,12 @@ $effect(() => {
|
|
|
1413
1437
|
} as any)
|
|
1414
1438
|
: testJob}
|
|
1415
1439
|
{pastPreviews}
|
|
1440
|
+
onTabChange={(tab) => {
|
|
1441
|
+
historyTabActive = tab === 'history'
|
|
1442
|
+
if (historyTabActive) {
|
|
1443
|
+
loadPastTests()
|
|
1444
|
+
}
|
|
1445
|
+
}}
|
|
1416
1446
|
previewIsLoading={debugMode
|
|
1417
1447
|
? $debugState.running && !$debugState.stopped
|
|
1418
1448
|
: testIsLoading}
|
|
@@ -16,7 +16,7 @@ declare const ShareModal: $$__sveltets_2_IsomorphicComponent<Record<string, neve
|
|
|
16
16
|
} & {
|
|
17
17
|
[evt: string]: CustomEvent<any>;
|
|
18
18
|
}, {}, {
|
|
19
|
-
openDrawer: (newPath: string, kind_l: "resource" | "volume" | "script" | "flow" | "app" | "variable" | "schedule" | "raw_app" | "
|
|
19
|
+
openDrawer: (newPath: string, kind_l: "resource" | "volume" | "script" | "flow" | "app" | "variable" | "schedule" | "raw_app" | "http_trigger" | "websocket_trigger" | "kafka_trigger" | "nats_trigger" | "postgres_trigger" | "mqtt_trigger" | "sqs_trigger" | "gcp_trigger" | "azure_trigger" | "email_trigger" | "group_", isOwnerOverride?: boolean) => Promise<void>;
|
|
20
20
|
}, "">;
|
|
21
21
|
type ShareModal = InstanceType<typeof ShareModal>;
|
|
22
22
|
export default ShareModal;
|
|
@@ -51,7 +51,7 @@ export function setCode(value) {
|
|
|
51
51
|
{#if deployTo}
|
|
52
52
|
<Label
|
|
53
53
|
label="Workspace specific"
|
|
54
|
-
tooltip="Prevents this variable from being deployed to prod/staging"
|
|
54
|
+
tooltip="Prevents this variable from being deployed to prod/staging. May have been enabled automatically because a workspace-specific resource references this variable via $var:. Disabling this toggle does not retroactively un-mark the resource that referenced it."
|
|
55
55
|
>
|
|
56
56
|
<Toggle bind:checked={wsSpecific} />
|
|
57
57
|
</Label>
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
<script lang="ts">import { workspaceStore } from '../../../stores';
|
|
2
2
|
import { Download } from 'lucide-svelte';
|
|
3
3
|
import { base } from '../../../base';
|
|
4
|
+
import { downloadViaClient, shouldDownloadViaClient } from '../../../utils/downloadFile';
|
|
4
5
|
let { s3object, workspaceId = undefined, appPath = undefined } = $props();
|
|
6
|
+
let workspace = $derived(workspaceId ?? $workspaceStore);
|
|
7
|
+
let filename = $derived(s3object?.s3?.split?.('/')?.pop() ?? 'unnamed_download.file');
|
|
8
|
+
let apiPath = $derived(`${appPath
|
|
9
|
+
? `/w/${workspace}/apps_u/download_s3_file/${appPath}`
|
|
10
|
+
: `/w/${workspace}/job_helpers/download_s3_file`}?${appPath ? 's3' : 'file_key'}=${encodeURIComponent(s3object?.s3 ?? '')}${s3object?.storage ? `&storage=${s3object.storage}` : ''}${appPath && s3object?.presigned ? `&${s3object?.presigned}` : ''}`);
|
|
11
|
+
let href = $derived(`${base}/api${apiPath}`);
|
|
12
|
+
async function onclick(e) {
|
|
13
|
+
if (!shouldDownloadViaClient())
|
|
14
|
+
return;
|
|
15
|
+
e.preventDefault();
|
|
16
|
+
await downloadViaClient(apiPath, filename);
|
|
17
|
+
}
|
|
5
18
|
</script>
|
|
6
19
|
|
|
7
20
|
{#if s3object && s3object?.s3}
|
|
@@ -10,12 +23,9 @@ let { s3object, workspaceId = undefined, appPath = undefined } = $props();
|
|
|
10
23
|
border border-dashed border-gray-400 hover:border-blue-500
|
|
11
24
|
focus-within:border-blue-500 hover:bg-blue-50 dark:hover:bg-frost-900 focus-within:bg-blue-50
|
|
12
25
|
duration-200 rounded-lg p-1 gap-2"
|
|
13
|
-
href
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
s3object?.storage ? `&storage=${s3object.storage}` : ''
|
|
17
|
-
}${appPath && s3object?.presigned ? `&${s3object?.presigned}` : ''}`}
|
|
18
|
-
download={s3object?.s3?.split?.('/')?.pop() ?? 'unnamed_download.file'}
|
|
26
|
+
{href}
|
|
27
|
+
download={filename}
|
|
28
|
+
{onclick}
|
|
19
29
|
>
|
|
20
30
|
<Download />
|
|
21
31
|
<span>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dfs, getPreviousModule, getStepPropPicker } from './previousResults';
|
|
1
|
+
import { dfs, getPreviousModule, getStepPropPicker, getFailureStepPropPicker } from './previousResults';
|
|
2
2
|
import { evalValue } from './utils.svelte';
|
|
3
3
|
export class StepsInputArgs {
|
|
4
4
|
#stepsEvaluated = $state({});
|
|
@@ -103,6 +103,11 @@ export class StepsInputArgs {
|
|
|
103
103
|
this.#steps[mod.id] = argsSnapshot;
|
|
104
104
|
}
|
|
105
105
|
updateStepArgs(id, flowState, flow, previewArgs) {
|
|
106
|
+
if (id === 'failure' && flow && flow.value.failure_module && flowState) {
|
|
107
|
+
const picker = getFailureStepPropPicker(flowState, flow, previewArgs);
|
|
108
|
+
this.initializeFromSchema(flow.value.failure_module, flowState['failure']?.schema ?? {}, picker.pickableProperties);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
106
111
|
if (!flowState || !flow) {
|
|
107
112
|
return;
|
|
108
113
|
}
|