changes
This commit is contained in:
commit
5e31dd0214
37 changed files with 2082 additions and 0 deletions
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
7
static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
static/js/chart.js
Normal file
14
static/js/chart.js
Normal file
File diff suppressed because one or more lines are too long
7
static/js/chartjs-adapter-date-fns.js
Normal file
7
static/js/chartjs-adapter-date-fns.js
Normal file
File diff suppressed because one or more lines are too long
6
static/js/materialize.min.js
vendored
Normal file
6
static/js/materialize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
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);
|
||||
});
|
||||
110
static/js/pages/arp.js
Normal file
110
static/js/pages/arp.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
function filterTable(searchInputId,tableId){
|
||||
const input=document.getElementById(searchInputId);
|
||||
const filter=input.value.toUpperCase();
|
||||
const table=document.getElementById(tableId);
|
||||
const tr=table.getElementsByTagName("tr");
|
||||
|
||||
for(let i=1;i<tr.length;i++){
|
||||
tr[i].style.display="none";
|
||||
const td=tr[i].getElementsByTagName("td");
|
||||
for(let j=0;j<td.length;j++){
|
||||
if(td[j]){
|
||||
const txtValue=td[j].textContent||td[j].innerText;
|
||||
if(txtValue.toUpperCase().indexOf(filter)>-1){
|
||||
tr[i].style.display="";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortTable(tableId,columnIndex,th){
|
||||
const table=document.getElementById(tableId);
|
||||
const tbody=table.tBodies[0];
|
||||
const rows=Array.from(tbody.rows);
|
||||
const headers=Array.from(th.parentNode.children);
|
||||
|
||||
headers.forEach(header=>{
|
||||
if(header!==th){
|
||||
header.classList.remove('asc','desc');
|
||||
header.setAttribute('data-sort-state','none');
|
||||
}
|
||||
});
|
||||
|
||||
let currentState=th.getAttribute('data-sort-state')||'none';
|
||||
let newState=currentState==='none'?'asc':currentState==='asc'?'desc':'none';
|
||||
|
||||
th.classList.remove('asc','desc');
|
||||
if(newState!=='none')th.classList.add(newState);
|
||||
th.setAttribute('data-sort-state',newState);
|
||||
|
||||
if(newState==='none'){
|
||||
rows.sort((a,b)=>parseInt(a.getAttribute('data-index'))-parseInt(b.getAttribute('data-index')));
|
||||
}else{
|
||||
rows.sort((a,b)=>{
|
||||
const aText=a.cells[columnIndex].textContent.trim();
|
||||
const bText=b.cells[columnIndex].textContent.trim();
|
||||
|
||||
const aNum=parseFloat(aText.replace(/[^0-9.-]/g,''));
|
||||
const bNum=parseFloat(bText.replace(/[^0-9.-]/g,''));
|
||||
const isNumeric=!isNaN(aNum)&&!isNaN(bNum);
|
||||
|
||||
return newState==='asc'
|
||||
?(isNumeric?aNum-bNum:aText.localeCompare(bText))
|
||||
:(isNumeric?bNum-aNum:bText.localeCompare(aText));
|
||||
});
|
||||
}
|
||||
|
||||
rows.forEach(row=>tbody.appendChild(row));
|
||||
}
|
||||
|
||||
async function loadArpTable(){
|
||||
const tableBody=document.getElementById("arpTableBody");
|
||||
|
||||
try{
|
||||
const response=await fetch("/arp/json");
|
||||
const data=await response.json();
|
||||
|
||||
tableBody.innerHTML="";
|
||||
|
||||
if(!data.arp_table||data.arp_table.length===0){
|
||||
tableBody.innerHTML="<tr><td colspan='4' class='text-center'>Geen data beschikbaar.</td></tr>";
|
||||
return;
|
||||
}
|
||||
|
||||
data.arp_table.forEach((entry,index)=>{
|
||||
const row=document.createElement("tr");
|
||||
row.setAttribute("data-index",index);
|
||||
row.innerHTML=`
|
||||
<td>${entry.address}</td>
|
||||
<td><a href="/port/${entry.interface}" target="_blank">${entry.interface}</a></td>
|
||||
<td><a href="https://bgp.tools/search?q=${entry.link_layer_address}" target="_blank">${entry.link_layer_address}</a></td>
|
||||
<td>${entry.state}</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}catch(err){
|
||||
tableBody.innerHTML="<tr><td colspan='4' class='text-center text-danger'>Fout bij ophalen van data.</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded",()=>{
|
||||
loadArpTable();
|
||||
});
|
||||
|
||||
function refreshArpTable(){
|
||||
const searchInput=document.getElementById("arpSearch");
|
||||
const refreshIcon=document.getElementById("refreshIcon");
|
||||
const refreshSpinner=document.getElementById("refreshSpinner");
|
||||
|
||||
searchInput.value="";
|
||||
|
||||
refreshIcon.classList.add("d-none");
|
||||
refreshSpinner.classList.remove("d-none");
|
||||
|
||||
loadArpTable().then(()=>{
|
||||
refreshIcon.classList.remove("d-none");
|
||||
refreshSpinner.classList.add("d-none");
|
||||
});
|
||||
}
|
||||
95
static/js/pages/bgp-route.js
Normal file
95
static/js/pages/bgp-route.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
function detectIpVersion(ip){
|
||||
const ipv4Regex=/^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
|
||||
const ipv6Regex=/^([0-9a-fA-F:]+)(\/\d{1,3})?$/;
|
||||
|
||||
if(ipv4Regex.test(ip)){
|
||||
return"ipv4";
|
||||
}else if(ipv6Regex.test(ip)){
|
||||
return"ipv6";
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function highlightBGPOutput(text){
|
||||
text=text.replace(/\b215085\b/g,'<span class="asn215085-highlight">215085</span>');
|
||||
|
||||
text=text.replace(/\b\d{4,6}\b/g,match=>{
|
||||
if(match==='215085')return match;
|
||||
return`<span class="asn-highlight">${match}</span>`;
|
||||
});
|
||||
|
||||
text=text.split('\n').map(line=>{
|
||||
if(line.toLowerCase().includes('best')){
|
||||
return`<strong class="best-line">${line}</strong>`;
|
||||
}
|
||||
return line;
|
||||
}).join('\n');
|
||||
|
||||
text=text.split('\n').map(line=>{
|
||||
if(line.toLowerCase().includes('table entry')){
|
||||
return`<strong class="best-line">${line}</strong>`;
|
||||
}
|
||||
return line;
|
||||
}).join('\n');
|
||||
|
||||
text=text.split('\n').map(line=>{
|
||||
if(line.toLowerCase().includes('multipath')){
|
||||
return`<strong class="best-line">${line}</strong>`;
|
||||
}
|
||||
return line;
|
||||
}).join('\n');
|
||||
|
||||
return text.replace(/\n/g,'<br>');
|
||||
}
|
||||
|
||||
function displayBGPRoute(data){
|
||||
const outputElem=document.getElementById("bgpOutput");
|
||||
const rawText=data.data||JSON.stringify(data,null,2);
|
||||
const highlighted=highlightBGPOutput(rawText);
|
||||
outputElem.innerHTML=highlighted;
|
||||
}
|
||||
|
||||
async function loadBGPRoute(){
|
||||
const outputElem=document.getElementById("bgpOutput");
|
||||
const prefix=document.getElementById("prefixInput").value.trim();
|
||||
|
||||
if(!prefix){
|
||||
outputElem.textContent="Enter a valid prefix first.";
|
||||
return;
|
||||
}
|
||||
|
||||
const ip_version=detectIpVersion(prefix);
|
||||
if(!ip_version){
|
||||
outputElem.textContent="Invalid IP address or prefix.";
|
||||
return;
|
||||
}
|
||||
|
||||
outputElem.textContent="Loading...";
|
||||
|
||||
const postData={
|
||||
ip_version:ip_version,
|
||||
bgprouteprefix:prefix
|
||||
};
|
||||
|
||||
try{
|
||||
const response=await fetch("/bgp-route/lookup",{
|
||||
method:"POST",
|
||||
headers:{
|
||||
"Content-Type":"application/json",
|
||||
},
|
||||
body:JSON.stringify(postData),
|
||||
});
|
||||
if(!response.ok)throw new Error("Network response was not ok");
|
||||
const data=await response.json();
|
||||
|
||||
if(data.error){
|
||||
outputElem.textContent="Error: "+data.error;
|
||||
return;
|
||||
}
|
||||
|
||||
displayBGPRoute(data);
|
||||
}catch(error){
|
||||
outputElem.textContent="Error retrieving data: "+error.message;
|
||||
}
|
||||
}
|
||||
169
static/js/pages/bgp.js
Normal file
169
static/js/pages/bgp.js
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
function filterTable(searchInputId,tableId){
|
||||
const input=document.getElementById(searchInputId);
|
||||
const filter=input.value.toUpperCase();
|
||||
const table=document.getElementById(tableId);
|
||||
const tr=table.getElementsByTagName("tr");
|
||||
|
||||
for(let i=1;i<tr.length;i++){
|
||||
tr[i].style.display="none";
|
||||
const td=tr[i].getElementsByTagName("td");
|
||||
for(let j=0;j<td.length;j++){
|
||||
if(td[j]){
|
||||
const txtValue=td[j].textContent||td[j].innerText;
|
||||
if(txtValue.toUpperCase().indexOf(filter)>-1){
|
||||
tr[i].style.display="";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortTable(tableId,columnIndex,th){
|
||||
const table=document.getElementById(tableId);
|
||||
const tbody=table.tBodies[0];
|
||||
const rows=Array.from(tbody.rows);
|
||||
const headers=Array.from(th.parentNode.children);
|
||||
|
||||
headers.forEach(header=>{
|
||||
if(header!==th){
|
||||
header.classList.remove('asc','desc');
|
||||
header.setAttribute('data-sort-state','none');
|
||||
}
|
||||
});
|
||||
|
||||
let currentState=th.getAttribute('data-sort-state')||'none';
|
||||
let newState=currentState==='none'?'asc':currentState==='asc'?'desc':'none';
|
||||
|
||||
th.classList.remove('asc','desc');
|
||||
if(newState!=='none')th.classList.add(newState);
|
||||
th.setAttribute('data-sort-state',newState);
|
||||
|
||||
if(newState==='none'){
|
||||
rows.sort((a,b)=>parseInt(a.getAttribute('data-index'))-parseInt(b.getAttribute('data-index')));
|
||||
}else{
|
||||
rows.sort((a,b)=>{
|
||||
const aText=a.cells[columnIndex].textContent.trim();
|
||||
const bText=b.cells[columnIndex].textContent.trim();
|
||||
|
||||
const aNum=parseFloat(aText.replace(/[^0-9.-]/g,''));
|
||||
const bNum=parseFloat(bText.replace(/[^0-9.-]/g,''));
|
||||
const isNumeric=!isNaN(aNum)&&!isNaN(bNum);
|
||||
|
||||
return newState==='asc'
|
||||
?(isNumeric?aNum-bNum:aText.localeCompare(bText))
|
||||
:(isNumeric?bNum-aNum:bText.localeCompare(aText));
|
||||
});
|
||||
}
|
||||
|
||||
rows.forEach(row=>tbody.appendChild(row));
|
||||
}
|
||||
|
||||
async function loadBgpTables(){
|
||||
const ipv4SummaryEl=document.getElementById("ipv4Summary");
|
||||
const ipv6SummaryEl=document.getElementById("ipv6Summary");
|
||||
const ipv4Body=document.getElementById("ipv4TableBody");
|
||||
const ipv6Body=document.getElementById("ipv6TableBody");
|
||||
|
||||
try{
|
||||
const response=await fetch("/bgp/json");
|
||||
const data=await response.json();
|
||||
|
||||
ipv4SummaryEl.innerHTML=`
|
||||
<p>BGP Router ID: ${data.ipv4_info.router_id}, Local AS Number: <a href="https://bgp.tools/search?q=${data.ipv4_info.local_as}" target="_blank" rel="noopener noreferrer">${data.ipv4_info.local_as}</a>, VRF ID: ${data.ipv4_info.vrf_id}</p>
|
||||
<p>BGP Table Version:${data.ipv4_info.table_version}</p>
|
||||
<p>RIB Entries:${data.ipv4_info.rib_entries},using ${data.ipv4_info.rib_memory}</p>
|
||||
<p>Peers:${data.ipv4_info.peers},using ${data.ipv4_info.peers_memory}</p>
|
||||
`;
|
||||
|
||||
ipv6SummaryEl.innerHTML=`
|
||||
<p>BGP Router ID: ${data.ipv6_info.router_id}, Local AS Number: <a href="https://bgp.tools/search?q=${data.ipv6_info.local_as}" target="_blank" rel="noopener noreferrer">${data.ipv6_info.local_as}</a>, VRF ID: ${data.ipv6_info.vrf_id}</p>
|
||||
<p>BGP Table Version:${data.ipv6_info.table_version}</p>
|
||||
<p>RIB Entries:${data.ipv6_info.rib_entries},using ${data.ipv6_info.rib_memory}</p>
|
||||
<p>Peers:${data.ipv6_info.peers},using ${data.ipv6_info.peers_memory}</p>
|
||||
`;
|
||||
|
||||
ipv4Body.innerHTML="";
|
||||
if(data.ipv4_peers.length===0){
|
||||
ipv4Body.innerHTML=`<tr><td colspan="12" class="text-center">No data available.</td></tr>`;
|
||||
}else{
|
||||
data.ipv4_peers.forEach((peer,index)=>{
|
||||
const row=document.createElement("tr");
|
||||
row.setAttribute("data-index",index);
|
||||
row.innerHTML=`
|
||||
<td>${peer.neighbor}</td>
|
||||
<td>${peer.version}</td>
|
||||
<td><a href="https://bgp.tools/search?q=${peer.as_number}" target="_blank">${peer.as_number}</a></td>
|
||||
<td>${peer.msg_received}</td>
|
||||
<td>${peer.msg_sent}</td>
|
||||
<td>${peer.table_version}</td>
|
||||
<td>${peer.in_queue}</td>
|
||||
<td>${peer.out_queue}</td>
|
||||
<td>${peer.up_down}</td>
|
||||
<td>${peer.state_pfx_rcd}</td>
|
||||
<td>${peer.prefix_sent}</td>
|
||||
<td>${peer.description}</td>
|
||||
`;
|
||||
ipv4Body.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
ipv6Body.innerHTML="";
|
||||
if(data.ipv6_peers.length===0){
|
||||
ipv6Body.innerHTML=`<tr><td colspan="12" class="text-center">No data available.</td></tr>`;
|
||||
}else{
|
||||
data.ipv6_peers.forEach((peer,index)=>{
|
||||
const row=document.createElement("tr");
|
||||
row.setAttribute("data-index",index);
|
||||
row.innerHTML=`
|
||||
<td>${peer.neighbor}</td>
|
||||
<td>${peer.version}</td>
|
||||
<td><a href="https://bgp.tools/search?q=${peer.as_number}" target="_blank">${peer.as_number}</a></td>
|
||||
<td>${peer.msg_received}</td>
|
||||
<td>${peer.msg_sent}</td>
|
||||
<td>${peer.table_version}</td>
|
||||
<td>${peer.in_queue}</td>
|
||||
<td>${peer.out_queue}</td>
|
||||
<td>${peer.up_down}</td>
|
||||
<td>${peer.state_pfx_rcd}</td>
|
||||
<td>${peer.prefix_sent}</td>
|
||||
<td>${peer.description}</td>
|
||||
`;
|
||||
ipv6Body.appendChild(row);
|
||||
});
|
||||
}
|
||||
}catch(error){
|
||||
ipv4SummaryEl.textContent="Error fetching IPv4 data.";
|
||||
ipv6SummaryEl.textContent="Error fetching IPv6 data.";
|
||||
ipv4Body.innerHTML=`<tr><td colspan="12" class="text-center text-danger">Error retrieving data.</td></tr>`;
|
||||
ipv6Body.innerHTML=`<tr><td colspan="12" class="text-center text-danger">Error retrieving data.</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded",()=>{
|
||||
loadBgpTables();
|
||||
});
|
||||
|
||||
function refreshBGPTable(){
|
||||
const searchInput=document.getElementById("ipv4Search");
|
||||
const searchInput2=document.getElementById("ipv6Search");
|
||||
const refreshIcon=document.getElementById("refreshIcon");
|
||||
const refreshIcon2=document.getElementById("refreshIcon2");
|
||||
const refreshSpinner=document.getElementById("refreshSpinner");
|
||||
const refreshSpinner2=document.getElementById("refreshSpinner2");
|
||||
|
||||
searchInput.value="";
|
||||
searchInput2.value="";
|
||||
|
||||
refreshIcon.classList.add("d-none");
|
||||
refreshIcon2.classList.add("d-none");
|
||||
refreshSpinner.classList.remove("d-none");
|
||||
refreshSpinner2.classList.remove("d-none");
|
||||
|
||||
loadBgpTables().then(()=>{
|
||||
refreshIcon.classList.remove("d-none");
|
||||
refreshIcon2.classList.remove("d-none");
|
||||
refreshSpinner.classList.add("d-none");
|
||||
refreshSpinner2.classList.add("d-none");
|
||||
});
|
||||
}
|
||||
113
static/js/pages/interfaces.js
Normal file
113
static/js/pages/interfaces.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
function filterTable(searchInputId,tableId){
|
||||
const input=document.getElementById(searchInputId);
|
||||
const filter=input.value.toUpperCase();
|
||||
const table=document.getElementById(tableId);
|
||||
const tr=table.getElementsByTagName("tr");
|
||||
|
||||
for(let i=1;i<tr.length;i++){
|
||||
tr[i].style.display="none";
|
||||
const td=tr[i].getElementsByTagName("td");
|
||||
for(let j=0;j<td.length;j++){
|
||||
if(td[j]){
|
||||
const txtValue=td[j].textContent||td[j].innerText;
|
||||
if(txtValue.toUpperCase().indexOf(filter)>-1){
|
||||
tr[i].style.display="";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortTable(tableId,columnIndex,th){
|
||||
const table=document.getElementById(tableId);
|
||||
const tbody=table.tBodies[0];
|
||||
const rows=Array.from(tbody.rows);
|
||||
const headers=Array.from(th.parentNode.children);
|
||||
|
||||
headers.forEach(header=>{
|
||||
if(header!==th){
|
||||
header.classList.remove('asc','desc');
|
||||
header.setAttribute('data-sort-state','none');
|
||||
}
|
||||
});
|
||||
|
||||
let currentState=th.getAttribute('data-sort-state')||'none';
|
||||
let newState=currentState==='none'?'asc':currentState==='asc'?'desc':'none';
|
||||
|
||||
th.classList.remove('asc','desc');
|
||||
if(newState!=='none')th.classList.add(newState);
|
||||
th.setAttribute('data-sort-state',newState);
|
||||
|
||||
if(newState==='none'){
|
||||
rows.sort((a,b)=>parseInt(a.getAttribute('data-index'))-parseInt(b.getAttribute('data-index')));
|
||||
}else{
|
||||
rows.sort((a,b)=>{
|
||||
const aText=a.cells[columnIndex].textContent.trim();
|
||||
const bText=b.cells[columnIndex].textContent.trim();
|
||||
const aNum=parseFloat(aText.replace(/[^0-9.-]/g,''));
|
||||
const bNum=parseFloat(bText.replace(/[^0-9.-]/g,''));
|
||||
const isNumeric=!isNaN(aNum)&&!isNaN(bNum);
|
||||
|
||||
return newState==='asc'
|
||||
?(isNumeric?aNum-bNum:aText.localeCompare(bText))
|
||||
:(isNumeric?bNum-aNum:bText.localeCompare(aText));
|
||||
});
|
||||
}
|
||||
|
||||
rows.forEach(row=>tbody.appendChild(row));
|
||||
}
|
||||
|
||||
async function loadInterfaceTable(){
|
||||
const tableBody=document.getElementById("interfaceTableBody");
|
||||
const loadingRow=document.getElementById("interface-loading-row");
|
||||
|
||||
try{
|
||||
const response=await fetch("/interfaces/json");
|
||||
const data=await response.json();
|
||||
|
||||
tableBody.innerHTML="";
|
||||
|
||||
if(!data.interface_table||data.interface_table.length===0){
|
||||
tableBody.innerHTML="<tr><td colspan='7' class='text-center'>No data available.</td></tr>";
|
||||
return;
|
||||
}
|
||||
|
||||
data.interface_table.forEach((entry,index)=>{
|
||||
const row=document.createElement("tr");
|
||||
row.setAttribute("data-index",index);
|
||||
row.innerHTML=`
|
||||
<td><a href="/port/${entry.interface}" target="_blank">${entry.interface}</a></td>
|
||||
<td>${entry.ip_address}</td>
|
||||
<td><a href="https://bgp.tools/search?q=${entry.mac_address}" target="_blank">${entry.mac_address}</a></td>
|
||||
<td>${entry.vrf}</td>
|
||||
<td>${entry.mtu}</td>
|
||||
<td>${entry.status}</td>
|
||||
<td>${entry.description}</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}catch(err){
|
||||
tableBody.innerHTML="<tr><td colspan='7' class='text-center text-danger'>Error retrieving data.</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded",()=>{
|
||||
loadInterfaceTable();
|
||||
});
|
||||
|
||||
function refreshTable(){
|
||||
const searchInput=document.getElementById("interfaceSearch");
|
||||
const refreshIcon=document.getElementById("refreshIcon");
|
||||
const refreshSpinner=document.getElementById("refreshSpinner");
|
||||
|
||||
searchInput.value="";
|
||||
|
||||
refreshIcon.classList.add("d-none");
|
||||
refreshSpinner.classList.remove("d-none");
|
||||
|
||||
loadInterfaceTable().then(()=>{
|
||||
refreshIcon.classList.remove("d-none");
|
||||
refreshSpinner.classList.add("d-none");
|
||||
});
|
||||
}
|
||||
108
static/js/pages/neighbors.js
Normal file
108
static/js/pages/neighbors.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
function filterTable(searchInputId,tableId){
|
||||
const input=document.getElementById(searchInputId);
|
||||
const filter=input.value.toUpperCase();
|
||||
const table=document.getElementById(tableId);
|
||||
const tr=table.getElementsByTagName("tr");
|
||||
|
||||
for(let i=1;i<tr.length;i++){
|
||||
tr[i].style.display="none";
|
||||
const td=tr[i].getElementsByTagName("td");
|
||||
for(let j=0;j<td.length;j++){
|
||||
if(td[j]){
|
||||
const txtValue=td[j].textContent||td[j].innerText;
|
||||
if(txtValue.toUpperCase().indexOf(filter)>-1){
|
||||
tr[i].style.display="";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortTable(tableId,columnIndex,th){
|
||||
const table=document.getElementById(tableId);
|
||||
const tbody=table.tBodies[0];
|
||||
const rows=Array.from(tbody.rows);
|
||||
const headers=Array.from(th.parentNode.children);
|
||||
|
||||
headers.forEach(header=>{
|
||||
if(header!==th){
|
||||
header.classList.remove('asc','desc');
|
||||
header.setAttribute('data-sort-state','none');
|
||||
}
|
||||
});
|
||||
|
||||
let currentState=th.getAttribute('data-sort-state')||'none';
|
||||
let newState=currentState==='none'?'asc':currentState==='asc'?'desc':'none';
|
||||
|
||||
th.classList.remove('asc','desc');
|
||||
if(newState!=='none')th.classList.add(newState);
|
||||
th.setAttribute('data-sort-state',newState);
|
||||
|
||||
if(newState==='none'){
|
||||
rows.sort((a,b)=>parseInt(a.getAttribute('data-index'))-parseInt(b.getAttribute('data-index')));
|
||||
}else{
|
||||
rows.sort((a,b)=>{
|
||||
const aText=a.cells[columnIndex].textContent.trim();
|
||||
const bText=b.cells[columnIndex].textContent.trim();
|
||||
const aNum=parseFloat(aText.replace(/[^0-9.-]/g,''));
|
||||
const bNum=parseFloat(bText.replace(/[^0-9.-]/g,''));
|
||||
const isNumeric=!isNaN(aNum)&&!isNaN(bNum);
|
||||
return newState==='asc'
|
||||
?(isNumeric?aNum-bNum:aText.localeCompare(bText))
|
||||
:(isNumeric?bNum-aNum:bText.localeCompare(aText));
|
||||
});
|
||||
}
|
||||
|
||||
rows.forEach(row=>tbody.appendChild(row));
|
||||
}
|
||||
|
||||
async function loadneighborsTable(){
|
||||
const tableBody=document.getElementById("neighborsTableBody");
|
||||
|
||||
try{
|
||||
const response=await fetch("/neighbors/json");
|
||||
const data=await response.json();
|
||||
|
||||
tableBody.innerHTML="";
|
||||
|
||||
if(!data.neighbors_table||data.neighbors_table.length===0){
|
||||
tableBody.innerHTML="<tr><td colspan='4' class='text-center'>No data available.</td></tr>";
|
||||
return;
|
||||
}
|
||||
|
||||
data.neighbors_table.forEach((entry,index)=>{
|
||||
const row=document.createElement("tr");
|
||||
row.setAttribute("data-index",index);
|
||||
row.innerHTML=`
|
||||
<td>${entry.address}</td>
|
||||
<td><a href="/port/${entry.interface}" target="_blank">${entry.interface}</a></td>
|
||||
<td><a href="https://bgp.tools/search?q=${entry.link_layer_address}" target="_blank">${entry.link_layer_address}</a></td>
|
||||
<td>${entry.state}</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}catch(err){
|
||||
tableBody.innerHTML="<tr><td colspan='4' class='text-center text-danger'>Error retrieving data.</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded",()=>{
|
||||
loadneighborsTable();
|
||||
});
|
||||
|
||||
function refreshNeighborsTable(){
|
||||
const searchInput=document.getElementById("neighborsSearch");
|
||||
const refreshIcon=document.getElementById("refreshIcon");
|
||||
const refreshSpinner=document.getElementById("refreshSpinner");
|
||||
|
||||
searchInput.value="";
|
||||
|
||||
refreshIcon.classList.add("d-none");
|
||||
refreshSpinner.classList.remove("d-none");
|
||||
|
||||
loadneighborsTable().then(()=>{
|
||||
refreshIcon.classList.remove("d-none");
|
||||
refreshSpinner.classList.add("d-none");
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue