const chartInstances = {}; async function loadPieChart(endpoint, canvasId, legendId) { try { const res = await fetch(endpoint); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); const canvas = document.getElementById(canvasId); const legend = document.getElementById(legendId); if (!json.top || json.top.length === 0) { legend.innerHTML = "

No data received.

"; return; } const totalPercent = json.top.reduce((sum, item) => sum + item.percent, 0); let topData = [...json.top]; if (totalPercent < 100) { topData.push({ name: "Other", percent: 100 - totalPercent }); } const labels = topData.map(item => item.name); const data = topData.map(item => item.percent); const baseColors = labels.map((label, i) => label === "Other" ? "#888888" : `hsl(${(i * 57) % 360}, 70%, 60%)` ); if (!chartInstances[canvasId]) { const ctx = canvas.getContext("2d"); chartInstances[canvasId] = new Chart(ctx, { type: "pie", data: { labels, datasets: [{ data, backgroundColor: baseColors, borderColor: "#fff", borderWidth: 2, hoverOffset: 15 }] }, options: { responsive: true, plugins: { legend: { display: false }, tooltip: { enabled: true, callbacks: { label: ctx => `${ctx.label}: ${ctx.parsed.toFixed(2)}%` } } } } }); chartInstances[canvasId].hiddenSlices = new Set(); } else { const chart = chartInstances[canvasId]; chart.data.labels = labels; chart.data.datasets[0].data = data; chart.data.datasets[0].backgroundColor = baseColors; chart.update(); } const chart = chartInstances[canvasId]; const hiddenSlices = chart.hiddenSlices; legend.innerHTML = ""; const ul = document.createElement("ul"); ul.style.listStyle = "none"; ul.style.padding = "0"; ul.style.margin = "0"; topData.forEach((item, i) => { const li = document.createElement("li"); li.style.cursor = "pointer"; li.style.display = "flex"; li.style.alignItems = "center"; li.style.marginBottom = "6px"; li.style.userSelect = "none"; const colorBox = document.createElement("span"); colorBox.style.display = "inline-block"; colorBox.style.width = "16px"; colorBox.style.height = "16px"; colorBox.style.marginRight = "8px"; colorBox.style.borderRadius = "3px"; const labelText = item.name === "Other" ? "Other" : item.name.split(":")[0].trim(); li.appendChild(colorBox); li.appendChild(document.createTextNode(labelText)); if (hiddenSlices.has(i)) { colorBox.style.backgroundColor = "#bbb"; li.style.opacity = "0.5"; } else { colorBox.style.backgroundColor = baseColors[i]; li.style.opacity = "1"; } li.addEventListener("mouseenter", () => { if (hiddenSlices.has(i)) return; chart.setActiveElements([{ datasetIndex: 0, index: i }]); chart.update(); }); li.addEventListener("mouseleave", () => { chart.setActiveElements([]); chart.update(); }); li.addEventListener("click", () => { if (hiddenSlices.has(i)) { hiddenSlices.delete(i); } else { hiddenSlices.add(i); } chart.getDatasetMeta(0).data[i].hidden = hiddenSlices.has(i); if (hiddenSlices.has(i)) { colorBox.style.backgroundColor = "#bbb"; li.style.opacity = "0.5"; } else { colorBox.style.backgroundColor = baseColors[i]; li.style.opacity = "1"; } chart.update(); }); ul.appendChild(li); }); legend.appendChild(ul); } catch (err) { console.error(err); document.getElementById(legendId).innerHTML = `

Error retrieving data: ${err.message}

`; } } function loadAllCharts() { loadPieChart("/stats/src-as", "pieChart1", "legend1"); loadPieChart("/stats/src-ports", "pieChart2", "legend2"); loadPieChart("/stats/protocol", "pieChart3", "legend3"); loadPieChart("/stats/src-country", "pieChart4", "legend4"); loadPieChart("/stats/etype", "pieChart5", "legend5"); } window.addEventListener("DOMContentLoaded", () => { loadAllCharts(); setInterval(loadAllCharts, 60000); }); let chart; async function fetchData() { const response = await fetch('/stats/graph'); const jsonData = await response.json(); const rawData = jsonData.data.map(item => { const date = new Date(item.t); return { x: date, y: item.gbps }; }); const maxVal = Math.max(...rawData.map(p => p.y)); const scaleFactor = maxVal < 1 ? 1000 : 1; const unitLabel = maxVal < 1 ? 'Mbit/s' : 'Gbit/s'; const data = rawData.map(p => ({ x: p.x, y: p.y * scaleFactor })); return { data, unitLabel }; } async function initChart() { const canvas = document.getElementById('liveChart'); const ctx = canvas.getContext('2d'); const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; ctx.scale(dpr, dpr); const { data, unitLabel } = await fetchData(); chart = new Chart(ctx, { type: 'line', data: { datasets: [{ label: `Network traffic (${unitLabel})`, data, borderColor: 'blue', backgroundColor: 'rgba(0, 0, 255, 0.1)', fill: true, pointRadius: 0, pointHoverRadius: 5 }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, scales: { x: { type: 'time', time: { unit: 'hour', displayFormats: { hour: 'HH:mm' } }, ticks: { autoSkip: false, callback: function(value, index, ticks) { const current = new Date(value); const prev = index > 0 ? new Date(ticks[index - 1].value) : null; const hours = current.getHours().toString().padStart(2, '0'); const minutes = current.getMinutes().toString().padStart(2, '0'); const day = current.getDate().toString().padStart(2, '0'); const isEvery4Hours = current.getHours() % 4 === 0 && minutes === '00'; const isNewDay = !prev || current.getDate() !== prev.getDate(); if (isNewDay) return `${day}`; if (isEvery4Hours) return `${hours}:${minutes}`; return ''; } }, title: { display: true, text: 'Time' } }, y: { beginAtZero: true, title: { display: true, text: unitLabel } } }, plugins: { tooltip: { enabled: true, callbacks: { label: function(context) { const label = context.dataset.label || ''; const value = context.parsed.y; return `${label}: ${value.toFixed(2)}`; } } }, legend: { display: true } } } }); } async function updateChart() { const { data, unitLabel } = await fetchData(); chart.data.datasets[0].data = data; chart.data.datasets[0].label = `Network traffic (${unitLabel})`; chart.options.scales.y.title.text = unitLabel; chart.update(); } window.addEventListener("DOMContentLoaded", () => { loadAllCharts?.(); initChart(); setInterval(() => { loadAllCharts?.(); updateChart(); }, 60000); }); document.addEventListener('DOMContentLoaded', () => { async function fetchData() { try { const flowResp = await fetch('/stats/flow-rate'); const flowData = await flowResp.json(); const exportersResp = await fetch('/stats/exporters'); const exportersData = await exportersResp.json(); const flowRateRounded = Math.round(flowData.rate); const exportersCount = exportersData.exporters.length; document.getElementById('flowRate').textContent = flowRateRounded; document.getElementById('exporterCount').textContent = exportersCount; } catch (error) { console.error('Error fetching stats:', error); document.getElementById('flowRate').textContent = 'Error'; document.getElementById('exporterCount').textContent = 'Error'; } } fetchData(); setInterval(fetchData, 10000); });