@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.
- package/backend/actions/Model/executeDocumentScript.js +61 -0
- package/backend/actions/Model/index.js +2 -0
- package/backend/actions/Model/listModels.js +36 -1
- package/backend/actions/Model/streamDocumentChanges.js +123 -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 +2 -0
- package/backend/index.js +7 -1
- package/eslint.config.js +4 -1
- package/express.js +4 -2
- package/frontend/public/app.js +14590 -13420
- package/frontend/public/tw.css +357 -4
- package/frontend/src/api.js +100 -0
- package/frontend/src/dashboard-result/dashboard-document/dashboard-document.html +4 -5
- package/frontend/src/dashboard-result/dashboard-document/dashboard-document.js +13 -14
- package/frontend/src/document/document.html +80 -0
- package/frontend/src/document/document.js +206 -19
- 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 +48 -4
- 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,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">×</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>
|