@mongoosejs/studio 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/backend/actions/Model/executeDocumentScript.js +61 -0
  2. package/backend/actions/Model/index.js +2 -0
  3. package/backend/actions/Model/listModels.js +36 -1
  4. package/backend/actions/Model/streamDocumentChanges.js +123 -0
  5. package/backend/actions/Task/cancelTask.js +24 -0
  6. package/backend/actions/Task/createTask.js +33 -0
  7. package/backend/actions/Task/getTasks.js +62 -0
  8. package/backend/actions/Task/index.js +7 -0
  9. package/backend/actions/Task/rescheduleTask.js +39 -0
  10. package/backend/actions/Task/runTask.js +25 -0
  11. package/backend/actions/index.js +1 -0
  12. package/backend/authorize.js +2 -0
  13. package/backend/index.js +7 -1
  14. package/eslint.config.js +4 -1
  15. package/express.js +4 -2
  16. package/frontend/public/app.js +14590 -13420
  17. package/frontend/public/tw.css +357 -4
  18. package/frontend/src/api.js +100 -0
  19. package/frontend/src/dashboard-result/dashboard-document/dashboard-document.html +4 -5
  20. package/frontend/src/dashboard-result/dashboard-document/dashboard-document.js +13 -14
  21. package/frontend/src/document/document.html +80 -0
  22. package/frontend/src/document/document.js +206 -19
  23. package/frontend/src/document/execute-script/execute-script.css +35 -0
  24. package/frontend/src/document/execute-script/execute-script.html +67 -0
  25. package/frontend/src/document/execute-script/execute-script.js +142 -0
  26. package/frontend/src/index.js +48 -4
  27. package/frontend/src/navbar/navbar.html +15 -2
  28. package/frontend/src/navbar/navbar.js +11 -0
  29. package/frontend/src/routes.js +13 -5
  30. package/frontend/src/tasks/task-details/task-details.html +284 -0
  31. package/frontend/src/tasks/task-details/task-details.js +182 -0
  32. package/frontend/src/tasks/tasks.css +0 -0
  33. package/frontend/src/tasks/tasks.html +220 -0
  34. package/frontend/src/tasks/tasks.js +372 -0
  35. package/package.json +4 -1
