windmill-components 1.430.6 → 1.433.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.
Files changed (35) hide show
  1. package/package/components/AppConnectInner.svelte +27 -2
  2. package/package/components/ConfirmButton.svelte +31 -0
  3. package/package/components/ConfirmButton.svelte.d.ts +20 -0
  4. package/package/components/DiffEditor.svelte +0 -6
  5. package/package/components/FlowStatusViewerInner.svelte +8 -4
  6. package/package/components/InstanceSettings.svelte +20 -1
  7. package/package/components/Login.svelte +31 -6
  8. package/package/components/ServiceLogsInner.svelte +365 -337
  9. package/package/components/apps/components/buttons/AppSchemaForm.svelte +1 -1
  10. package/package/components/apps/components/display/AppNavbarItem.svelte +1 -1
  11. package/package/components/apps/components/helpers/RunnableComponent.svelte +2 -2
  12. package/package/components/apps/components/inputs/AppS3FileInput.svelte +1 -1
  13. package/package/components/apps/editor/AppEditor.svelte +18 -8
  14. package/package/components/apps/editor/AppEditor.svelte.d.ts +5 -0
  15. package/package/components/apps/editor/AppEditorHeader.svelte +58 -58
  16. package/package/components/apps/editor/AppEditorHeader.svelte.d.ts +2 -0
  17. package/package/components/apps/editor/AppPreview.svelte +6 -1
  18. package/package/components/apps/editor/AppReportsDrawer.svelte +3 -613
  19. package/package/components/apps/editor/AppReportsDrawerInner.svelte +622 -0
  20. package/package/components/apps/editor/AppReportsDrawerInner.svelte.d.ts +17 -0
  21. package/package/components/apps/editor/component/components.d.ts +79 -79
  22. package/package/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte +1 -1
  23. package/package/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte +1 -1
  24. package/package/components/apps/editor/settingsPanel/GridNavbar.svelte +1 -1
  25. package/package/components/apps/types.d.ts +1 -1
  26. package/package/components/splitPanes/SplitPanesOrColumnOnMobile.svelte +34 -0
  27. package/package/components/splitPanes/SplitPanesOrColumnOnMobile.svelte.d.ts +23 -0
  28. package/package/components/wizards/AppPicker.svelte +4 -4
  29. package/package/gen/core/OpenAPI.js +1 -1
  30. package/package/gen/schemas.gen.d.ts +2 -2
  31. package/package/gen/schemas.gen.js +2 -2
  32. package/package/gen/services.gen.d.ts +0 -1
  33. package/package/gen/services.gen.js +0 -2
  34. package/package/gen/types.gen.d.ts +2 -4
  35. package/package.json +1 -1
@@ -1,619 +1,9 @@
1
- <script>import { enterpriseLicense } from '../../../stores';
2
- import CronInput from '../../CronInput.svelte';
3
- import ScriptPicker from '../../ScriptPicker.svelte';
4
- import Section from '../../Section.svelte';
5
- import Alert from '../../common/alert/Alert.svelte';
6
- import Drawer from '../../common/drawer/Drawer.svelte';
7
- import DrawerContent from '../../common/drawer/DrawerContent.svelte';
8
- import Tab from '../../common/tabs/Tab.svelte';
9
- import Tabs from '../../common/tabs/Tabs.svelte';
10
- import { FlowService, JobService, ScheduleService, SettingService, WorkspaceService } from '../../../gen';
11
- import { workspaceStore } from '../../../stores';
12
- import { base } from '../../../base';
13
- import { emptyString, formatCron, sendUserToast, tryEvery } from '../../../utils';
14
- import SchemaForm from '../../SchemaForm.svelte';
15
- import Button from '../../common/button/Button.svelte';
16
- import Toggle from '../../Toggle.svelte';
17
- import { RotateCw, Save } from 'lucide-svelte';
18
- import { CUSTOM_TAGS_SETTING, WORKSPACE_SLACK_BOT_TOKEN_PATH } from '../../../consts';
19
- import { loadSchemaFromPath } from '../../../infer';
20
- import { hubPaths } from '../../../hub';
1
+ <script>import { Drawer } from '../../common';
2
+ import AppReportsDrawerInner from './AppReportsDrawerInner.svelte';
21
3
  export let appPath;
