Reorganize simple mode backup scheduler layout

- Row 1: Frequency, Day (if weekly), Hour, Minute, Period (if 12h)
- Row 2: Retention, Save button
- Use wrap=nowrap to keep time selectors on same row
This commit is contained in:
Jim McBride 2025-12-13 18:49:36 -06:00
parent 1dc7700a62
commit 662c5ff89a
2 changed files with 139 additions and 100 deletions

View file

@ -1016,6 +1016,41 @@ class BackupSchedulerTestCase(TestCase):
scheduler.update_schedule_settings({'enabled': False})
CoreSettings.set_system_time_zone(original_tz)
def test_orphaned_crontab_cleanup(self):
"""Test that old CrontabSchedule is deleted when schedule changes"""
from . import scheduler
from django_celery_beat.models import PeriodicTask, CrontabSchedule
# Create initial daily schedule
scheduler.update_schedule_settings({
'enabled': True,
'frequency': 'daily',
'time': '03:00',
})
task = PeriodicTask.objects.get(name='backup-scheduled-task')
first_crontab_id = task.crontab.id
initial_count = CrontabSchedule.objects.count()
# Change to weekly schedule (different crontab)
scheduler.update_schedule_settings({
'enabled': True,
'frequency': 'weekly',
'day_of_week': 3,
'time': '03:00',
})
task.refresh_from_db()
second_crontab_id = task.crontab.id
# Verify old crontab was deleted
self.assertNotEqual(first_crontab_id, second_crontab_id)
self.assertFalse(CrontabSchedule.objects.filter(id=first_crontab_id).exists())
self.assertEqual(CrontabSchedule.objects.count(), initial_count)
# Cleanup
scheduler.update_schedule_settings({'enabled': False})
class BackupTasksTestCase(TestCase):
"""Test cases for backup Celery tasks"""

View file

