From af95d2ec736d4289691d5b035e7d774d4d3f9ea4 Mon Sep 17 00:00:00 2001 From: Blackwhitebear8 Date: Wed, 17 Sep 2025 20:05:23 +0200 Subject: [PATCH] Add static/js/pages/bgp_peer_graph.js --- static/js/pages/bgp_peer_graph.js | 187 ++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 static/js/pages/bgp_peer_graph.js diff --git a/static/js/pages/bgp_peer_graph.js b/static/js/pages/bgp_peer_graph.js new file mode 100644 index 0000000..06e0f39 --- /dev/null +++ b/static/js/pages/bgp_peer_graph.js @@ -0,0 +1,187 @@ +document.addEventListener('DOMContentLoaded', function () { + const ctx = document.getElementById('peerHistoryChart').getContext('2d'); + let chart; + let currentRange = '24h'; + + const timeRangeButtons = document.querySelectorAll('.btn-group .btn'); + + timeRangeButtons.forEach(button => { + button.addEventListener('click', () => { + currentRange = button.getAttribute('data-range'); + timeRangeButtons.forEach(btn => btn.classList.remove('active')); + button.classList.add('active'); + fetchDataAndRenderChart(currentRange); + }); + }); + + function updateChartScale(chartInstance) { + const visibleDatasets = chartInstance.data.datasets.filter(dataset => !dataset.hidden); + + let pointsForScaling = []; + visibleDatasets.forEach(dataset => { + const validData = (dataset.data || []).filter(val => typeof val === 'number'); + pointsForScaling.push(...validData); + }); + + let suggestedMin, suggestedMax; + + if (pointsForScaling.length > 0) { + const minValue = Math.min(...pointsForScaling); + const maxValue = Math.max(...pointsForScaling); + const padding = 200; + + suggestedMin = Math.floor(minValue - padding); + suggestedMax = Math.ceil(maxValue + padding); + } else { + const allPoints = chartInstance.data.datasets.flatMap(d => d.data || []).filter(v => typeof v === 'number'); + if (allPoints.length > 0) { + const maxVal = Math.max(...allPoints); + suggestedMin = 0; + suggestedMax = maxVal + 200; + } else { + suggestedMin = 0; + suggestedMax = 1000; + } + } + + chartInstance.options.scales.y.min = suggestedMin; + chartInstance.options.scales.y.max = suggestedMax; + chartInstance.update(); + } + + async function fetchDataAndRenderChart(range) { + try { + const response = await fetch(`/bgp/peer/${neighborIp}/history?range=${range}`); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + const data = await response.json(); + renderChart(data); + } catch (error) { + console.error('Error fetching chart data:', error); + const chartContainer = document.querySelector('.achart-wrapper-chart'); + if (chartContainer) { + chartContainer.innerHTML = '

Could not load chart data.

'; + } + } + } + + function renderChart(data) { + if (chart) { + chart.destroy(); + } + + const findLastValue = (arr) => { + if (!arr) return 'N/A'; + for (let i = arr.length - 1; i >= 0; i--) { + if (arr[i] !== null && typeof arr[i] !== 'undefined') { + return arr[i]; + } + } + return 'N/A'; + }; + + const receivedLastValue = findLastValue(data.received); + const sentLastValue = findLastValue(data.sent); + + const receivedLabel = `Prefixes Received: ${receivedLastValue.toLocaleString()}`; + const sentLabel = `Prefixes Sent: ${sentLastValue.toLocaleString()}`; + + const receivedData = (data.received || []).filter(val => typeof val === 'number'); + const sentData = (data.sent || []).filter(val => typeof val === 'number'); + const maxReceived = receivedData.length > 0 ? Math.max(...receivedData) : -Infinity; + const maxSent = sentData.length > 0 ? Math.max(...sentData) : -Infinity; + const isReceivedHidden = maxReceived < maxSent; + + chart = new Chart(ctx, { + type: 'line', + data: { + labels: data.labels || [], + datasets: [ + { + label: receivedLabel, + data: data.received || [], + borderColor: 'rgb(54, 162, 235)', + backgroundColor: 'rgba(54, 162, 235, 0.1)', + borderWidth: 1.5, + fill: true, + pointRadius: 2, + pointHoverRadius: 5, + hidden: isReceivedHidden + }, + { + label: sentLabel, + data: data.sent || [], + borderColor: 'rgb(255, 99, 132)', + backgroundColor: 'rgba(255, 99, 132, 0.1)', + borderWidth: 1.5, + fill: true, + pointRadius: 2, + pointHoverRadius: 5, + hidden: !isReceivedHidden + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + mode: 'index', + }, + scales: { + x: { + type: 'time', + time: { + unit: 'minute', + stepSize: 10, + displayFormats: { + millisecond: 'HH:mm:ss.SSS', + second: 'HH:mm:ss', + minute: 'HH:mm', + hour: 'HH:mm' + } + } + }, + y: { } + }, + plugins: { + legend: { + position: 'top', + onClick: (e, legendItem, legend) => { + const chartInstance = legend.chart; + const index = legendItem.datasetIndex; + chartInstance.data.datasets[index].hidden = !chartInstance.data.datasets[index].hidden; + updateChartScale(chartInstance); + } + }, + tooltip: { + callbacks: { + title: function(context) { + if (!context[0]) return ''; + const date = new Date(context[0].parsed.x); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hour = String(date.getHours()).padStart(2, '0'); + const minute = String(date.getMinutes()).padStart(2, '0'); + + return `${day}-${month}-${year} ${hour}:${minute}`; + }, + label: function(context) { + let label = context.dataset.label.split(':')[0] || ''; + if (label) label += ': '; + if (context.parsed.y !== null) { + label += context.parsed.y.toLocaleString(); + } + return label; + } + } + } + } + } + }); + + updateChartScale(chart); + } + + fetchDataAndRenderChart(currentRange); +}); \ No newline at end of file