@mongoosejs/studio 0.3.0 → 0.3.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/DEVGUIDE.md +8 -0
- package/backend/actions/ChatThread/createChatMessage.js +2 -0
- package/backend/actions/ChatThread/streamChatMessage.js +2 -0
- package/backend/actions/Dashboard/getDashboard.js +15 -11
- package/backend/actions/Dashboard/updateDashboard.js +2 -2
- package/backend/actions/Task/getTaskOverview.js +102 -0
- package/backend/actions/Task/getTasks.js +85 -45
- package/backend/actions/Task/index.js +1 -0
- package/frontend/public/app.js +318 -148
- package/frontend/public/tw.css +82 -0
- package/frontend/src/_util/dateRange.js +82 -0
- package/frontend/src/ace-editor/ace-editor.js +6 -0
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat-message-script/chat-message-script.html +11 -16
- package/frontend/src/chat/chat-message-script/chat-message-script.js +0 -6
- package/frontend/src/chat/chat.js +2 -0
- package/frontend/src/dashboard/dashboard.js +13 -2
- package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +4 -3
- package/frontend/src/dashboard-result/dashboard-result.js +3 -0
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.html +34 -0
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.js +37 -0
- package/frontend/src/models/models.js +9 -3
- package/frontend/src/navbar/navbar.js +3 -2
- package/frontend/src/task-by-name/task-by-name.html +77 -7
- package/frontend/src/task-by-name/task-by-name.js +84 -9
- package/frontend/src/tasks/task-details/task-details.html +8 -8
- package/frontend/src/tasks/task-details/task-details.js +2 -1
- package/frontend/src/tasks/tasks.js +25 -118
- package/local.js +38 -0
- package/package.json +4 -1
- package/seed/connect.js +23 -0
- package/seed/index.js +101 -0
package/frontend/public/tw.css
CHANGED
|
@@ -1260,6 +1260,10 @@ video {
|
|
|
1260
1260
|
min-width: 220px;
|
|
1261
1261
|
}
|
|
1262
1262
|
|
|
1263
|
+
.min-w-\[7rem\] {
|
|
1264
|
+
min-width: 7rem;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1263
1267
|
.min-w-\[80px\] {
|
|
1264
1268
|
min-width: 80px;
|
|
1265
1269
|
}
|
|
@@ -1280,6 +1284,10 @@ video {
|
|
|
1280
1284
|
max-width: 64rem;
|
|
1281
1285
|
}
|
|
1282
1286
|
|
|
1287
|
+
.max-w-6xl {
|
|
1288
|
+
max-width: 72rem;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1283
1291
|
.max-w-7xl {
|
|
1284
1292
|
max-width: 80rem;
|
|
1285
1293
|
}
|
|
@@ -1300,6 +1308,10 @@ video {
|
|
|
1300
1308
|
max-width: 36rem;
|
|
1301
1309
|
}
|
|
1302
1310
|
|
|
1311
|
+
.max-w-xs {
|
|
1312
|
+
max-width: 20rem;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1303
1315
|
.flex-1 {
|
|
1304
1316
|
flex: 1 1 0%;
|
|
1305
1317
|
}
|
|
@@ -1332,6 +1344,16 @@ video {
|
|
|
1332
1344
|
flex-grow: 1;
|
|
1333
1345
|
}
|
|
1334
1346
|
|
|
1347
|
+
.border-separate {
|
|
1348
|
+
border-collapse: separate;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
.border-spacing-0 {
|
|
1352
|
+
--tw-border-spacing-x: 0px;
|
|
1353
|
+
--tw-border-spacing-y: 0px;
|
|
1354
|
+
border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1335
1357
|
.origin-top-right {
|
|
1336
1358
|
transform-origin: top right;
|
|
1337
1359
|
}
|
|
@@ -1762,6 +1784,16 @@ video {
|
|
|
1762
1784
|
border-color: rgb(243 244 246 / var(--tw-border-opacity));
|
|
1763
1785
|
}
|
|
1764
1786
|
|
|
1787
|
+
.border-gray-200 {
|
|
1788
|
+
--tw-border-opacity: 1;
|
|
1789
|
+
border-color: rgb(229 231 235 / var(--tw-border-opacity));
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
.border-gray-300 {
|
|
1793
|
+
--tw-border-opacity: 1;
|
|
1794
|
+
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1765
1797
|
.border-green-200 {
|
|
1766
1798
|
--tw-border-opacity: 1;
|
|
1767
1799
|
border-color: rgb(187 247 208 / var(--tw-border-opacity));
|
|
@@ -2033,6 +2065,11 @@ video {
|
|
|
2033
2065
|
background-color: rgb(202 56 56 / var(--tw-bg-opacity));
|
|
2034
2066
|
}
|
|
2035
2067
|
|
|
2068
|
+
.bg-white {
|
|
2069
|
+
--tw-bg-opacity: 1;
|
|
2070
|
+
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2036
2073
|
.bg-yellow-100 {
|
|
2037
2074
|
--tw-bg-opacity: 1;
|
|
2038
2075
|
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
|
|
@@ -2253,6 +2290,10 @@ video {
|
|
|
2253
2290
|
padding-bottom: 0.5rem;
|
|
2254
2291
|
}
|
|
2255
2292
|
|
|
2293
|
+
.pb-24 {
|
|
2294
|
+
padding-bottom: 6rem;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2256
2297
|
.pl-1 {
|
|
2257
2298
|
padding-left: 0.25rem;
|
|
2258
2299
|
}
|
|
@@ -2484,6 +2525,11 @@ video {
|
|
|
2484
2525
|
color: rgb(75 85 99 / var(--tw-text-opacity));
|
|
2485
2526
|
}
|
|
2486
2527
|
|
|
2528
|
+
.text-gray-700 {
|
|
2529
|
+
--tw-text-opacity: 1;
|
|
2530
|
+
color: rgb(55 65 81 / var(--tw-text-opacity));
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2487
2533
|
.text-gray-800 {
|
|
2488
2534
|
--tw-text-opacity: 1;
|
|
2489
2535
|
color: rgb(31 41 55 / var(--tw-text-opacity));
|
|
@@ -2695,6 +2741,12 @@ video {
|
|
|
2695
2741
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
2696
2742
|
}
|
|
2697
2743
|
|
|
2744
|
+
.shadow-\[0_-4px_6px_-1px_rgba\(0\2c 0\2c 0\2c 0\.1\)\] {
|
|
2745
|
+
--tw-shadow: 0 -4px 6px -1px rgba(0,0,0,0.1);
|
|
2746
|
+
--tw-shadow-colored: 0 -4px 6px -1px var(--tw-shadow-color);
|
|
2747
|
+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2698
2750
|
.shadow-lg {
|
|
2699
2751
|
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
2700
2752
|
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
|
@@ -2974,6 +3026,11 @@ video {
|
|
|
2974
3026
|
border-color: var(--color-edge-strong);
|
|
2975
3027
|
}
|
|
2976
3028
|
|
|
3029
|
+
.hover\:border-gray-400:hover {
|
|
3030
|
+
--tw-border-opacity: 1;
|
|
3031
|
+
border-color: rgb(156 163 175 / var(--tw-border-opacity));
|
|
3032
|
+
}
|
|
3033
|
+
|
|
2977
3034
|
.hover\:border-green-300:hover {
|
|
2978
3035
|
--tw-border-opacity: 1;
|
|
2979
3036
|
border-color: rgb(134 239 172 / var(--tw-border-opacity));
|
|
@@ -3039,6 +3096,11 @@ video {
|
|
|
3039
3096
|
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
|
3040
3097
|
}
|
|
3041
3098
|
|
|
3099
|
+
.hover\:bg-gray-50:hover {
|
|
3100
|
+
--tw-bg-opacity: 1;
|
|
3101
|
+
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3042
3104
|
.hover\:bg-gray-600:hover {
|
|
3043
3105
|
--tw-bg-opacity: 1;
|
|
3044
3106
|
background-color: rgb(75 85 99 / var(--tw-bg-opacity));
|
|
@@ -3203,6 +3265,11 @@ video {
|
|
|
3203
3265
|
color: rgb(75 85 99 / var(--tw-text-opacity));
|
|
3204
3266
|
}
|
|
3205
3267
|
|
|
3268
|
+
.hover\:text-gray-700:hover {
|
|
3269
|
+
--tw-text-opacity: 1;
|
|
3270
|
+
color: rgb(55 65 81 / var(--tw-text-opacity));
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3206
3273
|
.hover\:text-gray-800:hover {
|
|
3207
3274
|
--tw-text-opacity: 1;
|
|
3208
3275
|
color: rgb(31 41 55 / var(--tw-text-opacity));
|
|
@@ -3266,6 +3333,11 @@ video {
|
|
|
3266
3333
|
border-color: transparent;
|
|
3267
3334
|
}
|
|
3268
3335
|
|
|
3336
|
+
.focus\:border-ultramarine-500:focus {
|
|
3337
|
+
--tw-border-opacity: 1;
|
|
3338
|
+
border-color: rgb(63 83 255 / var(--tw-border-opacity));
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3269
3341
|
.focus\:opacity-100:focus {
|
|
3270
3342
|
opacity: 1;
|
|
3271
3343
|
}
|
|
@@ -3346,6 +3418,11 @@ video {
|
|
|
3346
3418
|
--tw-ring-color: rgb(2 132 199 / var(--tw-ring-opacity));
|
|
3347
3419
|
}
|
|
3348
3420
|
|
|
3421
|
+
.focus\:ring-ultramarine-500:focus {
|
|
3422
|
+
--tw-ring-opacity: 1;
|
|
3423
|
+
--tw-ring-color: rgb(63 83 255 / var(--tw-ring-opacity));
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3349
3426
|
.focus\:ring-offset-0:focus {
|
|
3350
3427
|
--tw-ring-offset-width: 0px;
|
|
3351
3428
|
}
|
|
@@ -3461,6 +3538,11 @@ video {
|
|
|
3461
3538
|
opacity: 0.5;
|
|
3462
3539
|
}
|
|
3463
3540
|
|
|
3541
|
+
.disabled\:hover\:bg-white:hover:disabled {
|
|
3542
|
+
--tw-bg-opacity: 1;
|
|
3543
|
+
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3464
3546
|
.group:focus-within .group-focus-within\:pointer-events-auto {
|
|
3465
3547
|
pointer-events: auto;
|
|
3466
3548
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DATE_FILTERS = [
|
|
4
|
+
{ value: 'last_hour', label: 'Last Hour' },
|
|
5
|
+
{ value: 'today', label: 'Today' },
|
|
6
|
+
{ value: 'yesterday', label: 'Yesterday' },
|
|
7
|
+
{ value: 'thisWeek', label: 'This Week' },
|
|
8
|
+
{ value: 'lastWeek', label: 'Last Week' },
|
|
9
|
+
{ value: 'thisMonth', label: 'This Month' },
|
|
10
|
+
{ value: 'lastMonth', label: 'Last Month' }
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const DATE_FILTER_VALUES = DATE_FILTERS.map(f => f.value);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns { start, end } Date objects for a given range key (e.g. 'last_hour', 'today').
|
|
17
|
+
* Month ranges use UTC boundaries.
|
|
18
|
+
* @param {string} selectedRange - One of DATE_FILTER_VALUES
|
|
19
|
+
* @returns {{ start: Date, end: Date }}
|
|
20
|
+
*/
|
|
21
|
+
function getDateRangeForRange(selectedRange) {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
let start, end;
|
|
24
|
+
switch (selectedRange) {
|
|
25
|
+
case 'last_hour':
|
|
26
|
+
start = new Date();
|
|
27
|
+
start.setHours(start.getHours() - 1);
|
|
28
|
+
end = new Date();
|
|
29
|
+
break;
|
|
30
|
+
case 'today':
|
|
31
|
+
start = new Date();
|
|
32
|
+
start.setHours(0, 0, 0, 0);
|
|
33
|
+
end = new Date();
|
|
34
|
+
end.setHours(23, 59, 59, 999);
|
|
35
|
+
break;
|
|
36
|
+
case 'yesterday':
|
|
37
|
+
start = new Date(now);
|
|
38
|
+
start.setDate(start.getDate() - 1);
|
|
39
|
+
start.setHours(0, 0, 0, 0);
|
|
40
|
+
end = new Date(start);
|
|
41
|
+
end.setHours(23, 59, 59, 999);
|
|
42
|
+
break;
|
|
43
|
+
case 'thisWeek':
|
|
44
|
+
start = new Date(now.getTime() - (7 * 86400000));
|
|
45
|
+
start.setHours(0, 0, 0, 0);
|
|
46
|
+
end = new Date();
|
|
47
|
+
end.setHours(23, 59, 59, 999);
|
|
48
|
+
break;
|
|
49
|
+
case 'lastWeek':
|
|
50
|
+
start = new Date(now.getTime() - (14 * 86400000));
|
|
51
|
+
start.setHours(0, 0, 0, 0);
|
|
52
|
+
end = new Date(now.getTime() - (7 * 86400000));
|
|
53
|
+
end.setHours(23, 59, 59, 999);
|
|
54
|
+
break;
|
|
55
|
+
case 'thisMonth': {
|
|
56
|
+
const y = now.getUTCFullYear();
|
|
57
|
+
const m = now.getUTCMonth();
|
|
58
|
+
start = new Date(Date.UTC(y, m, 1, 0, 0, 0, 0));
|
|
59
|
+
end = new Date(Date.UTC(y, m + 1, 0, 23, 59, 59, 999));
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'lastMonth': {
|
|
63
|
+
const y = now.getUTCFullYear();
|
|
64
|
+
const m = now.getUTCMonth();
|
|
65
|
+
start = new Date(Date.UTC(y, m - 1, 1, 0, 0, 0, 0));
|
|
66
|
+
end = new Date(Date.UTC(y, m, 0, 23, 59, 59, 999));
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
default:
|
|
70
|
+
start = new Date();
|
|
71
|
+
start.setHours(start.getHours() - 1);
|
|
72
|
+
end = new Date();
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
return { start, end };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
DATE_FILTERS,
|
|
80
|
+
DATE_FILTER_VALUES,
|
|
81
|
+
getDateRangeForRange
|
|
82
|
+
};
|
|
@@ -80,6 +80,12 @@ module.exports = app => app.component('ace-editor', {
|
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
82
|
methods: {
|
|
83
|
+
getValue() {
|
|
84
|
+
if (this.editor) {
|
|
85
|
+
return this.editor.getValue();
|
|
86
|
+
}
|
|
87
|
+
return this.modelValue !== '' ? this.modelValue : this.value;
|
|
88
|
+
},
|
|
83
89
|
setValue(val) {
|
|
84
90
|
if (this.editor) {
|
|
85
91
|
this.editor.setValue(val ?? '', -1);
|
package/frontend/src/api.js
CHANGED
|
@@ -185,6 +185,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
185
185
|
getTasks: function getTasks(params) {
|
|
186
186
|
return client.post('', { action: 'Task.getTasks', ...params }).then(res => res.data);
|
|
187
187
|
},
|
|
188
|
+
getTaskOverview: function getTaskOverview(params) {
|
|
189
|
+
return client.post('', { action: 'Task.getTaskOverview', ...params }).then(res => res.data);
|
|
190
|
+
},
|
|
188
191
|
rescheduleTask: function rescheduleTask(params) {
|
|
189
192
|
return client.post('', { action: 'Task.rescheduleTask', ...params }).then(res => res.data);
|
|
190
193
|
},
|
|
@@ -519,6 +522,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
|
|
|
519
522
|
getTasks: function getTasks(params) {
|
|
520
523
|
return client.post('/Task/getTasks', params).then(res => res.data);
|
|
521
524
|
},
|
|
525
|
+
getTaskOverview: function getTaskOverview(params) {
|
|
526
|
+
return client.post('/Task/getTaskOverview', params).then(res => res.data);
|
|
527
|
+
},
|
|
522
528
|
rescheduleTask: function rescheduleTask(params) {
|
|
523
529
|
return client.post('/Task/rescheduleTask', params).then(res => res.data);
|
|
524
530
|
},
|
|
@@ -23,13 +23,6 @@
|
|
|
23
23
|
Output
|
|
24
24
|
</button>
|
|
25
25
|
<div class="ml-auto mr-1 flex items-center">
|
|
26
|
-
<button
|
|
27
|
-
v-if="activeTab === 'output' && isChartOutput"
|
|
28
|
-
class="px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center"
|
|
29
|
-
@click="exportChartPNG"
|
|
30
|
-
title="Export PNG">
|
|
31
|
-
<svg xmlns="http://www.w3.org/2000/svg" style="height: 1.2em;" viewBox="0 -960 960 960" fill="currentColor"><path d="M280-280h400v-80H280v80Zm200-120 160-160-56-56-64 62v-166h-80v166l-64-62-56 56 160 160Zm0 320q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
|
32
|
-
</button>
|
|
33
26
|
<button
|
|
34
27
|
v-if="activeTab === 'output'"
|
|
35
28
|
class="px-2 py-1 mr-1 text-xs bg-gray-700 text-white border-none rounded cursor-pointer hover:bg-primary-hover transition-colors flex items-center"
|
|
@@ -116,9 +109,11 @@
|
|
|
116
109
|
</div>
|
|
117
110
|
|
|
118
111
|
<div class="p-3 whitespace-pre-wrap max-h-[50vh] overflow-y-auto bg-surface border-t max-w-[calc(100vw-4rem)] lg:max-w-[calc(100vw-20rem)] relative" v-show="activeTab === 'output'">
|
|
119
|
-
<dashboard-
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
<dashboard-result
|
|
113
|
+
v-if="message.executionResult?.output != null"
|
|
114
|
+
:result="message.executionResult.output">
|
|
115
|
+
</dashboard-result>
|
|
116
|
+
<pre v-else>No output</pre>
|
|
122
117
|
|
|
123
118
|
<div v-if="message.executionResult?.logs?.length" class="mt-3 pt-3 border-t border-edge">
|
|
124
119
|
<div class="text-xs font-semibold text-gray-600 uppercase tracking-wide">Console</div>
|
|
@@ -130,12 +125,12 @@
|
|
|
130
125
|
<template #body>
|
|
131
126
|
<div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showDetailModal = false;">×</div>
|
|
132
127
|
<div class="h-full overflow-auto">
|
|
133
|
-
<dashboard-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
:
|
|
137
|
-
|
|
138
|
-
<pre v-else class="whitespace-pre-wrap">
|
|
128
|
+
<dashboard-result
|
|
129
|
+
v-if="message.executionResult?.output != null"
|
|
130
|
+
:result="message.executionResult.output"
|
|
131
|
+
:fullscreen="true">
|
|
132
|
+
</dashboard-result>
|
|
133
|
+
<pre v-else class="whitespace-pre-wrap">No output</pre>
|
|
139
134
|
</div>
|
|
140
135
|
</template>
|
|
141
136
|
</modal>
|
|
@@ -30,15 +30,9 @@ module.exports = app => app.component('chat-message-script', {
|
|
|
30
30
|
},
|
|
31
31
|
canOverwriteDashboard() {
|
|
32
32
|
return !!this.targetDashboardId;
|
|
33
|
-
},
|
|
34
|
-
isChartOutput() {
|
|
35
|
-
return !!this.message.executionResult?.output?.$chart;
|
|
36
33
|
}
|
|
37
34
|
},
|
|
38
35
|
methods: {
|
|
39
|
-
exportChartPNG() {
|
|
40
|
-
this.$refs.chartOutput?.exportPNG?.();
|
|
41
|
-
},
|
|
42
36
|
async executeScript() {
|
|
43
37
|
const scriptToRun = this.isEditing ? this.editedScript : this.script;
|
|
44
38
|
this.editedScript = scriptToRun;
|
|
@@ -28,6 +28,13 @@ module.exports = {
|
|
|
28
28
|
this.showEditor = !this.showEditor;
|
|
29
29
|
},
|
|
30
30
|
async updateCode(update) {
|
|
31
|
+
if (!update?.doc) {
|
|
32
|
+
const message = update?.error?.message || 'Dashboard update failed';
|
|
33
|
+
console.error(update?.error || new Error(message));
|
|
34
|
+
this.$toast.error(message);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
this.code = update.doc.code;
|
|
32
39
|
this.title = update.doc.title;
|
|
33
40
|
this.description = update.doc.description;
|
|
@@ -52,7 +59,9 @@ module.exports = {
|
|
|
52
59
|
this.dashboardResults.unshift({ error: { message: error.message || 'Evaluation failed' }, finishedEvaluatingAt: new Date() });
|
|
53
60
|
}
|
|
54
61
|
} catch (err) {
|
|
55
|
-
|
|
62
|
+
const message = err?.response?.data?.message || err?.message || 'Dashboard evaluation failed';
|
|
63
|
+
console.error(err || new Error(message));
|
|
64
|
+
this.$toast.error(message);
|
|
56
65
|
} finally {
|
|
57
66
|
this.status = 'loaded';
|
|
58
67
|
}
|
|
@@ -148,7 +157,9 @@ module.exports = {
|
|
|
148
157
|
return this.dashboardResults.length > 0 ? this.dashboardResults[0] : null;
|
|
149
158
|
}
|
|
150
159
|
},
|
|
151
|
-
mounted: async function() {
|
|
160
|
+
mounted: async function () {
|
|
161
|
+
window.pageState = this;
|
|
162
|
+
|
|
152
163
|
document.addEventListener('click', this.handleDocumentClick);
|
|
153
164
|
this.showEditor = this.$route.query.edit;
|
|
154
165
|
await this.loadInitial();
|
|
@@ -6,7 +6,7 @@ const template = require('./edit-dashboard.html');
|
|
|
6
6
|
module.exports = app => app.component('edit-dashboard', {
|
|
7
7
|
template: template,
|
|
8
8
|
props: ['dashboardId', 'code', 'currentDescription', 'currentTitle'],
|
|
9
|
-
emits: ['close'],
|
|
9
|
+
emits: ['close', 'update'],
|
|
10
10
|
data: function() {
|
|
11
11
|
return {
|
|
12
12
|
status: 'loaded',
|
|
@@ -27,7 +27,7 @@ module.exports = app => app.component('edit-dashboard', {
|
|
|
27
27
|
async updateCode() {
|
|
28
28
|
this.status = 'loading';
|
|
29
29
|
try {
|
|
30
|
-
const codeToSave = this.$refs.codeEditor ? this.$refs.codeEditor.getValue() : this.editCode;
|
|
30
|
+
const codeToSave = this.$refs.codeEditor?.getValue ? this.$refs.codeEditor.getValue() : this.editCode;
|
|
31
31
|
const { doc } = await api.Dashboard.updateDashboard({
|
|
32
32
|
dashboardId: this.dashboardId,
|
|
33
33
|
code: codeToSave,
|
|
@@ -43,7 +43,8 @@ module.exports = app => app.component('edit-dashboard', {
|
|
|
43
43
|
this.$toast.success('Dashboard updated!');
|
|
44
44
|
this.closeEditor();
|
|
45
45
|
} catch (err) {
|
|
46
|
-
|
|
46
|
+
const message = err?.response?.data?.message || err?.message || 'Dashboard update failed';
|
|
47
|
+
this.$emit('update', { error: { message } });
|
|
47
48
|
} finally {
|
|
48
49
|
this.status = 'loaded';
|
|
49
50
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<div class="overflow-x-auto">
|
|
2
|
+
<table class="min-w-full border-separate border-spacing-0">
|
|
3
|
+
<thead v-if="hasColumns" class="bg-slate-50">
|
|
4
|
+
<tr>
|
|
5
|
+
<th
|
|
6
|
+
v-for="(column, index) in columns"
|
|
7
|
+
:key="'column-' + index"
|
|
8
|
+
class="bg-slate-50 p-3 text-left text-sm font-semibold text-content border-b border-edge"
|
|
9
|
+
>
|
|
10
|
+
{{ column }}
|
|
11
|
+
</th>
|
|
12
|
+
</tr>
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
<tr v-for="(row, rowIndex) in rows" :key="'row-' + rowIndex" class="bg-surface hover:bg-slate-50">
|
|
16
|
+
<td
|
|
17
|
+
v-for="(cell, columnIndex) in row"
|
|
18
|
+
:key="'cell-' + rowIndex + '-' + columnIndex"
|
|
19
|
+
class="p-3 text-sm text-content border-b border-edge"
|
|
20
|
+
>
|
|
21
|
+
{{ displayValue(cell) }}
|
|
22
|
+
</td>
|
|
23
|
+
</tr>
|
|
24
|
+
<tr v-if="!hasRows">
|
|
25
|
+
<td
|
|
26
|
+
:colspan="Math.max(columns.length, 1)"
|
|
27
|
+
class="p-3 text-sm text-content-tertiary border-b border-edge"
|
|
28
|
+
>
|
|
29
|
+
No rows
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const template = require('./dashboard-table.html');
|
|
4
|
+
|
|
5
|
+
module.exports = app => app.component('dashboard-table', {
|
|
6
|
+
template,
|
|
7
|
+
props: ['value'],
|
|
8
|
+
computed: {
|
|
9
|
+
columns() {
|
|
10
|
+
return Array.isArray(this.value?.$table?.columns) ? this.value.$table.columns : [];
|
|
11
|
+
},
|
|
12
|
+
rows() {
|
|
13
|
+
return Array.isArray(this.value?.$table?.rows) ? this.value.$table.rows : [];
|
|
14
|
+
},
|
|
15
|
+
hasColumns() {
|
|
16
|
+
return this.columns.length > 0;
|
|
17
|
+
},
|
|
18
|
+
hasRows() {
|
|
19
|
+
return this.rows.length > 0;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
methods: {
|
|
23
|
+
displayValue(cell) {
|
|
24
|
+
if (cell == null) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
if (typeof cell === 'object') {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.stringify(cell);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
return String(cell);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return String(cell);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
@@ -65,6 +65,7 @@ module.exports = app => app.component('models', {
|
|
|
65
65
|
}),
|
|
66
66
|
created() {
|
|
67
67
|
this.currentModel = this.model;
|
|
68
|
+
this.setSearchTextFromRoute();
|
|
68
69
|
this.loadOutputPreference();
|
|
69
70
|
this.loadSelectedGeoField();
|
|
70
71
|
this.loadRecentlyViewedModels();
|
|
@@ -78,6 +79,7 @@ module.exports = app => app.component('models', {
|
|
|
78
79
|
this.destroyMap();
|
|
79
80
|
},
|
|
80
81
|
async mounted() {
|
|
82
|
+
window.pageState = this;
|
|
81
83
|
this.onScroll = () => this.checkIfScrolledToBottom();
|
|
82
84
|
document.addEventListener('scroll', this.onScroll, true);
|
|
83
85
|
this.onPopState = () => this.initSearchFromUrl();
|
|
@@ -101,6 +103,7 @@ module.exports = app => app.component('models', {
|
|
|
101
103
|
}
|
|
102
104
|
};
|
|
103
105
|
document.addEventListener('keydown', this.onCtrlP, true);
|
|
106
|
+
this.query = Object.assign({}, this.$route.query);
|
|
104
107
|
const { models, readyState } = await api.Model.listModels();
|
|
105
108
|
this.models = models;
|
|
106
109
|
await this.loadModelCounts();
|
|
@@ -483,14 +486,17 @@ module.exports = app => app.component('models', {
|
|
|
483
486
|
|
|
484
487
|
return params;
|
|
485
488
|
},
|
|
486
|
-
|
|
487
|
-
this.status = 'loading';
|
|
488
|
-
this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
|
|
489
|
+
setSearchTextFromRoute() {
|
|
489
490
|
if (this.$route.query?.search) {
|
|
490
491
|
this.searchText = this.$route.query.search;
|
|
491
492
|
} else {
|
|
492
493
|
this.searchText = '';
|
|
493
494
|
}
|
|
495
|
+
},
|
|
496
|
+
async initSearchFromUrl() {
|
|
497
|
+
this.status = 'loading';
|
|
498
|
+
this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
|
|
499
|
+
this.setSearchTextFromRoute();
|
|
494
500
|
if (this.$route.query?.sort) {
|
|
495
501
|
const sort = eval(`(${this.$route.query.sort})`);
|
|
496
502
|
const path = Object.keys(sort)[0];
|
|
@@ -17,7 +17,8 @@ module.exports = app => app.component('navbar', {
|
|
|
17
17
|
showFlyout: false,
|
|
18
18
|
darkMode: typeof localStorage !== 'undefined' && localStorage.getItem('studio-theme') === 'dark'
|
|
19
19
|
}),
|
|
20
|
-
mounted: function() {
|
|
20
|
+
mounted: function () {
|
|
21
|
+
window.navbar = this;
|
|
21
22
|
const mobileMenuMask = document.querySelector('#mobile-menu-mask');
|
|
22
23
|
const mobileMenu = document.querySelector('#mobile-menu');
|
|
23
24
|
const openBtn = document.querySelector('#open-mobile-menu');
|
|
@@ -71,7 +72,7 @@ module.exports = app => app.component('navbar', {
|
|
|
71
72
|
} else {
|
|
72
73
|
return 'https://www.npmjs.com/package/@mongoosejs/task';
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
78
|
methods: {
|
|
@@ -5,11 +5,81 @@
|
|
|
5
5
|
<div v-else-if="status === 'error'" class="text-red-600">
|
|
6
6
|
{{ errorMessage }}
|
|
7
7
|
</div>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
<template v-else-if="taskGroup">
|
|
9
|
+
<div class="pb-24">
|
|
10
|
+
<router-link :to="{ name: 'tasks' }" class="inline-flex items-center gap-1 text-gray-500 hover:text-gray-700 mb-4">
|
|
11
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
12
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
13
|
+
</svg>
|
|
14
|
+
Back to Task Groups
|
|
15
|
+
</router-link>
|
|
16
|
+
<div class="mb-4">
|
|
17
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Filter by Date:</label>
|
|
18
|
+
<select v-model="selectedRange" @change="updateDateRange" class="border-gray-300 rounded-md shadow-sm w-full p-2 max-w-xs">
|
|
19
|
+
<option v-for="option in dateFilters" :key="option.value" :value="option.value">
|
|
20
|
+
{{ option.label }}
|
|
21
|
+
</option>
|
|
22
|
+
</select>
|
|
23
|
+
</div>
|
|
24
|
+
<task-details
|
|
25
|
+
:task-group="taskGroup"
|
|
26
|
+
:back-to="{ name: 'tasks' }"
|
|
27
|
+
:show-back-button="false"
|
|
28
|
+
@task-created="onTaskCreated"
|
|
29
|
+
@task-cancelled="onTaskCancelled"
|
|
30
|
+
></task-details>
|
|
31
|
+
</div>
|
|
32
|
+
<div
|
|
33
|
+
v-if="numDocs > 0"
|
|
34
|
+
class="fixed bottom-0 left-0 right-0 z-10 px-4 py-4 bg-white border-t border-gray-200 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]"
|
|
35
|
+
>
|
|
36
|
+
<div class="flex flex-wrap items-center justify-between gap-4 max-w-6xl mx-auto">
|
|
37
|
+
<div class="flex items-center gap-6">
|
|
38
|
+
<p class="text-sm text-gray-600">
|
|
39
|
+
<span class="font-medium text-gray-900">{{ Math.min((page - 1) * pageSize + 1, numDocs) }}–{{ Math.min(page * pageSize, numDocs) }}</span>
|
|
40
|
+
<span class="mx-1">of</span>
|
|
41
|
+
<span class="font-medium text-gray-900">{{ numDocs }}</span>
|
|
42
|
+
<span class="ml-1 text-gray-500">tasks</span>
|
|
43
|
+
</p>
|
|
44
|
+
<div class="flex items-center gap-2">
|
|
45
|
+
<label class="text-sm font-medium text-gray-700">Per page</label>
|
|
46
|
+
<select
|
|
47
|
+
v-model.number="pageSize"
|
|
48
|
+
@change="onPageSizeChange"
|
|
49
|
+
class="border border-gray-300 rounded-md shadow-sm px-3 py-2 text-sm text-gray-700 bg-white hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-ultramarine-500 focus:border-ultramarine-500"
|
|
50
|
+
>
|
|
51
|
+
<option v-for="n in pageSizeOptions" :key="n" :value="n">{{ n }}</option>
|
|
52
|
+
</select>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="flex items-center gap-1">
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
:disabled="page <= 1"
|
|
59
|
+
@click="goToPage(page - 1)"
|
|
60
|
+
class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium rounded-md border transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:border-gray-400"
|
|
61
|
+
>
|
|
62
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
63
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
64
|
+
</svg>
|
|
65
|
+
Previous
|
|
66
|
+
</button>
|
|
67
|
+
<span class="px-4 py-2 text-sm text-gray-600 min-w-[7rem] text-center">
|
|
68
|
+
Page <span class="font-semibold text-gray-900">{{ page }}</span> of <span class="font-semibold text-gray-900">{{ Math.max(1, Math.ceil(numDocs / pageSize)) }}</span>
|
|
69
|
+
</span>
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
:disabled="page >= Math.ceil(numDocs / pageSize)"
|
|
73
|
+
@click="goToPage(page + 1)"
|
|
74
|
+
class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium rounded-md border transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white bg-white text-gray-700 border-gray-300 hover:bg-gray-50 hover:border-gray-400"
|
|
75
|
+
>
|
|
76
|
+
Next
|
|
77
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
78
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
79
|
+
</svg>
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
15
85
|
</div>
|