const jobsTable = document.getElementById('jobsTable'); const jobModal = new bootstrap.Modal(document.getElementById('jobModal')); let jobs = []; let editMode = false; // Theme toggle const themeToggle = document.getElementById('themeToggle'); const themeIcon = document.getElementById('themeIcon'); function applyTheme(theme) { document.documentElement.setAttribute('data-bs-theme', theme); themeIcon.className = theme === 'dark' ? 'bi bi-sun-fill' : 'bi bi-moon-fill'; localStorage.setItem('theme', theme); } applyTheme(localStorage.getItem('theme') || 'dark'); themeToggle.addEventListener('click', () => { const current = document.documentElement.getAttribute('data-bs-theme'); applyTheme(current === 'dark' ? 'light' : 'dark'); }); // Type toggle label function updateCommandLabel() { const isMsg = document.getElementById('jobType').value === 'message'; document.getElementById('jobCommandLabel').textContent = isMsg ? 'Nachricht' : 'Kommando'; document.getElementById('jobCommand').placeholder = isMsg ? 'Hallo Mesh!' : '/weather'; } document.getElementById('jobType').addEventListener('change', updateCommandLabel); // WebSocket for live updates function connectWebSocket() { const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; const ws = new WebSocket(`${proto}//${location.host}/ws`); ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.type === 'scheduler_update') { jobs = msg.data; renderJobs(); } }; ws.onclose = () => { setTimeout(connectWebSocket, 3000); }; ws.onerror = () => { ws.close(); }; } // Load jobs async function loadJobs() { try { const resp = await fetch('/api/scheduler/jobs'); jobs = await resp.json(); renderJobs(); } catch (e) { jobsTable.innerHTML = 'Fehler beim Laden'; } } function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function renderJobs() { if (jobs.length === 0) { jobsTable.innerHTML = 'Keine Jobs konfiguriert'; return; } jobsTable.innerHTML = jobs.map(job => { const statusClass = job.enabled ? 'text-success' : 'text-body-secondary'; const statusIcon = job.enabled ? 'bi-check-circle-fill' : 'bi-pause-circle'; const statusText = job.enabled ? 'Aktiv' : 'Inaktiv'; const jobType = job.type === 'message' ? 'Nachricht' : 'Kommando'; const typeBadge = job.type === 'message' ? 'bg-warning' : 'bg-info'; return ` ${escapeHtml(job.name || '')} ${escapeHtml(job.description || '')} ${jobType} ${escapeHtml(job.command || '')} ${escapeHtml(job.cron || '')} ${job.channel != null ? job.channel : 0} ${statusText} `; }).join(''); } async function toggleJob(name) { const job = jobs.find(j => j.name === name); if (!job) return; try { const resp = await fetch(`/api/scheduler/jobs/${encodeURIComponent(name)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: !job.enabled }) }); if (resp.ok) { jobs = await resp.json(); renderJobs(); } } catch (e) { console.error('Toggle failed:', e); } } function editJob(name) { const job = jobs.find(j => j.name === name); if (!job) return; editMode = true; document.getElementById('jobModalTitle').textContent = 'Job bearbeiten'; document.getElementById('jobOriginalName').value = job.name; document.getElementById('jobName').value = job.name; document.getElementById('jobDescription').value = job.description || ''; document.getElementById('jobType').value = job.type || 'command'; updateCommandLabel(); document.getElementById('jobCommand').value = job.command || ''; document.getElementById('jobCron').value = job.cron || ''; document.getElementById('jobChannel').value = job.channel != null ? job.channel : 0; document.getElementById('jobEnabled').checked = !!job.enabled; jobModal.show(); } async function deleteJob(name) { if (!confirm(`Job "${name}" wirklich loeschen?`)) return; try { const resp = await fetch(`/api/scheduler/jobs/${encodeURIComponent(name)}`, { method: 'DELETE' }); if (resp.ok) { jobs = await resp.json(); renderJobs(); } } catch (e) { console.error('Delete failed:', e); } } // New job button document.getElementById('btnAddJob').addEventListener('click', () => { editMode = false; document.getElementById('jobModalTitle').textContent = 'Neuer Job'; document.getElementById('jobOriginalName').value = ''; document.getElementById('jobForm').reset(); document.getElementById('jobType').value = 'command'; updateCommandLabel(); document.getElementById('jobChannel').value = 0; jobModal.show(); }); // Save job document.getElementById('btnSaveJob').addEventListener('click', async () => { const form = document.getElementById('jobForm'); if (!form.checkValidity()) { form.reportValidity(); return; } const jobData = { name: document.getElementById('jobName').value.trim(), description: document.getElementById('jobDescription').value.trim(), type: document.getElementById('jobType').value, command: document.getElementById('jobCommand').value.trim(), cron: document.getElementById('jobCron').value.trim(), channel: parseInt(document.getElementById('jobChannel').value) || 0, enabled: document.getElementById('jobEnabled').checked }; try { let resp; if (editMode) { const originalName = document.getElementById('jobOriginalName').value; resp = await fetch(`/api/scheduler/jobs/${encodeURIComponent(originalName)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(jobData) }); } else { resp = await fetch('/api/scheduler/jobs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(jobData) }); } if (resp.ok) { jobs = await resp.json(); renderJobs(); jobModal.hide(); } } catch (e) { console.error('Save failed:', e); } }); loadJobs(); connectWebSocket();