315 lines
No EOL
9.4 KiB
JavaScript
315 lines
No EOL
9.4 KiB
JavaScript
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 = "<p>No data received.</p>";
|
|
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 = `<p style="color:red">Error retrieving data: ${err.message}</p>`;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}); |