const jobsTable = document.getElementById('jobsTable'); const jobModal = new bootstrap.Modal(document.getElementById('jobModal')); let currentUser = null; let jobs = []; let editMode = false; initPage({ onAuth: (user) => { currentUser = user; if (!user || user.role !== 'admin') { const btn = document.getElementById('btnAddJob'); btn.disabled = true; btn.title = 'Nur Lesezugriff'; } loadJobs(); } }); // Template variables available in message jobs const MSG_VARS = [ { key: '{time}', label: '{time}', desc: 'Uhrzeit (HH:MM)' }, { key: '{date}', label: '{date}', desc: 'Datum (TT.MM.JJJJ)' }, { key: '{datetime}', label: '{datetime}', desc: 'Datum + Uhrzeit' }, { key: '{weekday}', label: '{weekday}', desc: 'Wochentag (Montag…)' }, { key: '{nodes}', label: '{nodes}', desc: 'Anzahl Nodes gesamt' }, { key: '{nodes_24h}', label: '{nodes_24h}', desc: 'Aktive Nodes (24h)' }, { key: '{nodes_online}', label: '{nodes_online}', desc: 'Nodes online (< 15 Min)' }, { key: '{version}', label: '{version}', desc: 'Bot-Version' }, ]; // Type toggle label + variable hints function updateCommandLabel() { const isMsg = document.getElementById('jobType').value === 'message'; document.getElementById('jobCommandLabel').textContent = isMsg ? 'Nachricht' : 'Kommando'; document.getElementById('jobCommand').placeholder = isMsg ? 'Hallo Mesh! Heute ist {weekday}, {time} Uhr.' : '/weather'; const hints = document.getElementById('varHints'); const badges = document.getElementById('varBadges'); if (isMsg) { badges.innerHTML = MSG_VARS.map(v => `${escapeHtml(v.label)}` ).join(''); hints.classList.remove('d-none'); } else { hints.classList.add('d-none'); } } document.getElementById('varBadges').addEventListener('click', (e) => { const badge = e.target.closest('[data-var]'); if (!badge) return; const input = document.getElementById('jobCommand'); const start = input.selectionStart; const end = input.selectionEnd; const val = input.value; input.value = val.slice(0, start) + badge.dataset.var + val.slice(end); const pos = start + badge.dataset.var.length; input.setSelectionRange(pos, pos); input.focus(); }); 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 renderJobs() { if (jobs.length === 0) { jobsTable.innerHTML = 'Keine Jobs konfiguriert'; return; } const isAdmin = currentUser && currentUser.role === 'admin'; 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 text-dark' : 'bg-info text-white'; const statusCell = isAdmin ? `${statusText}` : `${statusText}`; const actions = isAdmin ? ` ` : `Nur Lesezugriff`; return ` ${escapeHtml(job.name || '')} ${escapeHtml(job.description || '')} ${jobType} ${escapeHtml(job.command || '')} ${escapeHtml(job.cron || '')} ${job.channel != null ? job.channel : 0} ${statusCell} ${actions} `; }).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); } }); connectWebSocket();