@@ -0,0 +1,372 @@
1
+ 'use strict';
2
+
3
+ const template = require('./tasks.html');
4
+ const api = require('../api');
5
+
6
+ module.exports = app => app.component('tasks', {
7
+ data: () => ({
8
+ status: 'init',
9
+ tasks: [],
10
+ groupedTasks: {},
11
+ selectedRange: 'today',
12
+ start: null,
13
+ end: null,
14
+ dateFilters: [
15
+ { value: 'all', label: 'All Time' },
16
+ { value: 'today', label: 'Today' },
17
+ { value: 'yesterday', label: 'Yesterday' },
18
+ { value: 'thisWeek', label: 'This Week' },
19
+ { value: 'lastWeek', label: 'Last Week' },
20
+ { value: 'thisMonth', label: 'This Month' },
21
+ { value: 'lastMonth', label: 'Last Month' }
22
+ ],
23
+ selectedStatus: 'all',
24
+ statusFilters: [
25
+ { label: 'All', value: 'all' },
26
+ { label: 'Pending', value: 'pending' },
27
+ // { label: 'In Progress', value: 'in_progress' },
28
+ { label: 'Succeeded', value: 'succeeded' },
29
+ { label: 'Failed', value: 'failed' },
30
+ { label: 'Cancelled', value: 'cancelled' }
31
+ ],
32
+ searchQuery: '',
33
+ searchTimeout: null,
34
+ // Task details view state
35
+ showTaskDetails: false,
36
+ selectedTaskGroup: null,
37
+ taskDetailsFilter: null,
38
+ // Create task modal state
39
+ showCreateTaskModal: false,
40
+ newTask: {
41
+ name: '',
42
+ scheduledAt: '',
43
+ parameters: '',
44
+ repeatInterval: ''
45
+ },
46
+ parametersEditor: null
47
+ }),
48
+ methods: {
49
+ async getTasks() {
50
+ const params = {};
51
+ if (this.selectedStatus == 'all') {
52
+ params.status = null;
53
+ } else {
54
+ params.status = this.selectedStatus;
55
+ }
56
+
57
+ if (this.start && this.end) {
58
+ params.start = this.start;
59
+ params.end = this.end;
60
+ } else if (this.start) {
61
+ params.start = this.start;
62
+ }
63
+
64
+ if (this.searchQuery.trim()) {
65
+ params.name = this.searchQuery.trim();
66
+ }
67
+
68
+ const { tasks, groupedTasks } = await api.Task.getTasks(params);
69
+ this.tasks = tasks;
70
+ this.groupedTasks = groupedTasks;
71
+ },
72
+ openTaskGroupDetails(group) {
73
+ this.selectedTaskGroup = group;
74
+ this.showTaskDetails = true;
75
+ },
76
+ openTaskGroupDetailsWithFilter(group, status) {
77
+ // Create a filtered version of the task group with only the specified status
78
+ const filteredGroup = {
79
+ ...group,
80
+ tasks: group.tasks.filter(task => task.status === status),
81
+ filteredStatus: status
82
+ };
83
+ this.selectedTaskGroup = filteredGroup;
84
+ this.taskDetailsFilter = status;
85
+ this.showTaskDetails = true;
86
+ },
87
+ async onTaskCancelled() {
88
+ // Refresh the task data when a task is cancelled
89
+ await this.getTasks();
90
+ },
91
+ hideTaskDetails() {
92
+ this.showTaskDetails = false;
93
+ this.selectedTaskGroup = null;
94
+ this.taskDetailsFilter = null;
95
+ },
96
+ async onTaskCreated() {
97
+ // Refresh the task data when a new task is created
98
+ await this.getTasks();
99
+ },
100
+ formatDate(dateString) {
101
+ if (!dateString) return 'N/A';
102
+ return new Date(dateString).toLocaleString();
103
+ },
104
+ async createTask() {
105
+ try {
106
+ let parameters = {};
107
+ const parametersText = this.parametersEditor ? this.parametersEditor.getValue() : '';
108
+ if (parametersText.trim()) {
109
+ try {
110
+ parameters = JSON.parse(parametersText);
111
+ } catch (e) {
112
+ console.error('Invalid JSON in parameters field:', e);
113
+ this.$toast.create({
114
+ title: 'Invalid JSON Parameters',
115
+ text: 'Please check your JSON syntax in the parameters field',
116
+ type: 'error',
117
+ timeout: 5000,
118
+ positionClass: 'bottomRight'
119
+ });
120
+ return;
121
+ }
122
+ }
123
+
124
+ // Validate repeat interval
125
+ let repeatInterval = null;
126
+ if (this.newTask.repeatInterval && this.newTask.repeatInterval.trim()) {
127
+ const interval = parseInt(this.newTask.repeatInterval);
128
+ if (isNaN(interval) || interval < 0) {
129
+ console.error('Invalid repeat interval. Must be a positive number.');
130
+ this.$toast.create({
131
+ title: 'Invalid Repeat Interval',
132
+ text: 'Repeat interval must be a positive number (in milliseconds)',
133
+ type: 'error',
134
+ timeout: 5000,
135
+ positionClass: 'bottomRight'
136
+ });
137
+ return;
138
+ }
139
+ repeatInterval = interval;
140
+ }
141
+
142
+ const taskData = {
143
+ name: this.newTask.name,
144
+ scheduledAt: this.newTask.scheduledAt,
145
+ payload: parameters,
146
+ repeatAfterMS: repeatInterval
147
+ };
148
+
149
+ console.log('Creating task:', taskData);
150
+ await api.Task.createTask(taskData);
151
+
152
+ // Show success message
153
+ this.$toast.create({
154
+ title: 'Task Created Successfully!',
155
+ text: `Task "${taskData.name}" has been scheduled`,
156
+ type: 'success',
157
+ timeout: 3000,
158
+ positionClass: 'bottomRight'
159
+ });
160
+
161
+ // Close modal (which will reset form)
162
+ this.closeCreateTaskModal();
163
+
164
+ // Refresh the task data
165
+ await this.getTasks();
166
+ } catch (error) {
167
+ console.error('Error creating task:', error);
168
+ this.$toast.create({
169
+ title: 'Failed to Create Task',
170
+ text: error?.response?.data?.message || error.message || 'An unexpected error occurred',
171
+ type: 'error',
172
+ timeout: 5000,
173
+ positionClass: 'bottomRight'
174
+ });
175
+ }
176
+ },
177
+ resetCreateTaskForm() {
178
+ this.newTask = {
179
+ name: '',
180
+ scheduledAt: '',
181
+ parameters: '',
182
+ repeatInterval: ''
183
+ };
184
+ if (this.parametersEditor) {
185
+ this.parametersEditor.setValue('');
186
+ }
187
+ },
188
+ setDefaultCreateTaskValues() {
189
+ // Set default scheduled time to 1 hour from now
190
+ const defaultTime = new Date();
191
+ defaultTime.setHours(defaultTime.getHours() + 1);
192
+ this.newTask.scheduledAt = defaultTime.toISOString().slice(0, 16);
193
+ },
194
+ closeCreateTaskModal() {
195
+ this.showCreateTaskModal = false;
196
+ this.resetCreateTaskForm();
197
+ this.setDefaultCreateTaskValues();
198
+ },
199
+ initializeParametersEditor() {
200
+ if (this.$refs.parametersEditor && !this.parametersEditor) {
201
+ this.parametersEditor = CodeMirror.fromTextArea(this.$refs.parametersEditor, {
202
+ mode: 'javascript',
203
+ lineNumbers: true,
204
+ smartIndent: false,
205
+ theme: 'default'
206
+ });
207
+ }
208
+ },
209
+ openCreateTaskModal() {
210
+ this.showCreateTaskModal = true;
211
+ this.$nextTick(() => {
212
+ this.initializeParametersEditor();
213
+ });
214
+ },
215
+ getStatusColor(status) {
216
+ if (status === 'succeeded') {
217
+ // Green (success)
218
+ return 'bg-green-100 text-green-800';
219
+ } else if (status === 'pending') {
220
+ // Yellow (waiting)
221
+ return 'bg-yellow-100 text-yellow-800';
222
+ } else if (status === 'cancelled') {
223
+ // Gray (neutral/aborted)
224
+ return 'bg-gray-100 text-gray-800';
225
+ } else if (status === 'failed') {
226
+ // Red (error)
227
+ return 'bg-red-100 text-red-800';
228
+ } else if (status === 'in_progress') {
229
+ // Blue (active/running)
230
+ return 'bg-blue-100 text-blue-800';
231
+ } else {
232
+ // Default (fallback)
233
+ return 'bg-slate-100 text-slate-800';
234
+ }
235
+ },
236
+ async resetFilters() {
237
+ this.selectedStatus = 'all';
238
+ this.selectedRange = 'today';
239
+ this.searchQuery = '';
240
+ await this.updateDateRange();
241
+ },
242
+ async setStatusFilter(status) {
243
+ this.selectedStatus = status;
244
+ await this.getTasks();
245
+ },
246
+ async onSearchInput() {
247
+ // Debounce the search to avoid too many API calls
248
+ clearTimeout(this.searchTimeout);
249
+ this.searchTimeout = setTimeout(async() => {
250
+ await this.getTasks();
251
+ }, 300);
252
+ },
253
+ async updateDateRange() {
254
+ const now = new Date();
255
+ let start, end;
256
+
257
+ switch (this.selectedRange) {
258
+ case 'today':
259
+ start = new Date();
260
+ start.setHours(0, 0, 0, 0);
261
+ end = new Date();
262
+ end.setHours(23, 59, 59, 999);
263
+ break;
264
+ case 'yesterday':
265
+ start = new Date();
266
+ start.setDate(start.getDate() - 1);
267
+ start.setHours(0, 0, 0, 0);
268
+ end = new Date();
269
+ break;
270
+ case 'thisWeek':
271
+ start = new Date(now.getTime() - (7 * 86400000));
272
+ start.setHours(0, 0, 0, 0);
273
+ end = new Date();
274
+ end.setHours(23, 59, 59, 999);
275
+ break;
276
+ case 'lastWeek':
277
+ start = new Date(now.getTime() - (14 * 86400000));
278
+ start.setHours(0, 0, 0, 0);
279
+ end = new Date(now.getTime() - (7 * 86400000));
280
+ end.setHours(23, 59, 59, 999);
281
+ break;
282
+ case 'thisMonth':
283
+ start = new Date(now.getFullYear(), now.getMonth(), 1);
284
+ end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
285
+ break;
286
+ case 'lastMonth':
287
+ start = new Date(now.getFullYear(), now.getMonth() - 1, 1);
288
+ end = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
289
+ break;
290
+ case 'all':
291
+ default:
292
+ this.start = null;
293
+ this.end = null;
294
+ break;
295
+ }
296
+
297
+ this.start = start;
298
+ this.end = end;
299
+
300
+ await this.getTasks();
301
+ }
302
+ },
303
+ computed: {
304
+ tasksByName() {
305
+ const groups = {};
306
+
307
+ // Process tasks from groupedTasks to create name-based groups
308
+ Object.entries(this.groupedTasks).forEach(([status, tasks]) => {
309
+ tasks.forEach(task => {
310
+ if (!groups[task.name]) {
311
+ groups[task.name] = {
312
+ name: task.name,
313
+ tasks: [],
314
+ statusCounts: {
315
+ pending: 0,
316
+ succeeded: 0,
317
+ failed: 0,
318
+ cancelled: 0
319
+ },
320
+ totalCount: 0,
321
+ lastRun: null
322
+ };
323
+ }
324
+
325
+ groups[task.name].tasks.push(task);
326
+ groups[task.name].totalCount++;
327
+
328
+ // Count status using the status from groupedTasks
329
+ if (groups[task.name].statusCounts.hasOwnProperty(status)) {
330
+ groups[task.name].statusCounts[status]++;
331
+ }
332
+
333
+ // Track last run time
334
+ const taskTime = new Date(task.scheduledAt || task.createdAt || 0);
335
+ if (!groups[task.name].lastRun || taskTime > new Date(groups[task.name].lastRun)) {
336
+ groups[task.name].lastRun = taskTime;
337
+ }
338
+ });
339
+ });
340
+
341
+ // Convert to array and sort alphabetically by name
342
+ return Object.values(groups).sort((a, b) => {
343
+ return a.name.localeCompare(b.name);
344
+ });
345
+ },
346
+ succeededCount() {
347
+ return this.groupedTasks.succeeded ? this.groupedTasks.succeeded.length : 0;
348
+ },
349
+ failedCount() {
350
+ return this.groupedTasks.failed ? this.groupedTasks.failed.length : 0;
351
+ },
352
+ cancelledCount() {
353
+ return this.groupedTasks.cancelled ? this.groupedTasks.cancelled.length : 0;
354
+ },
355
+ pendingCount() {
356
+ return this.groupedTasks.pending ? this.groupedTasks.pending.length : 0;
357
+ }
358
+ },
359
+ mounted: async function() {
360
+ await this.updateDateRange();
361
+ await this.getTasks();
362
+ this.status = 'loaded';
363
+ this.setDefaultCreateTaskValues();
364
+ },
365
+ beforeDestroy() {
366
+ if (this.parametersEditor) {
367
+ this.parametersEditor.toTextArea();
368
+ }
369
+ },
370
+
371
+ template: template
372
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
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": {
@@ -28,6 +28,9 @@
28
28
  "peerDependencies": {
29
29
  "mongoose": "7.x || 8.x || ^9.0.0"
30
30
  },
31
+ "optionalPeerDependencies": {
32
+ "@mongoosejs/task": "0.5.x || 0.6.x"
33
+ },
31
34
  "devDependencies": {
32
35
  "@masteringjs/eslint-config": "0.1.1",
33
36
  "axios": "1.2.2",