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

6
static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

248
static/css/style.css Normal file
View file

@ -0,0 +1,248 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f9f9f9;
color: #333;
}
header {
position: relative;
background: url('../img/background.webp') no-repeat center center;
background-size: cover;
color: white;
padding: 40px 20px;
text-align: center;
}
header::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
}
header h1, header p {
position: relative;
z-index: 2;
}
header h1 {
margin: 0;
font-size: 2.5em;
}
.header-content {
position: relative;
z-index: 2; /* hoger dan de overlay */
}
main {
padding: 20px;
max-width: 1440px;
margin: 0 auto;
}
section {
background: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
section h2 {
color: #07AAF9;
margin-top: 0;
}
footer {
background-color: #2D2E43;
color: white;
text-align: center;
padding: 10px 0;
margin-top: 20px;
}
.container {
display: flex;
gap: 50px;
}
.upstreamcon {
border: 2px solid #07AAF9;
border-radius: 8px;
padding: 10px;
width: 45%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
th.sortable {
cursor: pointer;
}
.sort-arrow::after {
content: "⇅";
margin-left: 5px;
opacity: 0.4;
}
th.asc .sort-arrow::after {
content: "▲";
opacity: 1;
}
th.desc .sort-arrow::after {
content: "▼";
opacity: 1;
}
.spinner-border {
width: 4rem;
height: 4rem;
margin-top: 6rem;
margin-bottom: 6rem;
border-width: 0.4em;
}
.spinner-border {
width: 4rem;
height: 4rem;
margin-top: 6rem;
margin-bottom: 6rem;
border-width: 0.4em;
}
#refreshSpinner.spinner-border,
#refreshSpinner2.spinner-border {
width: 0.84rem;
height: 0.84rem;
margin-top: 0rem;
margin-bottom: 0rem;
border-width: 0.15em;
vertical-align: text-bottom;
}
#bgpOutput {
white-space: pre-wrap;
background-color: #1e1e1e;
color: #d4d4d4;
padding: 1rem;
border-radius: 5px;
min-height: 400px;
min-width: 800px;
height: 100%;
overflow-y: auto;
font-family: Consolas, monospace, monospace;
border: 1px solid #444;
}
.asn-highlight {
color: #e67e22;
font-weight: bold;
}
.asn215085-highlight {
color: #07AAF9;
font-weight: bold;
}
strong.best-line {
font-weight: bold;
}
#achart-container {
display: flex;
gap: 4rem;
flex-wrap: wrap;
justify-content: center;
max-width: 100%;
}
.achart-wrapper {
width: 220px;
position: relative;
overflow: visible;
text-align: center;
}
canvas {
max-width: 100%;
height: auto !important;
}
.alegend ul {
display: flex;
flex-wrap: wrap;
padding: 0;
margin-top: 1rem;
gap: 0.5rem 1rem;
list-style: none;
justify-content: center;
}
.alegend li {
display: flex;
align-items: center;
white-space: nowrap;
}
.alegend-color {
display: inline-block;
width: 12px;
height: 12px;
margin-right: 6px;
vertical-align: middle;
border-radius: 2px;
}
.achart-wrapper-chart{
width: 1300px;
height: 600px;
position: relative;
flex-wrap: wrap;
justify-content: center;
max-width: 100%;
}
.achart-wrapper-chart canvas{
width: 100%!important;
height: 100%!important;
}
.stats-overview-container {
display: flex;
justify-content: space-between;
align-items: center;
}
#stats-container {
text-align: right;
display: flex;
gap: 1em;
}
#stats-container p {
margin: 0;
}
#legend1 li.inactive,
#legend2 li.inactive,
#legend3 li.inactive,
#legend4 li.inactive,
#legend5 li.inactive {
opacity: 0.5;
text-decoration: line-through;
}

BIN
static/img/404.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
static/img/background.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

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