// 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);
}
}