Implement timer functions with full CRUD functionality and enhanced UI

- Add comprehensive routes for creating, editing, deleting, and toggling timer functions
- Create new HTML templates for timer function overview, new, and edit pages
- Extend Mithril editor component to support timer-specific settings like trigger type, frequency, and enabled status
- Implement database schema and versioning for timer functions
- Add UI improvements for timer function listing with detailed schedule and status information
This commit is contained in:
Peter Stockings
2025-02-16 19:50:58 +11:00
parent adeb62365b
commit e09437c7b8
6 changed files with 618 additions and 75 deletions

View File

@@ -55,6 +55,20 @@ const Editor = {
this.deleteUrl = vnode.attrs.deleteUrl;
this.dashboardUrl = vnode.attrs.dashboardUrl;
// New timer-specific props
this.isTimer = vnode.attrs.isTimer || false;
this.triggerType = vnode.attrs.triggerType || 'interval'; // 'interval' or 'date'
this.frequencyMinutes = vnode.attrs.frequencyMinutes || 60;
this.runDate = vnode.attrs.runDate || '';
// Show timer settings panel
this.showTimerSettings = vnode.attrs.showTimerSettings === true; // default false
this.cancelUrl = vnode.attrs.cancelUrl || '/dashboard/http_functions';
// Add enabled property for timer functions
this.isEnabled = vnode.attrs.isEnabled !== false; // default true
},
oncreate() {
@@ -122,7 +136,7 @@ const Editor = {
this.error = null;
try {
const payload = {
let payload = {
name: this.name,
script_content: this.jsValue,
environment_info: this.jsonValue,
@@ -131,6 +145,21 @@ const Editor = {
log_response: this.logResponse
};
// Create payload based on whether this is a timer function
payload = this.isTimer ? {
name: this.name,
script_content: this.jsValue,
environment_info: this.jsonValue,
trigger_type: this.triggerType,
frequency_minutes: this.triggerType === 'interval' ? parseInt(this.frequencyMinutes) : null,
run_date: this.triggerType === 'date' ? this.runDate : null,
is_enabled: this.isEnabled // Add enabled status to payload
} : {
name: this.name,
script_content: this.jsValue,
environment_info: this.jsonValue
};
const response = await m.request({
method: 'POST',
url: this.saveUrl,
@@ -175,7 +204,7 @@ const Editor = {
if (response.status === 'success') {
Alert.show(response.message || 'Function deleted successfully!', 'success');
// Optionally redirect to a different page after deletion
window.location.href = '/dashboard/http_functions';
window.location.href = this.cancelUrl;
} else {
Alert.show(response.message || 'Error deleting function', 'error');
this.error = new Error(response.message);
@@ -476,21 +505,85 @@ const Editor = {
])
]),
/* ─────────────────────────────────────────────────────────────────
// Timer settings panel (shown only if isTimer is true)
this.isTimer && this.showTimerSettings && m("div", {
class: "bg-gray-100 dark:bg-gray-800 p-4 border-b"
}, [
m("div", { class: "flex flex-col space-y-4" }, [
// Add Enabled toggle at the top
m("label", {
class: "flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer mb-4"
}, [
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.isEnabled,
onchange: (e) => this.isEnabled = e.target.checked
}),
m("div", {
class: "w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
})
]),
m("span", "Enabled")
]),
// Trigger Type Selection
m("div", { class: "flex flex-col space-y-2" }, [
m("label", { class: "text-sm font-medium text-gray-700 dark:text-gray-300" },
"Trigger Type"
),
m("select", {
class: "bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.triggerType,
onchange: (e) => this.triggerType = e.target.value
}, [
m("option", { value: "interval" }, "Interval"),
m("option", { value: "date" }, "Specific Date")
])
]),
// Interval Settings (shown only if triggerType is 'interval')
this.triggerType === 'interval' && m("div", { class: "flex flex-col space-y-2" }, [
m("label", { class: "text-sm font-medium text-gray-700 dark:text-gray-300" },
"Frequency (minutes)"
),
m("input[type=number]", {
class: "bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.frequencyMinutes,
min: 1,
onchange: (e) => this.frequencyMinutes = e.target.value
})
]),
// Date Settings (shown only if triggerType is 'date')
this.triggerType === 'date' && m("div", { class: "flex flex-col space-y-2" }, [
m("label", { class: "text-sm font-medium text-gray-700 dark:text-gray-300" },
"Run Date"
),
m("input[type=datetime-local]", {
class: "bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.runDate,
onchange: (e) => this.runDate = e.target.value
})
])
])
]),
/* ─────────────────────────────────────────────────────────────────
ResponseView (child) if needed
─────────────────────────────────────────────────────────────────*/
!this.executeLoading &&
!this.error &&
this.response &&
m(ResponseView, {
response: this.response,
responseTime: this.responseTime,
responseSize: this.responseSize,
envEditorValue: this.jsonValue,
onClose: () => {
this.response = null;
},
}),
!this.executeLoading &&
!this.error &&
this.response &&
m(ResponseView, {
response: this.response,
responseTime: this.responseTime,
responseSize: this.responseSize,
envEditorValue: this.jsonValue,
onClose: () => {
this.response = null;
},
}),
]);
},
};

View File

@@ -315,8 +315,8 @@ const ResponseView = {
: ""),
},
response.status === "SUCCESS"
? m.trust(response.result.body)
: JSON.stringify(response.result)
? m.trust(response?.result?.body)
: JSON.stringify(response?.result)
),
]),