feat: cron-based scheduler with auto-download after rescan
- Replace asyncio sleep loop with APScheduler AsyncIOScheduler + CronTrigger
- Add schedule_time (HH:MM), schedule_days (days of week), auto_download_after_rescan fields to SchedulerConfig
- Add _auto_download_missing() to queue missing episodes after rescan
- Reload config live via reload_config(SchedulerConfig) without restart
- Update GET/POST /api/scheduler/config to return {success, config, status} envelope
- Add day-of-week pill toggles to Settings -> Scheduler section in UI
- Update JS loadSchedulerConfig / saveSchedulerConfig for new API shape
- Add 29 unit tests for SchedulerConfig model, 18 unit tests for SchedulerService
- Rewrite 23 endpoint tests and 36 integration tests for APScheduler behaviour
- Coverage: 96% api/scheduler, 95% scheduler_service, 90% total (>= 80% threshold)
- Update docs: API.md, CONFIGURATION.md, features.md, CHANGELOG.md
This commit is contained in:
@@ -228,3 +228,122 @@
|
||||
font-size: var(--font-size-title);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Scheduler day-of-week toggle pills
|
||||
============================================================ */
|
||||
|
||||
.scheduler-days-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.scheduler-day-toggle-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Hide the raw checkbox visually */
|
||||
.scheduler-day-toggle-label .scheduler-day-checkbox {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* Pill styling */
|
||||
.scheduler-day-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2.6rem;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-xl);
|
||||
font-size: var(--font-size-caption);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
background-color: var(--color-bg-secondary);
|
||||
transition: background-color var(--transition-duration) var(--transition-easing),
|
||||
color var(--transition-duration) var(--transition-easing),
|
||||
border-color var(--transition-duration) var(--transition-easing);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Checked state – filled accent */
|
||||
.scheduler-day-checkbox:checked + .scheduler-day-label {
|
||||
background-color: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Hover for unchecked */
|
||||
.scheduler-day-toggle-label:hover .scheduler-day-label {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Hover for checked */
|
||||
.scheduler-day-toggle-label:hover .scheduler-day-checkbox:checked + .scheduler-day-label {
|
||||
background-color: var(--color-accent-hover);
|
||||
border-color: var(--color-accent-hover);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Dark theme overrides */
|
||||
[data-theme="dark"] .scheduler-day-label {
|
||||
border-color: var(--color-border-dark);
|
||||
color: var(--color-text-secondary-dark);
|
||||
background-color: var(--color-bg-secondary-dark);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .scheduler-day-checkbox:checked + .scheduler-day-label {
|
||||
background-color: var(--color-accent-dark);
|
||||
border-color: var(--color-accent-dark);
|
||||
color: var(--color-bg-primary-dark);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .scheduler-day-toggle-label:hover .scheduler-day-label {
|
||||
border-color: var(--color-accent-dark);
|
||||
color: var(--color-accent-dark);
|
||||
}
|
||||
|
||||
/* Next run display */
|
||||
#scheduler-next-run {
|
||||
font-style: italic;
|
||||
font-size: var(--font-size-caption);
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] #scheduler-next-run {
|
||||
color: var(--color-text-tertiary-dark);
|
||||
}
|
||||
|
||||
/* Advanced/collapsible section */
|
||||
.config-advanced {
|
||||
font-size: var(--font-size-caption);
|
||||
color: var(--color-text-secondary);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.config-advanced summary {
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-xs) 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Responsive: wrap day pills to 2 rows on mobile */
|
||||
@media (max-width: 480px) {
|
||||
.scheduler-days-container {
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.scheduler-day-label {
|
||||
min-width: 2.2rem;
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user