Files
gott/wwwroot/js/pages/cronjob-page.js
T

334 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ── 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 apiGetAgents();
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, emptyHtml) {
cardEl.innerHTML = '';
if (!session) {
cardEl.innerHTML = emptyHtml;
return;
}
const agent = session.agentId ? agentMap[session.agentId] : null;
const inner = buildSessionCard(session, agent);
while (inner.firstChild) cardEl.appendChild(inner.firstChild);
cardEl.insertAdjacentHTML('beforeend', '<span class="session-select-change"><i class="fa fa-pencil"></i></span>');
}
const sessionEmptyHtml = '<div class="session-select-empty"><i class="fa fa-comment-o"></i> Keine Session ausgewählt</div>';
const notifyEmptyHtml = '<div class="session-select-empty"><i class="fa fa-bell-o"></i> Keine (Antwort bleibt in Arbeits-Session)</div>';
// 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, sessionEmptyHtml);
}, { 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, notifyEmptyHtml);
}, { 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, sessionEmptyHtml);
cronInput.value = job.cronExpression || '';
promptInput.value = job.prompt || '';
notifySelect.value = job.notifySessionId || '';
renderSessionSelectCard(notifyCard, job.notifySessionId ? sessionMap[job.notifySessionId] : null, notifyEmptyHtml);
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', async () => {
runBtn.disabled = true;
runBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Läuft...';
try {
const result = await apiRunCronjob(params.id);
if (result.status === 'success') {
showToast('Cronjob erfolgreich ausgeführt');
} else {
showToast('Fehler: ' + (result.error || 'Unbekannt'), 'error');
}
// Status neu laden
const updated = await apiGetCronjob(params.id);
if (updated) showCronjobStatus(statusBox, updated);
} catch (e) {
showToast('Fehler: ' + e.message, 'error');
} finally {
runBtn.disabled = false;
runBtn.innerHTML = '<i class="fa fa-play"></i> Jetzt ausführen';
}
});
}
// 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;
}
saveBtn.disabled = true;
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');
} finally {
saveBtn.disabled = false;
}
});
}
});
function openSessionPickerModal(sessions, agentMap, currentId, onSelect, options) {
options = options || {};
const filterWithAgent = options.filterWithAgent || false;
const title = options.title || 'Session auswählen';
const noneLabel = options.noneLabel || 'Keine Session';
const noneIcon = options.noneIcon || 'fa-ban';
// Overlay
const overlay = document.createElement('div');
overlay.className = 'agent-picker-overlay';
const modal = document.createElement('div');
modal.className = 'agent-picker-modal';
// Header
const header = document.createElement('div');
header.className = 'agent-picker-header';
header.innerHTML = `<span>${esc(title)}</span><button class="icon-btn agent-picker-close"><i class="fa fa-times"></i></button>`;
modal.appendChild(header);
// Body
const body = document.createElement('div');
body.className = 'agent-picker-body';
// "Keine Session" Option
const noneCard = document.createElement('div');
noneCard.className = 'card agent-picker-item' + (!currentId ? ' selected' : '');
noneCard.innerHTML = `
<div class="card-image" style="display:flex;align-items:center;justify-content:center"><i class="fa ${esc(noneIcon)}" style="font-size:2em;opacity:var(--semi-transparent)"></i></div>
<div class="card-body">
<div class="card-title-row">
<span class="card-title">${esc(noneLabel)}</span>
</div>
</div>
`;
noneCard.addEventListener('click', () => {
onSelect(null);
overlay.remove();
});
body.appendChild(noneCard);
// Session Cards via Helper
for (const s of sessions) {
// Filter: nur Sessions mit Agent, wenn gewünscht
if (filterWithAgent && !s.agentId) continue;
const agent = s.agentId ? agentMap[s.agentId] : null;
const item = buildSessionCard(s, agent);
item.classList.add('agent-picker-item');
if (s.sessionId === currentId) item.classList.add('selected');
item.addEventListener('click', () => {
onSelect(s.sessionId);
overlay.remove();
});
body.appendChild(item);
}
modal.appendChild(body);
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Schließen
header.querySelector('.agent-picker-close').addEventListener('click', () => overlay.remove());
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.remove();
});
}
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('<span class="status-active"><i class="fa fa-check-circle"></i> Aktiv</span>');
} else {
parts.push('<span class="status-inactive"><i class="fa fa-pause-circle"></i> Pausiert</span>');
}
if (job.nextRunAt) {
const next = new Date(job.nextRunAt);
parts.push(`<span><i class="fa fa-clock-o"></i> Nächste Ausführung: ${next.toLocaleString('de-AT')}</span>`);
}
if (job.lastRunAt) {
const last = new Date(job.lastRunAt);
const statusIcon = job.lastRunStatus === 'success' ? '✅' : '❌';
parts.push(`<span>${statusIcon} Letzte Ausführung: ${last.toLocaleString('de-AT')}</span>`);
if (job.lastRunStatus === 'error' && job.lastRunError) {
parts.push(`<span class="status-error"><i class="fa fa-exclamation-triangle"></i> ${esc(job.lastRunError)}</span>`);
}
}
if (job.sessionName) {
parts.push(`<span><i class="fa fa-comment"></i> Session: ${esc(job.sessionName)}</span>`);
}
if (job.notifySessionName) {
parts.push(`<span><i class="fa fa-bell"></i> Benachrichtigung → ${esc(job.notifySessionName)}</span>`);
}
box.innerHTML = parts.join('');
}