24/7 Cycle
@@ -69,6 +73,20 @@
(with download)
{% endif %}
+ {% elif item.schedule.schedule_type == 'one_off' %}
+
+
+ {% if item.schedule.start_datetime %}
+ {{ item.schedule.start_datetime|local_datetime }} {{ timezone_abbr() }}
+ →
+ {{ item.schedule.end_datetime|local_datetime }} {{ timezone_abbr() }}
+ {% endif %}
+ {% if item.schedule.include_download %}
+ (with download)
+ {% endif %}
+
{% endif %}
{% if item.schedule.next_occurrence %}
diff --git a/templates/partials/projects/schedule_oneoff.html b/templates/partials/projects/schedule_oneoff.html
new file mode 100644
index 0000000..7453a1c
--- /dev/null
+++ b/templates/partials/projects/schedule_oneoff.html
@@ -0,0 +1,206 @@
+
+
+
+
+
+
One-Off Recording
+
+ Schedule a single recording session with a specific start and end time.
+ Duration can be between 15 minutes and 24 hours.
+
+
+
+
+
+
+
+
+
How it works:
+
+ - At the start time, the measurement will start
+ - At the end time, the measurement will stop
+ - If enabled, data will be downloaded via FTP after stop
+ - The schedule will be auto-disabled after completion
+
+
+
+
+
+
+
+
+
+
+ Must be in the future
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Duration:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/templates/projects/detail.html b/templates/projects/detail.html
index fecf085..fa6c79d 100644
--- a/templates/projects/detail.html
+++ b/templates/projects/detail.html
@@ -456,7 +456,7 @@
-
+
+
@@ -498,6 +512,11 @@
{% include "partials/projects/schedule_interval.html" %}
+
+
+ {% include "partials/projects/schedule_oneoff.html" %}
+
+
@@ -1108,13 +1127,18 @@ function getSelectedLocationIds() {
function toggleScheduleType(type) {
const weeklyEditor = document.getElementById('schedule-weekly-wrapper');
const intervalEditor = document.getElementById('schedule-interval-wrapper');
+ const oneoffEditor = document.getElementById('schedule-oneoff-wrapper');
+
+ weeklyEditor.classList.add('hidden');
+ intervalEditor.classList.add('hidden');
+ oneoffEditor.classList.add('hidden');
if (type === 'weekly_calendar') {
weeklyEditor.classList.remove('hidden');
- intervalEditor.classList.add('hidden');
- } else {
- weeklyEditor.classList.add('hidden');
+ } else if (type === 'simple_interval') {
intervalEditor.classList.remove('hidden');
+ } else if (type === 'one_off') {
+ oneoffEditor.classList.remove('hidden');
}
}
@@ -1178,7 +1202,7 @@ document.getElementById('schedule-form').addEventListener('submit', async functi
} else {
payload.include_download = true;
}
- } else {
+ } else if (scheduleType === 'simple_interval') {
// Get interval data
if (typeof getIntervalData === 'function') {
const intervalData = getIntervalData();
@@ -1190,6 +1214,45 @@ document.getElementById('schedule-form').addEventListener('submit', async functi
showScheduleError('Interval editor not loaded properly.');
return;
}
+ } else if (scheduleType === 'one_off') {
+ // Get one-off data
+ if (typeof getOneOffData === 'function') {
+ const oneOffData = getOneOffData();
+
+ if (!oneOffData.start_datetime || !oneOffData.end_datetime) {
+ showScheduleError('Please select both start and end date/time.');
+ return;
+ }
+
+ const start = new Date(oneOffData.start_datetime);
+ const end = new Date(oneOffData.end_datetime);
+ const diffMinutes = (end - start) / (1000 * 60);
+
+ if (diffMinutes <= 0) {
+ showScheduleError('End time must be after start time.');
+ return;
+ }
+ if (diffMinutes < 15) {
+ showScheduleError('Duration must be at least 15 minutes.');
+ return;
+ }
+ if (diffMinutes > 1440) {
+ showScheduleError('Duration cannot exceed 24 hours.');
+ return;
+ }
+ if (start <= new Date()) {
+ showScheduleError('Start time must be in the future.');
+ return;
+ }
+
+ payload.start_datetime = oneOffData.start_datetime;
+ payload.end_datetime = oneOffData.end_datetime;
+ payload.include_download = oneOffData.include_download;
+ payload.auto_increment_index = oneOffData.auto_increment_index;
+ } else {
+ showScheduleError('One-off editor not loaded properly.');
+ return;
+ }
}
try {
diff --git a/templates/roster.html b/templates/roster.html
index 5407c98..0ebc69d 100644
--- a/templates/roster.html
+++ b/templates/roster.html
@@ -772,6 +772,8 @@
// Handle Add Unit form submission
document.getElementById('addUnitForm').addEventListener('htmx:afterRequest', function(event) {
+ // Only handle the form's own POST request, not child HTMX requests (e.g. project picker search)
+ if (event.detail.elt !== this) return;
if (event.detail.successful) {
closeAddUnitModal();
refreshDeviceList();