windmill-components 1.698.0 → 1.700.1

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.
Files changed (46) hide show
  1. package/package/components/Auth0Setting.svelte +7 -5
  2. package/package/components/AuthSettings.svelte +11 -6
  3. package/package/components/AutheliaSetting.svelte +6 -4
  4. package/package/components/AuthentikSetting.svelte +7 -2
  5. package/package/components/DisplayResult.svelte +41 -23
  6. package/package/components/FlowStatusViewerInner.svelte +23 -9
  7. package/package/components/HistoricInputs.svelte +2 -1
  8. package/package/components/InstanceSetting.svelte +47 -5
  9. package/package/components/KanidmSetting.svelte +6 -4
  10. package/package/components/KeycloakSetting.svelte +6 -4
  11. package/package/components/LogViewer.svelte +69 -29
  12. package/package/components/NextcloudSetting.svelte +6 -4
  13. package/package/components/OAuthSetting.svelte +6 -4
  14. package/package/components/OktaSetting.svelte +7 -2
  15. package/package/components/ParqetCsvTableRenderer.svelte +21 -8
  16. package/package/components/Path.svelte +10 -0
  17. package/package/components/PocketIdSetting.svelte +6 -4
  18. package/package/components/S3FilePickerInner.svelte +22 -8
  19. package/package/components/ScriptEditor.svelte +34 -4
  20. package/package/components/ShareModal.svelte.d.ts +1 -1
  21. package/package/components/VariableForm.svelte +1 -1
  22. package/package/components/ZitadelSetting.svelte +6 -4
  23. package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +1 -0
  24. package/package/components/common/fileDownload/FileDownload.svelte +26 -17
  25. package/package/components/flows/idUtils.js +4 -1
  26. package/package/components/flows/stepsInputArgs.svelte.js +6 -1
  27. package/package/components/instanceSettings/SecretBackendConfig.svelte +36 -11
  28. package/package/components/propertyPicker/ObjectViewer.svelte +23 -9
  29. package/package/components/runs/runsFilter.d.ts +1 -1
  30. package/package/components/runs/useJobsLoader.svelte.d.ts +1 -0
  31. package/package/components/runs/useJobsLoader.svelte.js +3 -1
  32. package/package/components/scriptEditor/LogPanel.svelte +4 -1
  33. package/package/components/scriptEditor/LogPanel.svelte.d.ts +1 -0
  34. package/package/components/settings/WorkspaceOperatorSettings.svelte +1 -1
  35. package/package/components/sidebar/SidebarContent.svelte +40 -2
  36. package/package/gen/core/OpenAPI.js +1 -1
  37. package/package/gen/schemas.gen.d.ts +33 -4
  38. package/package/gen/schemas.gen.js +33 -4
  39. package/package/gen/services.gen.d.ts +20 -1
  40. package/package/gen/services.gen.js +40 -0
  41. package/package/gen/types.gen.d.ts +182 -3
  42. package/package/system_prompts/prompts.d.ts +2 -2
  43. package/package/system_prompts/prompts.js +2 -2
  44. package/package/utils/downloadFile.d.ts +11 -0
  45. package/package/utils/downloadFile.js +48 -0
  46. package/package.json +1 -1
@@ -6,6 +6,7 @@ import Tooltip from './Tooltip.svelte';
6
6
  import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte';
7
7
  import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte';
8
8
  import TextInput from './text_input/TextInput.svelte';
9
+ import Password from './Password.svelte';
9
10
  import SettingCard from './instanceSettings/SettingCard.svelte';
10
11
  let { value = $bindable() } = $props();
