document.addEventListener('DOMContentLoaded', () => { const monitorList = document.getElementById('monitor-list'); const addForm = document.getElementById('add-monitor-form'); const serviceStatus = document.getElementById('service-status'); let isFetching = false; const fetchMonitors = async (action = 'list') => { if (isFetching) return; isFetching = true; try { const response = await fetch(`api/monitor.php?action=${action}`); const result = await response.json(); if (result.success) { renderMonitors(result.data); updateSummary(result.data); updateServiceStatus(result.worker_active); } } catch (error) { console.error('Error fetching monitors:', error); } finally { isFetching = false; } }; const updateServiceStatus = (active) => { if (!serviceStatus) return; const text = serviceStatus.querySelector('.status-text'); if (active) { serviceStatus.classList.remove('border-danger'); serviceStatus.classList.add('border-cyan'); text.innerText = 'Online'; text.className = 'status-text text-success'; } else { serviceStatus.classList.remove('border-cyan'); serviceStatus.classList.add('border-danger'); text.innerText = 'Offline (Restarting...)'; text.className = 'status-text text-danger'; } }; const updateSummary = (monitors) => { const total = monitors.length; const online = monitors.filter(m => m.last_status >= 200 && m.last_status < 400).length; const offline = total - online; const onlineEl = document.getElementById('summary-online'); const offlineEl = document.getElementById('summary-offline'); const totalEl = document.getElementById('summary-total'); if (onlineEl) onlineEl.innerText = online; if (offlineEl) offlineEl.innerText = offline; if (totalEl) totalEl.innerText = total; }; const renderMonitors = (monitors) => { if (monitors.length === 0) { monitorList.innerHTML = '
No URLs added yet. Start by adding one!
'; return; } const currentCount = monitorList.querySelectorAll('.monitor-card').length; if (currentCount !== monitors.length) { monitorList.innerHTML = monitors.map(m => createMonitorHTML(m)).join(''); attachListeners(); } else { monitors.forEach(m => updateMonitorDOM(m)); } monitors.forEach(m => { const canvas = document.getElementById(`sparkline-${m.id}`); if (canvas) drawSparkline(canvas, m.history || []); }); }; const createMonitorHTML = (m) => { const history = m.history || []; const segments = Array(30).fill('
'); history.forEach((h, idx) => { const isUp = h.status_code >= 200 && h.status_code < 400; const segIdx = segments.length - history.length + idx; segments[segIdx] = `
`; }); return `
${m.name}
${m.last_status || '---'}

${m.url}

Health Status (Real-time)
${segments.join('')}
Latency
${m.last_latency || 0}ms
Live Feed
LIVE
`; }; const updateMonitorDOM = (m) => { const card = document.getElementById(`monitor-${m.id}`); if (!card) return; const indicator = card.querySelector('.status-indicator'); indicator.className = `status-indicator ${getStatusClass(m.last_status)}`; const statusText = card.querySelector('.status-text'); statusText.innerText = m.last_status || '---'; const latencyVal = card.querySelector('.latency-value'); latencyVal.innerText = (m.last_latency || 0) + 'ms'; const history = m.history || []; const barContainer = card.querySelector('.health-bars'); const segments = Array(30).fill('
'); history.forEach((h, idx) => { const isUp = h.status_code >= 200 && h.status_code < 400; const segIdx = segments.length - history.length + idx; segments[segIdx] = `
`; }); barContainer.innerHTML = segments.join(''); }; const getStatusClass = (code) => { if (!code || code === 0) return 'status-unknown'; if (code >= 200 && code < 400) return 'status-online'; return 'status-offline'; }; const drawSparkline = (canvas, data) => { const ctx = canvas.getContext('2d'); const w = canvas.width; const h = canvas.height; ctx.clearRect(0, 0, w, h); if (data.length < 2) return; const points = data.length; const xStep = w / (points - 1); const latencies = data.map(d => parseInt(d.latency) || 0); const maxLat = Math.max(...latencies, 500); const getColor = (status) => (status >= 200 && status < 400) ? '#00ff88' : '#ff4d4d'; ctx.beginPath(); ctx.moveTo(0, h); data.forEach((d, idx) => { const x = idx * xStep; const val = parseInt(d.latency) || 0; const y = h - (val / maxLat) * (h * 0.7) - 15; ctx.lineTo(x, y); }); ctx.lineTo(w, h); const fillGrad = ctx.createLinearGradient(0, 0, 0, h); fillGrad.addColorStop(0, 'rgba(0, 243, 255, 0.15)'); fillGrad.addColorStop(1, 'rgba(0, 243, 255, 0)'); ctx.fillStyle = fillGrad; ctx.fill(); ctx.lineWidth = 3; ctx.lineJoin = 'round'; ctx.lineCap = 'round'; for (let i = 0; i < data.length - 1; i++) { const d1 = data[i]; const d2 = data[i + 1]; const x1 = i * xStep; const y1 = h - (parseInt(d1.latency) || 0) / maxLat * (h * 0.7) - 15; const x2 = (i + 1) * xStep; const y2 = h - (parseInt(d2.latency) || 0) / maxLat * (h * 0.7) - 15; const color = getColor(d2.status_code); ctx.beginPath(); ctx.strokeStyle = color; ctx.shadowBlur = 8; ctx.shadowColor = color; ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } }; const attachListeners = () => { document.querySelectorAll('.ping-btn').forEach(btn => { const newBtn = btn.cloneNode(true); btn.parentNode.replaceChild(newBtn, btn); newBtn.addEventListener('click', () => pingMonitor(newBtn.dataset.id, newBtn)); }); document.querySelectorAll('.delete-btn').forEach(btn => { const newBtn = btn.cloneNode(true); btn.parentNode.replaceChild(newBtn, btn); newBtn.addEventListener('click', () => deleteMonitor(newBtn.dataset.id)); }); }; const pingMonitor = async (id, btn) => { btn.disabled = true; btn.innerHTML = ''; try { // Using list to refresh since worker is active await fetchMonitors('list'); } finally { setTimeout(() => { btn.disabled = false; btn.innerHTML = ' Test'; }, 500); } }; const deleteMonitor = async (id) => { if (!confirm('Are you sure you want to delete this monitor?')) return; try { const response = await fetch(`api/monitor.php?action=delete&id=${id}`); const result = await response.json(); if (result.success) { fetchMonitors('list'); } } catch (error) { console.error('Delete error:', error); } }; addForm.addEventListener('submit', async (e) => { e.preventDefault(); const submitBtn = addForm.querySelector('button[type="submit"]'); submitBtn.disabled = true; const formData = { name: document.getElementById('mon-name').value, url: document.getElementById('mon-url').value, interval: document.getElementById('mon-interval').value }; try { const response = await fetch('api/monitor.php?action=add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const result = await response.json(); if (result.success) { addForm.reset(); bootstrap.Modal.getInstance(document.getElementById('addMonitorModal')).hide(); fetchMonitors('list'); } else { alert('Error adding monitor: ' + result.error); } } catch (error) { console.error('Add error:', error); } finally { submitBtn.disabled = false; } }); fetchMonitors('list'); setInterval(() => fetchMonitors('ping_all'), 1000); });