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