@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,182 @@
1
+ 'use strict';
2
+
3
+ const template = require('./task-details.html');
4
+ const api = require('../../api');
5
+
6
+ module.exports = app => app.component('task-details', {
7
+ props: ['taskGroup', 'currentFilter'],
8
+ data: () => ({
9
+ showRescheduleModal: false,
10
+ showRunModal: false,
11
+ showCancelModal: false,
12
+ selectedTask: null,
13
+ newScheduledTime: ''
14
+ }),
15
+ computed: {
16
+ sortedTasks() {
17
+ let tasks = this.taskGroup.tasks;
18
+
19
+ // Apply filter if one is set
20
+ if (this.currentFilter) {
21
+ tasks = tasks.filter(task => task.status === this.currentFilter);
22
+ }
23
+
24
+ return tasks.sort((a, b) => {
25
+ const dateA = new Date(a.scheduledAt || a.createdAt || 0);
26
+ const dateB = new Date(b.scheduledAt || b.createdAt || 0);
27
+ return dateB - dateA; // Most recent first
28
+ });
29
+ }
30
+ },
31
+ methods: {
32
+ getStatusColor(status) {
33
+ if (status === 'succeeded') {
34
+ return 'bg-green-100 text-green-800';
35
+ } else if (status === 'pending') {
36
+ return 'bg-yellow-100 text-yellow-800';
37
+ } else if (status === 'cancelled') {
38
+ return 'bg-gray-100 text-gray-800';
39
+ } else if (status === 'failed') {
40
+ return 'bg-red-100 text-red-800';
41
+ } else if (status === 'in_progress') {
42
+ return 'bg-blue-100 text-blue-800';
43
+ } else {
44
+ return 'bg-slate-100 text-slate-800';
45
+ }
46
+ },
47
+ formatDate(dateString) {
48
+ if (!dateString) return 'N/A';
49
+ return new Date(dateString).toLocaleString();
50
+ },
51
+ async rescheduleTask(task) {
52
+ if (!this.newScheduledTime) {
53
+ return;
54
+ }
55
+ console.log('Rescheduling task:', task.id, 'to:', this.newScheduledTime);
56
+ await api.Task.rescheduleTask({ taskId: task.id, scheduledAt: this.newScheduledTime });
57
+ },
58
+ async runTask(task) {
59
+ console.log('Running task:', task.id);
60
+ await api.Task.runTask({ taskId: task.id });
61
+ },
62
+ async cancelTask(task) {
63
+ await api.Task.cancelTask({ taskId: task.id });
64
+ // Refresh the task data by emitting an event to the parent
65
+ this.$emit('task-cancelled');
66
+ },
67
+ filterByStatus(status) {
68
+ // If clicking the same status, clear the filter
69
+ if (this.currentFilter === status) {
70
+ this.$emit('update:currentFilter', null);
71
+ } else {
72
+ this.$emit('update:currentFilter', status);
73
+ }
74
+ },
75
+ clearFilter() {
76
+ this.$emit('update:currentFilter', null);
77
+ },
78
+ showRescheduleConfirmation(task) {
79
+ this.selectedTask = task;
80
+ // Set default time to 1 hour from now
81
+ const defaultTime = new Date();
82
+ defaultTime.setHours(defaultTime.getHours() + 1);
83
+ this.newScheduledTime = defaultTime.toISOString().slice(0, 16);
84
+ this.showRescheduleModal = true;
85
+ },
86
+ showRunConfirmation(task) {
87
+ this.selectedTask = task;
88
+ this.showRunModal = true;
89
+ },
90
+ showCancelConfirmation(task) {
91
+ this.selectedTask = task;
92
+ this.showCancelModal = true;
93
+ },
94
+ async confirmRescheduleTask() {
95
+ try {
96
+ await this.rescheduleTask(this.selectedTask);
97
+
98
+ // Show success message
99
+ this.$toast.create({
100
+ title: 'Task Rescheduled Successfully!',
101
+ text: `Task ${this.selectedTask.id} has been rescheduled`,
102
+ type: 'success',
103
+ timeout: 3000,
104
+ positionClass: 'bottomRight'
105
+ });
106
+
107
+ this.showRescheduleModal = false;
108
+ this.selectedTask = null;
109
+ this.newScheduledTime = '';
110
+ } catch (error) {
111
+ console.error('Error in confirmRescheduleTask:', error);
112
+ this.$toast.create({
113
+ title: 'Failed to Reschedule Task',
114
+ text: error?.response?.data?.message || error.message || 'An unexpected error occurred',
115
+ type: 'error',
116
+ timeout: 5000,
117
+ positionClass: 'bottomRight'
118
+ });
119
+ }
120
+ },
121
+ async confirmRunTask() {
122
+ try {
123
+ await this.runTask(this.selectedTask);
124
+
125
+ // Show success message
126
+ this.$toast.create({
127
+ title: 'Task Started Successfully!',
128
+ text: `Task ${this.selectedTask.id} is now running`,
129
+ type: 'success',
130
+ timeout: 3000,
131
+ positionClass: 'bottomRight'
132
+ });
133
+
134
+ this.showRunModal = false;
135
+ this.selectedTask = null;
136
+ } catch (error) {
137
+ console.error('Error in confirmRunTask:', error);
138
+ this.$toast.create({
139
+ title: 'Failed to Run Task',
140
+ text: error?.response?.data?.message || error.message || 'An unexpected error occurred',
141
+ type: 'error',
142
+ timeout: 5000,
143
+ positionClass: 'bottomRight'
144
+ });
145
+ }
146
+ },
147
+ async confirmCancelTask() {
148
+ try {
149
+ await this.cancelTask(this.selectedTask);
150
+
151
+ // Show success message
152
+ this.$toast.create({
153
+ title: 'Task Cancelled Successfully!',
154
+ text: `Task ${this.selectedTask.id} has been cancelled`,
155
+ type: 'success',
156
+ timeout: 3000,
157
+ positionClass: 'bottomRight'
158
+ });
159
+
160
+ this.showCancelModal = false;
161
+ this.selectedTask = null;
162
+ } catch (error) {
163
+ console.error('Error in confirmCancelTask:', error);
164
+ this.$toast.create({
165
+ title: 'Failed to Cancel Task',
166
+ text: error?.response?.data?.message || error.message || 'An unexpected error occurred',
167
+ type: 'error',
168
+ timeout: 5000,
169
+ positionClass: 'bottomRight'
170
+ });
171
+ }
172
+ }
173
+
174
+ },
175
+ mounted() {
176
+ // Check if the task group was already filtered when passed from parent
177
+ if (this.taskGroup.filteredStatus && !this.currentFilter) {
178
+ this.$emit('update:currentFilter', this.taskGroup.filteredStatus);
179
+ }
180
+ },
181
+ template: template
182
+ });
File without changes
@@ -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>