The scheduled tasks view carried an inline form above the list, so the page
was two things at once: the form was always on screen whether or not anyone
was editing, and the list it belonged to was pushed below the fold.
Make the view the list, and move adding and editing into a modal built by
web/ajax/modals/cronjob.php, following the pattern the Options modals use.
The Add button and the task name both open it; the modal posts back to
?view=cron, so includes/actions/cron.php still does the validating and the
crontab rebuild and did not have to change.
Two things follow from the form being built over ajax rather than with the
page. The modal emits getCSRFinputHTML() itself, since nothing else wraps it.
And cron.js binds the schedule pickers after the html is inserted instead of
on DOMContentLoaded: the controls do not exist until then, and a fresh set
arrives each time the modal opens.
While moving the When row, give the pickers a placeholder when they cannot
draw the stored schedule. Previously an existing task set to something like
"*/5 * * * *" rendered the pickers at their default, so the page showed
"Every day at 03:00" in large friendly controls beside a cron field reading
"*/5 * * * *" -- two different answers at once, with the wrong one the more
prominent. Adding a task still defaults to daily at 03:00, which is a default
rather than a contradiction.
Tests: tests/php/test_cron_modal.php, 42 checks. The field names are now a
contract between files that do not mention each other, so each side is parsed
out of its own source and compared rather than restated. The three states of
the When row are checked by rendering the modal against stubs, since which
picker is selected and whether the cron field is open are decisions the php
makes. Verified the new checks fail when the placeholder, a field name or the
csrf input is removed.
Ran: tests/php/test_cron_modal.php (42 passed), test_crontab.php (88 passed),
test_allowed_commands.php (31 passed), tests/js/cron-schedule.test.js (12
passed), the rest of tests/php and tests/js, npx eslint . (clean),
utils/check-homoglyphs.py (clean). test_bandwidth_clamp.php fails on this box
for an unrelated reason: it cannot read /etc/zm/conf.d/03custom.conf and the
permission warning lands in its captured output.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>