22
4
  export let open = false;
23
- let appReportingEnabled = false;
24
- let appReportingStartupDuration = 5;
25
- let appReportingSchedule = {
26
- cron: '0 0 12 * *',
27
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
28
- };
29
- let selectedTab = $enterpriseLicense
30
- ? 'slack'
31
- : 'custom';
32
- let screenshotKind = 'pdf';
33
- let customPath = undefined;
34
- let customPathSchema = {};
35
- let args = {};
36
- let areArgsValid = true;
37
- $: customPath
38
- ? loadSchemaFromPath(customPath).then((schema) => {
39
- customPathSchema = schema
40
- ? {
41
- ...schema,
42
- properties: Object.fromEntries(Object.entries(schema.properties ?? {}).filter(([key, _]) => key !== 'screenshot' && key !== 'app_path' && key !== 'kind'))
43
- }
44
- : {};
45
- })
46
- : (customPathSchema = {});
47
- let isSlackConnectedWorkspace = false;
48
- async function getWorspaceSlackSetting() {
49
- const settings = await WorkspaceService.getSettings({
50
- workspace: $workspaceStore
51
- });
52
- if (settings.slack_name) {
53
- isSlackConnectedWorkspace = true;
54
- }
55
- else {
56
- isSlackConnectedWorkspace = false;
57
- }
58
- }
59
- getWorspaceSlackSetting();
60
- async function getAppReportingInfo() {
61
- const flowPath = appPath + '_reports';
62
- try {
63
- const flow = await FlowService.getFlowByPath({
64
- workspace: $workspaceStore,
65
- path: flowPath
66
- });
67
- const schedule = await ScheduleService.getSchedule({
68
- workspace: $workspaceStore,
69
- path: flowPath
70
- });
71
- appReportingSchedule = {
72
- cron: schedule.schedule,
73
- timezone: schedule.timezone
74
- };
75
- appReportingStartupDuration =
76
- schedule.args?.startup_duration ?? appReportingStartupDuration;
77
- screenshotKind = schedule.args?.kind ?? screenshotKind;
78
- args = schedule.args
79
- ? Object.fromEntries(Object.entries(schedule.args).filter(([key, _]) => key !== 'app_path' && key !== 'startup_duration' && key !== 'kind'))
80
- : {};
81
- selectedTab =
82
- flow.value.modules[1]?.value.type === 'script'
83
- ? flow.value.modules[1].value.path === notificationScripts.email.path
84
- ? 'email'
85
- : flow.value.modules[1].value.path === notificationScripts.slack.path
86
- ? 'slack'
87
- : flow.value.modules[1].value.path === notificationScripts.discord.path
88
- ? 'discord'
89
- : 'custom'
90
- : 'custom';
91
- customPath =
92
- selectedTab === 'custom' &&
93
- flow.value.modules[1]?.value.type === 'script' &&
94
- !flow.value.modules[1].value.path.startsWith('hub/')
95
- ? flow.value.modules[1].value.path
96
- : undefined;
97
- appReportingEnabled = true;
98
- }
99
- catch (err) { }
100
- }
101
- $: appPath && getAppReportingInfo();
102
- async function disableAppReporting() {
103
- const flowPath = appPath + '_reports';
104
- await ScheduleService.deleteSchedule({
105
- workspace: $workspaceStore,
106
- path: flowPath
107
- });
108
- await FlowService.deleteFlowByPath({
109
- workspace: $workspaceStore,
110
- path: flowPath
111
- });
112
- appReportingEnabled = false;
113
- sendUserToast('App reporting disabled');
114
- }
115
- const appPreviewScript = `import puppeteer from 'puppeteer-core';
116
- import dayjs from 'dayjs';
117
- export async function main(app_path: string, startup_duration = 5, kind: 'pdf' | 'png' = 'pdf') {
118
- let browser = null
119
- try {
120
- browser = await puppeteer.launch({ headless: true, executablePath: '/usr/bin/chromium', args: ['--no-sandbox',
121
- '--no-zygote',
122
- '--disable-setuid-sandbox',
123
- '--disable-dev-shm-usage',
124
- '--disable-gpu'] });
125
- const page = await browser.newPage();
126
- await page.setCookie({
127
- "name": "token",
128
- "value": Bun.env["WM_TOKEN"],
129
- "domain": Bun.env["BASE_URL"]?.replace(/https?:\\/\\//, '')
130
- })
131
- page
132
- .on('console', msg =>
133
- console.log(dayjs().format("HH:mm:ss") + " " + msg.type().substr(0, 3).toUpperCase() + " " + msg.text()))
134
- .on('pageerror', ({ msg }) => console.log(dayjs().format("HH:mm:ss") + " " + msg));
135
- await page.setViewport({ width: 1200, height: 2000 });
136
- await page.goto(Bun.env["BASE_URL"] + '/apps/get/' + app_path + '?workspace=' + Bun.env["WM_WORKSPACE"] + "&hideRefreshBar=true&hideEditBtn=true");
137
- await page.waitForSelector("#app-content", { timeout: 20000 })
138
- await new Promise((resolve, _) => {
139
- setTimeout(resolve, startup_duration * 1000)
140
- })
141
- await page.$eval("#sidebar", el => el.remove())
142
- await page.$eval("#content", el => el.classList.remove("md:pl-12"))
143
- await page.$$eval(".app-component-refresh-btn", els => els.forEach(el => el.remove()))
144
- await page.$$eval(".app-table-footer-btn", els => els.forEach(el => el.remove()))
145
- const elem = await page.$('#app-content');
146
- const { height } = await elem.boundingBox();
147
- await page.setViewport({ width: 1200, height });
148
- await new Promise((resolve, _) => {
149
- setTimeout(resolve, 500)
150
- })
151
- const screenshot = kind === "pdf" ? await page.pdf({
152
- printBackground: true,
153
- width: 1200,
154
- height
155
- }) : await page.screenshot({
156
- fullPage: true,
157
- type: "png",
158
- captureBeyondViewport: false
159
- });
160
- await browser.close();
161
- return Buffer.from(screenshot).toString('base64');
162
- } catch (err) {
163
- if (browser) {
164
- await browser.close();
165
- }
166
- throw err;
167
- }
168
- }`;
169
- const notificationScripts = {
170
- discord: {
171
- path: hubPaths.discordReport,
172
- schema: {
173
- type: 'object',
174
- properties: {
175
- discord_webhook: {
176
- type: 'object',
177
- format: 'resource-discord_webhook',
178
- properties: {},
179
- required: [],
180
- description: ''
181
- }
182
- },
183
- required: ['discord_webhook']
184
- }
185
- },
186
- slack: {
187
- path: hubPaths.slackReport,
188
- schema: {
189
- type: 'object',
190
- properties: {
191
- channel: {
192
- type: 'string',
193
- default: ''
194
- }
195
- },
196
- required: ['channel']
197
- }
198
- },
199
- email: {
200
- path: hubPaths.smtpReport,
201
- schema: {
202
- type: 'object',
203
- properties: {
204
- smtp: {
205
- type: 'object',
206
- format: 'resource-smtp',
207
- properties: {},
208
- required: [],
209
- description: ''
210
- },
211
- from_email: {
212
- type: 'string',
213
- default: ''
214
- },
215
- to_email: {
216
- type: 'string',
217
- default: ''
218
- }
219
- },
220
- required: ['smtp', 'from_email', 'to_email']
221
- }
222
- }
223
- };
224
- function getFlowArgs() {
225
- return {
226
- app_path: appPath,
227
- startup_duration: appReportingStartupDuration,
228
- kind: screenshotKind,
229
- ...args,
230
- ...(selectedTab === 'slack'
231
- ? {
232
- slack: '$res:' + WORKSPACE_SLACK_BOT_TOKEN_PATH
233
- }
234
- : {})
235
- };
236
- }
237
- function getFlowValue() {
238
- const notifInputTransforms = {
239
- app_path: {
240
- type: 'javascript',
241
- expr: 'flow_input.app_path'
242
- },
243
- screenshot: {
244
- type: 'javascript',
245
- expr: 'results.a'
246
- },
247
- kind: {
248
- type: 'javascript',
249
- expr: 'flow_input.kind'
250
- },
251
- ...Object.fromEntries(Object.keys(args).map((key) => [
252
- key,
253
- {
254
- type: 'javascript',
255
- expr: `flow_input.${key}`
256
- }
257
- ])),
258
- ...(selectedTab === 'slack'
259
- ? {
260
- slack: {
261
- type: 'javascript',
262
- expr: 'flow_input.slack'
263
- }
264
- }
265
- : {})
266
- };
267
- const value = {
268
- modules: [
269
- {
270
- id: 'a',
271
- value: {
272
- type: 'rawscript',
273
- tag: 'chromium',
274
- content: appPreviewScript,
275
- language: 'bun',
276
- input_transforms: {
277
- app_path: {
278
- expr: 'flow_input.app_path',
279
- type: 'javascript'
280
- },
281
- startup_duration: {
282
- expr: 'flow_input.startup_duration',
283
- type: 'javascript'
284
- },
285
- kind: {
286
- expr: 'flow_input.kind',
287
- type: 'javascript'
288
- }
289
- }
290
- }
291
- },
292
- {
293
- id: 'b',
294
- value: {
295
- type: 'script',
296
- path: selectedTab === 'custom' ? customPath || '' : notificationScripts[selectedTab].path,
297
- input_transforms: notifInputTransforms
298
- }
299
- }
300
- ]
301
- };
302
- return value;
303
- }
304
- async function enableAppReporting() {
305
- const flowPath = appPath + '_reports';
306
- try {
307
- // will only work if the user is super admin
308
- const customTags = ((await SettingService.getGlobal({
309
- key: CUSTOM_TAGS_SETTING
310
- })) ?? []);
311
- if (!customTags.includes('chromium')) {
312
- await SettingService.setGlobal({
313
- key: CUSTOM_TAGS_SETTING,
314
- requestBody: {
315
- value: [...customTags, 'chromium']
316
- }
317
- });
318
- }
319
- }
320
- catch (err) { }
321
- await FlowService.deleteFlowByPath({
322
- workspace: $workspaceStore,
323
- path: flowPath
324
- });
325
- await FlowService.createFlow({
326
- workspace: $workspaceStore,
327
- requestBody: {
328
- summary: appPath + ' - Reports flow',
329
- value: getFlowValue(),
330
- schema: {
331
- $schema: 'https://json-schema.org/draft/2020-12/schema',
332
- properties: {
333
- app_path: {
334
- description: '',
335
- type: 'string',
336
- default: null,
337
- format: ''
338
- },
339
- startup_duration: {
340
- description: '',
341
- type: 'integer',
342
- default: 5,
343
- format: ''
344
- },
345
- kind: {
346
- description: '',
347
- type: 'string',
348
- enum: ['pdf', 'png'],
349
- default: 'pdf',
350
- format: ''
351
- },
352
- ...(selectedTab === 'custom'
353
- ? customPathSchema.properties
354
- : notificationScripts[selectedTab].schema.properties),
355
- ...(selectedTab === 'slack'
356
- ? {
357
- slack: {
358
- description: '',
359
- type: 'object',
360
- format: 'resource-slack',
361
- properties: {},
362
- required: []
363
- }
364
- }
365
- : {})
366
- },
367
- required: [
368
- 'app_path',
369
- 'startup_duration',
370
- 'kind',
371
- ...(selectedTab === 'custom'
372
- ? customPathSchema.required
373
- : notificationScripts[selectedTab].schema.required),
374
- ...(selectedTab === 'slack' ? ['slack'] : [])
375
- ],
376
- type: 'object'
377
- },
378
- path: flowPath
379
- }
380
- });
381
- await ScheduleService.deleteSchedule({
382
- workspace: $workspaceStore,
383
- path: flowPath
384
- });
385
- await ScheduleService.createSchedule({
386
- workspace: $workspaceStore,
387
- requestBody: {
388
- path: flowPath,
389
- schedule: formatCron(appReportingSchedule.cron),
390
- timezone: appReportingSchedule.timezone,
391
- script_path: flowPath,
392
- is_flow: true,
393
- args: getFlowArgs(),
394
- enabled: true
395
- }
396
- });
397
- appReportingEnabled = true;
398
- }
399
- let testLoading = false;
400
- async function testReport() {
401
- try {
402
- testLoading = true;
403
- const jobId = await JobService.runFlowPreview({
404
- workspace: $workspaceStore,
405
- requestBody: {
406
- args: getFlowArgs(),
407
- value: getFlowValue()
408
- }
409
- });
410
- tryEvery({
411
- tryCode: async () => {
412
- let testResult = await JobService.getCompletedJob({
413
- workspace: $workspaceStore,
414
- id: jobId
415
- });
416
- testLoading = false;
417
- sendUserToast(testResult.success
418
- ? 'Report sent successfully'
419
- : 'Report error: ' + testResult.result?.['error']?.['message'], !testResult.success);
420
- },
421
- timeoutCode: async () => {
422
- testLoading = false;
423
- sendUserToast('Reports flow did not return after 30s', true);
424
- try {
425
- await JobService.cancelQueuedJob({
426
- workspace: $workspaceStore,
427
- id: jobId,
428
- requestBody: {
429
- reason: 'Reports flow did not return after 30s'
430
- }
431
- });
432
- }
433
- catch (err) {
434
- console.error(err);
435
- }
436
- },
437
- interval: 500,
438
- timeout: 30000
439
- });
440
- }
441
- catch (err) {
442
- sendUserToast('Could not test reports flow: ' + err, true);
443
- testLoading = false;
444
- }
445
- }
446
- let disabled = true;
447
- $: disabled =
448
- emptyString(appReportingSchedule.cron) ||
449
- (selectedTab === 'custom' && emptyString(customPath)) ||
450
- (selectedTab === 'slack' && !isSlackConnectedWorkspace) ||
451
- !areArgsValid;
452
5
  </script>
