// This is temporary to prepare the new script const formatBytes = (bytes, decimals = 2) => { if (bytes === 0) return '0 B'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; }; const formatRate = (bytesPerSec) => { return formatBytes(bytesPerSec) + '/s'; }; const createInterfaceCard = (interfaceData, index) => { const interfaceId = interfaceData.id.replace(/[^a-z0-9]/gi, '-'); const isMultiple = networkData.interfaces.length > 1; return `

${interfaceData.name}

Live bandwidth • Updates every 5 seconds

Download 0 B/s
Peak: 0 B/s
Upload 0 B/s
Peak: 0 B/s

Combined

0 B/s

Total In

0 MB

Total Out

0 MB

`; }; const updateInterfaceData = (interfaceData) => { const interfaceId = interfaceData.id.replace(/[^a-z0-9]/gi, '-'); const scale = networkData.scale || (100 * 1024 * 1024); // Update peak rates if (!peakRates[interfaceId]) { peakRates[interfaceId] = { download: 0, upload: 0 }; } peakRates[interfaceId].download = Math.max(peakRates[interfaceId].download, interfaceData.downloadRate); peakRates[interfaceId].upload = Math.max(peakRates[interfaceId].upload, interfaceData.uploadRate); // Update text elements const setText = (key, value) => { const el = document.querySelector(`[data-traffic="${interfaceId}-${key}"]`); if (el) el.textContent = value; }; setText('download-rate', formatRate(interfaceData.downloadRate)); setText('upload-rate', formatRate(interfaceData.uploadRate)); setText('download-peak', formatRate(peakRates[interfaceId].download)); setText('upload-peak', formatRate(peakRates[interfaceId].upload)); setText('combined', formatRate(interfaceData.totalRate)); setText('total-in', formatBytes(networkData.total.downloadTotal)); setText('total-out', formatBytes(networkData.total.uploadTotal)); // Update progress bars const downloadPercent = Math.min(100, (interfaceData.downloadRate / scale) * 100); const uploadPercent = Math.min(100, (interfaceData.uploadRate / scale) * 100); const downloadBar = document.querySelector(`[data-traffic="${interfaceId}-download-bar"]`); const uploadBar = document.querySelector(`[data-traffic="${interfaceId}-upload-bar"]`); if (downloadBar) { downloadBar.style.width = `${downloadPercent}%`; } if (uploadBar) { uploadBar.style.width = `${uploadPercent}%`; } // Update chart if visible if (charts[interfaceId]) { const chart = charts[interfaceId]; const labels = interfaceData.history.download.map((_, i) => `${i * 3}m`).slice(-20); chart.data.labels = labels; chart.data.datasets[0].data = interfaceData.history.download.slice(-20); chart.data.datasets[1].data = interfaceData.history.upload.slice(-20); chart.update('none'); } }; const createChart = (interfaceId, interfaceData) => { const canvas = document.getElementById(`chart-${interfaceId}`); if (!canvas || charts[interfaceId]) return; const ctx = canvas.getContext('2d'); const labels = interfaceData.history.download.map((_, i) => `${i * 3}m`).slice(-20); charts[interfaceId] = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [ { label: 'Download', data: interfaceData.history.download.slice(-20), borderColor: 'rgb(34, 197, 94)', backgroundColor: 'rgba(34, 197, 94, 0.1)', tension: 0.4, fill: true, }, { label: 'Upload', data: interfaceData.history.upload.slice(-20), borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', tension: 0.4, fill: true, } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index', }, plugins: { legend: { display: true, position: 'top', labels: { usePointStyle: true, padding: 10, } }, tooltip: { callbacks: { label: (context) => { return context.dataset.label + ': ' + formatRate(context.parsed.y); } } } }, scales: { y: { beginAtZero: true, ticks: { callback: (value) => formatRate(value), } }, x: { grid: { display: false, } } } } }); }; const renderInterfaces = () => { const container = document.getElementById('network-traffic-container'); if (!container || !networkData) return; if (networkData.interfaces.length === 0) { container.innerHTML = `

No network data available

`; return; } const gridClass = networkData.interfaces.length > 1 ? 'grid grid-cols-1 md:grid-cols-2 gap-6' : 'grid grid-cols-1 gap-6'; container.className = gridClass; container.innerHTML = networkData.interfaces .map((iface, index) => createInterfaceCard(iface, index)) .join(''); // Set up chart toggles document.querySelectorAll('.chart-toggle').forEach(button => { button.addEventListener('click', (e) => { const interfaceId = e.currentTarget.dataset.interface; const container = document.querySelector(`[data-chart-container="${interfaceId}"]`); if (container) { container.classList.toggle('open'); // Create chart on first open if (container.classList.contains('open') && !charts[interfaceId]) { const interfaceData = networkData.interfaces.find(i => i.id.replace(/[^a-z0-9]/gi, '-') === interfaceId ); if (interfaceData) { setTimeout(() => createChart(interfaceId, interfaceData), 100); } } } }); }); }; const poll = async () => { if (pollInFlight || !pollingEndpoint) return; pollInFlight = true; try { const csrfElement = document.querySelector('meta[name="csrf-token"]'); const csrfToken = csrfElement ? csrfElement.content : ''; const response = await fetch(pollingEndpoint, { method: 'GET', headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-TOKEN': csrfToken, }, credentials: 'same-origin', }); if (response.ok) { const data = await response.json(); const wasEmpty = !networkData || networkData.interfaces.length === 0; networkData = data; // Render interfaces if first load or structure changed if (wasEmpty || data.interfaces.length !== (document.querySelectorAll('.chart-toggle').length || 0)) { renderInterfaces(); } // Update data for all interfaces data.interfaces.forEach(updateInterfaceData); } } catch (error) { console.error('Failed to fetch live traffic data', error); } finally { pollInFlight = false; } }; const bootstrap = () => { const container = document.getElementById('network-traffic-container'); pollingEndpoint = container?.dataset?.liveTrafficEndpoint || null; if (pollingEndpoint) { poll(); setInterval(poll, 5000); // Poll every 5 seconds } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bootstrap); } else { bootstrap(); }