@mongoosejs/studio 0.2.13 → 0.3.1
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/ChatMessage/executeScript.js +5 -1
- package/backend/actions/ChatThread/createChatMessage.js +4 -1
- package/backend/actions/ChatThread/streamChatMessage.js +4 -2
- package/backend/actions/Dashboard/updateDashboard.js +2 -2
- package/backend/actions/Task/getTaskOverview.js +102 -0
- package/backend/actions/Task/getTasks.js +85 -45
- package/backend/actions/Task/index.js +1 -0
- package/eslint.config.js +4 -1
- package/frontend/public/app.js +25025 -762
- package/frontend/public/dark-theme.css +365 -0
- package/frontend/public/images/mongoose-studio.svg +4 -0
- package/frontend/public/index.html +21 -1
- package/frontend/public/style.css +5 -7
- package/frontend/public/theme-variables.css +294 -0
- package/frontend/public/tw.css +348 -213
- package/frontend/src/_util/dateRange.js +82 -0
- package/frontend/src/ace-editor/ace-editor.html +4 -0
- package/frontend/src/ace-editor/ace-editor.js +95 -0
- package/frontend/src/aceEditor.js +69 -0
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat-message/chat-message.html +1 -1
- package/frontend/src/chat/chat-message/chat-message.js +1 -1
- package/frontend/src/chat/chat-message-script/chat-message-script.html +54 -42
- package/frontend/src/chat/chat-message-script/chat-message-script.js +6 -55
- package/frontend/src/chat/chat.html +68 -39
- package/frontend/src/chat/chat.js +26 -2
- package/frontend/src/clone-document/clone-document.html +7 -2
- package/frontend/src/clone-document/clone-document.js +1 -8
- package/frontend/src/create-dashboard/create-dashboard.html +11 -6
- package/frontend/src/create-dashboard/create-dashboard.js +0 -7
- package/frontend/src/create-document/create-document.html +15 -9
- package/frontend/src/create-document/create-document.js +5 -12
- package/frontend/src/dashboard/dashboard.html +14 -12
- package/frontend/src/dashboard/dashboard.js +21 -4
- package/frontend/src/dashboard/edit-dashboard/edit-dashboard.html +13 -7
- package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +16 -23
- package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html +19 -17
- package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js +97 -2
- package/frontend/src/dashboard-result/dashboard-map/dashboard-map.js +27 -3
- package/frontend/src/dashboard-result/dashboard-result.html +3 -3
- package/frontend/src/dashboard-result/dashboard-result.js +3 -0
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.html +34 -0
- package/frontend/src/dashboard-result/dashboard-table/dashboard-table.js +37 -0
- package/frontend/src/dashboards/dashboards.html +101 -109
- package/frontend/src/dashboards/dashboards.js +25 -1
- package/frontend/src/detail-default/detail-default.html +2 -2
- package/frontend/src/detail-default/detail-default.js +24 -3
- package/frontend/src/document/confirm-changes/confirm-changes.html +1 -1
- package/frontend/src/document/confirm-delete/confirm-delete.html +1 -1
- package/frontend/src/document/document.css +1 -1
- package/frontend/src/document/document.html +28 -28
- package/frontend/src/document/execute-script/execute-script.html +20 -21
- package/frontend/src/document/execute-script/execute-script.js +1 -43
- package/frontend/src/document-details/document-details.css +4 -9
- package/frontend/src/document-details/document-details.html +34 -33
- package/frontend/src/document-details/document-details.js +2 -53
- package/frontend/src/document-details/document-property/document-property.html +12 -12
- package/frontend/src/edit-array/edit-array.html +7 -6
- package/frontend/src/edit-array/edit-array.js +10 -50
- package/frontend/src/edit-boolean/edit-boolean.html +12 -12
- package/frontend/src/edit-date/edit-date.html +2 -2
- package/frontend/src/edit-default/edit-default.html +1 -1
- package/frontend/src/edit-string/edit-string.html +3 -3
- package/frontend/src/edit-subdocument/edit-subdocument.html +5 -3
- package/frontend/src/edit-subdocument/edit-subdocument.js +1 -15
- package/frontend/src/export-query-results/export-query-results.html +3 -3
- package/frontend/src/json-node/json-node.html +3 -3
- package/frontend/src/list-json/json-node.html +1 -1
- package/frontend/src/models/document-search/document-search.html +3 -3
- package/frontend/src/models/model-switcher/model-switcher.html +53 -0
- package/frontend/src/models/model-switcher/model-switcher.js +123 -0
- package/frontend/src/models/models.css +3 -10
- package/frontend/src/models/models.html +146 -80
- package/frontend/src/models/models.js +116 -7
- package/frontend/src/navbar/navbar.html +157 -97
- package/frontend/src/navbar/navbar.js +31 -12
- package/frontend/src/routes.js +1 -1
- package/frontend/src/splash/splash.html +5 -5
- package/frontend/src/task-by-name/task-by-name.html +77 -7
- package/frontend/src/task-by-name/task-by-name.js +84 -9
- package/frontend/src/task-single/task-single.html +29 -29
- package/frontend/src/task-single/task-single.js +10 -10
- package/frontend/src/tasks/task-details/task-details.html +43 -43
- package/frontend/src/tasks/task-details/task-details.js +9 -3
- package/frontend/src/tasks/tasks.html +36 -35
- package/frontend/src/tasks/tasks.js +27 -143
- package/frontend/src/team/new-invitation/new-invitation.html +8 -8
- package/frontend/src/team/team.html +27 -27
- package/frontend/src/update-document/update-document.html +7 -2
- package/frontend/src/update-document/update-document.js +2 -11
- package/package.json +3 -1
- package/tailwind.config.js +75 -11
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Page: all tasks with a given name. Reuses task-details to render the list (many tasks).
|
|
4
4
|
const template = require('./task-by-name.html');
|
|
5
5
|
const api = require('../api');
|
|
6
|
+
const { DATE_FILTERS, DATE_FILTER_VALUES, getDateRangeForRange } = require('../_util/dateRange');
|
|
6
7
|
|
|
7
8
|
function buildTaskGroup(name, tasks) {
|
|
8
9
|
const statusCounts = { pending: 0, succeeded: 0, failed: 0, cancelled: 0, in_progress: 0, unknown: 0 };
|
|
@@ -26,44 +27,118 @@ function buildTaskGroup(name, tasks) {
|
|
|
26
27
|
};
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
const PAGE_SIZE_OPTIONS = [25, 50, 100, 200];
|
|
31
|
+
|
|
29
32
|
module.exports = app => app.component('task-by-name', {
|
|
30
33
|
template,
|
|
31
34
|
data: () => ({
|
|
32
35
|
status: 'init',
|
|
33
36
|
taskGroup: null,
|
|
34
|
-
errorMessage: ''
|
|
37
|
+
errorMessage: '',
|
|
38
|
+
selectedRange: 'last_hour',
|
|
39
|
+
start: null,
|
|
40
|
+
end: null,
|
|
41
|
+
dateFilters: DATE_FILTERS,
|
|
42
|
+
page: 1,
|
|
43
|
+
pageSize: 50,
|
|
44
|
+
numDocs: 0,
|
|
45
|
+
pageSizeOptions: PAGE_SIZE_OPTIONS,
|
|
46
|
+
_loadId: 0,
|
|
47
|
+
_lastQueryFilters: null
|
|
35
48
|
}),
|
|
36
49
|
computed: {
|
|
37
50
|
taskName() {
|
|
38
51
|
return this.$route.params.name || '';
|
|
39
52
|
}
|
|
40
53
|
},
|
|
54
|
+
created() {
|
|
55
|
+
const fromQuery = this.$route.query.dateRange;
|
|
56
|
+
this.selectedRange = (fromQuery && DATE_FILTER_VALUES.includes(fromQuery)) ? fromQuery : 'last_hour';
|
|
57
|
+
if (!this.$route.query.dateRange) {
|
|
58
|
+
this.$router.replace({
|
|
59
|
+
path: this.$route.path,
|
|
60
|
+
query: { ...this.$route.query, dateRange: this.selectedRange }
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
41
64
|
watch: {
|
|
42
65
|
taskName: {
|
|
43
66
|
immediate: true,
|
|
44
67
|
handler() {
|
|
68
|
+
this.page = 1;
|
|
45
69
|
this.loadTasks();
|
|
46
70
|
}
|
|
71
|
+
},
|
|
72
|
+
'$route.query': {
|
|
73
|
+
handler(query) {
|
|
74
|
+
const dateRange = query.dateRange;
|
|
75
|
+
if (dateRange && DATE_FILTER_VALUES.includes(dateRange) && this.selectedRange !== dateRange) {
|
|
76
|
+
this.selectedRange = dateRange;
|
|
77
|
+
}
|
|
78
|
+
const effectiveDateRange = (dateRange && DATE_FILTER_VALUES.includes(dateRange)) ? dateRange : (this.selectedRange || 'last_hour');
|
|
79
|
+
const effectiveStatus = query.status ?? '';
|
|
80
|
+
const key = `${effectiveDateRange}|${effectiveStatus}`;
|
|
81
|
+
if (this._lastQueryFilters === key) return;
|
|
82
|
+
this.page = 1;
|
|
83
|
+
this.loadTasks();
|
|
84
|
+
},
|
|
85
|
+
deep: true
|
|
47
86
|
}
|
|
48
87
|
},
|
|
49
88
|
methods: {
|
|
89
|
+
updateDateRange() {
|
|
90
|
+
this.page = 1;
|
|
91
|
+
this.$router.replace({
|
|
92
|
+
path: this.$route.path,
|
|
93
|
+
query: { ...this.$route.query, dateRange: this.selectedRange }
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
goToPage(page) {
|
|
97
|
+
const maxPage = Math.max(1, Math.ceil(this.numDocs / this.pageSize));
|
|
98
|
+
const next = Math.max(1, Math.min(page, maxPage));
|
|
99
|
+
if (next === this.page) return;
|
|
100
|
+
this.page = next;
|
|
101
|
+
this.loadTasks();
|
|
102
|
+
},
|
|
103
|
+
onPageSizeChange() {
|
|
104
|
+
this.page = 1;
|
|
105
|
+
this.loadTasks();
|
|
106
|
+
},
|
|
50
107
|
async loadTasks() {
|
|
51
108
|
if (!this.taskName) return;
|
|
109
|
+
const loadId = ++this._loadId;
|
|
52
110
|
this.status = 'init';
|
|
53
111
|
this.taskGroup = null;
|
|
54
112
|
this.errorMessage = '';
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
113
|
+
const dateRangeFromQuery = this.$route.query.dateRange;
|
|
114
|
+
const dateRange = (dateRangeFromQuery && DATE_FILTER_VALUES.includes(dateRangeFromQuery))
|
|
115
|
+
? dateRangeFromQuery
|
|
116
|
+
: (this.selectedRange || 'last_hour');
|
|
117
|
+
this.selectedRange = dateRange;
|
|
118
|
+
const { start, end } = getDateRangeForRange(dateRange);
|
|
119
|
+
this.start = start;
|
|
120
|
+
this.end = end;
|
|
121
|
+
const skip = (this.page - 1) * this.pageSize;
|
|
122
|
+
const params = {
|
|
123
|
+
name: this.taskName,
|
|
124
|
+
start: start instanceof Date ? start.toISOString() : start,
|
|
125
|
+
end: end instanceof Date ? end.toISOString() : end,
|
|
126
|
+
skip,
|
|
127
|
+
limit: this.pageSize
|
|
128
|
+
};
|
|
129
|
+
const statusFromQuery = this.$route.query.status;
|
|
130
|
+
if (statusFromQuery) params.status = statusFromQuery;
|
|
131
|
+
this._lastQueryFilters = `${dateRange}|${statusFromQuery ?? ''}`;
|
|
58
132
|
try {
|
|
59
|
-
const { tasks } = await api.Task.getTasks(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
end
|
|
63
|
-
});
|
|
133
|
+
const { tasks, numDocs, statusCounts } = await api.Task.getTasks(params);
|
|
134
|
+
if (loadId !== this._loadId) return;
|
|
135
|
+
this.numDocs = numDocs ?? tasks.length;
|
|
64
136
|
this.taskGroup = buildTaskGroup(this.taskName, tasks);
|
|
137
|
+
this.taskGroup.totalCount = this.numDocs;
|
|
138
|
+
if (statusCounts) this.taskGroup.statusCounts = statusCounts;
|
|
65
139
|
this.status = 'loaded';
|
|
66
140
|
} catch (err) {
|
|
141
|
+
if (loadId !== this._loadId) return;
|
|
67
142
|
this.status = 'error';
|
|
68
143
|
this.errorMessage = err?.response?.data?.message || err.message || 'Failed to load tasks';
|
|
69
144
|
}
|
|
@@ -9,18 +9,18 @@
|
|
|
9
9
|
Task not found.
|
|
10
10
|
</div>
|
|
11
11
|
<div v-else-if="task" class="max-w-4xl">
|
|
12
|
-
<button @click="goBack" class="text-
|
|
12
|
+
<button @click="goBack" class="text-content-tertiary hover:text-content-secondary mb-4 flex items-center gap-1">
|
|
13
13
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
14
14
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
15
15
|
</svg>
|
|
16
16
|
Back to {{ task.name }}
|
|
17
17
|
</button>
|
|
18
|
-
<h1 class="text-2xl font-bold text-
|
|
19
|
-
<p class="text-
|
|
18
|
+
<h1 class="text-2xl font-bold text-content-secondary mb-1">{{ task.name }}</h1>
|
|
19
|
+
<p class="text-content-tertiary mb-6">Task details</p>
|
|
20
20
|
|
|
21
|
-
<div class="bg-
|
|
21
|
+
<div class="bg-surface rounded-lg shadow p-6 md:p-8">
|
|
22
22
|
<div class="flex items-center gap-3 mb-6">
|
|
23
|
-
<span class="text-sm font-medium text-
|
|
23
|
+
<span class="text-sm font-medium text-content">ID: {{ task.id }}</span>
|
|
24
24
|
<span
|
|
25
25
|
class="text-xs px-2 py-1 rounded-full font-medium"
|
|
26
26
|
:class="getStatusColor(task.status)"
|
|
@@ -31,41 +31,41 @@
|
|
|
31
31
|
|
|
32
32
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
33
33
|
<div>
|
|
34
|
-
<label class="block text-sm font-medium text-
|
|
35
|
-
<div class="text-sm text-
|
|
34
|
+
<label class="block text-sm font-medium text-content-secondary mb-1">Scheduled At</label>
|
|
35
|
+
<div class="text-sm text-content">{{ formatDate(task.scheduledAt) }}</div>
|
|
36
36
|
</div>
|
|
37
37
|
<div v-if="task?.startedAt">
|
|
38
|
-
<label class="block text-sm font-medium text-
|
|
39
|
-
<div class="text-sm text-
|
|
38
|
+
<label class="block text-sm font-medium text-content-secondary mb-1">Started At</label>
|
|
39
|
+
<div class="text-sm text-content">{{ formatDate(task.startedAt) }}</div>
|
|
40
40
|
</div>
|
|
41
41
|
<div v-if="task?.completedAt">
|
|
42
|
-
<label class="block text-sm font-medium text-
|
|
43
|
-
<div class="text-sm text-
|
|
42
|
+
<label class="block text-sm font-medium text-content-secondary mb-1">Completed At</label>
|
|
43
|
+
<div class="text-sm text-content">{{ formatDate(task.completedAt) }}</div>
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
<div v-if="task?.params" class="mb-6">
|
|
48
|
-
<label class="block text-sm font-medium text-
|
|
49
|
-
<div class="bg-
|
|
48
|
+
<label class="block text-sm font-medium text-content-secondary mb-2">Params</label>
|
|
49
|
+
<div class="bg-page rounded-md p-4">
|
|
50
50
|
<list-json :value="task.params"></list-json>
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
53
53
|
|
|
54
54
|
<div v-if="task?.result" class="mb-6">
|
|
55
|
-
<label class="block text-sm font-medium text-
|
|
56
|
-
<div class="bg-
|
|
55
|
+
<label class="block text-sm font-medium text-content-secondary mb-2">Result</label>
|
|
56
|
+
<div class="bg-page rounded-md p-4">
|
|
57
57
|
<list-json :value="task.result"></list-json>
|
|
58
58
|
</div>
|
|
59
59
|
</div>
|
|
60
60
|
|
|
61
61
|
<div v-if="task?.error" class="mb-6">
|
|
62
|
-
<label class="block text-sm font-medium text-
|
|
63
|
-
<div class="bg-
|
|
62
|
+
<label class="block text-sm font-medium text-content-secondary mb-2">Error</label>
|
|
63
|
+
<div class="bg-page rounded-md p-4">
|
|
64
64
|
<list-json :value="task.error"></list-json>
|
|
65
65
|
</div>
|
|
66
66
|
</div>
|
|
67
67
|
|
|
68
|
-
<div class="flex flex-wrap gap-3 pt-4 border-t border-
|
|
68
|
+
<div class="flex flex-wrap gap-3 pt-4 border-t border-edge">
|
|
69
69
|
<button
|
|
70
70
|
@click="showRescheduleConfirmation(task)"
|
|
71
71
|
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 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
@@ -105,19 +105,19 @@
|
|
|
105
105
|
<template #body>
|
|
106
106
|
<div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showRescheduleModal = false" role="button" aria-label="Close modal">×</div>
|
|
107
107
|
<div class="p-6">
|
|
108
|
-
<h3 class="text-lg font-medium text-
|
|
108
|
+
<h3 class="text-lg font-medium text-content mb-4">Reschedule Task</h3>
|
|
109
109
|
<p class="text-sm text-gray-600 mb-2">Reschedule task <strong>{{ selectedTask?.id }}</strong>?</p>
|
|
110
|
-
<p class="text-sm text-
|
|
111
|
-
<label for="newScheduledTime" class="block text-sm font-medium text-
|
|
110
|
+
<p class="text-sm text-content-tertiary mb-4">This will reset the task's status and schedule it to run again.</p>
|
|
111
|
+
<label for="newScheduledTime" class="block text-sm font-medium text-content-secondary mb-2">New Scheduled Time</label>
|
|
112
112
|
<input
|
|
113
113
|
id="newScheduledTime"
|
|
114
114
|
v-model="newScheduledTime"
|
|
115
115
|
type="datetime-local"
|
|
116
|
-
class="w-full px-3 py-2 border border-
|
|
116
|
+
class="w-full px-3 py-2 border border-edge-strong rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-4"
|
|
117
117
|
/>
|
|
118
118
|
<div class="flex gap-3">
|
|
119
119
|
<button @click="confirmRescheduleTask" class="flex-1 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 font-medium">Reschedule</button>
|
|
120
|
-
<button @click="showRescheduleModal = false" class="flex-1 bg-gray-300 text-
|
|
120
|
+
<button @click="showRescheduleModal = false" class="flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium">Cancel</button>
|
|
121
121
|
</div>
|
|
122
122
|
</div>
|
|
123
123
|
</template>
|
|
@@ -128,12 +128,12 @@
|
|
|
128
128
|
<template #body>
|
|
129
129
|
<div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showRunModal = false" role="button" aria-label="Close modal">×</div>
|
|
130
130
|
<div class="p-6">
|
|
131
|
-
<h3 class="text-lg font-medium text-
|
|
131
|
+
<h3 class="text-lg font-medium text-content mb-4">Run Task Now</h3>
|
|
132
132
|
<p class="text-sm text-gray-600 mb-2">Run task <strong>{{ selectedTask?.id }}</strong> immediately?</p>
|
|
133
|
-
<p class="text-sm text-
|
|
133
|
+
<p class="text-sm text-content-tertiary mb-4">This will execute the task right away, bypassing its scheduled time.</p>
|
|
134
134
|
<div class="flex gap-3">
|
|
135
135
|
<button @click="confirmRunTask" class="flex-1 bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 font-medium">Run Now</button>
|
|
136
|
-
<button @click="showRunModal = false" class="flex-1 bg-gray-300 text-
|
|
136
|
+
<button @click="showRunModal = false" class="flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium">Cancel</button>
|
|
137
137
|
</div>
|
|
138
138
|
</div>
|
|
139
139
|
</template>
|
|
@@ -144,12 +144,12 @@
|
|
|
144
144
|
<template #body>
|
|
145
145
|
<div class="absolute font-mono right-1 top-1 cursor-pointer text-xl" @click="showCancelModal = false" role="button" aria-label="Close modal">×</div>
|
|
146
146
|
<div class="p-6">
|
|
147
|
-
<h3 class="text-lg font-medium text-
|
|
147
|
+
<h3 class="text-lg font-medium text-content mb-4">Cancel Task</h3>
|
|
148
148
|
<p class="text-sm text-gray-600 mb-2">Cancel task <strong>{{ selectedTask?.id }}</strong>?</p>
|
|
149
|
-
<p class="text-sm text-
|
|
149
|
+
<p class="text-sm text-content-tertiary mb-4">This will permanently cancel the task and it cannot be undone.</p>
|
|
150
150
|
<div class="flex gap-3">
|
|
151
151
|
<button @click="confirmCancelTask" class="flex-1 bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 font-medium">Cancel Task</button>
|
|
152
|
-
<button @click="showCancelModal = false" class="flex-1 bg-gray-300 text-
|
|
152
|
+
<button @click="showCancelModal = false" class="flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium">Keep Task</button>
|
|
153
153
|
</div>
|
|
154
154
|
</div>
|
|
155
155
|
</template>
|
|
@@ -25,7 +25,7 @@ module.exports = app => app.component('task-single', {
|
|
|
25
25
|
const path = `/tasks/${encodeURIComponent(name || '')}`;
|
|
26
26
|
const query = this.$route.query?.status ? { status: this.$route.query.status } : {};
|
|
27
27
|
return { path, query };
|
|
28
|
-
}
|
|
28
|
+
}
|
|
29
29
|
},
|
|
30
30
|
watch: {
|
|
31
31
|
'$route.params': {
|
|
@@ -82,11 +82,11 @@ module.exports = app => app.component('task-single', {
|
|
|
82
82
|
async confirmRescheduleTask() {
|
|
83
83
|
if (!this.newScheduledTime) return;
|
|
84
84
|
await api.Task.rescheduleTask({ taskId: this.selectedTask.id, scheduledAt: this.newScheduledTime });
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
this.$toast.success({ title: 'Task Rescheduled', text: `Task ${this.selectedTask.id} has been rescheduled` });
|
|
86
|
+
this.showRescheduleModal = false;
|
|
87
|
+
this.selectedTask = null;
|
|
88
|
+
this.newScheduledTime = '';
|
|
89
|
+
await this.loadTask();
|
|
90
90
|
},
|
|
91
91
|
async confirmRunTask() {
|
|
92
92
|
await api.Task.runTask({ taskId: this.selectedTask.id });
|
|
@@ -104,10 +104,10 @@ module.exports = app => app.component('task-single', {
|
|
|
104
104
|
},
|
|
105
105
|
async confirmCancelTask() {
|
|
106
106
|
await api.Task.cancelTask({ taskId: this.selectedTask.id });
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
this.$toast.success({ title: 'Task Cancelled', text: `Task ${this.selectedTask.id} has been cancelled` });
|
|
108
|
+
this.showCancelModal = false;
|
|
109
|
+
this.selectedTask = null;
|
|
110
|
+
this.goBack();
|
|
111
111
|
}
|
|
112
112
|
},
|
|
113
113
|
mounted() {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<div class="p-4 space-y-6">
|
|
2
2
|
<div class="flex items-center justify-between">
|
|
3
3
|
<div>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
<button v-if="showBackButton" @click="goBack" class="text-content-tertiary hover:text-content-secondary 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
|
+
{{ backLabel }}
|
|
9
|
+
</button>
|
|
10
|
+
<h1 class="text-2xl font-bold text-content-secondary">{{ taskGroup.name }}</h1>
|
|
11
|
+
<p class="text-content-tertiary">Total: {{ taskGroup.totalCount }} tasks</p>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
</div>
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
<!-- Status Summary -->
|
|
17
17
|
<div class="space-y-3">
|
|
18
18
|
<div class="flex items-center justify-between">
|
|
19
|
-
<span class="text-sm font-medium text-
|
|
19
|
+
<span class="text-sm font-medium text-content-secondary">Status</span>
|
|
20
20
|
<div class="flex rounded-md shadow-sm" role="group">
|
|
21
21
|
<button
|
|
22
22
|
type="button"
|
|
23
23
|
@click="statusView = 'summary'"
|
|
24
24
|
class="px-3 py-1.5 text-sm font-medium rounded-l-md border transition-colors"
|
|
25
|
-
:class="statusView === 'summary' ? 'bg-
|
|
25
|
+
:class="statusView === 'summary' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'"
|
|
26
26
|
>
|
|
27
27
|
Summary
|
|
28
28
|
</button>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
type="button"
|
|
31
31
|
@click="statusView = 'chart'"
|
|
32
32
|
class="px-3 py-1.5 text-sm font-medium rounded-r-md border border-l-0 transition-colors"
|
|
33
|
-
:class="statusView === 'chart' ? 'bg-
|
|
33
|
+
:class="statusView === 'chart' ? 'bg-primary text-primary-text border-primary' : 'bg-surface text-content-secondary border-edge-strong hover:bg-page'"
|
|
34
34
|
>
|
|
35
35
|
Chart
|
|
36
36
|
</button>
|
|
@@ -64,19 +64,19 @@
|
|
|
64
64
|
</button>
|
|
65
65
|
<button
|
|
66
66
|
@click="filterByStatus('cancelled')"
|
|
67
|
-
class="bg-
|
|
67
|
+
class="bg-page border border-edge rounded-md p-3 text-center hover:bg-muted transition-colors cursor-pointer"
|
|
68
68
|
:class="{ 'ring-2 ring-gray-400': currentFilter === 'cancelled' }"
|
|
69
69
|
>
|
|
70
70
|
<div class="text-xs text-gray-600 font-medium">Cancelled</div>
|
|
71
|
-
<div class="text-lg font-bold text-
|
|
71
|
+
<div class="text-lg font-bold text-content-secondary">{{ taskGroup.statusCounts.cancelled || 0 }}</div>
|
|
72
72
|
</button>
|
|
73
73
|
</div>
|
|
74
74
|
<!-- Chart view -->
|
|
75
|
-
<div v-show="statusView === 'chart'" class="flex flex-col items-center justify-center bg-
|
|
75
|
+
<div v-show="statusView === 'chart'" class="flex flex-col items-center justify-center bg-surface border border-edge rounded-lg p-4 gap-3" style="min-height: 280px;">
|
|
76
76
|
<div v-if="taskGroup.totalCount > 0" class="w-[240px] h-[240px] shrink-0">
|
|
77
77
|
<canvas ref="statusPieChart" width="240" height="240" class="block"></canvas>
|
|
78
78
|
</div>
|
|
79
|
-
<p v-else class="text-
|
|
79
|
+
<p v-else class="text-content-tertiary text-sm py-8">No tasks to display</p>
|
|
80
80
|
<!-- Selection labels: show which segment is selected (click to filter) -->
|
|
81
81
|
<div v-if="taskGroup.totalCount > 0" class="flex flex-wrap justify-center gap-2">
|
|
82
82
|
<button
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
:key="status"
|
|
85
85
|
type="button"
|
|
86
86
|
class="text-xs px-2 py-1 rounded-full font-medium transition-all cursor-pointer"
|
|
87
|
-
:class="currentFilter === status ? getStatusPillClass(status) : 'bg-
|
|
87
|
+
:class="currentFilter === status ? getStatusPillClass(status) : 'bg-muted text-content-tertiary hover:bg-muted'"
|
|
88
88
|
@click="filterByStatus(status)"
|
|
89
89
|
>
|
|
90
90
|
{{ statusLabel(status) }}
|
|
@@ -94,18 +94,18 @@
|
|
|
94
94
|
</div>
|
|
95
95
|
|
|
96
96
|
<!-- Task List -->
|
|
97
|
-
<div class="bg-
|
|
98
|
-
<div class="px-6 py-6 border-b border-
|
|
99
|
-
<h2 class="text-xl font-bold text-
|
|
97
|
+
<div class="bg-surface rounded-lg shadow">
|
|
98
|
+
<div class="px-6 py-6 border-b border-edge flex items-center justify-between bg-page">
|
|
99
|
+
<h2 class="text-xl font-bold text-content">
|
|
100
100
|
Individual Tasks
|
|
101
|
-
<span v-if="currentFilter" class="ml-3 text-base font-semibold text-
|
|
101
|
+
<span v-if="currentFilter" class="ml-3 text-base font-semibold text-primary">
|
|
102
102
|
(Filtered by {{ currentFilter }})
|
|
103
103
|
</span>
|
|
104
104
|
</h2>
|
|
105
105
|
<button
|
|
106
106
|
v-if="currentFilter"
|
|
107
107
|
@click="clearFilter"
|
|
108
|
-
class="text-sm font-semibold text-
|
|
108
|
+
class="text-sm font-semibold text-primary hover:text-primary"
|
|
109
109
|
>
|
|
110
110
|
Show All
|
|
111
111
|
</button>
|
|
@@ -115,11 +115,11 @@
|
|
|
115
115
|
<div class="flex items-start justify-between">
|
|
116
116
|
<div class="flex-1">
|
|
117
117
|
<div class="flex items-center gap-3 mb-2">
|
|
118
|
-
<span class="text-sm font-medium text-
|
|
118
|
+
<span class="text-sm font-medium text-content">Task ID: {{ task.id }}</span>
|
|
119
119
|
<router-link
|
|
120
120
|
v-if="backTo"
|
|
121
121
|
:to="taskDetailRoute(task)"
|
|
122
|
-
class="text-sm text-
|
|
122
|
+
class="text-sm text-primary hover:text-primary font-medium"
|
|
123
123
|
>
|
|
124
124
|
View details
|
|
125
125
|
</router-link>
|
|
@@ -133,27 +133,27 @@
|
|
|
133
133
|
|
|
134
134
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
135
135
|
<div>
|
|
136
|
-
<label class="block text-sm font-medium text-
|
|
137
|
-
<div class="text-sm text-
|
|
136
|
+
<label class="block text-sm font-medium text-content-secondary mb-1">Scheduled At</label>
|
|
137
|
+
<div class="text-sm text-content">{{ formatDate(task.scheduledAt) }}</div>
|
|
138
138
|
</div>
|
|
139
139
|
<div v-if="task.startedAt">
|
|
140
|
-
<label class="block text-sm font-medium text-
|
|
141
|
-
<div class="text-sm text-
|
|
140
|
+
<label class="block text-sm font-medium text-content-secondary mb-1">Started At</label>
|
|
141
|
+
<div class="text-sm text-content">{{ formatDate(task.startedAt) }}</div>
|
|
142
142
|
</div>
|
|
143
143
|
<div v-if="task.completedAt">
|
|
144
|
-
<label class="block text-sm font-medium text-
|
|
145
|
-
<div class="text-sm text-
|
|
144
|
+
<label class="block text-sm font-medium text-content-secondary mb-1">Completed At</label>
|
|
145
|
+
<div class="text-sm text-content">{{ formatDate(task.completedAt) }}</div>
|
|
146
146
|
</div>
|
|
147
147
|
<div v-if="task.error">
|
|
148
|
-
<label class="block text-sm font-medium text-
|
|
148
|
+
<label class="block text-sm font-medium text-content-secondary mb-1">Error</label>
|
|
149
149
|
<div class="text-sm text-red-600">{{ task.error }}</div>
|
|
150
150
|
</div>
|
|
151
151
|
</div>
|
|
152
152
|
|
|
153
153
|
<!-- Task Parameters -->
|
|
154
154
|
<div v-if="task.parameters && Object.keys(task.parameters).length > 0">
|
|
155
|
-
<label class="block text-sm font-medium text-
|
|
156
|
-
<div class="bg-
|
|
155
|
+
<label class="block text-sm font-medium text-content-secondary mb-2">Parameters</label>
|
|
156
|
+
<div class="bg-page rounded-md p-3">
|
|
157
157
|
<pre class="text-sm text-gray-800 whitespace-pre-wrap">{{ JSON.stringify(task.parameters, null, 2) }}</pre>
|
|
158
158
|
</div>
|
|
159
159
|
</div>
|
|
@@ -208,26 +208,26 @@
|
|
|
208
208
|
</svg>
|
|
209
209
|
</div>
|
|
210
210
|
<div class="ml-3">
|
|
211
|
-
<h3 class="text-lg font-medium text-
|
|
211
|
+
<h3 class="text-lg font-medium text-content">Reschedule Task</h3>
|
|
212
212
|
</div>
|
|
213
213
|
</div>
|
|
214
214
|
<div class="mb-4">
|
|
215
215
|
<p class="text-sm text-gray-600">
|
|
216
216
|
Are you sure you want to reschedule task <strong>{{ selectedTask?.id }}</strong>?
|
|
217
217
|
</p>
|
|
218
|
-
<p class="text-sm text-
|
|
218
|
+
<p class="text-sm text-content-tertiary mt-2">
|
|
219
219
|
This will reset the task's status and schedule it to run again.
|
|
220
220
|
</p>
|
|
221
221
|
|
|
222
222
|
<div class="mt-4">
|
|
223
|
-
<label for="newScheduledTime" class="block text-sm font-medium text-
|
|
223
|
+
<label for="newScheduledTime" class="block text-sm font-medium text-content-secondary mb-2">
|
|
224
224
|
New Scheduled Time
|
|
225
225
|
</label>
|
|
226
226
|
<input
|
|
227
227
|
id="newScheduledTime"
|
|
228
228
|
v-model="newScheduledTime"
|
|
229
229
|
type="datetime-local"
|
|
230
|
-
class="w-full px-3 py-2 border border-
|
|
230
|
+
class="w-full px-3 py-2 border border-edge-strong rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
|
231
231
|
required
|
|
232
232
|
/>
|
|
233
233
|
</div>
|
|
@@ -241,7 +241,7 @@
|
|
|
241
241
|
</button>
|
|
242
242
|
<button
|
|
243
243
|
@click="showRescheduleModal = false"
|
|
244
|
-
class="flex-1 bg-gray-300 text-
|
|
244
|
+
class="flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium"
|
|
245
245
|
>
|
|
246
246
|
Cancel
|
|
247
247
|
</button>
|
|
@@ -262,14 +262,14 @@
|
|
|
262
262
|
</svg>
|
|
263
263
|
</div>
|
|
264
264
|
<div class="ml-3">
|
|
265
|
-
<h3 class="text-lg font-medium text-
|
|
265
|
+
<h3 class="text-lg font-medium text-content">Run Task Now</h3>
|
|
266
266
|
</div>
|
|
267
267
|
</div>
|
|
268
268
|
<div class="mb-4">
|
|
269
269
|
<p class="text-sm text-gray-600">
|
|
270
270
|
Are you sure you want to run task <strong>{{ selectedTask?.id }}</strong> immediately?
|
|
271
271
|
</p>
|
|
272
|
-
<p class="text-sm text-
|
|
272
|
+
<p class="text-sm text-content-tertiary mt-2">
|
|
273
273
|
This will execute the task right away, bypassing its scheduled time.
|
|
274
274
|
</p>
|
|
275
275
|
</div>
|
|
@@ -282,7 +282,7 @@
|
|
|
282
282
|
</button>
|
|
283
283
|
<button
|
|
284
284
|
@click="showRunModal = false"
|
|
285
|
-
class="flex-1 bg-gray-300 text-
|
|
285
|
+
class="flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium"
|
|
286
286
|
>
|
|
287
287
|
Cancel
|
|
288
288
|
</button>
|
|
@@ -303,14 +303,14 @@
|
|
|
303
303
|
</svg>
|
|
304
304
|
</div>
|
|
305
305
|
<div class="ml-3">
|
|
306
|
-
<h3 class="text-lg font-medium text-
|
|
306
|
+
<h3 class="text-lg font-medium text-content">Cancel Task</h3>
|
|
307
307
|
</div>
|
|
308
308
|
</div>
|
|
309
309
|
<div class="mb-4">
|
|
310
310
|
<p class="text-sm text-gray-600">
|
|
311
311
|
Are you sure you want to cancel task <strong>{{ selectedTask?.id }}</strong>?
|
|
312
312
|
</p>
|
|
313
|
-
<p class="text-sm text-
|
|
313
|
+
<p class="text-sm text-content-tertiary mt-2">
|
|
314
314
|
This will permanently cancel the task and it cannot be undone.
|
|
315
315
|
</p>
|
|
316
316
|
</div>
|
|
@@ -323,7 +323,7 @@
|
|
|
323
323
|
</button>
|
|
324
324
|
<button
|
|
325
325
|
@click="showCancelModal = false"
|
|
326
|
-
class="flex-1 bg-gray-300 text-
|
|
326
|
+
class="flex-1 bg-gray-300 text-content-secondary px-4 py-2 rounded-md hover:bg-gray-400 font-medium"
|
|
327
327
|
>
|
|
328
328
|
Keep Task
|
|
329
329
|
</button>
|
|
@@ -10,7 +10,8 @@ const PIE_HOVER = ['#ca8a04', '#16a34a', '#dc2626', '#4b5563'];
|
|
|
10
10
|
module.exports = app => app.component('task-details', {
|
|
11
11
|
props: {
|
|
12
12
|
taskGroup: { type: Object, required: true },
|
|
13
|
-
backTo: { type: Object, default: null }
|
|
13
|
+
backTo: { type: Object, default: null },
|
|
14
|
+
showBackButton: { type: Boolean, default: true }
|
|
14
15
|
},
|
|
15
16
|
data: () => ({
|
|
16
17
|
currentFilter: null,
|
|
@@ -80,7 +81,7 @@ module.exports = app => app.component('task-details', {
|
|
|
80
81
|
requestAnimationFrame(() => this.ensureStatusChart());
|
|
81
82
|
});
|
|
82
83
|
}
|
|
83
|
-
}
|
|
84
|
+
}
|
|
84
85
|
},
|
|
85
86
|
methods: {
|
|
86
87
|
destroyStatusChart() {
|
|
@@ -117,6 +118,10 @@ module.exports = app => app.component('task-details', {
|
|
|
117
118
|
return;
|
|
118
119
|
}
|
|
119
120
|
try {
|
|
121
|
+
const isDark = typeof document !== 'undefined' && document.documentElement.classList.contains('dark');
|
|
122
|
+
const legendColor = isDark
|
|
123
|
+
? (getComputedStyle(document.documentElement).getPropertyValue('--studio-text-primary')?.trim() || 'rgba(255,255,255,0.9)')
|
|
124
|
+
: undefined;
|
|
120
125
|
this.statusChart = new Chart(canvas, {
|
|
121
126
|
type: 'doughnut',
|
|
122
127
|
data,
|
|
@@ -136,7 +141,8 @@ module.exports = app => app.component('task-details', {
|
|
|
136
141
|
plugins: {
|
|
137
142
|
legend: {
|
|
138
143
|
display: true,
|
|
139
|
-
position: 'bottom'
|
|
144
|
+
position: 'bottom',
|
|
145
|
+
...(legendColor && { labels: { color: legendColor } })
|
|
140
146
|
}
|
|
141
147
|
}
|
|
142
148
|
}
|