// ── Cronjob Page ──
registerPage('cronjob', {
async init(params) {
const isEdit = params && params.id;
const title = document.getElementById('cronjobPageTitle');
const nameInput = document.getElementById('cronjobName');
const sessionSelect = document.getElementById('cronjobSession');
const sessionCard = document.getElementById('cronjobSessionCard');
const cronInput = document.getElementById('cronjobCron');
const cronPreview = document.getElementById('cronjobCronPreview');
const promptInput = document.getElementById('cronjobPrompt');
const notifySelect = document.getElementById('cronjobNotifySession');
const notifyCard = document.getElementById('cronjobNotifySessionCard');
const enabledCheckbox = document.getElementById('cronjobEnabled');
const statusBox = document.getElementById('cronjobStatusBox');
const saveBtn = document.getElementById('cronjobSaveBtn');
const runBtn = document.getElementById('cronjobRunBtn');
const deleteBtn = document.getElementById('cronjobDeleteBtn');
// Sessions + Agents laden
const sessions = await apiGetSessions();
const agents = await apiGetAvailableAgents();
const agentMap = {};
for (const a of agents) agentMap[a.agentId] = a;
const sessionMap = {};
for (const s of sessions) sessionMap[s.sessionId] = s;
// Session-Card rendern
function renderSessionSelectCard(cardEl, session, emptyIcon, emptyText) {
const inner = session ? buildSessionCard(session, session.agentId ? agentMap[session.agentId] : null) : null;
_renderCardPreview(cardEl, inner, emptyIcon, emptyText, 'session-select-change');
}
// Session-Card click → Picker (nur Sessions mit Agent)
sessionCard.addEventListener('click', () => {
openSessionPickerModal(sessions, agentMap, sessionSelect.value, (selectedId) => {
sessionSelect.value = selectedId || '';
renderSessionSelectCard(sessionCard, selectedId ? sessionMap[selectedId] : null, 'fa-comment-o', 'Keine Session ausgewählt');
}, { filterWithAgent: true, title: 'Session auswählen' });
});
// Notify-Session-Card click → Picker (alle Sessions)
notifyCard.addEventListener('click', () => {
openSessionPickerModal(sessions, agentMap, notifySelect.value, (selectedId) => {
notifySelect.value = selectedId || '';
renderSessionSelectCard(notifyCard, selectedId ? sessionMap[selectedId] : null, 'fa-bell-o', 'Keine (Antwort bleibt in Arbeits-Session)');
}, { filterWithAgent: false, title: 'Benachrichtigungs-Session auswählen', noneLabel: 'Keine Benachrichtigung', noneIcon: 'fa-bell-slash' });
});
// Cron-Expression Vorschau
cronInput.addEventListener('input', () => updateCronPreview(cronInput, cronPreview));
if (isEdit) {
title.textContent = 'Cronjob bearbeiten';
runBtn.hidden = false;
deleteBtn.hidden = false;
const job = await apiGetCronjob(params.id);
if (!job) {
showToast('Cronjob nicht gefunden', 'error');
navigateTo('dashboard');
return;
}
nameInput.value = job.name || '';
sessionSelect.value = job.sessionId || '';
renderSessionSelectCard(sessionCard, job.sessionId ? sessionMap[job.sessionId] : null, 'fa-comment-o', 'Keine Session ausgewählt');
cronInput.value = job.cronExpression || '';
promptInput.value = job.prompt || '';
notifySelect.value = job.notifySessionId || '';
renderSessionSelectCard(notifyCard, job.notifySessionId ? sessionMap[job.notifySessionId] : null, 'fa-bell-o', 'Keine (Antwort bleibt in Arbeits-Session)');
enabledCheckbox.checked = job.isEnabled;
updateCronPreview(cronInput, cronPreview);
showCronjobStatus(statusBox, job);
// Löschen
deleteBtn.addEventListener('click', async () => {
if (!confirm('Cronjob wirklich löschen?')) return;
await apiDeleteCronjob(params.id);
showToast('Cronjob gelöscht');
navigateTo('dashboard');
});
// Jetzt ausführen
runBtn.addEventListener('click', () => withButtonLoading(runBtn, 'Läuft...', async () => {
try {
const result = await apiRunCronjob(params.id);
if (result.status === 'success') {
showToast('Cronjob erfolgreich ausgeführt');
} else {
showToast('Fehler: ' + (result.error || 'Unbekannt'), 'error');
}
const updated = await apiGetCronjob(params.id);
if (updated) showCronjobStatus(statusBox, updated);
} catch (e) {
showToast('Fehler: ' + e.message, 'error');
}
}));
}
// Speichern
saveBtn.addEventListener('click', async () => {
const data = {
name: nameInput.value.trim(),
sessionId: sessionSelect.value,
cronExpression: cronInput.value.trim(),
prompt: promptInput.value.trim(),
notifySessionId: notifySelect.value,
isEnabled: enabledCheckbox.checked
};
if (!data.name || !data.sessionId || !data.cronExpression || !data.prompt) {
showToast('Bitte alle Felder ausfüllen', 'error');
return;
}
await withButtonLoading(saveBtn, 'Speichern...', async () => {
try {
if (isEdit) {
await apiUpdateCronjob(params.id, data);
showToast('Cronjob gespeichert');
const updated = await apiGetCronjob(params.id);
if (updated) showCronjobStatus(statusBox, updated);
} else {
const created = await apiCreateCronjob(data);
showToast('Cronjob erstellt');
navigateTo('cronjob', { id: created.cronjobId });
}
} catch (e) {
showToast('Fehler: ' + e.message, 'error');
}
});
});
}
});
function openSessionPickerModal(sessions, agentMap, currentId, onSelect, options) {
options = options || {};
const filterWithAgent = options.filterWithAgent || false;
const filtered = filterWithAgent ? sessions.filter(s => s.agentId) : sessions;
const items = filtered.map(s => {
s._pickerId = s.sessionId;
return s;
});
_openPickerModal(options.title || 'Session auswählen', items, currentId, onSelect, {
noneLabel: options.noneLabel || 'Keine Session',
noneIcon: options.noneIcon || 'fa-ban',
buildCard: (s) => buildSessionCard(s, s.agentId ? agentMap[s.agentId] : null)
});
}
function updateCronPreview(input, preview) {
const expr = input.value.trim();
if (!expr) {
preview.textContent = '';
return;
}
const desc = describeCron(expr);
if (desc) {
preview.textContent = desc;
preview.style.color = '';
} else {
preview.textContent = 'Ungültige Cron-Expression';
preview.style.color = 'var(--color-error)';
}
}
function describeCron(expr) {
try {
const parts = expr.split(/\s+/);
if (parts.length < 5 || parts.length > 5) return null;
const [min, hour, dom, month, dow] = parts;
// Einfache Fälle beschreiben
const dowNames = { '0': 'So', '1': 'Mo', '2': 'Di', '3': 'Mi', '4': 'Do', '5': 'Fr', '6': 'Sa', '7': 'So' };
let desc = '';
const tz = 'Europe/Berlin';
if (dom === '*' && month === '*' && dow === '*') {
if (min !== '*' && hour !== '*') {
desc = `Täglich um ${hour.padStart(2, '0')}:${min.padStart(2, '0')} Uhr`;
} else if (hour === '*' && min === '0') {
desc = 'Jede Stunde';
} else if (hour === '*' && min === '*') {
desc = 'Jede Minute';
} else if (hour !== '*') {
desc = `Täglich, stündlich um ${hour} Uhr`;
}
} else if (dom === '*' && month === '*' && dow !== '*') {
// Ranges wie 1-5 auflösen
let dayParts = [];
for (const part of dow.split(',')) {
if (part.includes('-')) {
const [from, to] = part.split('-');
dayParts.push(`${dowNames[from] || from}–${dowNames[to] || to}`);
} else {
dayParts.push(dowNames[part] || part);
}
}
const days = dayParts.join(', ');
if (min !== '*' && hour !== '*') {
desc = `${days} um ${hour.padStart(2, '0')}:${min.padStart(2, '0')} Uhr`;
} else {
desc = `An: ${days}`;
}
} else if (dow === '*' && month === '*' && dom !== '*') {
desc = `Am ${dom}. jedes Monats`;
if (min !== '*' && hour !== '*') {
desc += ` um ${hour.padStart(2, '0')}:${min.padStart(2, '0')} Uhr`;
}
}
if (!desc) {
desc = `${expr} (5-Feld Cron)`;
}
return desc;
} catch {
return null;
}
}
function showCronjobStatus(box, job) {
box.hidden = false;
const parts = [];
if (job.isEnabled) {
parts.push(' Aktiv');
} else {
parts.push(' Pausiert');
}
if (job.nextRunAt) {
const next = new Date(job.nextRunAt);
parts.push(` Nächste Ausführung: ${next.toLocaleString('de-AT')}`);
}
if (job.lastRunAt) {
const last = new Date(job.lastRunAt);
const statusIcon = job.lastRunStatus === 'success' ? '✅' : '❌';
parts.push(`${statusIcon} Letzte Ausführung: ${last.toLocaleString('de-AT')}`);
if (job.lastRunStatus === 'error' && job.lastRunError) {
parts.push(` ${esc(job.lastRunError)}`);
}
}
if (job.sessionName) {
parts.push(` Session: ${esc(job.sessionName)}`);
}
if (job.notifySessionName) {
parts.push(` Benachrichtigung → ${esc(job.notifySessionName)}`);
}
box.innerHTML = parts.join('');
}