changes
This commit is contained in:
commit
5e31dd0214
37 changed files with 2082 additions and 0 deletions
315
static/js/pages/akvorado.js
Normal file
315
static/js/pages/akvorado.js
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
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);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue