@mongoosejs/studio 0.2.10 → 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.
@@ -0,0 +1,220 @@
1
+ <div class="p-4 space-y-6">
2
+ <!-- Task Details View -->
3
+ <task-details
4
+ v-if="showTaskDetails && selectedTaskGroup"
5
+ :task-group="selectedTaskGroup"
6
+ :current-filter="taskDetailsFilter"
7
+ @back="hideTaskDetails"
8
+ @task-created="onTaskCreated"
9
+ @task-cancelled="onTaskCancelled"
10
+ @update:current-filter="taskDetailsFilter = $event"
11
+ ></task-details>
12
+
13
+ <!-- Main Tasks View -->
14
+ <div v-else>
15
+ <h1 class="text-2xl font-bold text-gray-700 mb-4">Task Overview</h1>
16
+ <div v-if="status == 'init'">
17
+ <img src="images/loader.gif" />
18
+ </div>
19
+ <!-- Task List -->
20
+ <div class="bg-white p-4 rounded-lg shadow" v-if="status == 'loaded'">
21
+ <div class="mb-4">
22
+ <label class="block text-sm font-medium text-gray-700 mb-1">Filter by Date:</label>
23
+ <select v-model="selectedRange" @change="updateDateRange" class="border-gray-300 rounded-md shadow-sm w-full p-2">
24
+ <option v-for="option in dateFilters" :key="option.value" :value="option.value">
25
+ {{ option.label }}
26
+ </option>
27
+ </select>
28
+ </div>
29
+ <div class="mb-4">
30
+ <label class="block text-sm font-medium text-gray-700 mb-1">Filter by Status:</label>
31
+ <select v-model="selectedStatus" @change="getTasks" class="border-gray-300 rounded-md shadow-sm w-full p-2">
32
+ <option v-for="option in statusFilters" :key="option.value" :value="option.value">
33
+ {{ option.label }}
34
+ </option>
35
+ </select>
36
+ </div>
37
+ <div class="mb-4">
38
+ <label class="block text-sm font-medium text-gray-700 mb-1">Search by Task Name:</label>
39
+ <input
40
+ v-model="searchQuery"
41
+ type="text"
42
+ @input="onSearchInput"
43
+ class="border-gray-300 rounded-md shadow-sm w-full p-2"
44
+ placeholder="Enter task name to search..."
45
+ >
46
+ </div>
47
+ <div class="mb-4">
48
+ <button
49
+ @click="resetFilters"
50
+ class="w-full bg-gray-200 text-gray-700 hover:bg-gray-300 font-medium py-2 px-4 rounded-md transition"
51
+ >
52
+ Reset Filters
53
+ </button>
54
+ </div>
55
+ <div class="mb-6">
56
+ <button
57
+ @click="openCreateTaskModal"
58
+ class="w-full bg-ultramarine-600 text-white hover:bg-ultramarine-700 font-medium py-2 px-4 rounded-md transition"
59
+ >
60
+ Create New Task
61
+ </button>
62
+ </div>
63
+ <!-- Summary Section -->
64
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
65
+ <button
66
+ @click="setStatusFilter('pending')"
67
+ :class="getStatusColor('pending') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-yellow-200 hover:border-yellow-300'"
68
+ >
69
+ <div class="text-sm">Scheduled</div>
70
+ <div class="text-2xl font-bold">{{pendingCount}}</div>
71
+ </button>
72
+ <button
73
+ @click="setStatusFilter('succeeded')"
74
+ :class="getStatusColor('succeeded') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-green-200 hover:border-green-300'"
75
+ >
76
+ <div class="text-sm">Completed</div>
77
+ <div class="text-2xl font-bold">{{succeededCount}}</div>
78
+ </button>
79
+ <button
80
+ @click="setStatusFilter('failed')"
81
+ :class="getStatusColor('failed') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-red-200 hover:border-red-300'"
82
+ >
83
+ <div class="text-sm">Failed</div>
84
+ <div class="text-2xl font-bold">{{failedCount}}</div>
85
+ </button>
86
+ <button
87
+ @click="setStatusFilter('cancelled')"
88
+ :class="getStatusColor('cancelled') + ' p-4 rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 cursor-pointer border-2 border-gray-200 hover:border-gray-300'"
89
+ >
90
+ <div class="text-sm">Cancelled</div>
91
+ <div class="text-2xl font-bold">{{cancelledCount}}</div>
92
+ </button>
93
+ </div>
94
+
95
+ <!-- Grouped Task List -->
96
+ <div class="mt-6">
97
+ <h2 class="text-lg font-semibold text-gray-700 mb-4">Tasks by Name</h2>
98
+ <ul class="divide-y divide-gray-200">
99
+ <li v-for="group in tasksByName" :key="group.name" class="p-4 group hover:border hover:rounded-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200">
100
+ <div class="flex items-center justify-between mb-3 ">
101
+ <div class="flex-1 cursor-pointer" @click="openTaskGroupDetails(group)">
102
+ <div class="flex items-center gap-2">
103
+ <div class="font-medium text-lg group-hover:text-ultramarine-600 transition-colors">{{ group.name }}</div>
104
+ <svg class="w-4 h-4 text-gray-400 group-hover:text-ultramarine-600 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
105
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
106
+ </svg>
107
+ </div>
108
+ <div class="text-sm text-gray-500 group-hover:text-gray-700 transition-colors">Total: {{ group.totalCount }} tasks</div>
109
+ <div class="text-xs text-ultramarine-600 opacity-0 group-hover:opacity-100 transition-opacity mt-1">
110
+ Click to view details
111
+ </div>
112
+ </div>
113
+ <div class="text-sm text-gray-500">
114
+ Last run: {{ group.lastRun ? new Date(group.lastRun).toLocaleString() : 'Never' }}
115
+ </div>
116
+ </div>
117
+
118
+ <!-- Status Counts -->
119
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
120
+ <button
121
+ @click.stop="openTaskGroupDetailsWithFilter(group, 'pending')"
122
+ class="bg-yellow-50 border border-yellow-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-yellow-300"
123
+ >
124
+ <div class="text-xs text-yellow-600 font-medium">Pending</div>
125
+ <div class="text-lg font-bold text-yellow-700">{{ group.statusCounts.pending || 0 }}</div>
126
+ </button>
127
+ <button
128
+ @click.stop="openTaskGroupDetailsWithFilter(group, 'succeeded')"
129
+ class="bg-green-50 border border-green-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-green-300"
130
+ >
131
+ <div class="text-xs text-green-600 font-medium">Succeeded</div>
132
+ <div class="text-lg font-bold text-green-700">{{ group.statusCounts.succeeded || 0 }}</div>
133
+ </button>
134
+ <button
135
+ @click.stop="openTaskGroupDetailsWithFilter(group, 'failed')"
136
+ class="bg-red-50 border border-red-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-red-300"
137
+ >
138
+ <div class="text-xs text-red-600 font-medium">Failed</div>
139
+ <div class="text-lg font-bold text-red-700">{{ group.statusCounts.failed || 0 }}</div>
140
+ </button>
141
+ <button
142
+ @click.stop="openTaskGroupDetailsWithFilter(group, 'cancelled')"
143
+ class="bg-gray-50 border border-gray-200 rounded-md p-2 text-center shadow-sm hover:shadow-md transform hover:-translate-y-1 transition-all duration-200 cursor-pointer hover:border-gray-300"
144
+ >
145
+ <div class="text-xs text-gray-600 font-medium">Cancelled</div>
146
+ <div class="text-lg font-bold text-gray-700">{{ group.statusCounts.cancelled || 0 }}</div>
147
+ </button>
148
+ </div>
149
+ </li>
150
+ </ul>
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+ <!-- Create Task Modal -->
156
+ <modal v-if="showCreateTaskModal" containerClass="!h-[90vh] !w-[90vw]">
157
+ <template #body>
158
+ <div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="closeCreateTaskModal" role="button" aria-label="Close modal">&times;</div>
159
+ <div class="space-y-4">
160
+ <h3 class="text-lg font-semibold text-gray-700 mb-4">Create New Task</h3>
161
+
162
+ <div>
163
+ <label class="block text-sm font-medium text-gray-700 mb-1">Task Name:</label>
164
+ <input
165
+ v-model="newTask.name"
166
+ type="text"
167
+ class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500"
168
+ placeholder="Enter task name"
169
+ >
170
+ </div>
171
+
172
+ <div>
173
+ <label class="block text-sm font-medium text-gray-700 mb-1">Scheduled Time:</label>
174
+ <input
175
+ v-model="newTask.scheduledAt"
176
+ type="datetime-local"
177
+ class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500"
178
+ >
179
+ </div>
180
+
181
+ <div>
182
+ <label class="block text-sm font-medium text-gray-700 mb-1">Parameters (JSON):</label>
183
+ <textarea
184
+ ref="parametersEditor"
185
+ class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500"
186
+ placeholder='{"key": "value"}'
187
+ ></textarea>
188
+ </div>
189
+
190
+ <div>
191
+ <label class="block text-sm font-medium text-gray-700 mb-1">Repeat Interval (ms):</label>
192
+ <input
193
+ v-model="newTask.repeatInterval"
194
+ type="number"
195
+ min="0"
196
+ step="1000"
197
+ class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-ultramarine-500"
198
+ placeholder="0 for no repetition"
199
+ >
200
+ <p class="text-xs text-gray-500 mt-1">Enter 0 or leave empty for no repetition. Use 1000 for 1 second, 60000 for 1 minute, etc.</p>
201
+ </div>
202
+
203
+ <div class="flex gap-2 pt-4">
204
+ <button
205
+ @click="createTask"
206
+ class="flex-1 bg-ultramarine-600 text-white px-4 py-2 rounded-md hover:bg-ultramarine-700"
207
+ >
208
+ Create Task
209
+ </button>
210
+ <button
211
+ @click="closeCreateTaskModal"
212
+ class="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400"
213
+ >
214
+ Cancel
215
+ </button>
216
+ </div>
217
+ </div>
218
+ </template>
219
+ </modal>
220
+ </div>
@@ -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.10",
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",