// schedules.js — 예약 작업(cron) 목록/생성/수정/삭제. // extracted from app.js (v4.3.19-3 split). let schedules = []; let editingScheduleId = null; async function refreshSchedules() { try { const result = await api('/api/schedules'); if (result.success && Array.isArray(result.schedules)) { schedules = result.schedules; renderScheduleList(); } } catch (err) { console.error('Failed to load schedules:', err); } } function renderScheduleList() { const list = document.getElementById('schedule-list'); const count = document.getElementById('schedule-count'); if (!list) return; if (schedules.length === 0) { list.innerHTML = '
No schedules yet. + 새 일정 to get started.
'; if (count) count.textContent = '0 schedules'; return; } if (count) count.textContent = `${schedules.length} schedule${schedules.length !== 1 ? 's' : ''}`; list.innerHTML = schedules.map(job => { const nextRun = job.next_run ? new Date(job.next_run).toLocaleString() : 'Never'; const lastRun = job.last_run ? new Date(job.last_run).toLocaleString() : 'Never'; const statusClass = job.status === 'active' ? 'status-active' : job.status === 'paused' ? 'status-paused' : 'status-failed'; return `
${job.name || 'Untitled'}
${job.status || 'unknown'}
${job.prompt ? job.prompt.slice(0, 60) + (job.prompt.length > 60 ? '...' : '') : 'No prompt'}
Next: ${nextRun}
Last: ${lastRun}
`; }).join(''); } function openScheduleCreateModal() { editingScheduleId = null; document.getElementById('schedule-modal-title').textContent = '일정 생성'; document.getElementById('schedule-save-btn').textContent = '일정 생성'; document.getElementById('schedule-name').value = ''; document.getElementById('schedule-pattern').value = ''; document.getElementById('schedule-prompt').value = ''; document.getElementById('schedule-timezone').value = 'UTC'; document.getElementById('schedule-channel').value = 'web'; document.getElementById('schedule-pattern-preview').style.display = 'none'; document.getElementById('schedule-modal').style.display = 'flex'; } function editSchedule(jobId) { const job = schedules.find(j => j.id === jobId); if (!job) return; editingScheduleId = jobId; document.getElementById('schedule-modal-title').textContent = '일정 편집'; document.getElementById('schedule-save-btn').textContent = '변경 저장'; document.getElementById('schedule-name').value = job.name || ''; document.getElementById('schedule-pattern').value = job.cron || job.run_at || ''; document.getElementById('schedule-prompt').value = job.prompt || ''; document.getElementById('schedule-timezone').value = job.timezone || 'UTC'; document.getElementById('schedule-channel').value = job.delivery_channel || 'web'; document.getElementById('schedule-pattern-preview').style.display = 'none'; document.getElementById('schedule-modal').style.display = 'flex'; } function closeScheduleModal() { document.getElementById('schedule-modal').style.display = 'none'; editingScheduleId = null; } async function parseSchedulePattern() { const pattern = document.getElementById('schedule-pattern').value.trim(); const timezone = document.getElementById('schedule-timezone').value; if (!pattern) { alert('Enter a schedule pattern (e.g., "daily at 09:00" or "0 9 * * *")'); return; } try { const result = await api('/api/schedules/parse', { method: 'POST', body: JSON.stringify({ text: pattern, timezone }) }); const preview = document.getElementById('schedule-pattern-preview'); if (result.success) { preview.textContent = `✓ ${result.preview || result.human_text || 'Valid pattern'}`; preview.style.display = 'block'; preview.style.color = 'var(--muted)'; } else { preview.textContent = `⚠ ${result.error || 'Invalid pattern'}`; preview.style.display = 'block'; preview.style.color = '#ff6b6b'; } } catch (err) { document.getElementById('schedule-pattern-preview').textContent = '⚠ Parse failed'; document.getElementById('schedule-pattern-preview').style.display = 'block'; document.getElementById('schedule-pattern-preview').style.color = '#ff6b6b'; } } async function saveSchedule() { const name = document.getElementById('schedule-name').value.trim(); const pattern = document.getElementById('schedule-pattern').value.trim(); const prompt = document.getElementById('schedule-prompt').value.trim(); const timezone = document.getElementById('schedule-timezone').value; const channel = document.getElementById('schedule-channel').value; if (!name) return alert('Schedule name is required'); if (!pattern) return alert('Schedule pattern is required'); if (!prompt) return alert('Prompt/action is required'); const method = editingScheduleId ? 'PUT' : 'POST'; const path = editingScheduleId ? `/api/schedules/${editingScheduleId}` : '/api/schedules'; try { const result = await api(path, { method, body: JSON.stringify({ name, pattern, prompt, timezone, delivery_channel: channel, confirm: true // Confirmation (as per scheduler rules) }) }); if (result.success) { closeScheduleModal(); await refreshSchedules(); } else { alert('Error: ' + (result.error || 'Unknown error')); } } catch (err) { alert('Failed to save schedule: ' + err.message); } } async function deleteSchedule(jobId) { if (!confirm('Delete this schedule? This action cannot be undone.')) return; try { const result = await api(`/api/schedules/${jobId}`, { method: 'DELETE', body: JSON.stringify({ confirm: true }) }); if (result.success) { await refreshSchedules(); } else { alert('Error: ' + (result.error || 'Failed to delete')); } } catch (err) { alert('Failed to delete schedule: ' + err.message); } } async function toggleSchedulePause(jobId) { const job = schedules.find(j => j.id === jobId); if (!job) return; const newStatus = job.status === 'active' ? 'paused' : 'active'; try { const result = await api(`/api/schedules/${jobId}`, { method: 'PATCH', body: JSON.stringify({ status: newStatus }) }); if (result.success) { await refreshSchedules(); } else { alert('Error: ' + (result.error || 'Failed to update')); } } catch (err) { alert('Failed to toggle pause: ' + err.message); } } async function runScheduleNow(jobId) { try { const result = await api(`/api/schedules/${jobId}/run`, { method: 'POST', body: JSON.stringify({}) }); if (result.success) { await refreshSchedules(); alert('Schedule running now...'); } else { alert('Error: ' + (result.error || 'Failed to run')); } } catch (err) { alert('Failed to run schedule: ' + err.message); } }