fix: use psycopg2.sql for safe database drop and create commands
fix: Also check POSTGRES_SSL is true before skipping default POSTGRES_PASSWORD
fix: update usage comment to reflect correct script path
fix: improve cleanup logic to conditionally remove certificate directory
Bug #954: The initialize_superuser endpoint only checked is_superuser=True, missing admin accounts created via the API (user_level=10). This caused users to intermittently see the "Create Super User" page instead of login.
Feature #1004: Admins can now disable/enable user accounts. Disabled users are blocked from JWT login, token refresh, XC API access, and all
authenticated endpoints. The last active admin cannot be disabled.
Files changed:
Backend:
- apps/accounts/api_views.py — Superuser check uses user_level>=10; token refresh blocks disabled users
- apps/accounts/models.py — CustomUserManager ensures create_superuser() always sets user_level=10
- apps/accounts/serializers.py — Last-admin protection guard; respect is_active on user creation
- apps/accounts/tests.py — new tests covering superuser detection, token
refresh blocking, last-admin protection, disabled user login/access
- apps/backups/api_views.py — Switched from DRF's IsAdminUser (checks is_staff) to app's IsAdmin (checks user_level) on all 8 endpoints
- apps/backups/tests.py — new tests verifying user_level-based admin permission on backup endpoints
- apps/output/views.py — is_active check on xc_get_user(), xc_movie_stream(), xc_series_stream()
- apps/proxy/ts_proxy/views.py — is_active check on stream_xc()
- core/api_views.py — Admin notification filter uses user_level>=10
- core/developer_notifications.py — Admin checks use user_level>=10
Frontend:
- frontend/src/App.jsx — Null-safe superuser existence check
- frontend/src/components/forms/User.jsx — Account Enabled switch with
self-disable prevention and tooltip
- frontend/src/components/tables/UsersTable.jsx — Status column (Active/Disabled badge)
Add cron expression support for M3U and EPG refreshes with interactive
builder modal and quick reference examples. Refactor backup scheduling
to use shared ScheduleInput component for consistency.
- New CronBuilder modal with preset buttons and custom field editors
- Info popover with common cron expression examples
- Shared ScheduleInput component for interval/cron toggle
The test expected enabled=False and retention_count=0, but commit
5371519d intentionally changed these defaults to enabled=True and
retention_count=3. Update test to match the intended behavior.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add _cleanup_orphaned_crontab() helper function
- Delete old crontab when disabling backup schedule
- Delete old crontab when schedule settings change
- Prevents database bloat from unused CrontabSchedule records
- 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
- Set CrontabSchedule timezone to system timezone for accurate scheduling
- Replace time TextInput with hour/minute Select dropdowns for cleaner UX
- Remove UTC/local time conversion logic (handled by Celery)
- Add tests for timezone functionality in simple and advanced modes
Updated create_backup to use the system's configured timezone for backup
filenames instead of always using UTC. This makes filenames more intuitive
and matches users' local time expectations.
Changes:
- Import pytz and CoreSettings
- Get system timezone from CoreSettings.get_system_time_zone()
- Convert current UTC time to system timezone for filename timestamp
- Fallback to UTC if timezone conversion fails
- Internal metadata timestamps remain UTC for consistency
Example:
- System timezone: America/New_York (EST)
- Created at 3:00 PM EST
- Old filename: dispatcharr-backup-2025.12.09.20.00.00.zip (UTC time)
- New filename: dispatcharr-backup-2025.12.09.15.00.00.zip (local time)
This aligns with the timezone-aware scheduling already implemented.
The list_backups function was creating timezone-naive datetime objects,
which caused the frontend to incorrectly interpret timestamps.
Now uses datetime.UTC when creating timestamps from file modification time
(consistent with other usage in this file on lines 186, 216), so the ISO
string includes timezone info (+00:00). This allows the browser to properly
convert UTC timestamps to the user's local timezone for display.
Before: Backend sends "2025-12-09T14:12:44" (ambiguous timezone)
After: Backend sends "2025-12-09T14:12:44+00:00" (explicit UTC)
The frontend's toLocaleString() will now correctly convert to local time.
Frontend changes:
- Add advanced mode toggle switch for cron expressions
- Show cron expression input with helpful examples when enabled
- Display format hints: "minute hour day month weekday"
- Provide common examples (daily, weekly, every 6 hours, etc.)
- Conditionally render simple or advanced scheduling UI
- Support switching between simple and advanced modes
Backend changes:
- Add cron_expression to schedule settings (SETTING_KEYS, DEFAULTS)
- Update get_schedule_settings to include cron_expression
- Update update_schedule_settings to handle cron_expression
- Extend _sync_periodic_task to parse and use cron expressions
- Parse 5-part cron format: minute hour day_of_month month_of_year day_of_week
- Create CrontabSchedule from cron expression or simple frequency
- Add validation and error handling for invalid cron expressions
This addresses maintainer feedback for "custom scheduler (cron style) for more control".
Users can now schedule backups with full cron flexibility beyond daily/weekly.
- Simplify to database-only backups (remove data directory backup)
- Update UI to match app styling patterns:
- Use ActionIcon with transparent variant for table actions
- Match icon/color conventions (SquareMinus/red.9, RotateCcw/yellow.5, Download/blue.5)
- Use standard button bar layout with Paper/Box/Flex
- Green "Create Backup" button matching "Add" pattern
- Remove Card wrapper, Alert, and Divider for cleaner layout
- Update to Mantine v8 Table syntax
- Use standard ConfirmationDialog (remove unused color prop)
- Update tests to remove get_data_dirs references