This commit is contained in:
Blackwhitebear8 2025-06-22 16:33:28 +02:00
commit 5e31dd0214
37 changed files with 2082 additions and 0 deletions

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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
View 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
View 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");
});
}

View 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
View 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");
});
}

View 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");
});
}

View 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");
});
}