@mongoosejs/studio 0.2.10 → 0.2.12

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,284 @@
1
+ <div class="p-4 space-y-6">
2
+ <div class="flex items-center justify-between">
3
+ <div>
4
+ <button @click="$emit('back')" class="text-gray-500 hover:text-gray-700 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
+ Back to Task Groups
9
+ </button>
10
+ <h1 class="text-2xl font-bold text-gray-700">{{ taskGroup.name }}</h1>
11
+ <p class="text-gray-500">Total: {{ taskGroup.totalCount }} tasks</p>
12
+ </div>
13
+
14
+ </div>
15
+
16
+ <!-- Status Summary -->
17
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
18
+ <button
19
+ @click="filterByStatus('pending')"
20
+ class="bg-yellow-50 border border-yellow-200 rounded-md p-3 text-center hover:bg-yellow-100 transition-colors cursor-pointer"
21
+ :class="{ 'ring-2 ring-yellow-400': currentFilter === 'pending' }"
22
+ >
23
+ <div class="text-xs text-yellow-600 font-medium">Pending</div>
24
+ <div class="text-lg font-bold text-yellow-700">{{ taskGroup.statusCounts.pending || 0 }}</div>
25
+ </button>
26
+ <button
27
+ @click="filterByStatus('succeeded')"
28
+ class="bg-green-50 border border-green-200 rounded-md p-3 text-center hover:bg-green-100 transition-colors cursor-pointer"
29
+ :class="{ 'ring-2 ring-green-400': currentFilter === 'succeeded' }"
30
+ >
31
+ <div class="text-xs text-green-600 font-medium">Succeeded</div>
32
+ <div class="text-lg font-bold text-green-700">{{ taskGroup.statusCounts.succeeded || 0 }}</div>
33
+ </button>
34
+ <button
35
+ @click="filterByStatus('failed')"
36
+ class="bg-red-50 border border-red-200 rounded-md p-3 text-center hover:bg-red-100 transition-colors cursor-pointer"
37
+ :class="{ 'ring-2 ring-red-400': currentFilter === 'failed' }"
38
+ >
39
+ <div class="text-xs text-red-600 font-medium">Failed</div>
40
+ <div class="text-lg font-bold text-red-700">{{ taskGroup.statusCounts.failed || 0 }}</div>
41
+ </button>
42
+ <button
43
+ @click="filterByStatus('cancelled')"
44
+ class="bg-gray-50 border border-gray-200 rounded-md p-3 text-center hover:bg-gray-100 transition-colors cursor-pointer"
45
+ :class="{ 'ring-2 ring-gray-400': currentFilter === 'cancelled' }"
46
+ >
47
+ <div class="text-xs text-gray-600 font-medium">Cancelled</div>
48
+ <div class="text-lg font-bold text-gray-700">{{ taskGroup.statusCounts.cancelled || 0 }}</div>
49
+ </button>
50
+ </div>
51
+
52
+ <!-- Task List -->
53
+ <div class="bg-white rounded-lg shadow">
54
+ <div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
55
+ <h2 class="text-lg font-semibold text-gray-700">
56
+ Individual Tasks
57
+ <span v-if="currentFilter" class="text-sm font-normal text-gray-500 ml-2">
58
+ (Filtered by {{ currentFilter }})
59
+ </span>
60
+ </h2>
61
+ <button
62
+ v-if="currentFilter"
63
+ @click="clearFilter"
64
+ class="text-sm text-ultramarine-600 hover:text-ultramarine-700 font-medium"
65
+ >
66
+ Show All
67
+ </button>
68
+ </div>
69
+ <div class="divide-y divide-gray-200">
70
+ <div v-for="task in sortedTasks" :key="task.id" class="p-6">
71
+ <div class="flex items-start justify-between">
72
+ <div class="flex-1">
73
+ <div class="flex items-center gap-3 mb-2">
74
+ <span class="text-sm font-medium text-gray-900">Task ID: {{ task.id }}</span>
75
+ <span
76
+ class="text-xs px-2 py-1 rounded-full font-medium"
77
+ :class="getStatusColor(task.status)"
78
+ >
79
+ {{ task.status }}
80
+ </span>
81
+ </div>
82
+
83
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
84
+ <div>
85
+ <label class="block text-sm font-medium text-gray-700 mb-1">Scheduled At</label>
86
+ <div class="text-sm text-gray-900">{{ formatDate(task.scheduledAt) }}</div>
87
+ </div>
88
+ <div v-if="task.startedAt">
89
+ <label class="block text-sm font-medium text-gray-700 mb-1">Started At</label>
90
+ <div class="text-sm text-gray-900">{{ formatDate(task.startedAt) }}</div>
91
+ </div>
92
+ <div v-if="task.completedAt">
93
+ <label class="block text-sm font-medium text-gray-700 mb-1">Completed At</label>
94
+ <div class="text-sm text-gray-900">{{ formatDate(task.completedAt) }}</div>
95
+ </div>
96
+ <div v-if="task.error">
97
+ <label class="block text-sm font-medium text-gray-700 mb-1">Error</label>
98
+ <div class="text-sm text-red-600">{{ task.error }}</div>
99
+ </div>
100
+ </div>
101
+
102
+ <!-- Task Parameters -->
103
+ <div v-if="task.parameters && Object.keys(task.parameters).length > 0">
104
+ <label class="block text-sm font-medium text-gray-700 mb-2">Parameters</label>
105
+ <div class="bg-gray-50 rounded-md p-3">
106
+ <pre class="text-sm text-gray-800 whitespace-pre-wrap">{{ JSON.stringify(task.parameters, null, 2) }}</pre>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <div class="flex flex-col gap-3 ml-6">
112
+ <button
113
+ @click="showRescheduleConfirmation(task)"
114
+ class="flex items-center justify-center gap-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-blue-600 hover:to-blue-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
115
+ :disabled="task.status === 'in_progress'"
116
+ >
117
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
118
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
119
+ </svg>
120
+ Reschedule
121
+ </button>
122
+ <button
123
+ @click="showRunConfirmation(task)"
124
+ class="flex items-center justify-center gap-2 bg-gradient-to-r from-green-500 to-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-green-600 hover:to-green-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
125
+ :disabled="task.status === 'in_progress'"
126
+ >
127
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
128
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3l14 9-14 9V3z"></path>
129
+ </svg>
130
+ Run Now
131
+ </button>
132
+ <button
133
+ v-if="task.status === 'pending'"
134
+ @click="showCancelConfirmation(task)"
135
+ class="flex items-center justify-center gap-2 bg-gradient-to-r from-red-500 to-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:from-red-600 hover:to-red-700 transform hover:scale-105 transition-all duration-200 shadow-md hover:shadow-lg"
136
+ >
137
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
138
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
139
+ </svg>
140
+ Cancel
141
+ </button>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+
148
+ <!-- Reschedule Confirmation Modal -->
149
+ <modal v-if="showRescheduleModal" containerClass="!max-w-md">
150
+ <template #body>
151
+ <div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showRescheduleModal = false;" role="button" aria-label="Close modal">&times;</div>
152
+ <div class="p-6">
153
+ <div class="flex items-center mb-4">
154
+ <div class="flex-shrink-0">
155
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
156
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
157
+ </svg>
158
+ </div>
159
+ <div class="ml-3">
160
+ <h3 class="text-lg font-medium text-gray-900">Reschedule Task</h3>
161
+ </div>
162
+ </div>
163
+ <div class="mb-4">
164
+ <p class="text-sm text-gray-600">
165
+ Are you sure you want to reschedule task <strong>{{ selectedTask?.id }}</strong>?
166
+ </p>
167
+ <p class="text-sm text-gray-500 mt-2">
168
+ This will reset the task's status and schedule it to run again.
169
+ </p>
170
+
171
+ <div class="mt-4">
172
+ <label for="newScheduledTime" class="block text-sm font-medium text-gray-700 mb-2">
173
+ New Scheduled Time
174
+ </label>
175
+ <input
176
+ id="newScheduledTime"
177
+ v-model="newScheduledTime"
178
+ type="datetime-local"
179
+ class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
180
+ required
181
+ />
182
+ </div>
183
+ </div>
184
+ <div class="flex gap-3">
185
+ <button
186
+ @click="confirmRescheduleTask"
187
+ class="flex-1 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 font-medium"
188
+ >
189
+ Reschedule
190
+ </button>
191
+ <button
192
+ @click="showRescheduleModal = false"
193
+ class="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 font-medium"
194
+ >
195
+ Cancel
196
+ </button>
197
+ </div>
198
+ </div>
199
+ </template>
200
+ </modal>
201
+
202
+ <!-- Run Task Confirmation Modal -->
203
+ <modal v-if="showRunModal" containerClass="!max-w-md">
204
+ <template #body>
205
+ <div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showRunModal = false;" role="button" aria-label="Close modal">&times;</div>
206
+ <div class="p-6">
207
+ <div class="flex items-center mb-4">
208
+ <div class="flex-shrink-0">
209
+ <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
210
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
211
+ </svg>
212
+ </div>
213
+ <div class="ml-3">
214
+ <h3 class="text-lg font-medium text-gray-900">Run Task Now</h3>
215
+ </div>
216
+ </div>
217
+ <div class="mb-4">
218
+ <p class="text-sm text-gray-600">
219
+ Are you sure you want to run task <strong>{{ selectedTask?.id }}</strong> immediately?
220
+ </p>
221
+ <p class="text-sm text-gray-500 mt-2">
222
+ This will execute the task right away, bypassing its scheduled time.
223
+ </p>
224
+ </div>
225
+ <div class="flex gap-3">
226
+ <button
227
+ @click="confirmRunTask"
228
+ class="flex-1 bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 font-medium"
229
+ >
230
+ Run Now
231
+ </button>
232
+ <button
233
+ @click="showRunModal = false"
234
+ class="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 font-medium"
235
+ >
236
+ Cancel
237
+ </button>
238
+ </div>
239
+ </div>
240
+ </template>
241
+ </modal>
242
+
243
+ <!-- Cancel Task Confirmation Modal -->
244
+ <modal v-if="showCancelModal" containerClass="!max-w-md">
245
+ <template #body>
246
+ <div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showCancelModal = false;" role="button" aria-label="Close modal">&times;</div>
247
+ <div class="p-6">
248
+ <div class="flex items-center mb-4">
249
+ <div class="flex-shrink-0">
250
+ <svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
251
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
252
+ </svg>
253
+ </div>
254
+ <div class="ml-3">
255
+ <h3 class="text-lg font-medium text-gray-900">Cancel Task</h3>
256
+ </div>
257
+ </div>
258
+ <div class="mb-4">
259
+ <p class="text-sm text-gray-600">
260
+ Are you sure you want to cancel task <strong>{{ selectedTask?.id }}</strong>?
261
+ </p>
262
+ <p class="text-sm text-gray-500 mt-2">
263
+ This will permanently cancel the task and it cannot be undone.
264
+ </p>
265
+ </div>
266
+ <div class="flex gap-3">
267
+ <button
268
+ @click="confirmCancelTask"
269
+ class="flex-1 bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 font-medium"
270
+ >
271
+ Cancel Task
272
+ </button>
273
+ <button
274
+ @click="showCancelModal = false"
275
+ class="flex-1 bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 font-medium"
276
+ >
277
+ Keep Task
278
+ </button>
279
+ </div>
280
+ </div>
281
+ </template>
282
+ </modal>
283
+ </div>
284
+
@@ -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