feat: Add schedule builder functionality to job form

- Introduced a new scheduling interface allowing users to select between interval, daily, weekly, monthly, or custom cron expressions.
- Implemented dynamic UI updates to show relevant input fields based on the selected schedule type.
- Enhanced cron expression parsing to pre-fill the form based on existing values.
- Updated job form layout to accommodate the new scheduling options and improve user experience.
This commit is contained in:
StarFleetCPTN
2025-04-11 07:29:29 -07:00
parent beb77d3fe9
commit 2f6c95dd51
2 changed files with 656 additions and 14 deletions
+652 -10
View File
@@ -242,9 +242,250 @@ templ configSearchScript() {
</script>
}
templ scheduleBuilderScript() {
<script>
document.addEventListener('DOMContentLoaded', () => {
// Initialize schedule builder functionality
const initScheduleBuilder = (formPrefix = '') => {
const prefixId = formPrefix ? `${formPrefix}-` : '';
const scheduleTypeSelect = document.getElementById(`${prefixId}schedule-type`);
const cronInputField = document.getElementById(`${prefixId}schedule`);
const scheduleBuilderDiv = document.getElementById(`${prefixId}schedule-builder`);
// All schedule sections
const intervalSection = document.getElementById(`${prefixId}interval-section`);
const dailySection = document.getElementById(`${prefixId}daily-section`);
const weeklySection = document.getElementById(`${prefixId}weekly-section`);
const monthlySection = document.getElementById(`${prefixId}monthly-section`);
if (!scheduleTypeSelect || !cronInputField || !scheduleBuilderDiv) return;
// Hide all sections initially except the selected one
const hideAllSections = () => {
if (intervalSection) intervalSection.classList.add('hidden');
if (dailySection) dailySection.classList.add('hidden');
if (weeklySection) weeklySection.classList.add('hidden');
if (monthlySection) monthlySection.classList.add('hidden');
};
// Show the selected section
const showSection = (sectionType) => {
hideAllSections();
switch (sectionType) {
case 'interval':
if (intervalSection) intervalSection.classList.remove('hidden');
break;
case 'daily':
if (dailySection) dailySection.classList.remove('hidden');
break;
case 'weekly':
if (weeklySection) weeklySection.classList.remove('hidden');
break;
case 'monthly':
if (monthlySection) monthlySection.classList.remove('hidden');
break;
case 'custom':
// Just leave all sections hidden for custom
break;
}
};
// Convert UI inputs to cron expression
const updateCronExpression = () => {
const scheduleType = scheduleTypeSelect.value;
let cronExpression = '';
switch (scheduleType) {
case 'interval':
const intervalValue = document.getElementById(`${prefixId}interval-value`).value || '15';
const intervalUnit = document.getElementById(`${prefixId}interval-unit`).value || 'minutes';
// Convert interval to cron
switch (intervalUnit) {
case 'minutes':
// Every X minutes
cronExpression = `*/${intervalValue} * * * *`;
break;
case 'hours':
// Every X hours
cronExpression = `0 */${intervalValue} * * *`;
break;
case 'days':
// Every X days at midnight
cronExpression = `0 0 */${intervalValue} * *`;
break;
}
break;
case 'daily':
const dailyHour = document.getElementById(`${prefixId}daily-hour`).value || '0';
const dailyMinute = document.getElementById(`${prefixId}daily-minute`).value || '0';
// At HH:MM every day
cronExpression = `${dailyMinute} ${dailyHour} * * *`;
break;
case 'weekly':
const weeklyHour = document.getElementById(`${prefixId}weekly-hour`).value || '0';
const weeklyMinute = document.getElementById(`${prefixId}weekly-minute`).value || '0';
const weekDays = [];
// Check which days are selected
for (let i = 0; i < 7; i++) {
const dayCheckbox = document.getElementById(`${prefixId}day-${i}`);
if (dayCheckbox && dayCheckbox.checked) {
weekDays.push(i);
}
}
// Default to Sunday if no days selected
if (weekDays.length === 0) weekDays.push(0);
// At HH:MM on specified days of week (0 = Sunday, 6 = Saturday)
cronExpression = `${weeklyMinute} ${weeklyHour} * * ${weekDays.join(',')}`;
break;
case 'monthly':
const monthlyHour = document.getElementById(`${prefixId}monthly-hour`).value || '0';
const monthlyMinute = document.getElementById(`${prefixId}monthly-minute`).value || '0';
const monthlyDay = document.getElementById(`${prefixId}monthly-day`).value || '1';
// At HH:MM on day-of-month
cronExpression = `${monthlyMinute} ${monthlyHour} ${monthlyDay} * *`;
break;
case 'custom':
// For custom, we don't modify the existing cron expression
cronExpression = cronInputField.value;
break;
}
// Only update if we calculated a new value
if (cronExpression && scheduleType !== 'custom') {
cronInputField.value = cronExpression;
}
};
// Try to parse existing cron expression and set UI accordingly
const parseCronExpression = (cronExp) => {
if (!cronExp) return;
// Parse the cron expression
const parts = cronExp.trim().split(/\s+/);
if (parts.length !== 5) return; // Invalid cron
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
// Check for interval patterns
if (minute.startsWith('*/') && hour === '*' && dayOfMonth === '*') {
// Every X minutes
const intervalValue = minute.replace('*/', '');
scheduleTypeSelect.value = 'interval';
document.getElementById(`${prefixId}interval-value`).value = intervalValue;
document.getElementById(`${prefixId}interval-unit`).value = 'minutes';
}
else if (minute === '0' && hour.startsWith('*/') && dayOfMonth === '*') {
// Every X hours
const intervalValue = hour.replace('*/', '');
scheduleTypeSelect.value = 'interval';
document.getElementById(`${prefixId}interval-value`).value = intervalValue;
document.getElementById(`${prefixId}interval-unit`).value = 'hours';
}
else if (minute === '0' && hour === '0' && dayOfMonth.startsWith('*/')) {
// Every X days
const intervalValue = dayOfMonth.replace('*/', '');
scheduleTypeSelect.value = 'interval';
document.getElementById(`${prefixId}interval-value`).value = intervalValue;
document.getElementById(`${prefixId}interval-unit`).value = 'days';
}
// Check for daily pattern
else if (dayOfMonth === '*' && month === '*' && dayOfWeek === '*') {
// Daily at specific time
scheduleTypeSelect.value = 'daily';
document.getElementById(`${prefixId}daily-hour`).value = hour;
document.getElementById(`${prefixId}daily-minute`).value = minute;
}
// Check for weekly pattern
else if (dayOfMonth === '*' && month === '*' && dayOfWeek !== '*') {
// Weekly on specific days
scheduleTypeSelect.value = 'weekly';
document.getElementById(`${prefixId}weekly-hour`).value = hour;
document.getElementById(`${prefixId}weekly-minute`).value = minute;
// Set selected days
const days = dayOfWeek.split(',');
days.forEach(day => {
const dayNum = parseInt(day.trim());
if (!isNaN(dayNum) && dayNum >= 0 && dayNum <= 6) {
const checkbox = document.getElementById(`${prefixId}day-${dayNum}`);
if (checkbox) checkbox.checked = true;
}
});
}
// Check for monthly pattern
else if (dayOfMonth !== '*' && !dayOfMonth.includes('/') && month === '*' && dayOfWeek === '*') {
// Monthly on specific day
scheduleTypeSelect.value = 'monthly';
document.getElementById(`${prefixId}monthly-hour`).value = hour;
document.getElementById(`${prefixId}monthly-minute`).value = minute;
document.getElementById(`${prefixId}monthly-day`).value = dayOfMonth;
}
// Default to custom for anything else
else {
scheduleTypeSelect.value = 'custom';
}
// Show the appropriate section
showSection(scheduleTypeSelect.value);
};
// Initial setup
if (cronInputField.value) {
parseCronExpression(cronInputField.value);
} else {
// Default to 'interval' if no value
scheduleTypeSelect.value = 'interval';
showSection('interval');
updateCronExpression(); // Set initial cron value
}
// Event listeners
scheduleTypeSelect.addEventListener('change', () => {
showSection(scheduleTypeSelect.value);
// If switching to custom, don't modify the existing expression
if (scheduleTypeSelect.value !== 'custom') {
updateCronExpression();
}
});
// Add listeners to all schedule builder inputs
const attachListeners = (selector) => {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
el.addEventListener('change', updateCronExpression);
el.addEventListener('input', updateCronExpression);
});
};
// Attach change listeners to all input fields
attachListeners(`#${prefixId}interval-section input, #${prefixId}interval-section select`);
attachListeners(`#${prefixId}daily-section input`);
attachListeners(`#${prefixId}weekly-section input`);
attachListeners(`#${prefixId}monthly-section input`);
};
// Initialize schedule builders
initScheduleBuilder(); // For new job form
initScheduleBuilder('edit'); // For edit job form
});
</script>
}
templ JobForm(ctx context.Context, data JobFormData) {
@LayoutWithContext(getJobFormTitle(data.IsNew), ctx) {
@configSearchScript()
@scheduleBuilderScript()
<!-- Main Content -->
<section class="py-8 px-4">
@@ -310,10 +551,210 @@ templ JobForm(ctx context.Context, data JobFormData) {
<!-- Schedule field -->
<div class="mb-6">
<label for="schedule" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Schedule (Cron Expression)
<label for="schedule-type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Schedule Type
</label>
<div class="relative">
<select
id="schedule-type"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<option value="interval">Run at intervals</option>
<option value="daily">Run daily</option>
<option value="weekly">Run weekly</option>
<option value="monthly">Run monthly</option>
<option value="custom">Custom (Cron expression)</option>
</select>
<div id="schedule-builder" class="mt-4">
<!-- Interval-based scheduling -->
<div id="interval-section" class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border border-gray-200 dark:border-gray-600">
<div class="flex items-center gap-4">
<div class="w-1/3">
<label for="interval-value" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Run every</label>
<input
type="number"
id="interval-value"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="1"
value="15"
/>
</div>
<div class="w-2/3">
<label for="interval-unit" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Unit</label>
<select
id="interval-unit"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
<option value="days">Days</option>
</select>
</div>
</div>
</div>
<!-- Daily scheduling -->
<div id="daily-section" class="hidden bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border border-gray-200 dark:border-gray-600">
<div class="flex items-end gap-4">
<div class="w-1/2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Run daily at</label>
<div class="flex gap-2">
<div class="w-1/2">
<input
type="number"
id="daily-hour"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="23"
placeholder="HH"
value="0"
/>
</div>
<div class="flex items-center">:</div>
<div class="w-1/2">
<input
type="number"
id="daily-minute"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="59"
placeholder="MM"
value="0"
/>
</div>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 pb-2.5">
24-hour format (00:00 - 23:59)
</div>
</div>
</div>
<!-- Weekly scheduling -->
<div id="weekly-section" class="hidden bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border border-gray-200 dark:border-gray-600">
<div class="mb-4">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Run on these days</label>
<div class="flex flex-wrap gap-2">
<div class="flex items-center">
<input type="checkbox" id="day-0" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="day-0" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Sun</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="day-1" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="day-1" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Mon</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="day-2" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="day-2" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Tue</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="day-3" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="day-3" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Wed</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="day-4" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="day-4" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Thu</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="day-5" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="day-5" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Fri</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="day-6" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="day-6" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Sat</label>
</div>
</div>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">At this time</label>
<div class="flex items-end gap-4">
<div class="w-1/2">
<div class="flex gap-2">
<div class="w-1/2">
<input
type="number"
id="weekly-hour"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="23"
placeholder="HH"
value="0"
/>
</div>
<div class="flex items-center">:</div>
<div class="w-1/2">
<input
type="number"
id="weekly-minute"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="59"
placeholder="MM"
value="0"
/>
</div>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 pb-2.5">
24-hour format (00:00 - 23:59)
</div>
</div>
</div>
</div>
<!-- Monthly scheduling -->
<div id="monthly-section" class="hidden bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border border-gray-200 dark:border-gray-600">
<div class="mb-4">
<label for="monthly-day" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Day of month</label>
<input
type="number"
id="monthly-day"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="1"
max="31"
value="1"
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">At this time</label>
<div class="flex items-end gap-4">
<div class="w-1/2">
<div class="flex gap-2">
<div class="w-1/2">
<input
type="number"
id="monthly-hour"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="23"
placeholder="HH"
value="0"
/>
</div>
<div class="flex items-center">:</div>
<div class="w-1/2">
<input
type="number"
id="monthly-minute"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="59"
placeholder="MM"
value="0"
/>
</div>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 pb-2.5">
24-hour format (00:00 - 23:59)
</div>
</div>
</div>
</div>
</div>
<!-- Hidden cron input field that will be submitted -->
<div class="relative mt-4">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<i class="fas fa-clock text-gray-500 dark:text-gray-400"></i>
</div>
@@ -324,11 +765,12 @@ templ JobForm(ctx context.Context, data JobFormData) {
required
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="*/15 * * * *"
value="*/15 * * * *"
/>
</div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
<i class="fas fa-info-circle mr-1"></i>
Use standard cron expression format. Example: */15 * * * * (every 15 minutes)
The schedule will be converted to a cron expression. <a href="https://crontab.guru/" target="_blank" class="font-medium underline hover:no-underline">Learn more</a>
</p>
</div>
@@ -471,17 +913,217 @@ templ JobForm(ctx context.Context, data JobFormData) {
<!-- Schedule field -->
<div class="mb-6">
<label for="schedule" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Schedule (Cron Expression)
<label for="edit-schedule-type" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Schedule Type
</label>
<div class="relative">
<select
id="edit-schedule-type"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<option value="interval">Run at intervals</option>
<option value="daily">Run daily</option>
<option value="weekly">Run weekly</option>
<option value="monthly">Run monthly</option>
<option value="custom">Custom (Cron expression)</option>
</select>
<div id="edit-schedule-builder" class="mt-4">
<!-- Interval-based scheduling -->
<div id="edit-interval-section" class="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border border-gray-200 dark:border-gray-600">
<div class="flex items-center gap-4">
<div class="w-1/3">
<label for="edit-interval-value" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Run every</label>
<input
type="number"
id="edit-interval-value"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="1"
value="15"
/>
</div>
<div class="w-2/3">
<label for="edit-interval-unit" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Unit</label>
<select
id="edit-interval-unit"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
>
<option value="minutes">Minutes</option>
<option value="hours">Hours</option>
<option value="days">Days</option>
</select>
</div>
</div>
</div>
<!-- Daily scheduling -->
<div id="edit-daily-section" class="hidden bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border border-gray-200 dark:border-gray-600">
<div class="flex items-end gap-4">
<div class="w-1/2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Run daily at</label>
<div class="flex gap-2">
<div class="w-1/2">
<input
type="number"
id="edit-daily-hour"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="23"
placeholder="HH"
value="0"
/>
</div>
<div class="flex items-center">:</div>
<div class="w-1/2">
<input
type="number"
id="edit-daily-minute"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="59"
placeholder="MM"
value="0"
/>
</div>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 pb-2.5">
24-hour format (00:00 - 23:59)
</div>
</div>
</div>
<!-- Weekly scheduling -->
<div id="edit-weekly-section" class="hidden bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border border-gray-200 dark:border-gray-600">
<div class="mb-4">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Run on these days</label>
<div class="flex flex-wrap gap-2">
<div class="flex items-center">
<input type="checkbox" id="edit-day-0" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="edit-day-0" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Sun</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="edit-day-1" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="edit-day-1" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Mon</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="edit-day-2" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="edit-day-2" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Tue</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="edit-day-3" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="edit-day-3" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Wed</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="edit-day-4" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="edit-day-4" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Thu</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="edit-day-5" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="edit-day-5" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Fri</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="edit-day-6" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
<label for="edit-day-6" class="ms-2 text-sm font-medium text-gray-900 dark:text-white">Sat</label>
</div>
</div>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">At this time</label>
<div class="flex items-end gap-4">
<div class="w-1/2">
<div class="flex gap-2">
<div class="w-1/2">
<input
type="number"
id="edit-weekly-hour"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="23"
placeholder="HH"
value="0"
/>
</div>
<div class="flex items-center">:</div>
<div class="w-1/2">
<input
type="number"
id="edit-weekly-minute"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="59"
placeholder="MM"
value="0"
/>
</div>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 pb-2.5">
24-hour format (00:00 - 23:59)
</div>
</div>
</div>
</div>
<!-- Monthly scheduling -->
<div id="edit-monthly-section" class="hidden bg-gray-50 dark:bg-gray-700 p-4 rounded-lg border border-gray-200 dark:border-gray-600">
<div class="mb-4">
<label for="edit-monthly-day" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Day of month</label>
<input
type="number"
id="edit-monthly-day"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="1"
max="31"
value="1"
/>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">At this time</label>
<div class="flex items-end gap-4">
<div class="w-1/2">
<div class="flex gap-2">
<div class="w-1/2">
<input
type="number"
id="edit-monthly-hour"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="23"
placeholder="HH"
value="0"
/>
</div>
<div class="flex items-center">:</div>
<div class="w-1/2">
<input
type="number"
id="edit-monthly-minute"
class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
min="0"
max="59"
placeholder="MM"
value="0"
/>
</div>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 pb-2.5">
24-hour format (00:00 - 23:59)
</div>
</div>
</div>
</div>
</div>
<!-- Hidden cron input field that will be submitted -->
<div class="relative mt-4">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<i class="fas fa-clock text-gray-500 dark:text-gray-400"></i>
</div>
<input
type="text"
name="schedule"
id="schedule"
id="edit-schedule"
value={ data.Job.Schedule }
required
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
@@ -490,7 +1132,7 @@ templ JobForm(ctx context.Context, data JobFormData) {
</div>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
<i class="fas fa-info-circle mr-1"></i>
Use standard cron expression format. Example: */15 * * * * (every 15 minutes)
The schedule will be converted to a cron expression. <a href="https://crontab.guru/" target="_blank" class="font-medium underline hover:no-underline">Learn more</a>
</p>
</div>
@@ -614,7 +1256,7 @@ templ JobForm(ctx context.Context, data JobFormData) {
<div class="ml-4">
<h5 class="text-sm font-medium text-gray-900 dark:text-white">Scheduling Tips</h5>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Need help with cron expressions? Try <a href="https://crontab.guru/" target="_blank" class="font-medium underline hover:no-underline">crontab.guru</a> for a visual editor. Jobs can run multiple configurations in sequence, useful for multi-step transfer workflows.
Choose from simple interval, daily, weekly, or monthly schedules. For advanced scheduling needs, select "Custom" and use cron expression format. Jobs can run multiple configurations in sequence, useful for multi-step transfer workflows.
</p>
</div>
</div>
+4 -4
View File
@@ -143,18 +143,18 @@ func (h *Handlers) BroadcastLog(level, message, source string) {
// Only attempt to write to channel if there are clients
if numClients > 0 {
// *** DEBUG: Print channel send attempt ***
fmt.Fprintf(os.Stderr, "[DEBUG-BROADCASTLOG-V4] Attempting to send to LogChannel: Level='%s', Source='%s'\n", level, source)
fmt.Fprintf(os.Stderr, "[DEBUG-BROADCASTLOG] Attempting to send to LogChannel: Level='%s', Source='%s'\n", level, source)
// Try to send the log entry to the channel with a timeout
select {
case LogChannel <- logEntry:
// Successfully sent
fmt.Fprintf(os.Stderr, "[DEBUG-BROADCASTLOG-V4] Successfully sent to LogChannel.\n")
fmt.Fprintf(os.Stderr, "[DEBUG-BROADCASTLOG] Successfully sent to LogChannel.\n")
case <-time.After(100 * time.Millisecond):
// Channel is full or blocked, log and continue
fmt.Fprintf(os.Stderr, "[DEBUG-BROADCASTLOG-V4] Log channel timeout, discarding log entry: %s\n", message)
fmt.Fprintf(os.Stderr, "[DEBUG-BROADCASTLOG] Log channel timeout, discarding log entry: %s\n", message)
}
} else {
fmt.Fprintf(os.Stderr, "[DEBUG-BROADCASTLOG-V4] No clients connected, skipping send to LogChannel.\n")
fmt.Fprintf(os.Stderr, "[DEBUG-BROADCASTLOG] No clients connected, skipping send to LogChannel.\n")
}
}