feat(scheduler): add Scheduled Jobs UI

- scheduler-ui.js: job list + create/edit form + Run Now/Enable/Disable/Delete,
  reacting to scheduler:* SSE events; same-agent Run Now warning
- index.html: "⏰ Schedules" toolbar button + #schedulerModal + script include
- constants.js / app.js: frontend SSE event constants + handler map entries
- styles.css: scheduler row/badge/form styles

Follows Codeman's vanilla-JS mixin + .modal/.form-row conventions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rp7JhmQXcYJhmxFMdZuuah
This commit is contained in:
Kris
2026-06-27 08:15:36 +05:30
co-authored by Claude Opus 4.8
parent 6ae86b53f6
commit 2d2f4e592b
5 changed files with 431 additions and 0 deletions
+91
View File
@@ -541,6 +541,7 @@
<div class="toolbar-right">
<!-- Orchestrator button hidden until feature is ready -->
<!-- <button class="btn-toolbar btn-sm" onclick="app.toggleOrchestratorPanel()" title="Orchestrator Loop">&#x2699; Orchestrator</button> -->
<button class="btn-toolbar btn-sm" onclick="app.openScheduler()" title="Scheduled Jobs">&#x23F0; Schedules</button>
<span class="version-display" id="versionDisplay" title="Codeman version">v0.0.0</span>
</div>
</footer>
@@ -570,6 +571,95 @@
</div>
</div>
<!-- Scheduled Jobs Modal (cron-style scheduler) -->
<div class="modal" id="schedulerModal">
<div class="modal-backdrop" onclick="app.closeScheduler()"></div>
<div class="modal-content modal-lg">
<div class="modal-header">
<h3>Scheduled Jobs</h3>
<button class="modal-close" onclick="app.closeScheduler()" aria-label="Close scheduler">&times;</button>
</div>
<div class="modal-body">
<p class="form-hint" style="margin-bottom:10px;">Times use the server's local timezone.</p>
<div style="margin-bottom:10px;">
<button class="btn-toolbar btn-sm btn-primary" onclick="app.openSchedulerJobForm()">+ New Job</button>
<button class="btn-toolbar btn-sm" onclick="app.refreshScheduler()">Refresh</button>
</div>
<!-- Job list -->
<div id="schedulerJobList" class="scheduler-job-list"></div>
<!-- Create/Edit form (hidden until New/Edit) -->
<div id="schedulerJobForm" class="scheduler-job-form hidden">
<div class="form-section-header" id="schedulerFormTitle">New Scheduled Job</div>
<input type="hidden" id="schJobId">
<div class="form-row"><label>Name</label><input type="text" id="schName" placeholder="My nightly job"></div>
<div class="form-row"><label>Agent Type</label>
<select id="schAgentType">
<option value="claude">Claude</option>
<option value="shell">Terminal / Shell</option>
<option value="opencode">OpenCode</option>
<option value="codex">Codex</option>
<option value="gemini">Gemini</option>
</select>
</div>
<div class="form-row"><label>Working Directory</label><input type="text" id="schWorkingDir" placeholder="/absolute/path"></div>
<div class="form-row"><label>Prompt Source</label>
<select id="schPromptMode" onchange="app.onSchedulerPromptModeChange()">
<option value="inline_text">Inline text</option>
<option value="prompt_file_path">Prompt file path</option>
</select>
</div>
<div class="form-row" id="schPromptTextRow"><label>Prompt</label><textarea id="schPromptText" rows="4" placeholder="Prompt to send into the session"></textarea></div>
<div class="form-row hidden" id="schPromptFileRow"><label>Prompt File Path</label><input type="text" id="schPromptFilePath" placeholder="/absolute/path/to/prompt.md"></div>
<div class="form-row"><label>Input Mode</label>
<select id="schInputMode">
<option value="typed">Typed (via tmux)</option>
<option value="paste">Paste (direct)</option>
</select>
</div>
<div class="form-row"><label>Schedule Type</label>
<select id="schScheduleType" onchange="app.onSchedulerScheduleTypeChange()">
<option value="once">Once</option>
<option value="interval">Interval</option>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
</select>
</div>
<div class="form-row" id="schRunAtRow"><label>Run At</label><input type="datetime-local" id="schRunAt"></div>
<div class="form-row hidden" id="schIntervalRow"><label>Every (minutes)</label><input type="number" id="schIntervalMinutes" min="1" value="60"></div>
<div class="form-row hidden" id="schDailyRow"><label>Daily Time (HH:MM)</label><input type="time" id="schDailyTime"></div>
<div class="form-row hidden" id="schWeeklyDaysRow"><label>Weekdays</label>
<span id="schWeeklyDays" class="scheduler-weekdays">
<label><input type="checkbox" value="0">Sun</label>
<label><input type="checkbox" value="1">Mon</label>
<label><input type="checkbox" value="2">Tue</label>
<label><input type="checkbox" value="3">Wed</label>
<label><input type="checkbox" value="4">Thu</label>
<label><input type="checkbox" value="5">Fri</label>
<label><input type="checkbox" value="6">Sat</label>
</span>
</div>
<div class="form-row hidden" id="schWeeklyTimeRow"><label>Weekly Time (HH:MM)</label><input type="time" id="schWeeklyTime"></div>
<div class="form-row"><label>On auto-run, if same agent running</label>
<select id="schConcurrencyPolicy">
<option value="warn_only">Run anyway</option>
<option value="skip_if_same_agent_running">Skip this run</option>
</select>
</div>
<div class="form-row form-row-switch"><label>Enabled</label>
<label class="switch"><input type="checkbox" id="schEnabled" checked><span class="slider"></span></label>
</div>
<div class="form-row"><label>Notes</label><input type="text" id="schNotes" placeholder="Optional"></div>
<div id="schedulerFormError" class="form-hint" style="color:var(--danger,#e55);"></div>
<div style="margin-top:10px;">
<button class="btn-toolbar btn-sm btn-primary" onclick="app.saveSchedulerJob()">Save</button>
<button class="btn-toolbar btn-sm" onclick="app.cancelSchedulerJobForm()">Cancel</button>
</div>
</div>
</div>
</div>
</div>
<!-- Monitor Panel (Tmux Sessions + Background Tasks) -->
<div class="monitor-panel" id="monitorPanel">
<div class="monitor-panel-header" id="monitorPanelHeader">
@@ -2056,6 +2146,7 @@
<script defer src="respawn-ui.js"></script>
<script defer src="ralph-panel.js"></script>
<script defer src="orchestrator-panel.js"></script>
<script defer src="scheduler-ui.js"></script>
<script defer src="settings-ui.js"></script>
<script defer src="panels-ui.js"></script>
<script defer src="ultracode-panel.js"></script>