-
Create New Task
-
-
-
-
-
+
+
+
+
+ Task ID: {{ task.id }}
+
+ {{ task.status }}
+
-
-
-
+
+
+
+
{{ formatDate(task.scheduledAt) }}
+
+
+
+
{{ formatDate(task.startedAt) }}
+
+
+
+
{{ formatDate(task.completedAt) }}
+
+
+
+
{{ task.error }}
+
-
-
-
-
+
+
+
+
+
+
{{ JSON.stringify(task.parameters, null, 2) }}
+
-
-
-
+
+
+
+
+
-
+
+
+
+
+ ×
+
+
Create New Task
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/tasks/task-details/task-details.js b/frontend/src/tasks/task-details/task-details.js
index cfdd09c..1242c68 100644
--- a/frontend/src/tasks/task-details/task-details.js
+++ b/frontend/src/tasks/task-details/task-details.js
@@ -4,14 +4,9 @@ const template = require('./task-details.html');
const api = require('../../api');
module.exports = app => app.component('task-details', {
- props: {
- taskGroup: {
- type: Object,
- required: true
- }
- },
+ props: ['taskGroup'],
data: () => ({
- showCreateTask: false,
+ showCreateTaskModal: false,
newTask: {
name: '',
scheduledAt: '',
@@ -102,7 +97,7 @@ module.exports = app => app.component('task-details', {
scheduledAt: '',
parameters: ''
};
- this.showCreateTask = false;
+ this.showCreateTaskModal = false;
// Emit event to refresh parent data
this.$emit('task-created');
diff --git a/frontend/src/tasks/tasks.html b/frontend/src/tasks/tasks.html
index bc994f3..5771f2d 100644
--- a/frontend/src/tasks/tasks.html
+++ b/frontend/src/tasks/tasks.html
@@ -65,9 +65,17 @@
Tasks by Name
-
-
-
{{ group.name }}
-
Total: {{ group.totalCount }} tasks
+
+
+
{{ group.name }}
+
+
+
Total: {{ group.totalCount }} tasks
+
+ Click to view details
+
Last run: {{ group.lastRun ? new Date(group.lastRun).toLocaleString() : 'Never' }}
diff --git a/frontend/src/tasks/tasks.js b/frontend/src/tasks/tasks.js
index 419126d..ba0b60b 100644
--- a/frontend/src/tasks/tasks.js
+++ b/frontend/src/tasks/tasks.js
@@ -66,6 +66,74 @@ module.exports = app => app.component('tasks', {
// Refresh the task data when a new task is created
await this.getTasks();
},
+ formatDate(dateString) {
+ if (!dateString) return 'N/A';
+ return new Date(dateString).toLocaleString();
+ },
+ async rescheduleTask(task) {
+ try {
+ // This would call a backend API to reschedule the task
+ // For now, we'll show a confirmation dialog
+ if (confirm(`Reschedule task ${task.id}?`)) {
+ // TODO: Implement reschedule API call
+ console.log('Rescheduling task:', task.id);
+ // await api.Task.rescheduleTask(task.id);
+ }
+ } catch (error) {
+ console.error('Error rescheduling task:', error);
+ alert('Failed to reschedule task');
+ }
+ },
+ async runTask(task) {
+ try {
+ // This would call a backend API to run the task immediately
+ if (confirm(`Run task ${task.id} now?`)) {
+ // TODO: Implement run task API call
+ console.log('Running task:', task.id);
+ // await api.Task.runTask(task.id);
+ }
+ } catch (error) {
+ console.error('Error running task:', error);
+ alert('Failed to run task');
+ }
+ },
+ async createTask() {
+ try {
+ let parameters = {};
+ if (this.newTask.parameters.trim()) {
+ try {
+ parameters = JSON.parse(this.newTask.parameters);
+ } catch (e) {
+ alert('Invalid JSON in parameters field');
+ return;
+ }
+ }
+
+ const taskData = {
+ name: this.newTask.name || this.selectedTaskGroup.name,
+ scheduledAt: this.newTask.scheduledAt,
+ parameters: parameters
+ };
+
+ // TODO: Implement create task API call
+ console.log('Creating task:', taskData);
+ // await api.Task.createTask(taskData);
+
+ // Reset form and close modal
+ this.newTask = {
+ name: '',
+ scheduledAt: '',
+ parameters: ''
+ };
+ this.showCreateTask = false;
+
+ // Refresh the task data
+ await this.getTasks();
+ } catch (error) {
+ console.error('Error creating task:', error);
+ alert('Failed to create task');
+ }
+ },
getStatusColor(status) {
if (status === 'succeeded') {
// Green (success)
From 386b5a80f0006f8b50ff00579a31643167ce9427 Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Fri, 29 Aug 2025 15:29:18 -0400
Subject: [PATCH 12/25] cleanup
---
.../src/tasks/task-details/task-details.js | 28 ++++++++-----------
frontend/src/tasks/tasks.js | 28 ++++++++-----------
package.json | 1 +
3 files changed, 23 insertions(+), 34 deletions(-)
diff --git a/frontend/src/tasks/task-details/task-details.js b/frontend/src/tasks/task-details/task-details.js
index 1242c68..21289c9 100644
--- a/frontend/src/tasks/task-details/task-details.js
+++ b/frontend/src/tasks/task-details/task-details.js
@@ -44,29 +44,22 @@ module.exports = app => app.component('task-details', {
},
async rescheduleTask(task) {
try {
- // This would call a backend API to reschedule the task
- // For now, we'll show a confirmation dialog
- if (confirm(`Reschedule task ${task.id}?`)) {
- // TODO: Implement reschedule API call
- console.log('Rescheduling task:', task.id);
- // await api.Task.rescheduleTask(task.id);
- }
+ // TODO: Implement reschedule API call
+ console.log('Rescheduling task:', task.id);
+ // await api.Task.rescheduleTask(task.id);
} catch (error) {
console.error('Error rescheduling task:', error);
- alert('Failed to reschedule task');
+ // TODO: Add proper error handling/notification
}
},
async runTask(task) {
try {
- // This would call a backend API to run the task immediately
- if (confirm(`Run task ${task.id} now?`)) {
- // TODO: Implement run task API call
- console.log('Running task:', task.id);
- // await api.Task.runTask(task.id);
- }
+ // TODO: Implement run task API call
+ console.log('Running task:', task.id);
+ // await api.Task.runTask(task.id);
} catch (error) {
console.error('Error running task:', error);
- alert('Failed to run task');
+ // TODO: Add proper error handling/notification
}
},
async createTask() {
@@ -76,7 +69,8 @@ module.exports = app => app.component('task-details', {
try {
parameters = JSON.parse(this.newTask.parameters);
} catch (e) {
- alert('Invalid JSON in parameters field');
+ console.error('Invalid JSON in parameters field:', e);
+ // TODO: Add proper validation feedback
return;
}
}
@@ -103,7 +97,7 @@ module.exports = app => app.component('task-details', {
this.$emit('task-created');
} catch (error) {
console.error('Error creating task:', error);
- alert('Failed to create task');
+ // TODO: Add proper error handling/notification
}
}
},
diff --git a/frontend/src/tasks/tasks.js b/frontend/src/tasks/tasks.js
index ba0b60b..fce8e05 100644
--- a/frontend/src/tasks/tasks.js
+++ b/frontend/src/tasks/tasks.js
@@ -72,29 +72,22 @@ module.exports = app => app.component('tasks', {
},
async rescheduleTask(task) {
try {
- // This would call a backend API to reschedule the task
- // For now, we'll show a confirmation dialog
- if (confirm(`Reschedule task ${task.id}?`)) {
- // TODO: Implement reschedule API call
- console.log('Rescheduling task:', task.id);
- // await api.Task.rescheduleTask(task.id);
- }
+ // TODO: Implement reschedule API call
+ console.log('Rescheduling task:', task.id);
+ // await api.Task.rescheduleTask(task.id);
} catch (error) {
console.error('Error rescheduling task:', error);
- alert('Failed to reschedule task');
+ // TODO: Add proper error handling/notification
}
},
async runTask(task) {
try {
- // This would call a backend API to run the task immediately
- if (confirm(`Run task ${task.id} now?`)) {
- // TODO: Implement run task API call
- console.log('Running task:', task.id);
- // await api.Task.runTask(task.id);
- }
+ // TODO: Implement run task API call
+ console.log('Running task:', task.id);
+ // await api.Task.runTask(task.id);
} catch (error) {
console.error('Error running task:', error);
- alert('Failed to run task');
+ // TODO: Add proper error handling/notification
}
},
async createTask() {
@@ -104,7 +97,8 @@ module.exports = app => app.component('tasks', {
try {
parameters = JSON.parse(this.newTask.parameters);
} catch (e) {
- alert('Invalid JSON in parameters field');
+ console.error('Invalid JSON in parameters field:', e);
+ // TODO: Add proper validation feedback
return;
}
}
@@ -131,7 +125,7 @@ module.exports = app => app.component('tasks', {
await this.getTasks();
} catch (error) {
console.error('Error creating task:', error);
- alert('Failed to create task');
+ // TODO: Add proper error handling/notification
}
},
getStatusColor(status) {
diff --git a/package.json b/package.json
index 0cc1fb1..e5fda64 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
},
"devDependencies": {
"@masteringjs/eslint-config": "0.1.1",
+ "@mongoosejs/task": "^0.3.0",
"axios": "1.2.2",
"dedent": "^1.6.0",
"eslint": "9.30.0",
From 6e104bb7839f473d92993ee02eec8fa8b3fde8d9 Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Fri, 29 Aug 2025 17:58:02 -0400
Subject: [PATCH 13/25] UI polishing
---
express.js | 8 +-
.../src/tasks/task-details/task-details.html | 234 +++++++++++-------
.../src/tasks/task-details/task-details.js | 107 ++++----
frontend/src/tasks/tasks.html | 142 +++++++++--
frontend/src/tasks/tasks.js | 65 ++++-
5 files changed, 369 insertions(+), 187 deletions(-)
diff --git a/express.js b/express.js
index 721a951..fbd1539 100644
--- a/express.js
+++ b/express.js
@@ -79,13 +79,7 @@ module.exports = async function(apiUrl, conn, options) {
console.log('Workspace', workspace);
const { config } = await frontend(apiUrl, false, options, workspace);
-
- try {
- require('@mongoosejs/task')
- config.enableTaskVisualizer = true;
- } catch(e) {
- config.enableTaskVisualizer = false;
- }
+ config.enableTaskVisualizer = options.enableTaskVisualizer;
router.get('/config.js', function (req, res) {
res.setHeader('Content-Type', 'application/javascript');
res.end(`window.MONGOOSE_STUDIO_CONFIG = ${JSON.stringify(config, null, 2)};`);
diff --git a/frontend/src/tasks/task-details/task-details.html b/frontend/src/tasks/task-details/task-details.html
index 77eabfe..411bd86 100644
--- a/frontend/src/tasks/task-details/task-details.html
+++ b/frontend/src/tasks/task-details/task-details.html
@@ -10,38 +10,62 @@
{{ taskGroup.name }}
Total: {{ taskGroup.totalCount }} tasks
-
-
-
-
-
-
-
-
Pending
-
{{ taskGroup.statusCounts.pending || 0 }}
-
-
-
Succeeded
-
{{ taskGroup.statusCounts.succeeded || 0 }}
-
-
-
Failed
-
{{ taskGroup.statusCounts.failed || 0 }}
-
-
-
Cancelled
-
{{ taskGroup.statusCounts.cancelled || 0 }}
-
-
-
-
-
Individual Tasks
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Individual Tasks
+
+ (Filtered by {{ currentFilter }})
+
+
+
+
@@ -84,19 +108,25 @@
Individual Tasks
-
+
@@ -105,56 +135,86 @@
Individual Tasks
-
-
-
- ×
-
-
Create New Task
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ ×
+
+
+
+
+
Reschedule Task
+
+
+
+
+ Are you sure you want to reschedule task {{ selectedTask?.id }}?
+
+
+ This will reset the task's status and schedule it to run again.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+ Are you sure you want to run task {{ selectedTask?.id }} immediately?
+
+
+ This will execute the task right away, bypassing its scheduled time.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/tasks/task-details/task-details.js b/frontend/src/tasks/task-details/task-details.js
index 21289c9..740e0f1 100644
--- a/frontend/src/tasks/task-details/task-details.js
+++ b/frontend/src/tasks/task-details/task-details.js
@@ -4,18 +4,22 @@ const template = require('./task-details.html');
const api = require('../../api');
module.exports = app => app.component('task-details', {
- props: ['taskGroup'],
+ props: ['taskGroup', 'currentFilter'],
data: () => ({
- showCreateTaskModal: false,
- newTask: {
- name: '',
- scheduledAt: '',
- parameters: ''
- }
+ showRescheduleModal: false,
+ showRunModal: false,
+ selectedTask: null
}),
computed: {
sortedTasks() {
- return [...this.taskGroup.tasks].sort((a, b) => {
+ let tasks = this.taskGroup.tasks;
+
+ // Apply filter if one is set
+ if (this.currentFilter) {
+ tasks = tasks.filter(task => task.status === this.currentFilter);
+ }
+
+ return tasks.sort((a, b) => {
const dateA = new Date(a.scheduledAt || a.createdAt || 0);
const dateB = new Date(b.scheduledAt || b.createdAt || 0);
return dateB - dateA; // Most recent first
@@ -62,53 +66,50 @@ module.exports = app => app.component('task-details', {
// TODO: Add proper error handling/notification
}
},
- async createTask() {
- try {
- let parameters = {};
- if (this.newTask.parameters.trim()) {
- try {
- parameters = JSON.parse(this.newTask.parameters);
- } catch (e) {
- console.error('Invalid JSON in parameters field:', e);
- // TODO: Add proper validation feedback
- return;
- }
- }
-
- const taskData = {
- name: this.newTask.name || this.taskGroup.name,
- scheduledAt: this.newTask.scheduledAt,
- parameters: parameters
- };
-
- // TODO: Implement create task API call
- console.log('Creating task:', taskData);
- // await api.Task.createTask(taskData);
-
- // Reset form and close modal
- this.newTask = {
- name: '',
- scheduledAt: '',
- parameters: ''
- };
- this.showCreateTaskModal = false;
-
- // Emit event to refresh parent data
- this.$emit('task-created');
- } catch (error) {
- console.error('Error creating task:', error);
- // TODO: Add proper error handling/notification
+ filterByStatus(status) {
+ // If clicking the same status, clear the filter
+ if (this.currentFilter === status) {
+ this.$emit('update:currentFilter', null);
+ } else {
+ this.$emit('update:currentFilter', status);
}
- }
- },
+ },
+ clearFilter() {
+ this.$emit('update:currentFilter', null);
+ },
+ showRescheduleConfirmation(task) {
+ this.selectedTask = task;
+ this.showRescheduleModal = true;
+ },
+ showRunConfirmation(task) {
+ this.selectedTask = task;
+ this.showRunModal = true;
+ },
+ async confirmRescheduleTask() {
+ try {
+ await this.rescheduleTask(this.selectedTask);
+ this.showRescheduleModal = false;
+ this.selectedTask = null;
+ } catch (error) {
+ console.error('Error in confirmRescheduleTask:', error);
+ }
+ },
+ async confirmRunTask() {
+ try {
+ await this.runTask(this.selectedTask);
+ this.showRunModal = false;
+ this.selectedTask = null;
+ } catch (error) {
+ console.error('Error in confirmRunTask:', error);
+ }
+ }
+
+ },
mounted() {
- // Set default values
- this.newTask.name = this.taskGroup.name;
-
- // Set default scheduled time to 1 hour from now
- const defaultTime = new Date();
- defaultTime.setHours(defaultTime.getHours() + 1);
- this.newTask.scheduledAt = defaultTime.toISOString().slice(0, 16);
+ // Check if the task group was already filtered when passed from parent
+ if (this.taskGroup.filteredStatus && !this.currentFilter) {
+ this.$emit('update:currentFilter', this.taskGroup.filteredStatus);
+ }
},
template: template
});
diff --git a/frontend/src/tasks/tasks.html b/frontend/src/tasks/tasks.html
index 5771f2d..f382f6d 100644
--- a/frontend/src/tasks/tasks.html
+++ b/frontend/src/tasks/tasks.html
@@ -1,11 +1,13 @@
-
-
+
+
@@ -31,41 +33,61 @@
Task Overview
-
+
+
+
+
-
+
-
+
+
-
+
+
-
+
+
+
Tasks by Name
- -
-
-
+
-
+
+
{{ group.name }}
+
+
+
+
+ ×
+
+
Create New Task
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/tasks/tasks.js b/frontend/src/tasks/tasks.js
index fce8e05..0280754 100644
--- a/frontend/src/tasks/tasks.js
+++ b/frontend/src/tasks/tasks.js
@@ -25,15 +25,22 @@ module.exports = app => app.component('tasks', {
statusFilters: [
{ label: 'All', value: 'all' },
{ label: 'Pending', value: 'pending' },
- { label: 'In Progress', value: 'in_progress' },
+ // { label: 'In Progress', value: 'in_progress' },
{ label: 'Succeeded', value: 'succeeded' },
{ label: 'Failed', value: 'failed' },
{ label: 'Cancelled', value: 'cancelled' }
],
- newTask: { status: 'pending' },
// Task details view state
showTaskDetails: false,
- selectedTaskGroup: null
+ selectedTaskGroup: null,
+ taskDetailsFilter: null,
+ // Create task modal state
+ showCreateTaskModal: false,
+ newTask: {
+ name: '',
+ scheduledAt: '',
+ parameters: ''
+ }
}),
methods: {
async getTasks() {
@@ -58,9 +65,21 @@ module.exports = app => app.component('tasks', {
this.selectedTaskGroup = group;
this.showTaskDetails = true;
},
+ openTaskGroupDetailsWithFilter(group, status) {
+ // Create a filtered version of the task group with only the specified status
+ const filteredGroup = {
+ ...group,
+ tasks: group.tasks.filter(task => task.status === status),
+ filteredStatus: status
+ };
+ this.selectedTaskGroup = filteredGroup;
+ this.taskDetailsFilter = status;
+ this.showTaskDetails = true;
+ },
hideTaskDetails() {
this.showTaskDetails = false;
this.selectedTaskGroup = null;
+ this.taskDetailsFilter = null;
},
async onTaskCreated() {
// Refresh the task data when a new task is created
@@ -104,7 +123,7 @@ module.exports = app => app.component('tasks', {
}
const taskData = {
- name: this.newTask.name || this.selectedTaskGroup.name,
+ name: this.newTask.name,
scheduledAt: this.newTask.scheduledAt,
parameters: parameters
};
@@ -114,12 +133,8 @@ module.exports = app => app.component('tasks', {
// await api.Task.createTask(taskData);
// Reset form and close modal
- this.newTask = {
- name: '',
- scheduledAt: '',
- parameters: ''
- };
- this.showCreateTask = false;
+ this.resetCreateTaskForm();
+ this.showCreateTaskModal = false;
// Refresh the task data
await this.getTasks();
@@ -128,6 +143,19 @@ module.exports = app => app.component('tasks', {
// TODO: Add proper error handling/notification
}
},
+ resetCreateTaskForm() {
+ this.newTask = {
+ name: '',
+ scheduledAt: '',
+ parameters: ''
+ };
+ },
+ setDefaultCreateTaskValues() {
+ // Set default scheduled time to 1 hour from now
+ const defaultTime = new Date();
+ defaultTime.setHours(defaultTime.getHours() + 1);
+ this.newTask.scheduledAt = defaultTime.toISOString().slice(0, 16);
+ },
getStatusColor(status) {
if (status === 'succeeded') {
// Green (success)
@@ -151,9 +179,11 @@ module.exports = app => app.component('tasks', {
},
async resetFilters() {
this.selectedStatus = 'all';
- this.selectedRange = 'all';
- this.start = null;
- this.end = null;
+ this.selectedRange = 'today';
+ await this.updateDateRange();
+ },
+ async setStatusFilter(status) {
+ this.selectedStatus = status;
await this.getTasks();
},
async updateDateRange() {
@@ -288,6 +318,15 @@ module.exports = app => app.component('tasks', {
await this.updateDateRange();
await this.getTasks();
this.status = 'loaded';
+ this.setDefaultCreateTaskValues();
+ },
+ watch: {
+ showCreateTaskModal(newVal) {
+ if (newVal) {
+ this.resetCreateTaskForm();
+ this.setDefaultCreateTaskValues();
+ }
+ }
},
template: template
});
\ No newline at end of file
From 4cd32ea0c1cf8a2e942027154ddef450d1792bf8 Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Fri, 29 Aug 2025 17:59:00 -0400
Subject: [PATCH 14/25] missed a spot
---
frontend/src/tasks/task-details/task-details.html | 2 +-
frontend/src/tasks/tasks.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/tasks/task-details/task-details.html b/frontend/src/tasks/task-details/task-details.html
index 411bd86..1336ce8 100644
--- a/frontend/src/tasks/task-details/task-details.html
+++ b/frontend/src/tasks/task-details/task-details.html
@@ -13,7 +13,7 @@
{{ taskGroup.name }}
-
+
diff --git a/frontend/src/tasks/tasks.js b/frontend/src/tasks/tasks.js
index 9812fca..b63a53b 100644
--- a/frontend/src/tasks/tasks.js
+++ b/frontend/src/tasks/tasks.js
@@ -112,9 +112,8 @@ module.exports = app => app.component('tasks', {
console.log('Creating task:', taskData);
// await api.Task.createTask(taskData);
- // Reset form and close modal
- this.resetCreateTaskForm();
- this.showCreateTaskModal = false;
+ // Close modal (which will reset form)
+ this.closeCreateTaskModal();
// Refresh the task data
await this.getTasks();
@@ -130,12 +129,17 @@ module.exports = app => app.component('tasks', {
parameters: ''
};
},
- setDefaultCreateTaskValues() {
- // Set default scheduled time to 1 hour from now
- const defaultTime = new Date();
- defaultTime.setHours(defaultTime.getHours() + 1);
- this.newTask.scheduledAt = defaultTime.toISOString().slice(0, 16);
- },
+ setDefaultCreateTaskValues() {
+ // Set default scheduled time to 1 hour from now
+ const defaultTime = new Date();
+ defaultTime.setHours(defaultTime.getHours() + 1);
+ this.newTask.scheduledAt = defaultTime.toISOString().slice(0, 16);
+ },
+ closeCreateTaskModal() {
+ this.showCreateTaskModal = false;
+ this.resetCreateTaskForm();
+ this.setDefaultCreateTaskValues();
+ },
getStatusColor(status) {
if (status === 'succeeded') {
// Green (success)
@@ -300,13 +304,6 @@ module.exports = app => app.component('tasks', {
this.status = 'loaded';
this.setDefaultCreateTaskValues();
},
- watch: {
- showCreateTaskModal(newVal) {
- if (newVal) {
- this.resetCreateTaskForm();
- this.setDefaultCreateTaskValues();
- }
- }
- },
+
template: template
});
\ No newline at end of file
From a4fd90fb6cc8cdcc49295b8faffdcf8bd43a3e63 Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Fri, 29 Aug 2025 18:30:14 -0400
Subject: [PATCH 17/25] fix: lint
---
.../src/tasks/task-details/task-details.js | 60 +++++++++----------
frontend/src/tasks/tasks.js | 26 ++++----
2 files changed, 43 insertions(+), 43 deletions(-)
diff --git a/frontend/src/tasks/task-details/task-details.js b/frontend/src/tasks/task-details/task-details.js
index 740e0f1..15348eb 100644
--- a/frontend/src/tasks/task-details/task-details.js
+++ b/frontend/src/tasks/task-details/task-details.js
@@ -74,37 +74,37 @@ module.exports = app => app.component('task-details', {
this.$emit('update:currentFilter', status);
}
},
- clearFilter() {
- this.$emit('update:currentFilter', null);
- },
- showRescheduleConfirmation(task) {
- this.selectedTask = task;
- this.showRescheduleModal = true;
- },
- showRunConfirmation(task) {
- this.selectedTask = task;
- this.showRunModal = true;
- },
- async confirmRescheduleTask() {
- try {
- await this.rescheduleTask(this.selectedTask);
- this.showRescheduleModal = false;
- this.selectedTask = null;
- } catch (error) {
- console.error('Error in confirmRescheduleTask:', error);
- }
- },
- async confirmRunTask() {
- try {
- await this.runTask(this.selectedTask);
- this.showRunModal = false;
- this.selectedTask = null;
- } catch (error) {
- console.error('Error in confirmRunTask:', error);
- }
- }
+ clearFilter() {
+ this.$emit('update:currentFilter', null);
+ },
+ showRescheduleConfirmation(task) {
+ this.selectedTask = task;
+ this.showRescheduleModal = true;
+ },
+ showRunConfirmation(task) {
+ this.selectedTask = task;
+ this.showRunModal = true;
+ },
+ async confirmRescheduleTask() {
+ try {
+ await this.rescheduleTask(this.selectedTask);
+ this.showRescheduleModal = false;
+ this.selectedTask = null;
+ } catch (error) {
+ console.error('Error in confirmRescheduleTask:', error);
+ }
+ },
+ async confirmRunTask() {
+ try {
+ await this.runTask(this.selectedTask);
+ this.showRunModal = false;
+ this.selectedTask = null;
+ } catch (error) {
+ console.error('Error in confirmRunTask:', error);
+ }
+ }
- },
+ },
mounted() {
// Check if the task group was already filtered when passed from parent
if (this.taskGroup.filteredStatus && !this.currentFilter) {
diff --git a/frontend/src/tasks/tasks.js b/frontend/src/tasks/tasks.js
index b63a53b..c6016dc 100644
--- a/frontend/src/tasks/tasks.js
+++ b/frontend/src/tasks/tasks.js
@@ -112,8 +112,8 @@ module.exports = app => app.component('tasks', {
console.log('Creating task:', taskData);
// await api.Task.createTask(taskData);
- // Close modal (which will reset form)
- this.closeCreateTaskModal();
+ // Close modal (which will reset form)
+ this.closeCreateTaskModal();
// Refresh the task data
await this.getTasks();
@@ -129,17 +129,17 @@ module.exports = app => app.component('tasks', {
parameters: ''
};
},
- setDefaultCreateTaskValues() {
- // Set default scheduled time to 1 hour from now
- const defaultTime = new Date();
- defaultTime.setHours(defaultTime.getHours() + 1);
- this.newTask.scheduledAt = defaultTime.toISOString().slice(0, 16);
- },
- closeCreateTaskModal() {
- this.showCreateTaskModal = false;
- this.resetCreateTaskForm();
- this.setDefaultCreateTaskValues();
- },
+ setDefaultCreateTaskValues() {
+ // Set default scheduled time to 1 hour from now
+ const defaultTime = new Date();
+ defaultTime.setHours(defaultTime.getHours() + 1);
+ this.newTask.scheduledAt = defaultTime.toISOString().slice(0, 16);
+ },
+ closeCreateTaskModal() {
+ this.showCreateTaskModal = false;
+ this.resetCreateTaskForm();
+ this.setDefaultCreateTaskValues();
+ },
getStatusColor(status) {
if (status === 'succeeded') {
// Green (success)
From 29c9bdfb6ed8349a56df7a7b722cdcdf2d5796cc Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Tue, 2 Sep 2025 15:00:30 -0400
Subject: [PATCH 18/25] complete task visualizer
---
backend/actions/Task/cancelTask.js | 24 ++++++
backend/actions/Task/createTask.js | 33 ++++++++
backend/actions/Task/getTasks.js | 10 ++-
backend/actions/Task/index.js | 6 +-
backend/actions/Task/rescheduleTask.js | 39 +++++++++
backend/actions/Task/runTask.js | 25 ++++++
frontend/src/api.js | 24 ++++++
.../src/tasks/task-details/task-details.html | 66 ++++++++++++++-
.../src/tasks/task-details/task-details.js | 40 ++++++++-
frontend/src/tasks/tasks.html | 24 ++++++
frontend/src/tasks/tasks.js | 82 +++++++++++--------
11 files changed, 331 insertions(+), 42 deletions(-)
create mode 100644 backend/actions/Task/cancelTask.js
create mode 100644 backend/actions/Task/createTask.js
create mode 100644 backend/actions/Task/rescheduleTask.js
create mode 100644 backend/actions/Task/runTask.js
diff --git a/backend/actions/Task/cancelTask.js b/backend/actions/Task/cancelTask.js
new file mode 100644
index 0000000..9e62343
--- /dev/null
+++ b/backend/actions/Task/cancelTask.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const Archetype = require('archetype');
+const mongoose = require('mongoose');
+
+const CancelTaskParams = new Archetype({
+ taskId: {
+ $type: mongoose.Types.ObjectId,
+ $required: true
+ }
+}).compile('CancelTaskParams');
+
+module.exports = ({ db }) => async function cancelTask(params) {
+ params = new CancelTaskParams(params);
+ const { taskId } = params;
+ const { Task } = db.models;
+
+ const task = await Task.findOne({ _id: taskId }).orFail();
+
+ const cancelledTask = await Task.cancelTask({ _id: taskId });
+ return {
+ task: cancelledTask
+ };
+};
\ No newline at end of file
diff --git a/backend/actions/Task/createTask.js b/backend/actions/Task/createTask.js
new file mode 100644
index 0000000..3639175
--- /dev/null
+++ b/backend/actions/Task/createTask.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const Archetype = require('archetype');
+
+const CreateTaskParams = new Archetype({
+ name: {
+ $type: 'string',
+ $required: true
+ },
+ scheduledAt: {
+ $type: Date,
+ $required: true
+ },
+ repeatAfterMS: {
+ $type: 'number'
+ },
+ payload: {
+ $type: Archetype.Any
+ }
+}).compile('CreateTaskParams');
+
+module.exports = ({ db }) => async function createTask(params) {
+ params = new CreateTaskParams(params);
+
+ const { name, scheduledAt, payload, repeatAfterMS } = params;
+ const { Task } = db.models;
+
+ const task = await Task.schedule(name, scheduledAt, payload, repeatAfterMS);
+
+ return {
+ task
+ };
+};
\ No newline at end of file
diff --git a/backend/actions/Task/getTasks.js b/backend/actions/Task/getTasks.js
index db385c6..23c1a2f 100644
--- a/backend/actions/Task/getTasks.js
+++ b/backend/actions/Task/getTasks.js
@@ -11,24 +11,30 @@ const GetTasksParams = new Archetype({
},
status: {
$type: 'string'
+ },
+ name: {
+ $type: 'string'
}
}).compile('GetTasksParams');
module.exports = ({ db }) => async function getTasks(params) {
params = new GetTasksParams(params);
- const { start, end, status } = params;
+ const { start, end, status, name } = params;
const { Task } = db.models;
const filter = {};
if (start && end) {
- filter.scheduledAt = { $gte: start, $lte: end };
+ filter.scheduledAt = { $gte: start, $lt: end };
} else if (start) {
filter.scheduledAt = { $gte: start };
}
if (status) {
filter.status = status;
}
+ if (name) {
+ filter.name = { $regex: name, $options: 'i' };
+ }
const tasks = await Task.find(filter);
diff --git a/backend/actions/Task/index.js b/backend/actions/Task/index.js
index 8adb3a7..4ed726e 100644
--- a/backend/actions/Task/index.js
+++ b/backend/actions/Task/index.js
@@ -1,3 +1,7 @@
'use strict';
-exports.getTasks = require('./getTasks');
\ No newline at end of file
+exports.cancelTask = require('./cancelTask');
+exports.createTask = require('./createTask');
+exports.getTasks = require('./getTasks');
+exports.rescheduleTask = require('./rescheduleTask');
+exports.runTask = require('./runTask');
\ No newline at end of file
diff --git a/backend/actions/Task/rescheduleTask.js b/backend/actions/Task/rescheduleTask.js
new file mode 100644
index 0000000..2acce1f
--- /dev/null
+++ b/backend/actions/Task/rescheduleTask.js
@@ -0,0 +1,39 @@
+'use strict';
+
+const Archetype = require('archetype');
+const mongoose = require('mongoose');
+
+const RescheduleTaskParams = new Archetype({
+ taskId: {
+ $type: mongoose.Types.ObjectId,
+ $required: true
+ },
+ scheduledAt: {
+ $type: Date,
+ $required: true
+ }
+}).compile('RescheduleTaskParams');
+
+module.exports = ({ db }) => async function rescheduleTask(params) {
+ params = new RescheduleTaskParams(params);
+ const { taskId, scheduledAt } = params;
+ const { Task } = db.models;
+
+ const task = await Task.findOne({ _id: taskId }).orFail();
+
+ if (scheduledAt < Date.now()) {
+ throw new Error ('Cannot reschedule a task for the past')
+ }
+
+ if (task.status != 'pending') {
+ throw new Error('Cannot reschedule a task that is not pending')
+ }
+
+ task.scheduledAt = scheduledAt;
+
+ await task.save();
+
+ return {
+ task
+ };
+};
\ No newline at end of file
diff --git a/backend/actions/Task/runTask.js b/backend/actions/Task/runTask.js
new file mode 100644
index 0000000..9c10150
--- /dev/null
+++ b/backend/actions/Task/runTask.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const Archetype = require('archetype');
+const mongoose = require('mongoose');
+
+const RunTaskParams = new Archetype({
+ taskId: {
+ $type: mongoose.Types.ObjectId,
+ $required: true
+ }
+}).compile('RunTaskParams');
+
+module.exports = ({ db }) => async function runTask(params) {
+ params = new RunTaskParams(params);
+ const { taskId } = params;
+ const { Task } = db.models;
+
+ const task = await Task.findOne({ _id: taskId }).orFail();
+
+ const executedTask = await Task.execute(task);
+
+ return {
+ task: executedTask
+ };
+};
\ No newline at end of file
diff --git a/frontend/src/api.js b/frontend/src/api.js
index 62eb137..ab238da 100644
--- a/frontend/src/api.js
+++ b/frontend/src/api.js
@@ -131,8 +131,20 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
}
};
exports.Task = {
+ cancelTask: function cancelTask(params) {
+ return client.post('', { action: 'Task.cancelTask', ...params }).then(res => res.data);
+ },
+ createTask: function createTask(params) {
+ return client.post('', { action: 'Task.createTask', ...params }).then(res => res.data);
+ },
getTasks: function getTasks(params) {
return client.post('', { action: 'Task.getTasks', ...params }).then(res => res.data);
+ },
+ rescheduleTask: function rescheduleTask(params) {
+ return client.post('', { action: 'Task.rescheduleTask', ...params }).then(res => res.data);
+ },
+ runTask: function runTask(params) {
+ return client.post('', { action: 'Task.runTask', ...params }).then(res => res.data);
}
};
} else {
@@ -241,8 +253,20 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
}
};
exports.Task = {
+ cancelTask: function cancelTask(params) {
+ return client.post('/Task/cancelTask', params).then(res => res.data);
+ },
+ createTask: function createTask(params) {
+ return client.post('/Task/createTask', params).then(res => res.data);
+ },
getTasks: function getTasks(params) {
return client.post('/Task/getTasks', params).then(res => res.data);
+ },
+ rescheduleTask: function rescheduleTask(params) {
+ return client.post('/Task/rescheduleTask', params).then(res => res.data);
+ },
+ runTask: function runTask(params) {
+ return client.post('/Task/runTask', params).then(res => res.data);
}
};
}
diff --git a/frontend/src/tasks/task-details/task-details.html b/frontend/src/tasks/task-details/task-details.html
index 1336ce8..e9c1370 100644
--- a/frontend/src/tasks/task-details/task-details.html
+++ b/frontend/src/tasks/task-details/task-details.html
@@ -125,10 +125,20 @@
:disabled="task.status === 'in_progress'"
>
Run Now
+
+
+ Cancel
+
@@ -157,6 +167,19 @@
Reschedule Task
This will reset the task's status and schedule it to run again.
+
+
+
+
+
Run Task Now
+
+
+
+
+ ×
+
+
+
+
+ Are you sure you want to cancel task {{ selectedTask?.id }}?
+
+
+ This will permanently cancel the task and it cannot be undone.
+
+
+
+
+ Cancel Task
+
+
+ Keep Task
+
+
+
+
+
diff --git a/frontend/src/tasks/task-details/task-details.js b/frontend/src/tasks/task-details/task-details.js
index 15348eb..6d8e658 100644
--- a/frontend/src/tasks/task-details/task-details.js
+++ b/frontend/src/tasks/task-details/task-details.js
@@ -8,7 +8,9 @@ module.exports = app => app.component('task-details', {
data: () => ({
showRescheduleModal: false,
showRunModal: false,
- selectedTask: null
+ showCancelModal: false,
+ selectedTask: null,
+ newScheduledTime: ''
}),
computed: {
sortedTasks() {
@@ -49,8 +51,12 @@ module.exports = app => app.component('task-details', {
async rescheduleTask(task) {
try {
// TODO: Implement reschedule API call
- console.log('Rescheduling task:', task.id);
- // await api.Task.rescheduleTask(task.id);
+ if (!this.newScheduledTime) {
+ return;
+ }
+ console.log('Rescheduling task:', task.id, 'to:', this.newScheduledTime);
+ await api.Task.rescheduleTask({ taskId: task.id, scheduledAt: this.newScheduledTime });
+ // await api.Task.rescheduleTask(task.id, { scheduledAt: this.newScheduledTime });
} catch (error) {
console.error('Error rescheduling task:', error);
// TODO: Add proper error handling/notification
@@ -66,6 +72,16 @@ module.exports = app => app.component('task-details', {
// TODO: Add proper error handling/notification
}
},
+ async cancelTask(task) {
+ try {
+ await api.Task.cancelTask({ taskId: task.id });
+ // Refresh the task data by emitting an event to the parent
+ this.$emit('task-cancelled');
+ } catch (error) {
+ console.error('Error cancelling task:', error);
+ // TODO: Add proper error handling/notification
+ }
+ },
filterByStatus(status) {
// If clicking the same status, clear the filter
if (this.currentFilter === status) {
@@ -79,17 +95,26 @@ module.exports = app => app.component('task-details', {
},
showRescheduleConfirmation(task) {
this.selectedTask = task;
+ // Set default time to 1 hour from now
+ const defaultTime = new Date();
+ defaultTime.setHours(defaultTime.getHours() + 1);
+ this.newScheduledTime = defaultTime.toISOString().slice(0, 16);
this.showRescheduleModal = true;
},
showRunConfirmation(task) {
this.selectedTask = task;
this.showRunModal = true;
},
+ showCancelConfirmation(task) {
+ this.selectedTask = task;
+ this.showCancelModal = true;
+ },
async confirmRescheduleTask() {
try {
await this.rescheduleTask(this.selectedTask);
this.showRescheduleModal = false;
this.selectedTask = null;
+ this.newScheduledTime = '';
} catch (error) {
console.error('Error in confirmRescheduleTask:', error);
}
@@ -102,6 +127,15 @@ module.exports = app => app.component('task-details', {
} catch (error) {
console.error('Error in confirmRunTask:', error);
}
+ },
+ async confirmCancelTask() {
+ try {
+ await this.cancelTask(this.selectedTask);
+ this.showCancelModal = false;
+ this.selectedTask = null;
+ } catch (error) {
+ console.error('Error in confirmCancelTask:', error);
+ }
}
},
diff --git a/frontend/src/tasks/tasks.html b/frontend/src/tasks/tasks.html
index 4ee65e2..b263995 100644
--- a/frontend/src/tasks/tasks.html
+++ b/frontend/src/tasks/tasks.html
@@ -6,6 +6,7 @@
:current-filter="taskDetailsFilter"
@back="hideTaskDetails"
@task-created="onTaskCreated"
+ @task-cancelled="onTaskCancelled"
@update:current-filter="taskDetailsFilter = $event"
>
@@ -33,6 +34,16 @@ Task Overview
+
+
+
+
Create New Task
>
+
+
+
+
Enter 0 or leave empty for no repetition. Use 1000 for 1 second, 60000 for 1 minute, etc.
+
+
app.component('tasks', {
{ label: 'Failed', value: 'failed' },
{ label: 'Cancelled', value: 'cancelled' }
],
+ searchQuery: '',
+ searchTimeout: null,
// Task details view state
showTaskDetails: false,
selectedTaskGroup: null,
@@ -39,7 +41,8 @@ module.exports = app => app.component('tasks', {
newTask: {
name: '',
scheduledAt: '',
- parameters: ''
+ parameters: '',
+ repeatInterval: ''
}
}),
methods: {
@@ -57,6 +60,11 @@ module.exports = app => app.component('tasks', {
} else if (this.start) {
params.start = this.start;
}
+
+ if (this.searchQuery.trim()) {
+ params.name = this.searchQuery.trim();
+ }
+
const { tasks, groupedTasks } = await api.Task.getTasks(params);
this.tasks = tasks;
this.groupedTasks = groupedTasks;
@@ -76,6 +84,10 @@ module.exports = app => app.component('tasks', {
this.taskDetailsFilter = status;
this.showTaskDetails = true;
},
+ async onTaskCancelled() {
+ // Refresh the task data when a task is cancelled
+ await this.getTasks();
+ },
hideTaskDetails() {
this.showTaskDetails = false;
this.selectedTaskGroup = null;
@@ -102,15 +114,28 @@ module.exports = app => app.component('tasks', {
}
}
+ // Validate repeat interval
+ let repeatInterval = null;
+ if (this.newTask.repeatInterval && this.newTask.repeatInterval.trim()) {
+ const interval = parseInt(this.newTask.repeatInterval);
+ if (isNaN(interval) || interval < 0) {
+ console.error('Invalid repeat interval. Must be a positive number.');
+ // TODO: Add proper validation feedback
+ return;
+ }
+ repeatInterval = interval;
+ }
+
const taskData = {
name: this.newTask.name,
scheduledAt: this.newTask.scheduledAt,
- parameters: parameters
+ payload: parameters,
+ repeatAfterMS: repeatInterval
};
// TODO: Implement create task API call
console.log('Creating task:', taskData);
- // await api.Task.createTask(taskData);
+ await api.Task.createTask(taskData);
// Close modal (which will reset form)
this.closeCreateTaskModal();
@@ -126,7 +151,8 @@ module.exports = app => app.component('tasks', {
this.newTask = {
name: '',
scheduledAt: '',
- parameters: ''
+ parameters: '',
+ repeatInterval: ''
};
},
setDefaultCreateTaskValues() {
@@ -164,32 +190,24 @@ module.exports = app => app.component('tasks', {
async resetFilters() {
this.selectedStatus = 'all';
this.selectedRange = 'today';
+ this.searchQuery = '';
await this.updateDateRange();
},
async setStatusFilter(status) {
this.selectedStatus = status;
await this.getTasks();
},
+ async onSearchInput() {
+ // Debounce the search to avoid too many API calls
+ clearTimeout(this.searchTimeout);
+ this.searchTimeout = setTimeout(async () => {
+ await this.getTasks();
+ }, 300);
+ },
async updateDateRange() {
const now = new Date();
let start, end;
- const getStartOfWeek = (d) => {
- const date = new Date(d);
- const day = date.getDay();
- date.setDate(date.getDate() - day);
- date.setHours(0, 0, 0, 0);
- return date;
- };
-
- const getEndOfWeek = (d) => {
- const date = new Date(d);
- const day = date.getDay();
- date.setDate(date.getDate() + (6 - day));
- date.setHours(23, 59, 59, 999);
- return date;
- };
-
switch (this.selectedRange) {
case 'today':
start = new Date();
@@ -202,18 +220,18 @@ module.exports = app => app.component('tasks', {
start.setDate(start.getDate() - 1);
start.setHours(0, 0, 0, 0);
end = new Date();
- end.setDate(end.getDate() - 1);
- end.setHours(23, 59, 59, 999);
break;
case 'thisWeek':
- start = getStartOfWeek(now);
- end = getEndOfWeek(now);
+ start = new Date(now.getTime() - (7 * 86400000));
+ start.setHours(0, 0, 0, 0);
+ end = new Date();
+ end.setHours(23, 59, 59, 999);
break;
case 'lastWeek':
- const lastWeekStart = getStartOfWeek(new Date(now.getTime() - 7 * 86400000));
- const lastWeekEnd = getEndOfWeek(new Date(now.getTime() - 7 * 86400000));
- start = lastWeekStart;
- end = lastWeekEnd;
+ start = new Date(now.getTime() - (14 * 86400000));
+ start.setHours(0, 0, 0, 0);
+ end = new Date(now.getTime() - (7 * 86400000));
+ end.setHours(23, 59, 59, 999);
break;
case 'thisMonth':
start = new Date(now.getFullYear(), now.getMonth(), 1);
@@ -227,7 +245,7 @@ module.exports = app => app.component('tasks', {
default:
this.start = null;
this.end = null;
- return;
+ break;
}
this.start = start;
@@ -237,12 +255,6 @@ module.exports = app => app.component('tasks', {
}
},
computed: {
- canCreateNewTask() {
- if (this.newTask.status && this.newTask.time && this.newTask.name) {
- return true;
- }
- return false;
- },
tasksByName() {
const groups = {};
From 684d8909d2b3a1079a80f3fda0d655a73f0864ca Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Tue, 2 Sep 2025 15:01:55 -0400
Subject: [PATCH 19/25] fix: lint
---
backend/actions/Task/cancelTask.js | 8 ++++----
backend/actions/Task/createTask.js | 28 +++++++++++++-------------
backend/actions/Task/rescheduleTask.js | 20 +++++++++---------
backend/actions/Task/runTask.js | 8 ++++----
frontend/src/tasks/tasks.js | 2 +-
5 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/backend/actions/Task/cancelTask.js b/backend/actions/Task/cancelTask.js
index 9e62343..3e00d44 100644
--- a/backend/actions/Task/cancelTask.js
+++ b/backend/actions/Task/cancelTask.js
@@ -4,10 +4,10 @@ const Archetype = require('archetype');
const mongoose = require('mongoose');
const CancelTaskParams = new Archetype({
- taskId: {
- $type: mongoose.Types.ObjectId,
- $required: true
- }
+ taskId: {
+ $type: mongoose.Types.ObjectId,
+ $required: true
+ }
}).compile('CancelTaskParams');
module.exports = ({ db }) => async function cancelTask(params) {
diff --git a/backend/actions/Task/createTask.js b/backend/actions/Task/createTask.js
index 3639175..bdb5041 100644
--- a/backend/actions/Task/createTask.js
+++ b/backend/actions/Task/createTask.js
@@ -3,20 +3,20 @@
const Archetype = require('archetype');
const CreateTaskParams = new Archetype({
- name: {
- $type: 'string',
- $required: true
- },
- scheduledAt: {
- $type: Date,
- $required: true
- },
- repeatAfterMS: {
- $type: 'number'
- },
- payload: {
- $type: Archetype.Any
- }
+ name: {
+ $type: 'string',
+ $required: true
+ },
+ scheduledAt: {
+ $type: Date,
+ $required: true
+ },
+ repeatAfterMS: {
+ $type: 'number'
+ },
+ payload: {
+ $type: Archetype.Any
+ }
}).compile('CreateTaskParams');
module.exports = ({ db }) => async function createTask(params) {
diff --git a/backend/actions/Task/rescheduleTask.js b/backend/actions/Task/rescheduleTask.js
index 2acce1f..8c7260e 100644
--- a/backend/actions/Task/rescheduleTask.js
+++ b/backend/actions/Task/rescheduleTask.js
@@ -4,14 +4,14 @@ const Archetype = require('archetype');
const mongoose = require('mongoose');
const RescheduleTaskParams = new Archetype({
- taskId: {
- $type: mongoose.Types.ObjectId,
- $required: true
- },
- scheduledAt: {
- $type: Date,
- $required: true
- }
+ taskId: {
+ $type: mongoose.Types.ObjectId,
+ $required: true
+ },
+ scheduledAt: {
+ $type: Date,
+ $required: true
+ }
}).compile('RescheduleTaskParams');
module.exports = ({ db }) => async function rescheduleTask(params) {
@@ -22,11 +22,11 @@ module.exports = ({ db }) => async function rescheduleTask(params) {
const task = await Task.findOne({ _id: taskId }).orFail();
if (scheduledAt < Date.now()) {
- throw new Error ('Cannot reschedule a task for the past')
+ throw new Error('Cannot reschedule a task for the past');
}
if (task.status != 'pending') {
- throw new Error('Cannot reschedule a task that is not pending')
+ throw new Error('Cannot reschedule a task that is not pending');
}
task.scheduledAt = scheduledAt;
diff --git a/backend/actions/Task/runTask.js b/backend/actions/Task/runTask.js
index 9c10150..e4df320 100644
--- a/backend/actions/Task/runTask.js
+++ b/backend/actions/Task/runTask.js
@@ -4,10 +4,10 @@ const Archetype = require('archetype');
const mongoose = require('mongoose');
const RunTaskParams = new Archetype({
- taskId: {
- $type: mongoose.Types.ObjectId,
- $required: true
- }
+ taskId: {
+ $type: mongoose.Types.ObjectId,
+ $required: true
+ }
}).compile('RunTaskParams');
module.exports = ({ db }) => async function runTask(params) {
diff --git a/frontend/src/tasks/tasks.js b/frontend/src/tasks/tasks.js
index feaae03..acff2aa 100644
--- a/frontend/src/tasks/tasks.js
+++ b/frontend/src/tasks/tasks.js
@@ -200,7 +200,7 @@ module.exports = app => app.component('tasks', {
async onSearchInput() {
// Debounce the search to avoid too many API calls
clearTimeout(this.searchTimeout);
- this.searchTimeout = setTimeout(async () => {
+ this.searchTimeout = setTimeout(async() => {
await this.getTasks();
}, 300);
},
From 8a27ee26e39b574ce2342ddee62e880da41b2e8b Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Fri, 26 Sep 2025 17:19:48 -0400
Subject: [PATCH 20/25] Update eslint.config.js
---
eslint.config.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/eslint.config.js b/eslint.config.js
index 833f1fd..0ba9b14 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -37,6 +37,7 @@ module.exports = defineConfig([
__dirname: true,
process: true,
setTimeout: true,
+ clearTimeout: true,
navigator: true
},
sourceType: 'commonjs'
From 24dd8ca593937909f4b5453a075828dbae27316d Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Thu, 2 Oct 2025 15:48:24 -0400
Subject: [PATCH 21/25] fix: model navigation not working
---
frontend/src/models/models.css | 2 +-
frontend/src/models/models.js | 6 ++++++
frontend/src/routes.js | 3 +--
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/frontend/src/models/models.css b/frontend/src/models/models.css
index 836d706..92f8e18 100644
--- a/frontend/src/models/models.css
+++ b/frontend/src/models/models.css
@@ -92,7 +92,7 @@ td {
.models .documents-menu {
position: fixed;
background-color: white;
- z-index: 1;
+ z-index: 10;
padding: 4px;
display: flex;
width: 100vw;
diff --git a/frontend/src/models/models.js b/frontend/src/models/models.js
index 5e09a29..153ea6c 100644
--- a/frontend/src/models/models.js
+++ b/frontend/src/models/models.js
@@ -354,6 +354,12 @@ module.exports = app => app.component('models', {
}
this.edittingDoc = null;
},
+ handleModelClick(model) {
+ console.log('Model clicked:', model);
+ console.log('Current model:', this.currentModel);
+ console.log('Router:', this.$router);
+ // Let the router-link handle the navigation
+ },
handleDocumentClick(document) {
console.log(this.selectedDocuments);
if (this.selectMultiple) {
diff --git a/frontend/src/routes.js b/frontend/src/routes.js
index 6601b91..f91bbb1 100644
--- a/frontend/src/routes.js
+++ b/frontend/src/routes.js
@@ -9,14 +9,13 @@ const roleAccess = {
dashboards: ['dashboards', 'dashboard']
};
-const allowedRoutesForLocalDev = ['document', 'root', 'chat'];
+const allowedRoutesForLocalDev = ['document', 'root', 'chat', 'model'];
// Helper function to check if a role has access to a route
function hasAccess(roles, routeName) {
// change to true for local development
if (!roles) return allowedRoutesForLocalDev.includes(routeName);
return roles.some(role => roleAccess[role]?.includes(routeName));
-
}
module.exports = {
From c55eba7d36a385b93b9e558c5968051797e4f074 Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Thu, 2 Oct 2025 15:48:36 -0400
Subject: [PATCH 22/25] Update models.js
---
frontend/src/models/models.js | 6 ------
1 file changed, 6 deletions(-)
diff --git a/frontend/src/models/models.js b/frontend/src/models/models.js
index 153ea6c..5e09a29 100644
--- a/frontend/src/models/models.js
+++ b/frontend/src/models/models.js
@@ -354,12 +354,6 @@ module.exports = app => app.component('models', {
}
this.edittingDoc = null;
},
- handleModelClick(model) {
- console.log('Model clicked:', model);
- console.log('Current model:', this.currentModel);
- console.log('Router:', this.$router);
- // Let the router-link handle the navigation
- },
handleDocumentClick(document) {
console.log(this.selectedDocuments);
if (this.selectMultiple) {
From fd264686be22ca90470a0e501d6c6e7a5e359614 Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Tue, 7 Oct 2025 10:53:47 -0400
Subject: [PATCH 23/25] manually fix lint
---
eslint.config.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/eslint.config.js b/eslint.config.js
index d00708d..915a4df 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -37,7 +37,8 @@ module.exports = defineConfig([
__dirname: true,
process: true,
clearTimeout: true,
- navigator: true
+ setTimeout: true,
+ navigator: true,
TextDecoder: true
},
sourceType: 'commonjs'
From 5a994eed0567c5985c2c8294e94881392b324c92 Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Wed, 15 Oct 2025 17:01:09 -0400
Subject: [PATCH 24/25] better UX
---
backend/actions/Model/updateDocument.js | 4 +-
backend/actions/Model/updateDocuments.js | 4 +-
.../src/document-details/document-details.js | 6 +-
.../src/tasks/task-details/task-details.js | 88 +++++++++++++------
frontend/src/tasks/tasks.html | 7 +-
frontend/src/tasks/tasks.js | 67 ++++++++++++--
6 files changed, 131 insertions(+), 45 deletions(-)
diff --git a/backend/actions/Model/updateDocument.js b/backend/actions/Model/updateDocument.js
index 5dbb3d4..4d4cf80 100644
--- a/backend/actions/Model/updateDocument.js
+++ b/backend/actions/Model/updateDocument.js
@@ -31,8 +31,8 @@ module.exports = ({ db }) => async function updateDocument(params) {
throw new Error(`Model ${model} not found`);
}
- let setFields = {};
- let unsetFields = {};
+ const setFields = {};
+ const unsetFields = {};
if (Object.keys(update).length > 0) {
Object.entries(update).forEach(([key, value]) => {
diff --git a/backend/actions/Model/updateDocuments.js b/backend/actions/Model/updateDocuments.js
index 07a8113..ee89463 100644
--- a/backend/actions/Model/updateDocuments.js
+++ b/backend/actions/Model/updateDocuments.js
@@ -31,8 +31,8 @@ module.exports = ({ db }) => async function updateDocuments(params) {
throw new Error(`Model ${model} not found`);
}
- let setFields = {};
- let unsetFields = {};
+ const setFields = {};
+ const unsetFields = {};
if (Object.keys(update).length > 0) {
Object.entries(update).forEach(([key, value]) => {
diff --git a/frontend/src/document-details/document-details.js b/frontend/src/document-details/document-details.js
index 1f96c95..9ffcf69 100644
--- a/frontend/src/document-details/document-details.js
+++ b/frontend/src/document-details/document-details.js
@@ -320,9 +320,9 @@ module.exports = app => app.component('document-details', {
toSnakeCase(str) {
return str
.trim()
- .replace(/\s+/g, '_') // Replace spaces with underscores
- .replace(/[^a-zA-Z0-9_$]/g, '') // Remove invalid characters
- .replace(/^[0-9]/, '_$&') // Prefix numbers with underscore
+ .replace(/\s+/g, '_') // Replace spaces with underscores
+ .replace(/[^a-zA-Z0-9_$]/g, '') // Remove invalid characters
+ .replace(/^[0-9]/, '_$&') // Prefix numbers with underscore
.toLowerCase();
},
getTransformedFieldName() {
diff --git a/frontend/src/tasks/task-details/task-details.js b/frontend/src/tasks/task-details/task-details.js
index 6d8e658..5dbfc5b 100644
--- a/frontend/src/tasks/task-details/task-details.js
+++ b/frontend/src/tasks/task-details/task-details.js
@@ -2,6 +2,7 @@
const template = require('./task-details.html');
const api = require('../../api');
+const vanillatoasts = require('vanillatoasts');
module.exports = app => app.component('task-details', {
props: ['taskGroup', 'currentFilter'],
@@ -49,38 +50,20 @@ module.exports = app => app.component('task-details', {
return new Date(dateString).toLocaleString();
},
async rescheduleTask(task) {
- try {
- // TODO: Implement reschedule API call
- if (!this.newScheduledTime) {
- return;
- }
- console.log('Rescheduling task:', task.id, 'to:', this.newScheduledTime);
- await api.Task.rescheduleTask({ taskId: task.id, scheduledAt: this.newScheduledTime });
- // await api.Task.rescheduleTask(task.id, { scheduledAt: this.newScheduledTime });
- } catch (error) {
- console.error('Error rescheduling task:', error);
- // TODO: Add proper error handling/notification
+ if (!this.newScheduledTime) {
+ return;
}
+ console.log('Rescheduling task:', task.id, 'to:', this.newScheduledTime);
+ await api.Task.rescheduleTask({ taskId: task.id, scheduledAt: this.newScheduledTime });
},
async runTask(task) {
- try {
- // TODO: Implement run task API call
- console.log('Running task:', task.id);
- // await api.Task.runTask(task.id);
- } catch (error) {
- console.error('Error running task:', error);
- // TODO: Add proper error handling/notification
- }
+ console.log('Running task:', task.id);
+ await api.Task.runTask({ taskId: task.id });
},
async cancelTask(task) {
- try {
- await api.Task.cancelTask({ taskId: task.id });
- // Refresh the task data by emitting an event to the parent
- this.$emit('task-cancelled');
- } catch (error) {
- console.error('Error cancelling task:', error);
- // TODO: Add proper error handling/notification
- }
+ await api.Task.cancelTask({ taskId: task.id });
+ // Refresh the task data by emitting an event to the parent
+ this.$emit('task-cancelled');
},
filterByStatus(status) {
// If clicking the same status, clear the filter
@@ -112,29 +95,80 @@ module.exports = app => app.component('task-details', {
async confirmRescheduleTask() {
try {
await this.rescheduleTask(this.selectedTask);
+
+ // Show success message
+ vanillatoasts.create({
+ title: 'Task Rescheduled Successfully!',
+ text: `Task ${this.selectedTask.id} has been rescheduled`,
+ type: 'success',
+ timeout: 3000,
+ positionClass: 'bottomRight'
+ });
+
this.showRescheduleModal = false;
this.selectedTask = null;
this.newScheduledTime = '';
} catch (error) {
console.error('Error in confirmRescheduleTask:', error);
+ vanillatoasts.create({
+ title: 'Failed to Reschedule Task',
+ text: error?.response?.data?.message || error.message || 'An unexpected error occurred',
+ type: 'error',
+ timeout: 5000,
+ positionClass: 'bottomRight'
+ });
}
},
async confirmRunTask() {
try {
await this.runTask(this.selectedTask);
+
+ // Show success message
+ vanillatoasts.create({
+ title: 'Task Started Successfully!',
+ text: `Task ${this.selectedTask.id} is now running`,
+ type: 'success',
+ timeout: 3000,
+ positionClass: 'bottomRight'
+ });
+
this.showRunModal = false;
this.selectedTask = null;
} catch (error) {
console.error('Error in confirmRunTask:', error);
+ vanillatoasts.create({
+ title: 'Failed to Run Task',
+ text: error?.response?.data?.message || error.message || 'An unexpected error occurred',
+ type: 'error',
+ timeout: 5000,
+ positionClass: 'bottomRight'
+ });
}
},
async confirmCancelTask() {
try {
await this.cancelTask(this.selectedTask);
+
+ // Show success message
+ vanillatoasts.create({
+ title: 'Task Cancelled Successfully!',
+ text: `Task ${this.selectedTask.id} has been cancelled`,
+ type: 'success',
+ timeout: 3000,
+ positionClass: 'bottomRight'
+ });
+
this.showCancelModal = false;
this.selectedTask = null;
} catch (error) {
console.error('Error in confirmCancelTask:', error);
+ vanillatoasts.create({
+ title: 'Failed to Cancel Task',
+ text: error?.response?.data?.message || error.message || 'An unexpected error occurred',
+ type: 'error',
+ timeout: 5000,
+ positionClass: 'bottomRight'
+ });
}
}
diff --git a/frontend/src/tasks/tasks.html b/frontend/src/tasks/tasks.html
index b263995..e17a7c3 100644
--- a/frontend/src/tasks/tasks.html
+++ b/frontend/src/tasks/tasks.html
@@ -54,7 +54,7 @@ Task Overview
Create New Task
@@ -155,7 +155,7 @@ Tasks by Name
- ×
+ ×
Create New Task
@@ -181,8 +181,7 @@
Create New Task
diff --git a/frontend/src/tasks/tasks.js b/frontend/src/tasks/tasks.js
index acff2aa..eadc1fb 100644
--- a/frontend/src/tasks/tasks.js
+++ b/frontend/src/tasks/tasks.js
@@ -2,6 +2,7 @@
const template = require('./tasks.html');
const api = require('../api');
+const vanillatoasts = require('vanillatoasts');
module.exports = app => app.component('tasks', {
@@ -43,7 +44,8 @@ module.exports = app => app.component('tasks', {
scheduledAt: '',
parameters: '',
repeatInterval: ''
- }
+ },
+ parametersEditor: null
}),
methods: {
async getTasks() {
@@ -104,12 +106,19 @@ module.exports = app => app.component('tasks', {
async createTask() {
try {
let parameters = {};
- if (this.newTask.parameters.trim()) {
+ const parametersText = this.parametersEditor ? this.parametersEditor.getValue() : '';
+ if (parametersText.trim()) {
try {
- parameters = JSON.parse(this.newTask.parameters);
+ parameters = JSON.parse(parametersText);
} catch (e) {
console.error('Invalid JSON in parameters field:', e);
- // TODO: Add proper validation feedback
+ vanillatoasts.create({
+ title: 'Invalid JSON Parameters',
+ text: 'Please check your JSON syntax in the parameters field',
+ type: 'error',
+ timeout: 5000,
+ positionClass: 'bottomRight'
+ });
return;
}
}
@@ -120,7 +129,13 @@ module.exports = app => app.component('tasks', {
const interval = parseInt(this.newTask.repeatInterval);
if (isNaN(interval) || interval < 0) {
console.error('Invalid repeat interval. Must be a positive number.');
- // TODO: Add proper validation feedback
+ vanillatoasts.create({
+ title: 'Invalid Repeat Interval',
+ text: 'Repeat interval must be a positive number (in milliseconds)',
+ type: 'error',
+ timeout: 5000,
+ positionClass: 'bottomRight'
+ });
return;
}
repeatInterval = interval;
@@ -133,10 +148,18 @@ module.exports = app => app.component('tasks', {
repeatAfterMS: repeatInterval
};
- // TODO: Implement create task API call
console.log('Creating task:', taskData);
await api.Task.createTask(taskData);
+ // Show success message
+ vanillatoasts.create({
+ title: 'Task Created Successfully!',
+ text: `Task "${taskData.name}" has been scheduled`,
+ type: 'success',
+ timeout: 3000,
+ positionClass: 'bottomRight'
+ });
+
// Close modal (which will reset form)
this.closeCreateTaskModal();
@@ -144,7 +167,13 @@ module.exports = app => app.component('tasks', {
await this.getTasks();
} catch (error) {
console.error('Error creating task:', error);
- // TODO: Add proper error handling/notification
+ vanillatoasts.create({
+ title: 'Failed to Create Task',
+ text: error?.response?.data?.message || error.message || 'An unexpected error occurred',
+ type: 'error',
+ timeout: 5000,
+ positionClass: 'bottomRight'
+ });
}
},
resetCreateTaskForm() {
@@ -154,6 +183,9 @@ module.exports = app => app.component('tasks', {
parameters: '',
repeatInterval: ''
};
+ if (this.parametersEditor) {
+ this.parametersEditor.setValue('');
+ }
},
setDefaultCreateTaskValues() {
// Set default scheduled time to 1 hour from now
@@ -166,6 +198,22 @@ module.exports = app => app.component('tasks', {
this.resetCreateTaskForm();
this.setDefaultCreateTaskValues();
},
+ initializeParametersEditor() {
+ if (this.$refs.parametersEditor && !this.parametersEditor) {
+ this.parametersEditor = CodeMirror.fromTextArea(this.$refs.parametersEditor, {
+ mode: 'javascript',
+ lineNumbers: true,
+ smartIndent: false,
+ theme: 'default'
+ });
+ }
+ },
+ openCreateTaskModal() {
+ this.showCreateTaskModal = true;
+ this.$nextTick(() => {
+ this.initializeParametersEditor();
+ });
+ },
getStatusColor(status) {
if (status === 'succeeded') {
// Green (success)
@@ -316,6 +364,11 @@ module.exports = app => app.component('tasks', {
this.status = 'loaded';
this.setDefaultCreateTaskValues();
},
+ beforeDestroy() {
+ if (this.parametersEditor) {
+ this.parametersEditor.toTextArea();
+ }
+ },
template: template
});
\ No newline at end of file
From ac33e4431e1cf4a10a4494ae36d3e5b644abbe99 Mon Sep 17 00:00:00 2001
From: Daniel Diaz <39510674+IslandRhythms@users.noreply.github.com>
Date: Thu, 11 Dec 2025 16:50:57 -0500
Subject: [PATCH 25/25] fix: lint
---
frontend/src/dashboard/dashboard.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/dashboard/dashboard.js b/frontend/src/dashboard/dashboard.js
index 9b0c18c..2d37513 100644
--- a/frontend/src/dashboard/dashboard.js
+++ b/frontend/src/dashboard/dashboard.js
@@ -141,5 +141,5 @@ module.exports = app => app.component('dashboard', {
},
beforeDestroy() {
document.removeEventListener('click', this.handleDocumentClick);
- },
+ }
});