@ -618,116 +618,120 @@ export default function BackupManager() {
</Group>
</>
) : (
<Group grow align="flex-end">
<Select
label="Frequency"
value={schedule.frequency}
onChange={(value) => handleScheduleChange('frequency', value)}
data={[
{ value: 'daily', label: 'Daily' },
{ value: 'weekly', label: 'Weekly' },
]}
disabled={!schedule.enabled}
/>
{schedule.frequency === 'weekly' && (
<Select
label="Day"
value={String(schedule.day_of_week)}
onChange={(value) => handleScheduleChange('day_of_week', parseInt(value, 10))}
data={DAYS_OF_WEEK}
disabled={!schedule.enabled}
/>
)}
{is12Hour ? (
<Group grow align="flex-end" gap="xs">
<Stack gap="sm">
<Group align="flex-end" gap="xs" wrap="nowrap">
<Select
label="Hour"
value={displayTime ? displayTime.split(':')[0] : '12'}
onChange={(value) => {
const minute = displayTime ? displayTime.split(':')[1] : '00';
handleTimeChange12h(`${value}:${minute}`, null);
}}
data={Array.from({ length: 12 }, (_, i) => ({
value: String(i + 1),
label: String(i + 1),
}))}
disabled={!schedule.enabled}
searchable
/>
<Select
label="Minute"
value={displayTime ? displayTime.split(':')[1] : '00'}
onChange={(value) => {
const hour = displayTime ? displayTime.split(':')[0] : '12';
handleTimeChange12h(`${hour}:${value}`, null);
}}
data={Array.from({ length: 60 }, (_, i) => ({
value: String(i).padStart(2, '0'),
label: String(i).padStart(2, '0'),
}))}
disabled={!schedule.enabled}
searchable
/>
<Select
label="Period"
value={timePeriod}
onChange={(value) => handleTimeChange12h(null, value)}
label="Frequency"
value={schedule.frequency}
onChange={(value) => handleScheduleChange('frequency', value)}
data={[
{ value: 'AM', label: 'AM' },
{ value: 'PM', label: 'PM' },
{ value: 'daily', label: 'Daily' },
{ value: 'weekly', label: 'Weekly' },
]}
disabled={!schedule.enabled}
/>
{schedule.frequency === 'weekly' && (
<Select
label="Day"
value={String(schedule.day_of_week)}
onChange={(value) => handleScheduleChange('day_of_week', parseInt(value, 10))}
data={DAYS_OF_WEEK}
disabled={!schedule.enabled}
/>
)}
{is12Hour ? (
<>
<Select
label="Hour"
value={displayTime ? displayTime.split(':')[0] : '12'}
onChange={(value) => {
const minute = displayTime ? displayTime.split(':')[1] : '00';
handleTimeChange12h(`${value}:${minute}`, null);
}}
data={Array.from({ length: 12 }, (_, i) => ({
value: String(i + 1),
label: String(i + 1),
}))}
disabled={!schedule.enabled}
searchable
/>
<Select
label="Minute"
value={displayTime ? displayTime.split(':')[1] : '00'}
onChange={(value) => {
const hour = displayTime ? displayTime.split(':')[0] : '12';
handleTimeChange12h(`${hour}:${value}`, null);
}}
data={Array.from({ length: 60 }, (_, i) => ({
value: String(i).padStart(2, '0'),
label: String(i).padStart(2, '0'),
}))}
disabled={!schedule.enabled}
searchable
/>
<Select
label="Period"
value={timePeriod}
onChange={(value) => handleTimeChange12h(null, value)}
data={[
{ value: 'AM', label: 'AM' },
{ value: 'PM', label: 'PM' },
]}
disabled={!schedule.enabled}
/>
</>
) : (
<>
<Select
label="Hour"
value={schedule.time ? schedule.time.split(':')[0] : '00'}
onChange={(value) => {
const minute = schedule.time ? schedule.time.split(':')[1] : '00';
handleTimeChange24h(`${value}:${minute}`);
}}
data={Array.from({ length: 24 }, (_, i) => ({
value: String(i).padStart(2, '0'),
label: String(i).padStart(2, '0'),
}))}
disabled={!schedule.enabled}
searchable
/>
<Select
label="Minute"
value={schedule.time ? schedule.time.split(':')[1] : '00'}
onChange={(value) => {
const hour = schedule.time ? schedule.time.split(':')[0] : '00';
handleTimeChange24h(`${hour}:${value}`);
}}
data={Array.from({ length: 60 }, (_, i) => ({
value: String(i).padStart(2, '0'),
label: String(i).padStart(2, '0'),
}))}
disabled={!schedule.enabled}
searchable
/>
</>
)}
</Group>
) : (
<Group grow align="flex-end" gap="xs">
<Select
label="Hour"
value={schedule.time ? schedule.time.split(':')[0] : '00'}
onChange={(value) => {
const minute = schedule.time ? schedule.time.split(':')[1] : '00';
handleTimeChange24h(`${value}:${minute}`);
}}
data={Array.from({ length: 24 }, (_, i) => ({
value: String(i).padStart(2, '0'),
label: String(i).padStart(2, '0'),
}))}
<NumberInput
label="Retention"
description="0 = keep all"
value={schedule.retention_count}
onChange={(value) => handleScheduleChange('retention_count', value || 0)}
min={0}
disabled={!schedule.enabled}
searchable
/>
<Select
label="Minute"
value={schedule.time ? schedule.time.split(':')[1] : '00'}
onChange={(value) => {
const hour = schedule.time ? schedule.time.split(':')[0] : '00';
handleTimeChange24h(`${hour}:${value}`);
}}
data={Array.from({ length: 60 }, (_, i) => ({
value: String(i).padStart(2, '0'),
label: String(i).padStart(2, '0'),
}))}
disabled={!schedule.enabled}
searchable
/>
<Button
onClick={handleSaveSchedule}
loading={scheduleSaving}
disabled={!scheduleChanged}
variant="default"
>
Save
</Button>
</Group>
)}
<NumberInput
label="Retention"
description="0 = keep all"
value={schedule.retention_count}
onChange={(value) => handleScheduleChange('retention_count', value || 0)}
min={0}
disabled={!schedule.enabled}
/>
<Button
onClick={handleSaveSchedule}
loading={scheduleSaving}
disabled={!scheduleChanged}
variant="default"
>
Save
</Button>
</Group>
</Stack>
)}
{/* Timezone info - only show in simple mode */}