@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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Page: all tasks with a given name. Reuses task-details to render the list (many tasks).
|
|
4
4
|
const template = require('./task-by-name.html');
|
|
5
5
|
const api = require('../api');
|
|
6
|
+
const { DATE_FILTERS, DATE_FILTER_VALUES, getDateRangeForRange } = require('../_util/dateRange');
|
|
6
7
|
|
|
7
8
|
function buildTaskGroup(name, tasks) {
|
|
8
9
|
const statusCounts = { pending: 0, succeeded: 0, failed: 0, cancelled: 0, in_progress: 0, unknown: 0 };
|
|
@@ -26,44 +27,118 @@ function buildTaskGroup(name, tasks) {
|
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
const PAGE_SIZE_OPTIONS = [25, 50, 100, 200];
|
|
31
|
+
|
|
29
32
|
module.exports = app => app.component('task-by-name', {
|
|
30
33
|
template,
|
|
31
34
|
data: () => ({
|
|
32
35
|
status: 'init',
|
|
33
36
|
taskGroup: null,
|
|
34
|
-
errorMessage: ''
|
|
37
|
+
errorMessage: '',
|
|
38
|
+
selectedRange: 'last_hour',
|
|
39
|
+
start: null,
|
|
40
|
+
end: null,
|
|
41
|
+
dateFilters: DATE_FILTERS,
|
|
42
|
+
page: 1,
|
|
43
|
+
pageSize: 50,
|
|
44
|
+
numDocs: 0,
|
|
45
|
+
pageSizeOptions: PAGE_SIZE_OPTIONS,
|
|
46
|
+
_loadId: 0,
|
|
47
|
+
_lastQueryFilters: null
|
|
35
48
|
}),
|
|
36
49
|
computed: {
|
|
37
50
|
taskName() {
|
|
38
51
|
return this.$route.params.name || '';
|
|
39
52
|
}
|
|
40
53
|
},
|
|
54
|
+
created() {
|
|
55
|
+
const fromQuery = this.$route.query.dateRange;
|
|
56
|
+
this.selectedRange = (fromQuery && DATE_FILTER_VALUES.includes(fromQuery)) ? fromQuery : 'last_hour';
|
|
57
|
+
if (!this.$route.query.dateRange) {
|
|
58
|
+
this.$router.replace({
|
|
59
|
+
path: this.$route.path,
|
|
60
|
+
query: { ...this.$route.query, dateRange: this.selectedRange }
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
41
64
|
watch: {
|
|
42
65
|
taskName: {
|
|
43
66
|
immediate: true,
|
|
44
67
|
handler() {
|
|
68
|
+
this.page = 1;
|
|
45
69
|
this.loadTasks();
|
|
46
70
|
}
|
|
71
|
+
},
|
|
72
|
+
'$route.query': {
|
|
73
|
+
handler(query) {
|
|
74
|
+
const dateRange = query.dateRange;
|
|
75
|
+
if (dateRange && DATE_FILTER_VALUES.includes(dateRange) && this.selectedRange !== dateRange) {
|
|
76
|
+
this.selectedRange = dateRange;
|
|
77
|
+
}
|
|
78
|
+
const effectiveDateRange = (dateRange && DATE_FILTER_VALUES.includes(dateRange)) ? dateRange : (this.selectedRange || 'last_hour');
|
|
79
|
+
const effectiveStatus = query.status ?? '';
|
|
80
|
+
const key = `${effectiveDateRange}|${effectiveStatus}`;
|
|
81
|
+
if (this._lastQueryFilters === key) return;
|
|
82
|
+
this.page = 1;
|
|
83
|
+
this.loadTasks();
|
|
84
|
+
},
|
|
85
|
+
deep: true
|
|
47
86
|
}
|
|
48
87
|
},
|
|
49
88
|
methods: {
|
|
89
|
+
updateDateRange() {
|
|
90
|
+
this.page = 1;
|
|
91
|
+
this.$router.replace({
|
|
92
|
+
path: this.$route.path,
|
|
93
|
+
query: { ...this.$route.query, dateRange: this.selectedRange }
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
goToPage(page) {
|
|
97
|
+
const maxPage = Math.max(1, Math.ceil(this.numDocs / this.pageSize));
|
|
98
|
+
const next = Math.max(1, Math.min(page, maxPage));
|
|
99
|
+
if (next === this.page) return;
|
|
100
|
+
this.page = next;
|
|
101
|
+
this.loadTasks();
|
|
102
|
+
},
|
|
103
|
+
onPageSizeChange() {
|
|
104
|
+
this.page = 1;
|
|
105
|
+
this.loadTasks();
|
|
106
|
+
},
|
|
50
107
|
async loadTasks() {
|
|
51
108
|
if (!this.taskName) return;
|
|
109
|
+
const loadId = ++this._loadId;
|
|
52
110
|
this.status = 'init';
|
|
53
111
|
this.taskGroup = null;
|
|
54
112
|
this.errorMessage = '';
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
113
|
+
const dateRangeFromQuery = this.$route.query.dateRange;
|
|
114
|
+
const dateRange = (dateRangeFromQuery && DATE_FILTER_VALUES.includes(dateRangeFromQuery))
|
|
115
|
+
? dateRangeFromQuery
|
|
116
|
+
: (this.selectedRange || 'last_hour');
|
|
117
|
+
this.selectedRange = dateRange;
|
|
118
|
+
const { start, end } = getDateRangeForRange(dateRange);
|
|
119
|
+
this.start = start;
|
|
120
|
+
this.end = end;
|
|
121
|
+
const skip = (this.page - 1) * this.pageSize;
|
|
122
|
+
const params = {
|
|
123
|
+
name: this.taskName,
|
|
124
|
+
start: start instanceof Date ? start.toISOString() : start,
|
|
125
|
+
end: end instanceof Date ? end.toISOString() : end,
|
|
126
|
+
skip,
|
|
127
|
+
limit: this.pageSize
|
|
128
|
+
};
|
|
129
|
+
const statusFromQuery = this.$route.query.status;
|
|
130
|
+
if (statusFromQuery) params.status = statusFromQuery;
|
|
131
|
+
this._lastQueryFilters = `${dateRange}|${statusFromQuery ?? ''}`;
|
|
58
132
|
try {
|
|
59
|
-
const { tasks } = await api.Task.getTasks(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
end
|
|
63
|
-
});
|
|
133
|
+
const { tasks, numDocs, statusCounts } = await api.Task.getTasks(params);
|
|
134
|
+
if (loadId !== this._loadId) return;
|
|
135
|
+
this.numDocs = numDocs ?? tasks.length;
|
|
64
136
|
this.taskGroup = buildTaskGroup(this.taskName, tasks);
|
|
137
|
+
this.taskGroup.totalCount = this.numDocs;
|
|
138
|
+
if (statusCounts) this.taskGroup.statusCounts = statusCounts;
|
|
65
139
|
this.status = 'loaded';
|
|
66
140
|
} catch (err) {
|
|
141
|
+
if (loadId !== this._loadId) return;
|
|
67
142
|
this.status = 'error';
|
|
68
143
|
this.errorMessage = err?.response?.data?.message || err.message || 'Failed to load tasks';
|
|
69
144
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<div class="p-4 space-y-6">
|
|
2
2
|
<div class="flex items-center justify-between">
|
|
3
3
|
<div>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
<button v-if="showBackButton" @click="goBack" class="text-content-tertiary hover:text-content-secondary mb-2">
|
|
5
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
6
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
7
|
+
</svg>
|
|
8
|
+
{{ backLabel }}
|
|
9
|
+
</button>
|
|
10
|
+
<h1 class="text-2xl font-bold text-content-secondary">{{ taskGroup.name }}</h1>
|
|
11
|
+
<p class="text-content-tertiary">Total: {{ taskGroup.totalCount }} tasks</p>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
</div>
|
|
@@ -10,7 +10,8 @@ const PIE_HOVER = ['#ca8a04', '#16a34a', '#dc2626', '#4b5563'];
|
|
|
10
10
|
module.exports = app => app.component('task-details', {
|
|
11
11
|
props: {
|
|
12
12
|
taskGroup: { type: Object, required: true },
|
|
13
|
-
backTo: { type: Object, default: null }
|
|
13
|
+
backTo: { type: Object, default: null },
|
|
14
|
+
showBackButton: { type: Boolean, default: true }
|
|
14
15
|
},
|
|
15
16
|
data: () => ({
|
|
16
17
|
currentFilter: null,
|
|
@@ -2,25 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
const template = require('./tasks.html');
|
|
4
4
|
const api = require('../api');
|
|
5
|
+
const { DATE_FILTERS, getDateRangeForRange } = require('../_util/dateRange');
|
|
5
6
|
|
|
6
7
|
module.exports = app => app.component('tasks', {
|
|
7
8
|
data: () => ({
|
|
8
9
|
status: 'init',
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
statusCounts: { pending: 0, succeeded: 0, failed: 0, cancelled: 0 },
|
|
11
|
+
tasksByName: [],
|
|
11
12
|
selectedRange: 'last_hour',
|
|
12
13
|
start: null,
|
|
13
14
|
end: null,
|
|
14
|
-
dateFilters:
|
|
15
|
-
{ value: 'all', label: 'All Time' },
|
|
16
|
-
{ value: 'last_hour', label: 'Last Hour' },
|
|
17
|
-
{ value: 'today', label: 'Today' },
|
|
18
|
-
{ value: 'yesterday', label: 'Yesterday' },
|
|
19
|
-
{ value: 'thisWeek', label: 'This Week' },
|
|
20
|
-
{ value: 'lastWeek', label: 'Last Week' },
|
|
21
|
-
{ value: 'thisMonth', label: 'This Month' },
|
|
22
|
-
{ value: 'lastMonth', label: 'Last Month' }
|
|
23
|
-
],
|
|
15
|
+
dateFilters: DATE_FILTERS,
|
|
24
16
|
selectedStatus: 'all',
|
|
25
17
|
statusFilters: [
|
|
26
18
|
{ label: 'All', value: 'all' },
|
|
@@ -50,26 +42,31 @@ module.exports = app => app.component('tasks', {
|
|
|
50
42
|
params.status = this.selectedStatus;
|
|
51
43
|
}
|
|
52
44
|
|
|
53
|
-
if (this.start && this.end) {
|
|
54
|
-
params.start = this.start;
|
|
55
|
-
params.end = this.end;
|
|
56
|
-
} else if (this.start) {
|
|
57
|
-
params.start = this.start;
|
|
45
|
+
if (this.start != null && this.end != null) {
|
|
46
|
+
params.start = this.start instanceof Date ? this.start.toISOString() : this.start;
|
|
47
|
+
params.end = this.end instanceof Date ? this.end.toISOString() : this.end;
|
|
48
|
+
} else if (this.start != null) {
|
|
49
|
+
params.start = this.start instanceof Date ? this.start.toISOString() : this.start;
|
|
58
50
|
}
|
|
59
51
|
|
|
60
52
|
if (this.searchQuery.trim()) {
|
|
61
53
|
params.name = this.searchQuery.trim();
|
|
62
54
|
}
|
|
63
55
|
|
|
64
|
-
const {
|
|
65
|
-
this.
|
|
66
|
-
this.
|
|
56
|
+
const { statusCounts, tasksByName } = await api.Task.getTaskOverview(params);
|
|
57
|
+
this.statusCounts = statusCounts || this.statusCounts;
|
|
58
|
+
this.tasksByName = tasksByName || [];
|
|
67
59
|
},
|
|
68
60
|
openTaskGroupDetails(group) {
|
|
69
|
-
|
|
61
|
+
const query = { dateRange: this.selectedRange || 'last_hour' };
|
|
62
|
+
if (this.selectedStatus && this.selectedStatus !== 'all') query.status = this.selectedStatus;
|
|
63
|
+
this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query });
|
|
70
64
|
},
|
|
71
65
|
openTaskGroupDetailsWithFilter(group, status) {
|
|
72
|
-
|
|
66
|
+
const query = { dateRange: this.selectedRange || 'last_hour' };
|
|
67
|
+
if (status) query.status = status;
|
|
68
|
+
else if (this.selectedStatus && this.selectedStatus !== 'all') query.status = this.selectedStatus;
|
|
69
|
+
this.$router.push({ path: `/tasks/${encodeURIComponent(group.name || '')}`, query });
|
|
73
70
|
},
|
|
74
71
|
async onTaskCreated() {
|
|
75
72
|
// Refresh the task data when a new task is created
|
|
@@ -213,114 +210,24 @@ module.exports = app => app.component('tasks', {
|
|
|
213
210
|
}, 300);
|
|
214
211
|
},
|
|
215
212
|
async updateDateRange() {
|
|
216
|
-
const
|
|
217
|
-
let start, end;
|
|
218
|
-
|
|
219
|
-
switch (this.selectedRange) {
|
|
220
|
-
case 'last_hour':
|
|
221
|
-
start = new Date();
|
|
222
|
-
start.setHours(start.getHours() - 1);
|
|
223
|
-
end = new Date();
|
|
224
|
-
break;
|
|
225
|
-
case 'today':
|
|
226
|
-
start = new Date();
|
|
227
|
-
start.setHours(0, 0, 0, 0);
|
|
228
|
-
end = new Date();
|
|
229
|
-
end.setHours(23, 59, 59, 999);
|
|
230
|
-
break;
|
|
231
|
-
case 'yesterday':
|
|
232
|
-
start = new Date();
|
|
233
|
-
start.setDate(start.getDate() - 1);
|
|
234
|
-
start.setHours(0, 0, 0, 0);
|
|
235
|
-
end = new Date();
|
|
236
|
-
break;
|
|
237
|
-
case 'thisWeek':
|
|
238
|
-
start = new Date(now.getTime() - (7 * 86400000));
|
|
239
|
-
start.setHours(0, 0, 0, 0);
|
|
240
|
-
end = new Date();
|
|
241
|
-
end.setHours(23, 59, 59, 999);
|
|
242
|
-
break;
|
|
243
|
-
case 'lastWeek':
|
|
244
|
-
start = new Date(now.getTime() - (14 * 86400000));
|
|
245
|
-
start.setHours(0, 0, 0, 0);
|
|
246
|
-
end = new Date(now.getTime() - (7 * 86400000));
|
|
247
|
-
end.setHours(23, 59, 59, 999);
|
|
248
|
-
break;
|
|
249
|
-
case 'thisMonth':
|
|
250
|
-
start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
251
|
-
end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
252
|
-
break;
|
|
253
|
-
case 'lastMonth':
|
|
254
|
-
start = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
255
|
-
end = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
|
|
256
|
-
break;
|
|
257
|
-
case 'all':
|
|
258
|
-
default:
|
|
259
|
-
this.start = null;
|
|
260
|
-
this.end = null;
|
|
261
|
-
break;
|
|
262
|
-
}
|
|
263
|
-
|
|
213
|
+
const { start, end } = getDateRangeForRange(this.selectedRange);
|
|
264
214
|
this.start = start;
|
|
265
215
|
this.end = end;
|
|
266
|
-
|
|
267
216
|
await this.getTasks();
|
|
268
217
|
}
|
|
269
218
|
},
|
|
270
219
|
computed: {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// Process tasks from groupedTasks to create name-based groups
|
|
275
|
-
Object.entries(this.groupedTasks).forEach(([status, tasks]) => {
|
|
276
|
-
tasks.forEach(task => {
|
|
277
|
-
if (!groups[task.name]) {
|
|
278
|
-
groups[task.name] = {
|
|
279
|
-
name: task.name,
|
|
280
|
-
tasks: [],
|
|
281
|
-
statusCounts: {
|
|
282
|
-
pending: 0,
|
|
283
|
-
succeeded: 0,
|
|
284
|
-
failed: 0,
|
|
285
|
-
cancelled: 0
|
|
286
|
-
},
|
|
287
|
-
totalCount: 0,
|
|
288
|
-
lastRun: null
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
groups[task.name].tasks.push(task);
|
|
293
|
-
groups[task.name].totalCount++;
|
|
294
|
-
|
|
295
|
-
// Count status using the status from groupedTasks
|
|
296
|
-
if (groups[task.name].statusCounts.hasOwnProperty(status)) {
|
|
297
|
-
groups[task.name].statusCounts[status]++;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Track last run time
|
|
301
|
-
const taskTime = new Date(task.scheduledAt || task.createdAt || 0);
|
|
302
|
-
if (!groups[task.name].lastRun || taskTime > new Date(groups[task.name].lastRun)) {
|
|
303
|
-
groups[task.name].lastRun = taskTime;
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Convert to array and sort alphabetically by name
|
|
309
|
-
return Object.values(groups).sort((a, b) => {
|
|
310
|
-
return a.name.localeCompare(b.name);
|
|
311
|
-
});
|
|
220
|
+
pendingCount() {
|
|
221
|
+
return this.statusCounts.pending || 0;
|
|
312
222
|
},
|
|
313
223
|
succeededCount() {
|
|
314
|
-
return this.
|
|
224
|
+
return this.statusCounts.succeeded || 0;
|
|
315
225
|
},
|
|
316
226
|
failedCount() {
|
|
317
|
-
return this.
|
|
227
|
+
return this.statusCounts.failed || 0;
|
|
318
228
|
},
|
|
319
229
|
cancelledCount() {
|
|
320
|
-
return this.
|
|
321
|
-
},
|
|
322
|
-
pendingCount() {
|
|
323
|
-
return this.groupedTasks.pending ? this.groupedTasks.pending.length : 0;
|
|
230
|
+
return this.statusCounts.cancelled || 0;
|
|
324
231
|
}
|
|
325
232
|
},
|
|
326
233
|
mounted: async function() {
|
package/local.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const mongoose = require('mongoose');
|
|
4
|
+
const connect = require('./seed/connect');
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const studio = require('./express');
|
|
8
|
+
|
|
9
|
+
const getModelDescriptions = require('./backend/helpers/getModelDescriptions');
|
|
10
|
+
|
|
11
|
+
Error.stackTraceLimit = 25;
|
|
12
|
+
|
|
13
|
+
run().catch(err => {
|
|
14
|
+
console.error(err);
|
|
15
|
+
process.exit(-1);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function run() {
|
|
19
|
+
const app = express();
|
|
20
|
+
await connect();
|
|
21
|
+
|
|
22
|
+
console.log(getModelDescriptions(mongoose.connection));
|
|
23
|
+
|
|
24
|
+
app.use('/studio', await studio(
|
|
25
|
+
null,
|
|
26
|
+
null,
|
|
27
|
+
{
|
|
28
|
+
__build: true,
|
|
29
|
+
__watch: process.env.WATCH,
|
|
30
|
+
_mothershipUrl: 'http://localhost:7777/.netlify/functions',
|
|
31
|
+
apiKey: 'TACO',
|
|
32
|
+
openAIAPIKey: process.env.OPENAI_API_KEY
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
await app.listen(3333);
|
|
37
|
+
console.log('Listening on port 3333');
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.",
|
|
5
5
|
"homepage": "https://mongoosestudio.app/",
|
|
6
6
|
"repository": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"extrovert": "^0.2.0",
|
|
21
21
|
"marked": "15.0.12",
|
|
22
22
|
"node-inspect-extracted": "3.x",
|
|
23
|
+
"regexp.escape": "^2.0.1",
|
|
23
24
|
"tailwindcss": "3.4.0",
|
|
24
25
|
"vue": "3.x",
|
|
25
26
|
"vue-toastification": "^2.0.0-rc.5",
|
|
@@ -45,6 +46,8 @@
|
|
|
45
46
|
},
|
|
46
47
|
"scripts": {
|
|
47
48
|
"lint": "eslint .",
|
|
49
|
+
"seed": "node seed/index.js",
|
|
50
|
+
"start": "node ./local.js",
|
|
48
51
|
"tailwind": "tailwindcss -o ./frontend/public/tw.css",
|
|
49
52
|
"tailwind:watch": "tailwindcss -o ./frontend/public/tw.css --watch",
|
|
50
53
|
"test": "mocha test/*.test.js",
|
package/seed/connect.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const mongoose = require('mongoose');
|
|
4
|
+
|
|
5
|
+
const uri = 'mongodb://127.0.0.1:27017/mongoose_studio_test';
|
|
6
|
+
|
|
7
|
+
module.exports = async function connect() {
|
|
8
|
+
await mongoose.connect(uri);
|
|
9
|
+
|
|
10
|
+
mongoose.model('User', new mongoose.Schema({
|
|
11
|
+
name: String,
|
|
12
|
+
email: String,
|
|
13
|
+
role: String,
|
|
14
|
+
plan: String,
|
|
15
|
+
status: String,
|
|
16
|
+
isDeleted: Boolean,
|
|
17
|
+
lastLoginAt: Date,
|
|
18
|
+
createdAt: Date,
|
|
19
|
+
updatedAt: Date
|
|
20
|
+
}, { collection: 'User' }));
|
|
21
|
+
|
|
22
|
+
return mongoose.connection;
|
|
23
|
+
};
|
package/seed/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const connect = require('./connect');
|
|
4
|
+
const mongoose = require('mongoose');
|
|
5
|
+
|
|
6
|
+
if (require.main === module) {
|
|
7
|
+
run().catch(err => {
|
|
8
|
+
console.error(err);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function run() {
|
|
14
|
+
await connect();
|
|
15
|
+
const { User } = mongoose.models;
|
|
16
|
+
const dashboardCollection = mongoose.connection.collection('studio__dashboards');
|
|
17
|
+
const now = new Date();
|
|
18
|
+
|
|
19
|
+
const users = [
|
|
20
|
+
{
|
|
21
|
+
name: 'Ada Lovelace',
|
|
22
|
+
email: 'ada@example.com',
|
|
23
|
+
role: 'admin',
|
|
24
|
+
plan: 'enterprise',
|
|
25
|
+
status: 'active',
|
|
26
|
+
isDeleted: false,
|
|
27
|
+
lastLoginAt: new Date('2026-03-11T14:21:00.000Z'),
|
|
28
|
+
createdAt: new Date('2025-10-03T09:15:00.000Z'),
|
|
29
|
+
updatedAt: now
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'Grace Hopper',
|
|
33
|
+
email: 'grace@example.com',
|
|
34
|
+
role: 'analyst',
|
|
35
|
+
plan: 'pro',
|
|
36
|
+
status: 'active',
|
|
37
|
+
isDeleted: false,
|
|
38
|
+
lastLoginAt: new Date('2026-03-10T17:42:00.000Z'),
|
|
39
|
+
createdAt: new Date('2025-11-16T13:05:00.000Z'),
|
|
40
|
+
updatedAt: now
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Linus Torvalds',
|
|
44
|
+
email: 'linus@example.com',
|
|
45
|
+
role: 'editor',
|
|
46
|
+
plan: 'starter',
|
|
47
|
+
status: 'invited',
|
|
48
|
+
isDeleted: false,
|
|
49
|
+
lastLoginAt: null,
|
|
50
|
+
createdAt: new Date('2026-01-09T11:30:00.000Z'),
|
|
51
|
+
updatedAt: now
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Margaret Hamilton',
|
|
55
|
+
email: 'margaret@example.com',
|
|
56
|
+
role: 'viewer',
|
|
57
|
+
plan: 'pro',
|
|
58
|
+
status: 'inactive',
|
|
59
|
+
isDeleted: false,
|
|
60
|
+
lastLoginAt: new Date('2026-02-22T08:00:00.000Z'),
|
|
61
|
+
createdAt: new Date('2025-12-01T16:20:00.000Z'),
|
|
62
|
+
updatedAt: now
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const dashboard = {
|
|
67
|
+
title: 'User directory table',
|
|
68
|
+
description: 'Sample dashboard for testing the dashboard-table component.',
|
|
69
|
+
code: `const users = await db.model('User')
|
|
70
|
+
.find({ isDeleted: false })
|
|
71
|
+
.sort({ createdAt: -1 })
|
|
72
|
+
.lean();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
$table: {
|
|
76
|
+
columns: ['Name', 'Email', 'Role', 'Plan', 'Status', 'Last Login'],
|
|
77
|
+
rows: users.map(user => [
|
|
78
|
+
user.name,
|
|
79
|
+
user.email,
|
|
80
|
+
user.role,
|
|
81
|
+
user.plan,
|
|
82
|
+
user.status,
|
|
83
|
+
user.lastLoginAt ? new Date(user.lastLoginAt).toISOString().slice(0, 10) : 'Never'
|
|
84
|
+
])
|
|
85
|
+
}
|
|
86
|
+
};`
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
await User.deleteMany({ email: { $in: users.map(user => user.email) } });
|
|
90
|
+
await dashboardCollection.deleteMany({ title: dashboard.title });
|
|
91
|
+
|
|
92
|
+
const insertedUsers = await User.insertMany(users);
|
|
93
|
+
const insertedDashboard = await dashboardCollection.insertOne(dashboard);
|
|
94
|
+
|
|
95
|
+
console.log(`Inserted ${insertedUsers.length} users`);
|
|
96
|
+
console.log(`Inserted dashboard ${insertedDashboard.insertedId.toString()}`);
|
|
97
|
+
|
|
98
|
+
await mongoose.disconnect();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = run;
|