@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.
- package/backend/actions/Model/executeDocumentScript.js +61 -0
- package/backend/actions/Model/index.js +1 -0
- package/backend/actions/Task/cancelTask.js +24 -0
- package/backend/actions/Task/createTask.js +33 -0
- package/backend/actions/Task/getTasks.js +62 -0
- package/backend/actions/Task/index.js +7 -0
- package/backend/actions/Task/rescheduleTask.js +39 -0
- package/backend/actions/Task/runTask.js +25 -0
- package/backend/actions/index.js +1 -0
- package/backend/authorize.js +1 -0
- package/eslint.config.js +1 -0
- package/express.js +1 -0
- package/frontend/public/app.js +14306 -13379
- package/frontend/public/tw.css +311 -4
- package/frontend/src/api.js +40 -0
- package/frontend/src/document/document.html +22 -0
- package/frontend/src/document/document.js +13 -1
- package/frontend/src/document/execute-script/execute-script.css +35 -0
- package/frontend/src/document/execute-script/execute-script.html +67 -0
- package/frontend/src/document/execute-script/execute-script.js +142 -0
- package/frontend/src/index.js +36 -1
- package/frontend/src/navbar/navbar.html +15 -2
- package/frontend/src/navbar/navbar.js +11 -0
- package/frontend/src/routes.js +13 -5
- package/frontend/src/tasks/task-details/task-details.html +284 -0
- package/frontend/src/tasks/task-details/task-details.js +182 -0
- package/frontend/src/tasks/tasks.css +0 -0
- package/frontend/src/tasks/tasks.html +220 -0
- package/frontend/src/tasks/tasks.js +372 -0
- package/package.json +4 -1
|
@@ -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">×</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.
|
|
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",
|