11
12
  function changeDomain(domain, custom) {
@@ -96,14 +97,15 @@ $effect.pre(() => {
96
97
  class="max-w-lg"
97
98
  />
98
99
  </label>
99
- <label class="flex flex-col gap-1">
100
+ <label for="auth0_client_secret" class="flex flex-col gap-1">
100
101
  <span class="text-emphasis font-semibold text-xs"
101
102
  >Client Secret <Tooltip>Client Secret of the auth0 service configuration</Tooltip></span
102
103
  >
103
- <TextInput
104
- inputProps={{ type: 'text', placeholder: 'Client Secret' }}
105
- bind:value={value['secret']}
106
- class="max-w-lg"
104
+ <Password
105
+ id="auth0_client_secret"
106
+ small
107
+ placeholder="Client Secret"
108
+ bind:password={value['secret']}
107
109
  />
108
110
  </label>
109
111
  <CollapseLink text="Instructions">
@@ -12,6 +12,7 @@ import KanidmSetting from './KanidmSetting.svelte';
12
12
  import ZitadelSetting from './ZitadelSetting.svelte';
13
13
  import NextcloudSetting from './NextcloudSetting.svelte';
14
14
  import CustomOauth from './CustomOauth.svelte';
15
+ import Password from './Password.svelte';
15
16
  import { capitalize } from '../utils';
16
17
  import ClipboardPanel from './details/ClipboardPanel.svelte';
17
18
  import Toggle from './Toggle.svelte';
@@ -259,12 +260,12 @@ function generateOAuthDropdownItems() {
259
260
  <span class="text-primary font-semibold text-xs">Client Id</span>
260
261
  <input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
261
262
  </label>
262
- <label class="block pb-6">
263
+ <label for="{k}_client_secret_sso" class="block pb-6">
263
264
  <span class="text-primary font-semibold text-xs">Client Secret</span>
264
- <input
265
- type="text"
265
+ <Password
266
+ id="{k}_client_secret_sso"
266
267
  placeholder="Client Secret"
267
- bind:value={oauths[k]['secret']}
268
+ bind:password={oauths[k]['secret']}
268
269
  />
269
270
  </label>
270
271
  {#if !windmillBuiltins.includes(k) && k != 'slack'}
@@ -356,9 +357,13 @@ function generateOAuthDropdownItems() {
356
357
  <span class="text-primary font-semibold text-xs">Client Id</span>
357
358
  <input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
358
359
  </label>
359
- <label>
360
+ <label for="{k}_client_secret_oauth">
360
361
  <span class="text-primary font-semibold text-xs">Client Secret</span>
361
- <input type="text" placeholder="Client Secret" bind:value={oauths[k]['secret']} />
362
+ <Password
363
+ id="{k}_client_secret_oauth"
364
+ placeholder="Client Secret"
365
+ bind:password={oauths[k]['secret']}
366
+ />
362
367
  </label>
363
368
  {#if k === 'visma' || !windmillBuiltins.includes(k)}
364
369
  <div class="mb-8">
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">import { run } from 'svelte/legacy';
2
2
  import IconedResourceType from './IconedResourceType.svelte';
3
3
  import TextInput from './text_input/TextInput.svelte';
4
+ import Password from './Password.svelte';
4
5
  import Toggle from './Toggle.svelte';
5
6
  import SettingCard from './instanceSettings/SettingCard.svelte';
6
7
  let { value = $bindable() } = $props();
@@ -66,11 +67,12 @@ run(() => {
66
67
  bind:value={value['id']}
67
68
  />
68
69
  </label>
69
- <label class="flex flex-col gap-1">
70
+ <label for="authelia_client_secret" class="flex flex-col gap-1">
70
71
  <span class="text-emphasis font-semibold text-xs">Client Secret </span>
71
- <TextInput
72
- inputProps={{ type: 'text', placeholder: 'Client Secret' }}
73
- bind:value={value['secret']}
72
+ <Password
73
+ id="authelia_client_secret"
74
+ placeholder="Client Secret"
75
+ bind:password={value['secret']}
74
76
  />
75
77
  </label>
76
78
  </SettingCard>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">import { run } from 'svelte/legacy';
2
2
  import IconedResourceType from './IconedResourceType.svelte';
3
+ import Password from './Password.svelte';
3
4
  import Toggle from './Toggle.svelte';
4
5
  import SettingCard from './instanceSettings/SettingCard.svelte';
5
6
  let { value = $bindable() } = $props();
@@ -60,9 +61,13 @@ run(() => {
60
61
  <span class="text-emphasis font-semibold text-xs">Client Id</span>
61
62
  <input type="text" placeholder="Client Id" bind:value={value['id']} />
62
63
  </label>
63
- <label>
64
+ <label for="authentik_client_secret">
64
65
  <span class="text-emphasis font-semibold text-xs">Client Secret </span>
65
- <input type="text" placeholder="Client Secret" bind:value={value['secret']} />
66
+ <Password
67
+ id="authentik_client_secret"
68
+ placeholder="Client Secret"
69
+ bind:password={value['secret']}
70
+ />
66
71
  </label>
67
72
  </SettingCard>
68
73
  {/if}
@@ -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,15 @@ 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`);
75
85
  function checkIfS3(result, keys) {
76
86
  return keys.includes('s3') && typeof result.s3 === 'string';
77
87
  }
@@ -859,16 +869,17 @@ $effect(() => {
859
869
  {:else}
860
870
  {#if largeObject}
861
871
  <div class="text-xs text-emphasis"
862
- ><a
863
- download="{filename ?? 'result'}.json"
864
- href={workspaceId && jobId
865
- ? nodeId
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))}`}
869
- >
870
- Download {filename ? '' : 'as JSON'}
871
- </a>
872
+ >{#if resultApiPath && shouldDownloadViaClient()}
873
+ <button
874
+ onclick={() => downloadViaClient(resultApiPath!, resultDownloadName)}
875
+ >
876
+ Download {filename ? '' : 'as JSON'}
877
+ </button>
878
+ {:else}
879
+ <a download={resultDownloadName} href={resultDownloadHref}>
880
+ Download {filename ? '' : 'as JSON'}
881
+ </a>
882
+ {/if}
872
883
  {#if download_as_csv}
873
884
  <DownloadCsv
874
885
  getContent={() => convertJsonToCsv(result)}
@@ -933,19 +944,26 @@ $effect(() => {
933
944
  <DrawerContent title="Expanded Result" on:close={jsonViewer.closeDrawer}>
934
945
  {#snippet actions()}
935
946
  {#if customUi?.disableDownload !== true}
936
- <Button
937
- download="{filename ?? 'result'}.json"
938
- href={workspaceId && jobId
939
- ? nodeId
940
- ? `${base}/api/w/${workspaceId}/jobs/result_by_id/${jobId}/${nodeId}`
941
- : `${base}/api/w/${workspaceId}/jobs_u/completed/get_result/${jobId}`
942
- : `data:text/json;charset=utf-8,${encodeURIComponent(toJsonStr(result))}`}
943
- startIcon={{ icon: Download }}
944
- variant="subtle"
945
- unifiedSize="md"
946
- >
947
- Download
948
- </Button>
947
+ {#if resultApiPath && shouldDownloadViaClient()}
948
+ <Button
949
+ on:click={() => downloadViaClient(resultApiPath!, resultDownloadName)}
950
+ startIcon={{ icon: Download }}
951
+ variant="subtle"
952
+ unifiedSize="md"
953
+ >
954
+ Download
955
+ </Button>
956
+ {:else}
957
+ <Button
958
+ download={resultDownloadName}
959
+ href={resultDownloadHref}
960
+ startIcon={{ icon: Download }}
961
+ variant="subtle"
962
+ unifiedSize="md"
963
+ >
964
+ Download
965
+ </Button>
966
+ {/if}
949
967
  {/if}
950
968
  <Button
951
969
  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
- <Button
1486
- href="{base}/api/w/{workspace}/jobs_u/get_flow_all_logs/{job.id}"
1487
- download="windmill_flow_logs_{job.id}.txt"
1488
- color="light"
1489
- size="xs"
1490
- startIcon={{ icon: Download }}
1491
- >
1492
- Download all logs
1493
- </Button>
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(() => reloadKeyrenewalAttemptInfo());
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
- <Button on:click={renewLicenseKey} loading={renewing} size="xs" variant="accent"
469
- >Renew key
470
- </Button>
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>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">import { run } from 'svelte/legacy';
2
2
  import IconedResourceType from './IconedResourceType.svelte';
3
3
  import TextInput from './text_input/TextInput.svelte';
4
+ import Password from './Password.svelte';
4
5
  import Toggle from './Toggle.svelte';
5
6
  import SettingCard from './instanceSettings/SettingCard.svelte';
6
7
  let { value = $bindable() } = $props();
@@ -72,11 +73,12 @@ run(() => {
72
73
  bind:value={value['id']}
73
74
  />
74
75
  </label>
75
- <label class="flex flex-col gap-1">
76
+ <label for="kanidm_client_secret" class="flex flex-col gap-1">
76
77
  <span class="text-emphasis font-semibold text-xs">Client Secret </span>
77
- <TextInput
78
- inputProps={{ type: 'text', placeholder: 'Client Secret' }}
79
- bind:value={value['secret']}
78
+ <Password
79
+ id="kanidm_client_secret"
80
+ placeholder="Client Secret"
81
+ bind:password={value['secret']}
80
82
  />
81
83
  </label>
82
84
  </SettingCard>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">import { untrack } from 'svelte';
2
2
  import IconedResourceType from './IconedResourceType.svelte';
3
3
  import TextInput from './text_input/TextInput.svelte';
4
+ import Password from './Password.svelte';
4
5
  import Toggle from './Toggle.svelte';
5
6
  import SettingCard from './instanceSettings/SettingCard.svelte';
6
7
  let { value = $bindable() } = $props();
@@ -72,11 +73,12 @@ $effect.pre(() => {
72
73
  bind:value={value['id']}
73
74
  />
74
75
  </label>
75
- <label class="flex flex-col gap-1">
76
+ <label for="keycloak_client_secret" class="flex flex-col gap-1">
76
77
  <span class="text-emphasis font-semibold text-xs">Client Secret </span>
77
- <TextInput
78
- inputProps={{ type: 'text', placeholder: 'Client Secret' }}
79
- bind:value={value['secret']}
78
+ <Password
79
+ id="keycloak_client_secret"
80
+ placeholder="Client Secret"
81
+ bind:password={value['secret']}
80
82
  />
81
83
  </label>
82
84
  </SettingCard>
@@ -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,9 @@ $effect.pre(() => {
133
133
  scroll = true;
134
134
  }
135
135
  });
136
- let downloadHref = $derived(withExternalDomain(`${base}/api/w/${$workspaceStore}/jobs_u/get_logs/${jobId}`));
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`);
137
139
  let truncatedContent = $derived(truncateContent(content, loadedFromObjectStore, LOG_LIMIT));
138
140
  let prefixInfo = $derived(findPrefixInfo(truncatedContent));
139
141
  let downloadStartUrl = $derived(findStartUrl(truncatedContent, prefixInfo));
@@ -171,17 +173,30 @@ let html = $derived.by(() => {
171
173
  <DrawerContent title="Expanded Logs" on:close={logViewer.closeDrawer}>
172
174
  {#snippet actions()}
173
175
  {#if jobId && download}
174
- <Button
175
- href={downloadHref}
176
- download="windmill_logs_{jobId}.txt"
177
- color="light"
178
- size="xs"
179
- startIcon={{
180
- icon: Download
181
- }}
182
- >
183
- Download
184
- </Button>
176
+ {#if shouldDownloadViaClient()}
177
+ <Button
178
+ on:click={() => downloadViaClient(logsApiPath, downloadName)}
179
+ color="light"
180
+ size="xs"
181
+ startIcon={{
182
+ icon: Download
183
+ }}
184
+ >
185
+ Download
186
+ </Button>
187
+ {:else}
188
+ <Button
189
+ href={downloadHref}
190
+ download={downloadName}
191
+ color="light"
192
+ size="xs"
193
+ startIcon={{
194
+ icon: Download
195
+ }}
196
+ >
197
+ Download
198
+ </Button>
199
+ {/if}
185
200
  {/if}
186
201
 
187
202
  <Button
@@ -220,7 +235,9 @@ let html = $derived.by(() => {
220
235
  class="w-full h-full bg-surface-secondary flex flex-col {noMaxH ? '' : 'max-h-screen'}"
221
236
  data-nav-id={navigationId}
222
237
  >
223
- <div class="flex gap-2 ml-2 {small ? 'py-1' : 'py-2'} border-b">
238
+ <div
239
+ class="flex gap-2 ml-2 {small ? 'py-1' : 'py-2'} border-b overflow-x-auto overflow-y-hidden"
240
+ >
224
241
  {#if isLoading}
225
242
  <div class="flex gap-2 items-center">
226
243
  <Loader2 class="animate-spin" />
@@ -236,31 +253,54 @@ let html = $derived.by(() => {
236
253
  </div>
237
254
  {:else if duration}
238
255
  <span
239
- class={twMerge('text-secondary dark:text-gray-400', small ? '!text-2xs' : '!text-xs')}
240
- >took {duration}ms</span
256
+ class={twMerge(
257
+ 'flex items-center gap-1 text-secondary dark:text-gray-400',
258
+ small ? '!text-2xs' : '!text-xs'
259
+ )}
260
+ title="Duration"
241
261
  >
262
+ <Timer size={small ? 10 : 12} />
263
+ {duration}ms
264
+ </span>
242
265
  {/if}
243
266
  {#if mem}
244
- <span class="{small ? '!text-2xs' : '!text-xs'} text-secondary dark:text-gray-400"
245
- >mem peak: {(mem / 1024).toPrecision(4)}MB</span
267
+ <span
268
+ class={twMerge(
269
+ 'flex items-center gap-1 text-secondary dark:text-gray-400',
270
+ small ? '!text-2xs' : '!text-xs'
271
+ )}
272
+ title="Memory peak"
246
273
  >
274
+ <Cpu size={small ? 10 : 12} />
275
+ {(mem / 1024).toPrecision(4)}MB
276
+ </span>
247
277
  {/if}
248
278
  <div class="flex gap-2 justify-end flex-1">
249
279
  {#if jobId && download}
250
280
  <div class="flex items-center">
251
- <a
252
- class="text-primary pb-0.5"
253
- target="_blank"
254
- href={downloadHref}
255
- download="windmill_logs_{jobId}.txt"
256
- ><Download size="14" />
257
- </a>
281
+ {#if shouldDownloadViaClient()}
282
+ <button
283
+ class="text-primary pb-0.5"
284
+ onclick={() => downloadViaClient(logsApiPath, downloadName)}
285
+ ><Download size="14" />
286
+ </button>
287
+ {:else}
288
+ <a
289
+ class="text-primary pb-0.5"
290
+ target="_blank"
291
+ href={downloadHref}
292
+ download={downloadName}
293
+ ><Download size="14" />
294
+ </a>
295
+ {/if}
258
296
  </div>
259
297
  {/if}
260
298
  <button onclick={logViewer.openDrawer}><Expand size="12" /></button>
261
299
  {#if !noAutoScroll}
262
- <label class="pr-2 text-2xs flex gap-2 font-normal text-primary items-center">
263
- Auto scroll
300
+ <label
301
+ class="pr-2 text-2xs flex gap-2 font-normal text-primary items-center whitespace-nowrap"
302
+ >
303
+ auto-scroll
264
304
  <input class="windmillapp" type="checkbox" bind:checked={scroll} />
265
305
  </label>
266
306
  {/if}
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">import CollapseLink from './CollapseLink.svelte';
2
2
  import IconedResourceType from './IconedResourceType.svelte';
3
3
  import TextInput from './text_input/TextInput.svelte';
4
+ import Password from './Password.svelte';
4
5
  import Toggle from './Toggle.svelte';
5
6
  import SettingCard from './instanceSettings/SettingCard.svelte';
6
7
  let { value = $bindable(), baseUrl } = $props();
@@ -76,14 +77,15 @@ $effect(() => {
76
77
  bind:value={value['id']}
77
78
  />
78
79
  </label>
79
- <label class="flex flex-col gap-1">
80
+ <label for="nextcloud_client_secret" class="flex flex-col gap-1">
80
81
  <span class="text-emphasis font-semibold text-xs">Client Secret </span>
81
82
  <span class="text-secondary font-normal text-xs"
82
83
  >Client Secret from your Nextcloud OAuth2 app configuration</span
83
84
  >
84
- <TextInput
85
- inputProps={{ type: 'password', placeholder: 'Client Secret' }}
86
- bind:value={value['secret']}
85
+ <Password
86
+ id="nextcloud_client_secret"
87
+ placeholder="Client Secret"
88
+ bind:password={value['secret']}
87
89
  />
88
90
  </label>
89
91
  <CollapseLink text="Instructions">
@@ -6,6 +6,7 @@ import { untrack } from 'svelte';
6
6
  import { enterpriseLicense } from '../stores';
7
7
  import Button from './common/button/Button.svelte';
8
8
  import TextInput from './text_input/TextInput.svelte';
9
+ import Password from './Password.svelte';
9
10
  import SettingCard from './instanceSettings/SettingCard.svelte';
10
11
  let { name, value = $bindable(), login = true, eeOnly = false } = $props();
11
12
  // For Microsoft SSO, tenant is embedded in the login_config URLs, so we need
@@ -110,11 +111,12 @@ let enabled = $derived(value != undefined && !(eeOnly && !$enterpriseLicense));
110
111
  bind:value={value['id']}
111
112
  />
112
113
  </label>
113
- <label class="flex flex-col gap-1">
114
+ <label for="{name}_client_secret" class="flex flex-col gap-1">
114
115
  <span class="text-emphasis font-semibold text-xs">Client Secret</span>
115
- <TextInput
116
- inputProps={{ type: 'text', placeholder: 'Client Secret' }}
117
- bind:value={value['secret']}
116
+ <Password
117
+ id="{name}_client_secret"
118
+ placeholder="Client Secret"
119
+ bind:password={value['secret']}
118
120
  />
119
121
  </label>
120
122
  {#if name == 'microsoft' || name == 'teams'}
@@ -4,6 +4,7 @@ import IconedResourceType from './IconedResourceType.svelte';
4
4
  import Toggle from './Toggle.svelte';
5
5
  import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte';
6
6
  import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte';
7
+ import Password from './Password.svelte';
7
8
  import SettingCard from './instanceSettings/SettingCard.svelte';
8
9
  let { value = $bindable() } = $props();
9
10
  let lastValues = { domain: undefined, custom: undefined };
@@ -87,12 +88,16 @@ $effect.pre(() => {
87
88
  >
88
89
  <input type="text" placeholder="Client Id" bind:value={value['id']} />
89
90
  </label>
90
- <label class="flex flex-col gap-1">
91
+ <label for="okta_client_secret" class="flex flex-col gap-1">
91
92
  <span class="text-emphasis font-semibold text-xs">Client Secret </span>
92
93
  <span class="text-secondary font-normal text-xs"
93
94
  >from the CLIENT SECRETS section of the okta service configuration</span
94
95
  >
95
- <input type="text" placeholder="Client Secret" bind:value={value['secret']} />
96
+ <Password
97
+ id="okta_client_secret"
98
+ placeholder="Client Secret"
99
+ bind:password={value['secret']}
100
+ />
96
101
  </label>
97
102
  <CollapseLink text="Instructions">
98
103
  <div class="text-xs text-primary border rounded-md p-4 space-y-3">