@flowerforce/flowerbase 1.6.3-beta.0 → 1.6.3-beta.2
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/dist/monitoring/plugin.d.ts.map +1 -1
- package/dist/monitoring/plugin.js +11 -32
- package/package.json +1 -1
- package/src/monitoring/plugin.ts +14 -31
- package/src/monitoring/ui.css +129 -7
- package/src/monitoring/ui.html +80 -53
- package/src/monitoring/ui.js +330 -29
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/monitoring/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAA;AAG3B,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/monitoring/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAA;AAG3B,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAA;AA6jB5E,QAAA,MAAM,sBAAsB,QACrB,eAAe,SACd;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,kBAg6BH,CAAA;AAE1B,eAAe,sBAAsB,CAAA"}
|
|
@@ -282,36 +282,8 @@ const buildRulesMeta = (meta) => {
|
|
|
282
282
|
};
|
|
283
283
|
};
|
|
284
284
|
const buildCollectionRulesSnapshot = (rules, collection, user, runAsSystem) => {
|
|
285
|
-
var _a, _b;
|
|
286
285
|
const collectionRules = rules === null || rules === void 0 ? void 0 : rules[collection];
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
collection,
|
|
290
|
-
rules: null,
|
|
291
|
-
filters: [],
|
|
292
|
-
matchedFilters: [],
|
|
293
|
-
roles: [],
|
|
294
|
-
matchedRoles: [],
|
|
295
|
-
runAsSystem: !!runAsSystem
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
const filters = (_a = collectionRules.filters) !== null && _a !== void 0 ? _a : [];
|
|
299
|
-
const roles = (_b = collectionRules.roles) !== null && _b !== void 0 ? _b : [];
|
|
300
|
-
const safeUser = (user !== null && user !== void 0 ? user : {});
|
|
301
|
-
const matchedFilters = (0, utils_1.getValidRule)({ filters, user: safeUser });
|
|
302
|
-
const matchedFilterNames = matchedFilters.map((filter) => filter.name);
|
|
303
|
-
const matchedRoles = roles
|
|
304
|
-
.filter((role) => (0, utils_2.checkApplyWhen)(role.apply_when, safeUser, null))
|
|
305
|
-
.map((role) => role.name);
|
|
306
|
-
return {
|
|
307
|
-
collection,
|
|
308
|
-
rules: sanitize(collectionRules),
|
|
309
|
-
filters: filters.map((filter) => filter.name),
|
|
310
|
-
matchedFilters: matchedFilterNames,
|
|
311
|
-
roles: roles.map((role) => role.name),
|
|
312
|
-
matchedRoles,
|
|
313
|
-
runAsSystem: !!runAsSystem
|
|
314
|
-
};
|
|
286
|
+
return collectionRules !== null && collectionRules !== void 0 ? collectionRules : null;
|
|
315
287
|
};
|
|
316
288
|
const resolveAssetCandidates = (filename, prefix) => {
|
|
317
289
|
const rootDir = process.cwd();
|
|
@@ -410,11 +382,11 @@ const wrapServicesForMonitoring = (addEvent) => {
|
|
|
410
382
|
api: 'api',
|
|
411
383
|
aws: 'aws',
|
|
412
384
|
auth: 'auth',
|
|
413
|
-
'mongodb-atlas': '
|
|
385
|
+
'mongodb-atlas': 'mongo'
|
|
414
386
|
};
|
|
415
387
|
const initMethodMap = {
|
|
416
388
|
aws: new Set(['lambda', 's3']),
|
|
417
|
-
'mongodb-atlas': new Set(['db', 'collection'])
|
|
389
|
+
'mongodb-atlas': new Set(['db', 'collection', 'limit', 'skip', 'toArray'])
|
|
418
390
|
};
|
|
419
391
|
const cache = new WeakMap();
|
|
420
392
|
const wrapValue = (value, path, serviceName, meta) => {
|
|
@@ -854,6 +826,10 @@ const createMonitoringPlugin = (0, fastify_plugin_1.default)((app_1, ...args_1)
|
|
|
854
826
|
});
|
|
855
827
|
return { items };
|
|
856
828
|
}));
|
|
829
|
+
app.get(`${prefix}/api/triggers`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
830
|
+
const triggersList = state_1.StateManager.select('triggers');
|
|
831
|
+
return { items: triggersList || [] };
|
|
832
|
+
}));
|
|
857
833
|
app.get(`${prefix}/api/functions/:name`, (req, reply) => __awaiter(void 0, void 0, void 0, function* () {
|
|
858
834
|
var _a;
|
|
859
835
|
if (!allowEdit) {
|
|
@@ -926,12 +902,15 @@ const createMonitoringPlugin = (0, fastify_plugin_1.default)((app_1, ...args_1)
|
|
|
926
902
|
: undefined)
|
|
927
903
|
}
|
|
928
904
|
: undefined;
|
|
905
|
+
const codeModified = typeof overrideCode === 'string' && overrideCode !== currentFunction.code;
|
|
929
906
|
addFunctionHistory({
|
|
930
907
|
ts: Date.now(),
|
|
931
908
|
name,
|
|
932
909
|
args: safeArgs,
|
|
933
910
|
runAsSystem: effectiveRunAsSystem,
|
|
934
|
-
user: userInfo
|
|
911
|
+
user: userInfo,
|
|
912
|
+
code: codeModified ? overrideCode : undefined,
|
|
913
|
+
codeModified
|
|
935
914
|
});
|
|
936
915
|
addEvent({
|
|
937
916
|
id: createEventId(),
|
package/package.json
CHANGED
package/src/monitoring/plugin.ts
CHANGED
|
@@ -31,6 +31,8 @@ type FunctionHistoryItem = {
|
|
|
31
31
|
args: unknown[]
|
|
32
32
|
runAsSystem: boolean
|
|
33
33
|
user?: { id?: string; email?: string }
|
|
34
|
+
code?: string
|
|
35
|
+
codeModified?: boolean
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
type CollectionHistoryItem = {
|
|
@@ -339,34 +341,7 @@ const buildCollectionRulesSnapshot = (
|
|
|
339
341
|
runAsSystem?: boolean
|
|
340
342
|
) => {
|
|
341
343
|
const collectionRules = rules?.[collection]
|
|
342
|
-
|
|
343
|
-
return {
|
|
344
|
-
collection,
|
|
345
|
-
rules: null,
|
|
346
|
-
filters: [],
|
|
347
|
-
matchedFilters: [],
|
|
348
|
-
roles: [],
|
|
349
|
-
matchedRoles: [],
|
|
350
|
-
runAsSystem: !!runAsSystem
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
const filters = collectionRules.filters ?? []
|
|
354
|
-
const roles = collectionRules.roles ?? []
|
|
355
|
-
const safeUser = (user ?? {}) as Parameters<typeof getValidRule>[0]['user']
|
|
356
|
-
const matchedFilters = getValidRule({ filters, user: safeUser })
|
|
357
|
-
const matchedFilterNames = matchedFilters.map((filter) => filter.name)
|
|
358
|
-
const matchedRoles = roles
|
|
359
|
-
.filter((role) => checkApplyWhen(role.apply_when, safeUser as never, null))
|
|
360
|
-
.map((role) => role.name)
|
|
361
|
-
return {
|
|
362
|
-
collection,
|
|
363
|
-
rules: sanitize(collectionRules),
|
|
364
|
-
filters: filters.map((filter) => filter.name),
|
|
365
|
-
matchedFilters: matchedFilterNames,
|
|
366
|
-
roles: roles.map((role) => role.name),
|
|
367
|
-
matchedRoles,
|
|
368
|
-
runAsSystem: !!runAsSystem
|
|
369
|
-
}
|
|
344
|
+
return collectionRules ?? null
|
|
370
345
|
}
|
|
371
346
|
|
|
372
347
|
const resolveAssetCandidates = (filename: string, prefix: string) => {
|
|
@@ -490,11 +465,11 @@ const wrapServicesForMonitoring = (addEvent: (event: MonitorEvent) => void) => {
|
|
|
490
465
|
api: 'api',
|
|
491
466
|
aws: 'aws',
|
|
492
467
|
auth: 'auth',
|
|
493
|
-
'mongodb-atlas': '
|
|
468
|
+
'mongodb-atlas': 'mongo'
|
|
494
469
|
}
|
|
495
470
|
const initMethodMap: Record<string, Set<string>> = {
|
|
496
471
|
aws: new Set(['lambda', 's3']),
|
|
497
|
-
'mongodb-atlas': new Set(['db', 'collection'])
|
|
472
|
+
'mongodb-atlas': new Set(['db', 'collection', 'limit', 'skip', 'toArray'])
|
|
498
473
|
}
|
|
499
474
|
|
|
500
475
|
const cache = new WeakMap<object, unknown>()
|
|
@@ -972,6 +947,11 @@ const createMonitoringPlugin = fp(async (
|
|
|
972
947
|
return { items }
|
|
973
948
|
})
|
|
974
949
|
|
|
950
|
+
app.get(`${prefix}/api/triggers`, async () => {
|
|
951
|
+
const triggersList = StateManager.select('triggers') as { fileName: string; content: unknown }[] | undefined
|
|
952
|
+
return { items: triggersList || [] }
|
|
953
|
+
})
|
|
954
|
+
|
|
975
955
|
app.get(`${prefix}/api/functions/:name`, async (req, reply) => {
|
|
976
956
|
if (!allowEdit) {
|
|
977
957
|
reply.code(403)
|
|
@@ -1059,12 +1039,15 @@ const createMonitoringPlugin = fp(async (
|
|
|
1059
1039
|
: undefined)
|
|
1060
1040
|
}
|
|
1061
1041
|
: undefined
|
|
1042
|
+
const codeModified = typeof overrideCode === 'string' && overrideCode !== currentFunction.code
|
|
1062
1043
|
addFunctionHistory({
|
|
1063
1044
|
ts: Date.now(),
|
|
1064
1045
|
name,
|
|
1065
1046
|
args: safeArgs,
|
|
1066
1047
|
runAsSystem: effectiveRunAsSystem,
|
|
1067
|
-
user: userInfo
|
|
1048
|
+
user: userInfo,
|
|
1049
|
+
code: codeModified ? overrideCode : undefined,
|
|
1050
|
+
codeModified
|
|
1068
1051
|
})
|
|
1069
1052
|
|
|
1070
1053
|
addEvent({
|
package/src/monitoring/ui.css
CHANGED
|
@@ -173,6 +173,10 @@ main {
|
|
|
173
173
|
grid-template-columns: minmax(220px, 30%) minmax(0, 1fr);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
.triggers-grid {
|
|
177
|
+
grid-template-columns: minmax(220px, 30%) minmax(0, 1fr);
|
|
178
|
+
}
|
|
179
|
+
|
|
176
180
|
.collections-grid {
|
|
177
181
|
grid-template-columns: minmax(220px, 30%) minmax(0, 1fr);
|
|
178
182
|
}
|
|
@@ -276,6 +280,12 @@ button {
|
|
|
276
280
|
cursor: pointer;
|
|
277
281
|
}
|
|
278
282
|
|
|
283
|
+
button:disabled {
|
|
284
|
+
opacity: 0.45;
|
|
285
|
+
cursor: not-allowed;
|
|
286
|
+
filter: grayscale(0.2);
|
|
287
|
+
}
|
|
288
|
+
|
|
279
289
|
button.secondary {
|
|
280
290
|
color: var(--muted);
|
|
281
291
|
border-color: var(--border);
|
|
@@ -301,7 +311,7 @@ button.danger {
|
|
|
301
311
|
|
|
302
312
|
.event-row {
|
|
303
313
|
display: grid;
|
|
304
|
-
grid-template-columns: 86px 90px 1fr;
|
|
314
|
+
grid-template-columns: 86px 90px 160px 1fr;
|
|
305
315
|
gap: 8px;
|
|
306
316
|
padding: 4px 6px;
|
|
307
317
|
border-bottom: 1px dashed rgba(26, 47, 34, 0.6);
|
|
@@ -328,6 +338,22 @@ button.danger {
|
|
|
328
338
|
color: var(--warn);
|
|
329
339
|
}
|
|
330
340
|
|
|
341
|
+
.event-user {
|
|
342
|
+
color: var(--muted);
|
|
343
|
+
overflow: hidden;
|
|
344
|
+
text-overflow: ellipsis;
|
|
345
|
+
white-space: nowrap;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.event-user.is-link {
|
|
349
|
+
color: var(--accent);
|
|
350
|
+
cursor: pointer;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.event-user.is-link:hover {
|
|
354
|
+
text-decoration: underline;
|
|
355
|
+
}
|
|
356
|
+
|
|
331
357
|
.event-detail {
|
|
332
358
|
margin-top: 10px;
|
|
333
359
|
background: var(--panel);
|
|
@@ -416,6 +442,26 @@ button.small {
|
|
|
416
442
|
background: rgba(49, 233, 129, 0.08);
|
|
417
443
|
}
|
|
418
444
|
|
|
445
|
+
.history-modified {
|
|
446
|
+
color: var(--warn);
|
|
447
|
+
text-transform: uppercase;
|
|
448
|
+
letter-spacing: 0.6px;
|
|
449
|
+
font-size: 10px;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.modified-pill {
|
|
453
|
+
display: inline-flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
padding: 1px 6px;
|
|
456
|
+
border-radius: 999px;
|
|
457
|
+
font-size: 10px;
|
|
458
|
+
text-transform: uppercase;
|
|
459
|
+
letter-spacing: 0.6px;
|
|
460
|
+
color: var(--warn);
|
|
461
|
+
border: 1px solid rgba(255, 176, 64, 0.5);
|
|
462
|
+
background: rgba(255, 176, 64, 0.12);
|
|
463
|
+
}
|
|
464
|
+
|
|
419
465
|
.history-meta {
|
|
420
466
|
display: flex;
|
|
421
467
|
flex-direction: column;
|
|
@@ -462,6 +508,10 @@ button.small {
|
|
|
462
508
|
border-color: rgba(49, 233, 129, 0.45);
|
|
463
509
|
}
|
|
464
510
|
|
|
511
|
+
.user-row.disabled {
|
|
512
|
+
opacity: 0.55;
|
|
513
|
+
}
|
|
514
|
+
|
|
465
515
|
.user-row:hover {
|
|
466
516
|
background: rgba(49, 233, 129, 0.08);
|
|
467
517
|
}
|
|
@@ -500,6 +550,16 @@ button.small {
|
|
|
500
550
|
cursor: pointer;
|
|
501
551
|
}
|
|
502
552
|
|
|
553
|
+
.trigger-row {
|
|
554
|
+
display: flex;
|
|
555
|
+
align-items: center;
|
|
556
|
+
justify-content: space-between;
|
|
557
|
+
gap: 8px;
|
|
558
|
+
padding: 6px;
|
|
559
|
+
border-bottom: 1px dashed rgba(26, 47, 34, 0.6);
|
|
560
|
+
cursor: pointer;
|
|
561
|
+
}
|
|
562
|
+
|
|
503
563
|
.function-row.active {
|
|
504
564
|
background: rgba(49, 233, 129, 0.12);
|
|
505
565
|
border-color: rgba(49, 233, 129, 0.35);
|
|
@@ -509,6 +569,24 @@ button.small {
|
|
|
509
569
|
background: rgba(49, 233, 129, 0.08);
|
|
510
570
|
}
|
|
511
571
|
|
|
572
|
+
.trigger-row.active {
|
|
573
|
+
background: rgba(49, 233, 129, 0.12);
|
|
574
|
+
border-color: rgba(49, 233, 129, 0.35);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.trigger-row:hover {
|
|
578
|
+
background: rgba(49, 233, 129, 0.08);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.trigger-link {
|
|
582
|
+
color: var(--accent);
|
|
583
|
+
cursor: pointer;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.trigger-link:hover {
|
|
587
|
+
text-decoration: underline;
|
|
588
|
+
}
|
|
589
|
+
|
|
512
590
|
.user-meta {
|
|
513
591
|
display: flex;
|
|
514
592
|
flex-direction: column;
|
|
@@ -549,6 +627,26 @@ button.small {
|
|
|
549
627
|
min-height: 0;
|
|
550
628
|
}
|
|
551
629
|
|
|
630
|
+
.collection-actions {
|
|
631
|
+
display: flex;
|
|
632
|
+
flex-wrap: wrap;
|
|
633
|
+
align-items: center;
|
|
634
|
+
justify-content: space-between;
|
|
635
|
+
gap: 10px;
|
|
636
|
+
margin-top: 20px;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.collection-actions .editor-actions {
|
|
640
|
+
display: flex;
|
|
641
|
+
flex-wrap: wrap;
|
|
642
|
+
align-items: center;
|
|
643
|
+
gap: 12px;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.collection-actions .result-toolbar {
|
|
647
|
+
margin-left: auto;
|
|
648
|
+
}
|
|
649
|
+
|
|
552
650
|
.mini-tabs {
|
|
553
651
|
display: flex;
|
|
554
652
|
gap: 8px;
|
|
@@ -597,22 +695,46 @@ button.small {
|
|
|
597
695
|
min-height: 0;
|
|
598
696
|
}
|
|
599
697
|
|
|
600
|
-
.collection-
|
|
698
|
+
.collection-layout {
|
|
601
699
|
display: grid;
|
|
602
|
-
grid-template-columns:
|
|
700
|
+
grid-template-columns: minmax(200px, 30%) minmax(0, 70%);
|
|
701
|
+
gap: 12px;
|
|
702
|
+
align-items: start;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.collection-controls {
|
|
706
|
+
display: flex;
|
|
707
|
+
flex-direction: column;
|
|
603
708
|
gap: 10px;
|
|
604
|
-
align-items: end;
|
|
605
709
|
}
|
|
606
710
|
|
|
607
711
|
.collection-io {
|
|
608
712
|
display: grid;
|
|
609
|
-
grid-template-columns: minmax(0, 1fr)
|
|
713
|
+
grid-template-columns: minmax(0, 1fr);
|
|
610
714
|
gap: 12px;
|
|
611
715
|
align-items: stretch;
|
|
612
716
|
}
|
|
613
717
|
|
|
718
|
+
.collection-io[data-mode="query"] [data-collection-mode="aggregate"] {
|
|
719
|
+
display: none;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.collection-io[data-mode="aggregate"] [data-collection-mode="query"] {
|
|
723
|
+
display: none;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.collection-io {
|
|
727
|
+
height: 100%;
|
|
728
|
+
}
|
|
729
|
+
|
|
614
730
|
.collection-io textarea {
|
|
615
731
|
min-height: 140px;
|
|
732
|
+
height: 100%;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.collection-io .json-editor {
|
|
736
|
+
height: 100%;
|
|
737
|
+
min-height: 100%;
|
|
616
738
|
}
|
|
617
739
|
|
|
618
740
|
.json-editor {
|
|
@@ -977,7 +1099,7 @@ button.small {
|
|
|
977
1099
|
grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
|
|
978
1100
|
}
|
|
979
1101
|
|
|
980
|
-
.collection-
|
|
1102
|
+
.collection-layout {
|
|
981
1103
|
grid-template-columns: minmax(0, 1fr);
|
|
982
1104
|
}
|
|
983
|
-
}
|
|
1105
|
+
}
|
package/src/monitoring/ui.html
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
<button class="tab-button active" data-tab="events">events</button>
|
|
27
27
|
<button class="tab-button" data-tab="users">users</button>
|
|
28
28
|
<button class="tab-button" data-tab="functions">functions</button>
|
|
29
|
+
<button class="tab-button" data-tab="triggers">triggers</button>
|
|
29
30
|
<button class="tab-button" data-tab="collections">collections</button>
|
|
30
31
|
</div>
|
|
31
32
|
<div class="tab-panels">
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
<option value="http_endpoint">http_endpoint</option>
|
|
47
48
|
<option value="api">api</option>
|
|
48
49
|
<option value="aws">aws</option>
|
|
49
|
-
<option value="
|
|
50
|
+
<option value="mongo">mongo</option>
|
|
50
51
|
<option value="log">log</option>
|
|
51
52
|
<option value="error">error</option>
|
|
52
53
|
</select>
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
<div class="controls">
|
|
74
75
|
<input id="userSearch" class="wide-input" type="text" placeholder="search users (email, id, status)" />
|
|
75
76
|
</div>
|
|
76
|
-
<div class="split-grid users-grid">
|
|
77
|
+
<div class="split-grid users-grid functions-grid">
|
|
77
78
|
<div class="column-stack">
|
|
78
79
|
<div class="subpanel list-panel">
|
|
79
80
|
<div class="subpanel-title">users</div>
|
|
@@ -166,7 +167,6 @@
|
|
|
166
167
|
<button id="restoreFunction" class="secondary">restore original</button>
|
|
167
168
|
<button id="invokeFunction">invoke</button>
|
|
168
169
|
</div>
|
|
169
|
-
<div class="hint" id="functionEditorStatus"></div>
|
|
170
170
|
</div>
|
|
171
171
|
<div class="function-io">
|
|
172
172
|
<div class="io-column">
|
|
@@ -182,6 +182,29 @@
|
|
|
182
182
|
</div>
|
|
183
183
|
</div>
|
|
184
184
|
</section>
|
|
185
|
+
<section class="panel tab-panel" data-panel="triggers">
|
|
186
|
+
<div class="panel-header">
|
|
187
|
+
<span>Triggers</span>
|
|
188
|
+
<button id="refreshTriggers" class="secondary">refresh</button>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="split-grid triggers-grid">
|
|
191
|
+
<div class="column-stack">
|
|
192
|
+
<div class="subpanel list-panel">
|
|
193
|
+
<div class="subpanel-title">triggers</div>
|
|
194
|
+
<div id="triggerList" class="user-list"></div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
<div class="subpanel detail-panel">
|
|
198
|
+
<div class="detail-header">
|
|
199
|
+
<div class="subpanel-title">trigger detail</div>
|
|
200
|
+
<button id="triggerFunctionButton" type="button" class="secondary small is-hidden">
|
|
201
|
+
go to function
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
<pre id="triggerDetail" class="event-detail">select a trigger to inspect</pre>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</section>
|
|
185
208
|
<section class="panel tab-panel" data-panel="collections">
|
|
186
209
|
<div class="panel-header">
|
|
187
210
|
<span>Collections</span>
|
|
@@ -213,61 +236,65 @@
|
|
|
213
236
|
</div>
|
|
214
237
|
<div class="mini-panels">
|
|
215
238
|
<div class="mini-panel active" data-collection-panel="query">
|
|
216
|
-
<div class="collection-
|
|
217
|
-
<div class="
|
|
218
|
-
<
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
<div class="collection-io">
|
|
242
|
-
<div class="io-column">
|
|
243
|
-
<label for="collectionQuery">query</label>
|
|
244
|
-
<div class="json-editor">
|
|
245
|
-
<pre id="collectionQueryHighlight" class="json-highlight"></pre>
|
|
246
|
-
<textarea id="collectionQuery" placeholder='{"field": "value"}'></textarea>
|
|
239
|
+
<div class="collection-layout">
|
|
240
|
+
<div class="collection-controls">
|
|
241
|
+
<div class="control-group">
|
|
242
|
+
<label for="collectionUserInput">user</label>
|
|
243
|
+
<input id="collectionUserInput" list="collectionUserList" placeholder="select user" />
|
|
244
|
+
<datalist id="collectionUserList"></datalist>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="control-group">
|
|
247
|
+
<label for="collectionRunMode">run as</label>
|
|
248
|
+
<select id="collectionRunMode">
|
|
249
|
+
<option value="system" selected>system</option>
|
|
250
|
+
<option value="user">user</option>
|
|
251
|
+
</select>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="control-group">
|
|
254
|
+
<label for="collectionMode">mode</label>
|
|
255
|
+
<select id="collectionMode">
|
|
256
|
+
<option value="query" selected>query</option>
|
|
257
|
+
<option value="aggregate">aggregate</option>
|
|
258
|
+
</select>
|
|
259
|
+
</div>
|
|
260
|
+
<div class="control-group">
|
|
261
|
+
<label for="collectionSort">sort</label>
|
|
262
|
+
<input id="collectionSort" type="text" placeholder='{"createdAt": -1}' />
|
|
263
|
+
</div>
|
|
247
264
|
</div>
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
265
|
+
<div id="collectionIo" class="collection-io" data-mode="query">
|
|
266
|
+
<div class="io-column" data-collection-mode="query">
|
|
267
|
+
<label for="collectionQuery">query</label>
|
|
268
|
+
<div class="json-editor">
|
|
269
|
+
<pre id="collectionQueryHighlight" class="json-highlight"></pre>
|
|
270
|
+
<textarea id="collectionQuery" placeholder='{"field": "value"}'></textarea>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="io-column" data-collection-mode="aggregate">
|
|
274
|
+
<label for="collectionAggregate">aggregate</label>
|
|
275
|
+
<div class="json-editor">
|
|
276
|
+
<pre id="collectionAggregateHighlight" class="json-highlight"></pre>
|
|
277
|
+
<textarea id="collectionAggregate" placeholder='[{ "$match": { "field": "value" } }]'></textarea>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
254
280
|
</div>
|
|
255
281
|
</div>
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
282
|
+
<div class="collection-actions">
|
|
283
|
+
<div class="editor-actions">
|
|
284
|
+
<button id="runCollectionQuery">run</button>
|
|
285
|
+
<div class="pager">
|
|
286
|
+
<div class="pager-controls">
|
|
287
|
+
<button id="collectionPrev" class="secondary">prev</button>
|
|
288
|
+
<button id="collectionNext" class="secondary">next</button>
|
|
289
|
+
</div>
|
|
290
|
+
<div>page <span id="collectionPage">1</span>/<span id="collectionPages">1</span> · total <span id="collectionTotal">0</span></div>
|
|
263
291
|
</div>
|
|
264
|
-
<div>page <span id="collectionPage">1</span> · total <span id="collectionTotal">0</span></div>
|
|
265
292
|
</div>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
293
|
+
<div class="result-toolbar">
|
|
294
|
+
<div class="toggle-buttons">
|
|
295
|
+
<button id="collectionViewJson" class="secondary active">json</button>
|
|
296
|
+
<button id="collectionViewTable" class="secondary">table</button>
|
|
297
|
+
</div>
|
|
271
298
|
</div>
|
|
272
299
|
</div>
|
|
273
300
|
<div id="collectionResult" class="event-detail function-result"></div>
|
package/src/monitoring/ui.js
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
functionUserMap: {},
|
|
20
20
|
functionUserQuery: '',
|
|
21
21
|
__functionUserTimer: null,
|
|
22
|
+
triggers: [],
|
|
23
|
+
selectedTrigger: null,
|
|
24
|
+
triggerFunctionMap: {},
|
|
22
25
|
collections: [],
|
|
23
26
|
selectedCollection: null,
|
|
24
27
|
collectionSearch: '',
|
|
@@ -28,6 +31,9 @@
|
|
|
28
31
|
collectionPage: 1,
|
|
29
32
|
collectionHasMore: false,
|
|
30
33
|
collectionTotal: 0,
|
|
34
|
+
collectionPageSize: 50,
|
|
35
|
+
collectionLoading: false,
|
|
36
|
+
collectionTotalsLoading: false,
|
|
31
37
|
collectionResultView: 'json',
|
|
32
38
|
collectionResultPayload: null,
|
|
33
39
|
collectionResultHighlight: false,
|
|
@@ -74,18 +80,22 @@
|
|
|
74
80
|
const functionHighlight = document.getElementById('functionHighlight');
|
|
75
81
|
const functionGutter = document.getElementById('functionGutter');
|
|
76
82
|
const restoreFunction = document.getElementById('restoreFunction');
|
|
77
|
-
const functionEditorStatus = document.getElementById('functionEditorStatus');
|
|
78
83
|
const functionArgs = document.getElementById('functionArgs');
|
|
79
84
|
const invokeFunction = document.getElementById('invokeFunction');
|
|
80
85
|
const functionResult = document.getElementById('functionResult');
|
|
81
86
|
const refreshFunctions = document.getElementById('refreshFunctions');
|
|
87
|
+
const refreshTriggers = document.getElementById('refreshTriggers');
|
|
82
88
|
const functionHistory = document.getElementById('functionHistory');
|
|
89
|
+
const triggerList = document.getElementById('triggerList');
|
|
90
|
+
const triggerDetail = document.getElementById('triggerDetail');
|
|
91
|
+
const triggerFunctionButton = document.getElementById('triggerFunctionButton');
|
|
83
92
|
const refreshCollections = document.getElementById('refreshCollections');
|
|
84
93
|
const collectionSearch = document.getElementById('collectionSearch');
|
|
85
94
|
const collectionList = document.getElementById('collectionList');
|
|
86
95
|
const collectionHistory = document.getElementById('collectionHistory');
|
|
87
96
|
const collectionRules = document.getElementById('collectionRules');
|
|
88
97
|
const collectionSelected = document.getElementById('collectionSelected');
|
|
98
|
+
const collectionIo = document.getElementById('collectionIo');
|
|
89
99
|
const collectionUserInput = document.getElementById('collectionUserInput');
|
|
90
100
|
const collectionUserList = document.getElementById('collectionUserList');
|
|
91
101
|
const collectionRunMode = document.getElementById('collectionRunMode');
|
|
@@ -100,6 +110,7 @@
|
|
|
100
110
|
const collectionPrev = document.getElementById('collectionPrev');
|
|
101
111
|
const collectionNext = document.getElementById('collectionNext');
|
|
102
112
|
const collectionPage = document.getElementById('collectionPage');
|
|
113
|
+
const collectionPages = document.getElementById('collectionPages');
|
|
103
114
|
const collectionTotal = document.getElementById('collectionTotal');
|
|
104
115
|
const collectionViewJson = document.getElementById('collectionViewJson');
|
|
105
116
|
const collectionViewTable = document.getElementById('collectionViewTable');
|
|
@@ -172,9 +183,26 @@
|
|
|
172
183
|
};
|
|
173
184
|
|
|
174
185
|
const updateCollectionPager = () => {
|
|
186
|
+
if (state.collectionLoading) {
|
|
187
|
+
if (collectionPage) collectionPage.textContent = '...';
|
|
188
|
+
if (state.collectionTotalsLoading) {
|
|
189
|
+
if (collectionPages) collectionPages.textContent = '...';
|
|
190
|
+
if (collectionTotal) collectionTotal.textContent = '...';
|
|
191
|
+
}
|
|
192
|
+
if (collectionPrev) collectionPrev.disabled = true;
|
|
193
|
+
if (collectionNext) collectionNext.disabled = true;
|
|
194
|
+
if (state.collectionTotalsLoading) return;
|
|
195
|
+
}
|
|
175
196
|
if (collectionPage) {
|
|
176
197
|
collectionPage.textContent = String(state.collectionPage || 1);
|
|
177
198
|
}
|
|
199
|
+
const totalPages = Math.max(
|
|
200
|
+
1,
|
|
201
|
+
Math.ceil((state.collectionTotal || 0) / Math.max(state.collectionPageSize || 1, 1))
|
|
202
|
+
);
|
|
203
|
+
if (collectionPages) {
|
|
204
|
+
collectionPages.textContent = String(totalPages);
|
|
205
|
+
}
|
|
178
206
|
if (collectionTotal) {
|
|
179
207
|
collectionTotal.textContent = String(state.collectionTotal || 0);
|
|
180
208
|
}
|
|
@@ -503,6 +531,40 @@
|
|
|
503
531
|
return time + ' ' + type.padEnd(12, ' ') + ' ' + msg;
|
|
504
532
|
};
|
|
505
533
|
|
|
534
|
+
const getEventUserId = (event) => {
|
|
535
|
+
if (!event || typeof event !== 'object') return '';
|
|
536
|
+
if (typeof event.userId === 'string') return event.userId;
|
|
537
|
+
if (event.user && typeof event.user === 'object') {
|
|
538
|
+
if (typeof event.user.id === 'string') return event.user.id;
|
|
539
|
+
if (typeof event.user._id === 'string') return event.user._id;
|
|
540
|
+
if (typeof event.user.userId === 'string') return event.user.userId;
|
|
541
|
+
}
|
|
542
|
+
const data = event.data;
|
|
543
|
+
if (!data || typeof data !== 'object') return '';
|
|
544
|
+
if (typeof data.userId === 'string') return data.userId;
|
|
545
|
+
if (typeof data.user_id === 'string') return data.user_id;
|
|
546
|
+
if (data.user && typeof data.user === 'object') {
|
|
547
|
+
if (typeof data.user.id === 'string') return data.user.id;
|
|
548
|
+
if (typeof data.user._id === 'string') return data.user._id;
|
|
549
|
+
if (typeof data.user.userId === 'string') return data.user.userId;
|
|
550
|
+
}
|
|
551
|
+
if (data.user_data && typeof data.user_data === 'object') {
|
|
552
|
+
if (typeof data.user_data.id === 'string') return data.user_data.id;
|
|
553
|
+
if (typeof data.user_data._id === 'string') return data.user_data._id;
|
|
554
|
+
}
|
|
555
|
+
return '';
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const goToUser = (userId) => {
|
|
559
|
+
if (!userId || !userSearch) return;
|
|
560
|
+
state.selectedUserId = String(userId);
|
|
561
|
+
state.userQuery = state.selectedUserId;
|
|
562
|
+
state.customPage = 1;
|
|
563
|
+
userSearch.value = state.userQuery;
|
|
564
|
+
setActiveTab('users');
|
|
565
|
+
loadUsers();
|
|
566
|
+
};
|
|
567
|
+
|
|
506
568
|
const getFunctionEventData = (event) => {
|
|
507
569
|
if (!event || !event.type || event.type !== 'function') return null;
|
|
508
570
|
const data = event.data || {};
|
|
@@ -542,16 +604,28 @@
|
|
|
542
604
|
if (type && event.type !== type) return false;
|
|
543
605
|
return matchesQuery(event, query);
|
|
544
606
|
});
|
|
545
|
-
const recent = filtered.slice(-350);
|
|
607
|
+
const recent = filtered.slice(-350).reverse();
|
|
546
608
|
eventsList.innerHTML = '';
|
|
547
609
|
recent.forEach((event) => {
|
|
610
|
+
const userId = getEventUserId(event);
|
|
548
611
|
const row = document.createElement('div');
|
|
549
612
|
row.className = 'event-row';
|
|
550
613
|
row.dataset.id = event.id;
|
|
551
614
|
const typeClass = event.type === 'error' ? 'error' : (event.type === 'warn' ? 'warn' : '');
|
|
552
615
|
row.innerHTML = '<div>' + formatTime(event.ts) + '</div>' +
|
|
553
616
|
'<div class="event-type ' + typeClass + '">' + (event.type || '-') + '</div>' +
|
|
617
|
+
'<div class="event-user" title="' + (userId || '-') + '">' + (userId || '-') + '</div>' +
|
|
554
618
|
'<div>' + (event.message || '') + '</div>';
|
|
619
|
+
if (userId) {
|
|
620
|
+
const userCell = row.querySelector('.event-user');
|
|
621
|
+
if (userCell) {
|
|
622
|
+
userCell.classList.add('is-link');
|
|
623
|
+
userCell.addEventListener('click', (clickEvent) => {
|
|
624
|
+
clickEvent.stopPropagation();
|
|
625
|
+
goToUser(userId);
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
555
629
|
row.addEventListener('click', () => showDetail(event));
|
|
556
630
|
eventsList.appendChild(row);
|
|
557
631
|
});
|
|
@@ -560,7 +634,9 @@
|
|
|
560
634
|
const showDetail = (event) => {
|
|
561
635
|
state.selectedId = event.id;
|
|
562
636
|
state.selectedEvent = event;
|
|
563
|
-
|
|
637
|
+
const text = JSON.stringify(event, null, 2) || '';
|
|
638
|
+
eventDetail.classList.add('json-highlight');
|
|
639
|
+
eventDetail.innerHTML = highlightJson(text);
|
|
564
640
|
const functionData = getFunctionEventData(event);
|
|
565
641
|
if (functionData && eventFunctionButton) {
|
|
566
642
|
eventFunctionButton.classList.remove('is-hidden');
|
|
@@ -694,7 +770,10 @@
|
|
|
694
770
|
const hint = createdLabel ? status + ' · ' + createdLabel : status;
|
|
695
771
|
const hasAuth = !!(auth && auth._id);
|
|
696
772
|
const row = document.createElement('div');
|
|
697
|
-
|
|
773
|
+
const isDisabled = auth && auth.status === 'disabled';
|
|
774
|
+
row.className = 'user-row' +
|
|
775
|
+
(state.selectedUserId === userId ? ' active' : '') +
|
|
776
|
+
(isDisabled ? ' disabled' : '');
|
|
698
777
|
row.dataset.id = userId;
|
|
699
778
|
row.innerHTML = '<div class="user-meta">' +
|
|
700
779
|
'<div class="code">' + primaryEmail + '</div>' +
|
|
@@ -754,12 +833,112 @@
|
|
|
754
833
|
row.dataset.name = fn.name;
|
|
755
834
|
const runMode = fn.run_as_system ? 'system' : 'user';
|
|
756
835
|
const visibility = fn.private ? 'private' : 'public';
|
|
836
|
+
const triggerName = state.triggerFunctionMap ? state.triggerFunctionMap[fn.name] : null;
|
|
837
|
+
const metaParts = [visibility, runMode];
|
|
838
|
+
const triggerTag = triggerName
|
|
839
|
+
? ' · <span class="trigger-link" data-trigger="' + escapeHtml(triggerName) + '">isTrigger</span>'
|
|
840
|
+
: '';
|
|
757
841
|
row.innerHTML = '<div class="code">' + fn.name + '</div>' +
|
|
758
|
-
'<div class="hint">' +
|
|
842
|
+
'<div class="hint">' + metaParts.join(' · ') + triggerTag + '</div>';
|
|
759
843
|
functionList.appendChild(row);
|
|
760
844
|
});
|
|
761
845
|
};
|
|
762
846
|
|
|
847
|
+
const buildTriggerFunctionMap = (items) => {
|
|
848
|
+
const map = {};
|
|
849
|
+
(items || []).forEach((entry) => {
|
|
850
|
+
const content = entry && entry.content ? entry.content : null;
|
|
851
|
+
const handler = content && content.event_processors ? content.event_processors.FUNCTION : null;
|
|
852
|
+
const fnName = handler && handler.config ? handler.config.function_name : null;
|
|
853
|
+
const triggerName = (content && content.name) || entry.fileName || 'trigger';
|
|
854
|
+
if (typeof fnName === 'string' && fnName) {
|
|
855
|
+
map[fnName] = triggerName;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
state.triggerFunctionMap = map;
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
const renderTriggers = (items) => {
|
|
862
|
+
if (!triggerList) return;
|
|
863
|
+
triggerList.innerHTML = '';
|
|
864
|
+
if (!items || !items.length) {
|
|
865
|
+
const empty = document.createElement('div');
|
|
866
|
+
empty.className = 'history-empty';
|
|
867
|
+
empty.textContent = 'no triggers yet';
|
|
868
|
+
triggerList.appendChild(empty);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
items.forEach((entry) => {
|
|
872
|
+
const content = entry && entry.content ? entry.content : null;
|
|
873
|
+
const name = (content && content.name) || entry.fileName || 'trigger';
|
|
874
|
+
const type = content && content.type ? content.type : 'unknown';
|
|
875
|
+
const handler = content && content.event_processors ? content.event_processors.FUNCTION : null;
|
|
876
|
+
const fnName = handler && handler.config ? handler.config.function_name : null;
|
|
877
|
+
const hint = fnName ? (type + ' · ' + fnName) : type;
|
|
878
|
+
const row = document.createElement('div');
|
|
879
|
+
row.className = 'trigger-row' + (state.selectedTrigger === entry ? ' active' : '');
|
|
880
|
+
row.dataset.name = name;
|
|
881
|
+
row.innerHTML = '<div class="code">' + name + '</div>' +
|
|
882
|
+
'<div class="hint">' + hint + '</div>';
|
|
883
|
+
triggerList.appendChild(row);
|
|
884
|
+
});
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
const showTriggerDetail = (entry) => {
|
|
888
|
+
state.selectedTrigger = entry;
|
|
889
|
+
const content = entry && entry.content ? entry.content : null;
|
|
890
|
+
const handler = content && content.event_processors ? content.event_processors.FUNCTION : null;
|
|
891
|
+
const fnName = handler && handler.config ? handler.config.function_name : null;
|
|
892
|
+
if (triggerList) {
|
|
893
|
+
triggerList.querySelectorAll('.trigger-row').forEach((row) => {
|
|
894
|
+
const name = row.dataset.name;
|
|
895
|
+
const currentName = (content && content.name) || entry.fileName || 'trigger';
|
|
896
|
+
row.classList.toggle('active', name === currentName);
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
if (triggerDetail) {
|
|
900
|
+
triggerDetail.classList.add('json-highlight');
|
|
901
|
+
triggerDetail.innerHTML = highlightJson(JSON.stringify(entry, null, 2) || '');
|
|
902
|
+
}
|
|
903
|
+
if (triggerFunctionButton) {
|
|
904
|
+
if (fnName) {
|
|
905
|
+
triggerFunctionButton.classList.remove('is-hidden');
|
|
906
|
+
triggerFunctionButton.dataset.functionName = fnName;
|
|
907
|
+
} else {
|
|
908
|
+
triggerFunctionButton.classList.add('is-hidden');
|
|
909
|
+
triggerFunctionButton.dataset.functionName = '';
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
const loadTriggers = async () => {
|
|
915
|
+
try {
|
|
916
|
+
const data = await api('/triggers');
|
|
917
|
+
state.triggers = data.items || [];
|
|
918
|
+
buildTriggerFunctionMap(state.triggers);
|
|
919
|
+
renderTriggers(state.triggers);
|
|
920
|
+
renderFunctions(state.functions);
|
|
921
|
+
return state.triggers;
|
|
922
|
+
} catch (err) {
|
|
923
|
+
console.error(err);
|
|
924
|
+
return [];
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const openTriggerByName = async (triggerName) => {
|
|
929
|
+
if (!triggerName) return;
|
|
930
|
+
setActiveTab('triggers');
|
|
931
|
+
const entries = state.triggers && state.triggers.length ? state.triggers : await loadTriggers();
|
|
932
|
+
const entry = (entries || []).find((item) => {
|
|
933
|
+
const content = item && item.content ? item.content : null;
|
|
934
|
+
const name = (content && content.name) || item.fileName || 'trigger';
|
|
935
|
+
return name === triggerName;
|
|
936
|
+
});
|
|
937
|
+
if (entry) {
|
|
938
|
+
showTriggerDetail(entry);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
|
|
763
942
|
const formatArgsPreview = (args) => {
|
|
764
943
|
try {
|
|
765
944
|
let preview = JSON.stringify(args);
|
|
@@ -794,9 +973,11 @@
|
|
|
794
973
|
metaParts.push(formatTime(entry.ts));
|
|
795
974
|
metaParts.push(runMode);
|
|
796
975
|
if (userLabel) metaParts.push(userLabel);
|
|
976
|
+
const modifiedTag = entry.codeModified ? '<span class="modified-pill">modified</span>' : '';
|
|
797
977
|
row.innerHTML = '<div class="history-meta">' +
|
|
798
978
|
'<div class="code">' + entry.name + '</div>' +
|
|
799
|
-
'<div class="hint">' + metaParts.join(' · ') + ' · ' + formatArgsPreview(entry.args) +
|
|
979
|
+
'<div class="hint">' + metaParts.join(' · ') + ' · ' + formatArgsPreview(entry.args) +
|
|
980
|
+
(modifiedTag ? (' · ' + modifiedTag) : '') + '</div>' +
|
|
800
981
|
'</div>' +
|
|
801
982
|
'<div class="hint"></div>';
|
|
802
983
|
functionHistory.appendChild(row);
|
|
@@ -821,10 +1002,34 @@
|
|
|
821
1002
|
functionRunMode.value = fn.run_as_system ? 'system' : 'user';
|
|
822
1003
|
};
|
|
823
1004
|
|
|
824
|
-
const
|
|
825
|
-
if (!
|
|
826
|
-
|
|
827
|
-
|
|
1005
|
+
const setFunctionSelectedLabel = (name, modified) => {
|
|
1006
|
+
if (!functionSelected) return;
|
|
1007
|
+
if (!name) {
|
|
1008
|
+
functionSelected.textContent = 'select a function';
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
if (modified) {
|
|
1012
|
+
functionSelected.innerHTML =
|
|
1013
|
+
escapeHtml(name) + ' <span class="modified-pill">modified</span>';
|
|
1014
|
+
} else {
|
|
1015
|
+
functionSelected.textContent = name;
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
|
|
1019
|
+
const isFunctionCodeModified = (name) => {
|
|
1020
|
+
if (!name || !functionCode) return false;
|
|
1021
|
+
const base = state.functionCodeCache[name];
|
|
1022
|
+
if (typeof base !== 'string') return false;
|
|
1023
|
+
return functionCode.value !== base;
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
const updateFunctionModifiedState = () => {
|
|
1027
|
+
const name = state.selectedFunction;
|
|
1028
|
+
const modified = isFunctionCodeModified(name);
|
|
1029
|
+
setFunctionSelectedLabel(name, modified);
|
|
1030
|
+
if (restoreFunction) {
|
|
1031
|
+
restoreFunction.disabled = !modified;
|
|
1032
|
+
}
|
|
828
1033
|
};
|
|
829
1034
|
|
|
830
1035
|
const loadFunctionCode = async () => {
|
|
@@ -833,20 +1038,26 @@
|
|
|
833
1038
|
if (!name) {
|
|
834
1039
|
functionCode.value = '';
|
|
835
1040
|
updateFunctionEditor();
|
|
836
|
-
|
|
1041
|
+
updateFunctionModifiedState();
|
|
837
1042
|
return;
|
|
838
1043
|
}
|
|
839
1044
|
try {
|
|
840
|
-
setEditorStatus('loading...');
|
|
841
1045
|
const data = await api('/functions/' + encodeURIComponent(name));
|
|
842
1046
|
const baseCode = data && data.code ? data.code : '';
|
|
843
1047
|
state.functionCodeCache[name] = baseCode;
|
|
844
1048
|
functionCode.value = baseCode;
|
|
845
1049
|
updateFunctionEditor();
|
|
846
|
-
setEditorStatus('loaded');
|
|
847
1050
|
} catch (err) {
|
|
848
|
-
|
|
1051
|
+
console.error(err);
|
|
849
1052
|
}
|
|
1053
|
+
updateFunctionModifiedState();
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
const applyFunctionOverride = (code) => {
|
|
1057
|
+
if (!functionCode) return;
|
|
1058
|
+
functionCode.value = code || '';
|
|
1059
|
+
updateFunctionEditor();
|
|
1060
|
+
updateFunctionModifiedState();
|
|
850
1061
|
};
|
|
851
1062
|
|
|
852
1063
|
const clearFunctionOverride = () => {
|
|
@@ -856,7 +1067,7 @@
|
|
|
856
1067
|
functionCode.value = state.functionCodeCache[name] || '';
|
|
857
1068
|
updateFunctionEditor();
|
|
858
1069
|
}
|
|
859
|
-
|
|
1070
|
+
updateFunctionModifiedState();
|
|
860
1071
|
};
|
|
861
1072
|
|
|
862
1073
|
const buildFunctionUserOptions = (authItems, customItems) => {
|
|
@@ -1011,6 +1222,17 @@
|
|
|
1011
1222
|
}
|
|
1012
1223
|
};
|
|
1013
1224
|
|
|
1225
|
+
const updateCollectionModeView = () => {
|
|
1226
|
+
const mode = collectionMode ? collectionMode.value : (state.collectionMode || 'query');
|
|
1227
|
+
if (collectionIo && collectionIo.dataset) {
|
|
1228
|
+
collectionIo.dataset.mode = mode;
|
|
1229
|
+
}
|
|
1230
|
+
document.querySelectorAll('[data-collection-mode]').forEach((panel) => {
|
|
1231
|
+
const panelMode = panel.dataset ? panel.dataset.collectionMode : null;
|
|
1232
|
+
panel.classList.toggle('is-hidden', panelMode !== mode);
|
|
1233
|
+
});
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1014
1236
|
const renderCollections = (items) => {
|
|
1015
1237
|
if (!collectionList) return;
|
|
1016
1238
|
collectionList.innerHTML = '';
|
|
@@ -1093,6 +1315,7 @@
|
|
|
1093
1315
|
if (collectionMode && entry.mode) {
|
|
1094
1316
|
collectionMode.value = entry.mode;
|
|
1095
1317
|
state.collectionMode = entry.mode;
|
|
1318
|
+
updateCollectionModeView();
|
|
1096
1319
|
}
|
|
1097
1320
|
if (collectionRunMode) {
|
|
1098
1321
|
collectionRunMode.value = entry.runAsSystem ? 'system' : 'user';
|
|
@@ -1173,8 +1396,13 @@
|
|
|
1173
1396
|
if (!keepPage) {
|
|
1174
1397
|
state.collectionPage = 1;
|
|
1175
1398
|
}
|
|
1399
|
+
const shouldRefreshTotals = !keepPage || !state.collectionTotal;
|
|
1176
1400
|
state.collectionHasMore = false;
|
|
1177
|
-
|
|
1401
|
+
if (shouldRefreshTotals) {
|
|
1402
|
+
state.collectionTotal = 0;
|
|
1403
|
+
}
|
|
1404
|
+
state.collectionLoading = true;
|
|
1405
|
+
state.collectionTotalsLoading = shouldRefreshTotals;
|
|
1178
1406
|
updateCollectionPager();
|
|
1179
1407
|
const runAsSystem = !collectionRunMode || collectionRunMode.value === 'system';
|
|
1180
1408
|
const selectedUser = state.selectedCollectionUser;
|
|
@@ -1208,10 +1436,15 @@
|
|
|
1208
1436
|
if (typeof data.page === 'number') {
|
|
1209
1437
|
state.collectionPage = data.page;
|
|
1210
1438
|
}
|
|
1211
|
-
if (
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1439
|
+
if (shouldRefreshTotals) {
|
|
1440
|
+
if (typeof data.total === 'number') {
|
|
1441
|
+
state.collectionTotal = data.total;
|
|
1442
|
+
} else if (typeof data.count === 'number') {
|
|
1443
|
+
state.collectionTotal = data.count;
|
|
1444
|
+
}
|
|
1445
|
+
if (typeof data.pageSize === 'number') {
|
|
1446
|
+
state.collectionPageSize = data.pageSize;
|
|
1447
|
+
}
|
|
1215
1448
|
}
|
|
1216
1449
|
updateCollectionPager();
|
|
1217
1450
|
setCollectionTab('query');
|
|
@@ -1235,10 +1468,15 @@
|
|
|
1235
1468
|
if (typeof data.page === 'number') {
|
|
1236
1469
|
state.collectionPage = data.page;
|
|
1237
1470
|
}
|
|
1238
|
-
if (
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1471
|
+
if (shouldRefreshTotals) {
|
|
1472
|
+
if (typeof data.total === 'number') {
|
|
1473
|
+
state.collectionTotal = data.total;
|
|
1474
|
+
} else if (typeof data.count === 'number') {
|
|
1475
|
+
state.collectionTotal = data.count;
|
|
1476
|
+
}
|
|
1477
|
+
if (typeof data.pageSize === 'number') {
|
|
1478
|
+
state.collectionPageSize = data.pageSize;
|
|
1479
|
+
}
|
|
1242
1480
|
}
|
|
1243
1481
|
updateCollectionPager();
|
|
1244
1482
|
setCollectionTab('query');
|
|
@@ -1258,6 +1496,9 @@
|
|
|
1258
1496
|
setCollectionResult('Error: ' + err.message, false);
|
|
1259
1497
|
}
|
|
1260
1498
|
} finally {
|
|
1499
|
+
state.collectionLoading = false;
|
|
1500
|
+
state.collectionTotalsLoading = false;
|
|
1501
|
+
updateCollectionPager();
|
|
1261
1502
|
if (recordHistory) {
|
|
1262
1503
|
loadCollectionHistory();
|
|
1263
1504
|
}
|
|
@@ -1282,6 +1523,7 @@
|
|
|
1282
1523
|
typeFilter.value = '';
|
|
1283
1524
|
state.events = [];
|
|
1284
1525
|
state.selectedEvent = null;
|
|
1526
|
+
eventDetail.classList.remove('json-highlight');
|
|
1285
1527
|
eventDetail.textContent = 'select an event to inspect payload';
|
|
1286
1528
|
if (eventFunctionButton) {
|
|
1287
1529
|
eventFunctionButton.classList.add('is-hidden');
|
|
@@ -1292,6 +1534,9 @@
|
|
|
1292
1534
|
|
|
1293
1535
|
refreshUsers.addEventListener('click', loadUsers);
|
|
1294
1536
|
refreshFunctions.addEventListener('click', loadFunctions);
|
|
1537
|
+
if (refreshTriggers) {
|
|
1538
|
+
refreshTriggers.addEventListener('click', loadTriggers);
|
|
1539
|
+
}
|
|
1295
1540
|
if (refreshCollections) {
|
|
1296
1541
|
refreshCollections.addEventListener('click', loadCollections);
|
|
1297
1542
|
}
|
|
@@ -1379,7 +1624,9 @@
|
|
|
1379
1624
|
state.collectionMode = collectionMode.value;
|
|
1380
1625
|
state.collectionPage = 1;
|
|
1381
1626
|
state.collectionHasMore = false;
|
|
1627
|
+
state.collectionLoading = false;
|
|
1382
1628
|
updateCollectionPager();
|
|
1629
|
+
updateCollectionModeView();
|
|
1383
1630
|
});
|
|
1384
1631
|
}
|
|
1385
1632
|
|
|
@@ -1418,6 +1665,36 @@
|
|
|
1418
1665
|
});
|
|
1419
1666
|
}
|
|
1420
1667
|
|
|
1668
|
+
if (triggerList) {
|
|
1669
|
+
triggerList.addEventListener('click', (event) => {
|
|
1670
|
+
const target = (event.target && event.target.closest)
|
|
1671
|
+
? event.target.closest('.trigger-row')
|
|
1672
|
+
: null;
|
|
1673
|
+
if (!target) return;
|
|
1674
|
+
const name = target.dataset.name;
|
|
1675
|
+
const entry = (state.triggers || []).find((item) => {
|
|
1676
|
+
const content = item && item.content ? item.content : null;
|
|
1677
|
+
const triggerName = (content && content.name) || item.fileName || 'trigger';
|
|
1678
|
+
return triggerName === name;
|
|
1679
|
+
});
|
|
1680
|
+
if (!entry) return;
|
|
1681
|
+
showTriggerDetail(entry);
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
if (triggerFunctionButton) {
|
|
1686
|
+
triggerFunctionButton.addEventListener('click', () => {
|
|
1687
|
+
const fnName = triggerFunctionButton.dataset.functionName;
|
|
1688
|
+
if (!fnName) return;
|
|
1689
|
+
state.selectedFunction = fnName;
|
|
1690
|
+
setActiveTab('functions');
|
|
1691
|
+
renderFunctions(state.functions);
|
|
1692
|
+
loadFunctionCode(fnName);
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
updateCollectionModeView();
|
|
1697
|
+
|
|
1421
1698
|
mergedUsers.addEventListener('click', async (event) => {
|
|
1422
1699
|
const target = event.target;
|
|
1423
1700
|
if (!target) return;
|
|
@@ -1427,6 +1704,10 @@
|
|
|
1427
1704
|
if (!id) return;
|
|
1428
1705
|
if (action === 'toggle') {
|
|
1429
1706
|
const disabled = target.textContent === 'disable';
|
|
1707
|
+
if (disabled) {
|
|
1708
|
+
const ok = confirm('Disable user ' + id + '?');
|
|
1709
|
+
if (!ok) return;
|
|
1710
|
+
}
|
|
1430
1711
|
await api('/users/' + id + '/status', {
|
|
1431
1712
|
method: 'PATCH',
|
|
1432
1713
|
body: JSON.stringify({ disabled })
|
|
@@ -1454,8 +1735,10 @@
|
|
|
1454
1735
|
});
|
|
1455
1736
|
const entry = state.mergedUserMap[id];
|
|
1456
1737
|
if (entry) {
|
|
1457
|
-
userDetail.
|
|
1738
|
+
userDetail.classList.add('json-highlight');
|
|
1739
|
+
userDetail.innerHTML = highlightJson(JSON.stringify(entry, null, 2) || '');
|
|
1458
1740
|
} else {
|
|
1741
|
+
userDetail.classList.remove('json-highlight');
|
|
1459
1742
|
userDetail.textContent = 'User not found in cache';
|
|
1460
1743
|
}
|
|
1461
1744
|
});
|
|
@@ -1481,6 +1764,7 @@
|
|
|
1481
1764
|
if (functionCode) {
|
|
1482
1765
|
functionCode.addEventListener('input', () => {
|
|
1483
1766
|
updateFunctionEditor();
|
|
1767
|
+
updateFunctionModifiedState();
|
|
1484
1768
|
});
|
|
1485
1769
|
functionCode.addEventListener('scroll', () => {
|
|
1486
1770
|
syncFunctionEditorScroll();
|
|
@@ -1596,12 +1880,20 @@
|
|
|
1596
1880
|
const target = (event.target && event.target.closest)
|
|
1597
1881
|
? event.target.closest('.function-row')
|
|
1598
1882
|
: null;
|
|
1883
|
+
if (event.target && event.target.classList && event.target.classList.contains('trigger-link')) {
|
|
1884
|
+
const triggerName = event.target.dataset ? event.target.dataset.trigger : null;
|
|
1885
|
+
if (triggerName) {
|
|
1886
|
+
openTriggerByName(triggerName);
|
|
1887
|
+
}
|
|
1888
|
+
event.stopPropagation();
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1599
1891
|
if (!target) return;
|
|
1600
1892
|
const name = target.dataset.name;
|
|
1601
1893
|
if (!name) return;
|
|
1602
1894
|
state.selectedFunction = name;
|
|
1603
1895
|
state.selectedHistoryIndex = null;
|
|
1604
|
-
|
|
1896
|
+
setFunctionSelectedLabel(name, false);
|
|
1605
1897
|
functionResult.textContent = '';
|
|
1606
1898
|
setRunModeForFunction(name);
|
|
1607
1899
|
loadFunctionCode();
|
|
@@ -1677,7 +1969,7 @@
|
|
|
1677
1969
|
if (!functionData) return;
|
|
1678
1970
|
state.selectedFunction = functionData.name;
|
|
1679
1971
|
state.selectedHistoryIndex = null;
|
|
1680
|
-
|
|
1972
|
+
setFunctionSelectedLabel(functionData.name, false);
|
|
1681
1973
|
functionArgs.value = JSON.stringify(functionData.args || [], null, 2);
|
|
1682
1974
|
functionResult.textContent = '';
|
|
1683
1975
|
setRunModeForFunction(functionData.name);
|
|
@@ -1699,7 +1991,7 @@
|
|
|
1699
1991
|
if (!entry) return;
|
|
1700
1992
|
state.selectedFunction = entry.name;
|
|
1701
1993
|
state.selectedHistoryIndex = index;
|
|
1702
|
-
|
|
1994
|
+
setFunctionSelectedLabel(entry.name, false);
|
|
1703
1995
|
functionArgs.value = JSON.stringify(entry.args || [], null, 2);
|
|
1704
1996
|
if (functionRunMode && typeof entry.runAsSystem === 'boolean') {
|
|
1705
1997
|
functionRunMode.value = entry.runAsSystem ? 'system' : 'user';
|
|
@@ -1720,7 +2012,14 @@
|
|
|
1720
2012
|
setSelectedFunctionUser(null);
|
|
1721
2013
|
}
|
|
1722
2014
|
functionResult.textContent = '';
|
|
1723
|
-
loadFunctionCode();
|
|
2015
|
+
const loadPromise = loadFunctionCode();
|
|
2016
|
+
if (entry.code) {
|
|
2017
|
+
Promise.resolve(loadPromise).then(() => {
|
|
2018
|
+
applyFunctionOverride(entry.code);
|
|
2019
|
+
});
|
|
2020
|
+
} else {
|
|
2021
|
+
updateFunctionModifiedState();
|
|
2022
|
+
}
|
|
1724
2023
|
renderFunctions(state.functions);
|
|
1725
2024
|
renderHistory();
|
|
1726
2025
|
});
|
|
@@ -1785,6 +2084,7 @@
|
|
|
1785
2084
|
setInterval(updateClock, 1000);
|
|
1786
2085
|
updateClock();
|
|
1787
2086
|
updateFunctionEditor();
|
|
2087
|
+
updateFunctionModifiedState();
|
|
1788
2088
|
updateCollectionPager();
|
|
1789
2089
|
setCollectionTab('query');
|
|
1790
2090
|
setCollectionResultView('json');
|
|
@@ -1821,6 +2121,7 @@
|
|
|
1821
2121
|
connectWs();
|
|
1822
2122
|
loadUsers();
|
|
1823
2123
|
loadFunctions();
|
|
2124
|
+
loadTriggers();
|
|
1824
2125
|
loadCollections();
|
|
1825
2126
|
loadCollectionHistory();
|
|
1826
2127
|
})();
|