453
6
 
454
7
  <Drawer bind:open size="800px">
455
- <DrawerContent
456
- on:close={() => (open = false)}
457
- title="Schedule Reports"
458
- tooltip="Send a PDF or PNG preview of any app at a given schedule"
459
- documentationLink="https://www.windmill.dev/docs/apps/schedule_reports"
460
- ><svelte:fragment slot="actions">
461
- <div class="mr-4 center-center -mt-2">
462
- <Toggle
463
- checked={appReportingEnabled}
464
- options={{ right: 'enable', left: 'disable' }}
465
- on:change={async () => {
466
- if (appReportingEnabled) {
467
- disableAppReporting()
468
- } else {
469
- await enableAppReporting()
470
- sendUserToast('App reporting enabled')
471
- }
472
- }}
473
- disabled={disabled && !appReportingEnabled}
474
- />
475
- </div>
476
- <Button
477
- color="dark"
478
- startIcon={{ icon: Save }}
479
- size="sm"
480
- on:click={async () => {
481
- await enableAppReporting()
482
- sendUserToast('App reporting updated')
483
- open = false
484
- }}
485
- {disabled}
486
- >
487
- {appReportingEnabled ? 'Update' : 'Save and enable'}
488
- </Button>
489
- </svelte:fragment>
490
- <div class="flex flex-col gap-8">
491
- <Alert type="info" title="Scheduled PDF/PNG reports"
492
- >Send a PDF or PNG preview of the app at a given schedule. Enabling this feature will create
493
- a flow and a schedule in your workspace.
494
- <br /><br />
495
- For the flow to be executed, you need to set the WORKER_GROUP environment variable of one of
496
- your workers to "reports" or add the tag "chromium" to one of your worker groups.
497
- </Alert>
498
-
499
- <Section label="Reporting schedule">
500
- <CronInput
501
- bind:schedule={appReportingSchedule.cron}
502
- bind:timezone={appReportingSchedule.timezone}
503
- />
504
- </Section>
505
-
506
- <Section
507
- label="Startup duration in seconds"
508
- tooltip="The number of seconds to wait before capturing a preview to ensure that all startup scripts
509
- have been executed."
510
- >
511
- <div class="w-full pt-2">
512
- <input
513
- type="number"
514
- class="text-sm w-full font-semibold"
515
- bind:value={appReportingStartupDuration}
516
- />
517
- </div>
518
- </Section>
519
-
520
- <Section label="Screenshot kind">
521
- <div class="w-full pt-2">
522
- <select class="text-sm w-full font-semibold" bind:value={screenshotKind}>
523
- <option value="pdf">PDF</option>
524
- <option value="png">PNG</option>
525
- </select>
526
- </div></Section
527
- >
528
-
529
- <Section label="Notification">
530
- <Tabs bind:selected={selectedTab}>
531
- {#if !$enterpriseLicense}
532
- <Tab value="custom">Custom</Tab>
533
- {/if}
534
- <Tab value="slack" disabled={!$enterpriseLicense}
535
- >Slack{!$enterpriseLicense ? ' (EE only)' : ''}</Tab
536
- >
537
- <Tab value="discord" disabled={!$enterpriseLicense}
538
- >Discord{!$enterpriseLicense ? ' (EE only)' : ''}</Tab
539
- >
540
- <Tab value="email" disabled={!$enterpriseLicense}>
541
- <div class="flex flex-row gap-1 items-center"
542
- >Email{!$enterpriseLicense ? ' (EE only)' : ''}
543
- </div>
544
- </Tab>
545
- {#if $enterpriseLicense}
546
- <Tab value="custom">Custom</Tab>
547
- {/if}
548
- </Tabs>
549
- {#if selectedTab === 'custom'}
550
- <div class="pt-2">
551
- <ScriptPicker
552
- on:select={(ev) => {
553
- customPath = ev.detail.path
554
- }}
555
- initialPath={customPath}
556
- allowRefresh
557
- />
558
- </div>
559
- <div class="prose text-2xs text-tertiary mt-2">
560
- Pick a script that does whatever with the PDF/PNG report.
561
-
562
- <br />
563
-
564
- The script chosen is passed the parameters `screenshot: string`, `kind: 'pdf' | 'png'`,
565
- `app_path: string` where `screenshot` is the base64 encoded PDF/PNG report, `kind` is
566
- the type of the screenshot, and `app_path` is the path of the app being reported.
567
- </div>
568
- {/if}
569
- {#if selectedTab === 'slack'}
570
- <div class="pt-4">
571
- {#if isSlackConnectedWorkspace}
572
- <Alert type="info" title="Will use the Slack resource linked to the workspace" />
573
- {:else}
574
- <Alert type="error" title="Workspace not connected to Slack">
575
- <div class="flex flex-row gap-x-1 w-full items-center">
576
- <p class="text-clip grow min-w-0">
577
- The workspace needs to be connected to Slack to use this feature. You can <a
578
- target="_blank"
579
- href="{base}/workspace_settings?tab=slack">configure it here</a
580
- >.
581
- </p>
582
- <Button
583
- variant="border"
584
- color="light"
585
- on:click={getWorspaceSlackSetting}
586
- startIcon={{ icon: RotateCw }}
587
- />
588
- </div>
589
- </Alert>
590
- {/if}
591
- </div>
592
- {/if}
593
- <div class="w-full pt-4">
594
- {#if selectedTab !== 'custom' || customPath !== undefined}
595
- {#key selectedTab + JSON.stringify(customPathSchema ?? {})}
596
- <SchemaForm
597
- bind:isValid={areArgsValid}
598
- bind:args
599
- schema={selectedTab !== 'custom'
600
- ? notificationScripts[selectedTab].schema
601
- : customPathSchema}
602
- />
603
- {/key}
604
- {/if}
605
- </div>
606
- <Button
607
- loading={testLoading}
608
- {disabled}
609
- on:click={testReport}
610
- size="xs"
611
- color="dark"
612
- btnClasses="w-auto"
613
- >
614
- Send test report
615
- </Button>
616
- </Section>
617
- </div>
618
- </DrawerContent>
8
+ <AppReportsDrawerInner bind:open {appPath} />
619
9
  </Drawer>