Update static/js/pages/bgp.js
This commit is contained in:
parent
e53815a14c
commit
5046d3a002
1 changed files with 120 additions and 77 deletions
|
|
@ -1,7 +1,6 @@
|
|||
function filterTable(searchInputId, tableId) {
|
||||
const filter = document.getElementById(searchInputId).value.toUpperCase();
|
||||
const rows = document.getElementById(tableId).getElementsByTagName("tr");
|
||||
|
||||
const rows = document.getElementById(tableId).getElementsByTagName("tr");
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
rows[i].style.display = "none";
|
||||
for (const cell of rows[i].getElementsByTagName("td")) {
|
||||
|
|
@ -14,17 +13,14 @@ function filterTable(searchInputId, tableId) {
|
|||
}
|
||||
|
||||
function sortTable(tableId, columnIndex, th) {
|
||||
const tbody = document.getElementById(tableId).tBodies[0];
|
||||
const rows = Array.from(tbody.rows);
|
||||
const tbody = document.getElementById(tableId).tBodies[0];
|
||||
const rows = Array.from(tbody.rows);
|
||||
const headers = Array.from(th.parentNode.children);
|
||||
|
||||
headers.forEach(h => h !== th && (h.className = "", h.dataset.sortState = "none"));
|
||||
|
||||
const state = th.dataset.sortState || "none";
|
||||
const state = th.dataset.sortState || "none";
|
||||
const newState = state === "none" ? "asc" : state === "asc" ? "desc" : "none";
|
||||
th.dataset.sortState = newState;
|
||||
th.className = newState !== "none" ? newState : "";
|
||||
|
||||
if (newState === "none") {
|
||||
rows.sort((a, b) => a.dataset.index - b.dataset.index);
|
||||
} else {
|
||||
|
|
@ -42,24 +38,19 @@ function sortTable(tableId, columnIndex, th) {
|
|||
}
|
||||
|
||||
async function loadBgpTables() {
|
||||
const ipv4Sum = document.getElementById("ipv4Summary");
|
||||
const ipv6Sum = document.getElementById("ipv6Summary");
|
||||
const ipv4Sum = document.getElementById("ipv4Summary");
|
||||
const ipv6Sum = document.getElementById("ipv6Summary");
|
||||
const ipv4Body = document.getElementById("ipv4TableBody");
|
||||
const ipv6Body = document.getElementById("ipv6TableBody");
|
||||
|
||||
try {
|
||||
const res = await fetch("/bgp/json");
|
||||
const res = await fetch("/bgp/json");
|
||||
const data = await res.json();
|
||||
|
||||
ipv4Sum.innerHTML = buildSummary("IPv4", data.ipv4_info);
|
||||
ipv6Sum.innerHTML = buildSummary("IPv6", data.ipv6_info);
|
||||
|
||||
const bfdPeersSet = new Set(data.bfd_peers);
|
||||
|
||||
renderPeers(ipv4Body, data.ipv4_peers, bfdPeersSet);
|
||||
renderPeers(ipv6Body, data.ipv6_peers, bfdPeersSet);
|
||||
|
||||
activatePopovers();
|
||||
activatePopoversAndTooltips();
|
||||
} catch (e) {
|
||||
showError(ipv4Sum, ipv4Body, "IPv4");
|
||||
showError(ipv6Sum, ipv6Body, "IPv6");
|
||||
|
|
@ -67,25 +58,9 @@ async function loadBgpTables() {
|
|||
}
|
||||
|
||||
function buildSummary(label, info) {
|
||||
const popContent = `
|
||||
<strong>BGP Router ID:</strong> ${info.router_id}<br>
|
||||
<strong>Local AS:</strong> <a href='https://bgp.tools/search?q=${info.local_as}' target='_blank'>${info.local_as}</a><br>
|
||||
<strong>VRF ID:</strong> ${info.vrf_id}<br>
|
||||
<strong>RIB Entries:</strong> ${info.rib_entries} (using ${info.rib_memory})<br>
|
||||
<strong>Peers:</strong> ${info.peers} (using ${info.peers_memory})<br>
|
||||
<strong>BGP Table Version:</strong> ${info.table_version}
|
||||
`.trim();
|
||||
|
||||
return `
|
||||
<h2 class="d-inline me-2">${label} Unicast Summary</h2>
|
||||
<span tabindex="0" role="button" class="info-icon"
|
||||
data-bs-toggle="popover" data-bs-trigger="hover focus"
|
||||
data-bs-html="true" data-bs-title="${label} Summary Info"
|
||||
data-bs-content="${popContent}">
|
||||
ℹ️
|
||||
</span>
|
||||
<div class="mt-1"><strong>Peers:</strong> ${info.peers}</div>
|
||||
`;
|
||||
const popContent = `<strong>BGP Router ID:</strong> ${info.router_id}<br><strong>Local AS:</strong> <a href='https://bgp.tools/search?q=${info.local_as}' target='_blank'>${info.local_as}</a><br><strong>VRF ID:</strong> ${info.vrf_id}<br><strong>RIB Entries:</strong> ${info.rib_entries} (using ${info.rib_memory})<br><strong>Peers:</strong> ${info.peers} (using ${info.peers_memory})<br><strong>BGP Table Version:</strong> ${info.table_version}`.trim();
|
||||
|
||||
return `<h2 class="d-inline me-2">${label} Unicast Summary</h2><span tabindex="0" role="button" class="icon-info" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-html="true" data-bs-title="${label} Summary Info" data-bs-content="${popContent}"></span><div class="mt-1"><strong>Peers:</strong> ${info.peers}</div>`;
|
||||
}
|
||||
|
||||
function renderPeers(tbody, peers, bfdPeersSet) {
|
||||
|
|
@ -94,68 +69,60 @@ function renderPeers(tbody, peers, bfdPeersSet) {
|
|||
tbody.innerHTML = `<tr><td colspan="7" class="text-center">No data available.</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const ip_version = tbody.id.includes('ipv4') ? 'ipv4' : 'ipv6';
|
||||
|
||||
peers.forEach((p, i) => {
|
||||
const popTitle = `<a href="https://bgp.tools/search?q=${p.as_number}" target="_blank" rel="noopener noreferrer">${p.as_number}</a> details<a>`;
|
||||
const popContent = `
|
||||
Messages Received: ${p.msg_received}<br>
|
||||
Messages Sent: ${p.msg_sent}<br>
|
||||
Inbound Queue: ${p.in_queue}<br>
|
||||
Outbound Queue: ${p.out_queue}
|
||||
`.trim();
|
||||
|
||||
const popTitle = `<a href="https://bgp.tools/search?q=${p.as_number}" target="_blank" rel="noopener noreferrer">${p.as_number}</a> details<a>`;
|
||||
const popContent = `Messages Received: ${p.msg_received}<br>Messages Sent: ${p.msg_sent}<br>Inbound Queue: ${p.in_queue}<br>Outbound Queue: ${p.out_queue}`.trim();
|
||||
const hasBfd = bfdPeersSet.has(p.neighbor);
|
||||
|
||||
let isShutdown = p.prefix_sent === '(Admin)';
|
||||
let displayState = p.state_pfx_rcd;
|
||||
let displayPrefixSent = p.prefix_sent;
|
||||
let displayDescription = p.description;
|
||||
if (isShutdown) {
|
||||
displayState = `${p.state_pfx_rcd} ${p.prefix_sent}`;
|
||||
const descParts = p.description.split(' ');
|
||||
displayPrefixSent = descParts.shift() || '0';
|
||||
displayDescription = descParts.join(' ');
|
||||
}
|
||||
const tr = document.createElement("tr");
|
||||
tr.dataset.index = i;
|
||||
tr.innerHTML = `
|
||||
<td>${p.neighbor}</td>
|
||||
<td><a href="https://bgp.tools/search?q=${p.as_number}" target="_blank">${p.as_number}</a></td>
|
||||
<td>${p.up_down}</td>
|
||||
<td>${p.state_pfx_rcd}</td>
|
||||
<td>${p.prefix_sent}</td>
|
||||
<td>${p.description}</td>
|
||||
<td class="d-flex justify-content-start">
|
||||
<span tabindex="0" role="button" class="icon-info"
|
||||
data-bs-toggle="popover" data-bs-trigger="hover focus"
|
||||
data-bs-html="true" data-bs-title='${popTitle}'
|
||||
data-bs-content="${popContent}">
|
||||
</span>
|
||||
<td>${displayState}</td>
|
||||
<td>${displayPrefixSent}</td>
|
||||
<td>${displayDescription}</td>
|
||||
<td class="actions-cell d-flex justify-content-start align-items-center">
|
||||
<span tabindex="0" role="button" class="icon-info" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-html="true" data-bs-title='${popTitle}' data-bs-content="${popContent}"></span>
|
||||
<a href="/bgp/neighbor/${ip_version}/${p.neighbor}" class="icon-details" target="_blank" role="button" data-bs-toggle="tooltip" data-bs-placement="top" title="Display BGP neighbor details"></a>
|
||||
${hasBfd ? `<a href="/bfd/peer/${p.neighbor}" class="icon-bfd" target="_blank" role="button" data-bs-toggle="tooltip" data-bs-placement="top" title="Display BFD peer details"></a>` : ''}
|
||||
|
||||
<a href="/bgp/neighbor/${ip_version}/${p.neighbor}" class="icon-details" target="_blank" role="button"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title="Display BGP neighbor details">
|
||||
</a>
|
||||
|
||||
${hasBfd ? `
|
||||
<a href="/bfd/peer/${p.neighbor}" class="icon-bfd" target="_blank" role="button"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title="Display BFD peer details">
|
||||
</a>
|
||||
` : ''}
|
||||
<div class="dropdown" data-bs-toggle="tooltip" data-bs-placement="top" title="Peer Actions">
|
||||
<span class="icon-gear" role="button" data-bs-toggle="dropdown" aria-expanded="false">Actions</span>
|
||||
<ul class="dropdown-menu">
|
||||
<li><button class="dropdown-item action-btn" data-action="soft_reset" data-neighbor-ip="${p.neighbor}">Soft Reset</button></li>
|
||||
<li><button class="dropdown-item action-btn" data-action="hard_reset" data-neighbor-ip="${p.neighbor}">Hard Reset</button></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
${isShutdown ? `<li><button class="dropdown-item action-btn text-success" data-action="enable" data-neighbor-ip="${p.neighbor}">Enable</button></li>` : `<li><button class="dropdown-item action-btn text-danger" data-action="shutdown" data-neighbor-ip="${p.neighbor}">Shutdown</button></li>`}
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function activatePopovers() {
|
||||
function activatePopoversAndTooltips() {
|
||||
const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||
popoverTriggerList.map(function (popoverTriggerEl) {
|
||||
return new bootstrap.Popover(popoverTriggerEl);
|
||||
});
|
||||
}
|
||||
|
||||
function activateTooltips() {
|
||||
popoverTriggerList.map(el => new bootstrap.Popover(el));
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
tooltipTriggerList.map(el => new bootstrap.Tooltip(el));
|
||||
}
|
||||
|
||||
function showError(sumEl, bodyEl, label) {
|
||||
sumEl.textContent = `Error fetching ${label} data.`;
|
||||
bodyEl.innerHTML = `<tr><td colspan="8" class="text-center text-danger">Error retrieving data.</td></tr>`;
|
||||
bodyEl.innerHTML = `<tr><td colspan="8" class="text-center text-danger">Error retrieving data.</td></tr>`;
|
||||
}
|
||||
|
||||
function refreshBGPTable() {
|
||||
|
|
@ -171,4 +138,80 @@ function toggleRefresh(loading) {
|
|||
document.getElementById("refreshSpinner2").classList.toggle("d-none", !loading);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", loadBgpTables);
|
||||
function showConfirmationModal(action, neighborIp) {
|
||||
const modal = document.getElementById('confirmationModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalBody = document.getElementById('modalBody');
|
||||
const modalConfirmBtn = document.getElementById('modalConfirmBtn');
|
||||
const modalCancelBtn = document.getElementById('modalCancelBtn');
|
||||
modalTitle.textContent = `Confirm: ${action.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase())}`;
|
||||
modalTitle.style.color = '';
|
||||
modalBody.innerHTML = `<p>Are you sure you want to perform a ${action.replace('_', ' ').toLowerCase()} on neighbor ${neighborIp}?</p>`;
|
||||
modalConfirmBtn.style.display = 'inline-block';
|
||||
modalCancelBtn.style.display = 'inline-block';
|
||||
modalCancelBtn.textContent = 'Cancel';
|
||||
modal.style.display = 'flex';
|
||||
const newConfirmBtn = modalConfirmBtn.cloneNode(true);
|
||||
modalConfirmBtn.parentNode.replaceChild(newConfirmBtn, modalConfirmBtn);
|
||||
newConfirmBtn.addEventListener('click', () => executeAction(action, neighborIp));
|
||||
}
|
||||
|
||||
async function executeAction(action, neighborIp) {
|
||||
const modal = document.getElementById('confirmationModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalBody = document.getElementById('modalBody');
|
||||
const modalConfirmBtn = modal.querySelector('#modalConfirmBtn');
|
||||
const modalCancelBtn = modal.querySelector('#modalCancelBtn');
|
||||
modalTitle.textContent = 'Processing...';
|
||||
modalBody.innerHTML = `<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>`;
|
||||
modalConfirmBtn.style.display = 'none';
|
||||
modalCancelBtn.style.display = 'none';
|
||||
try {
|
||||
const response = await fetch('/bgp/action', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action, neighbor_ip: neighborIp }),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
modalTitle.textContent = 'Success';
|
||||
modalTitle.style.color = '#198754';
|
||||
modalBody.textContent = result.message || 'Action completed successfully.';
|
||||
modalCancelBtn.textContent = 'OK';
|
||||
modalCancelBtn.style.display = 'inline-block';
|
||||
modalCancelBtn.onclick = () => {
|
||||
modal.style.display = 'none';
|
||||
refreshBGPTable();
|
||||
};
|
||||
} else {
|
||||
throw new Error(result.error || 'An unknown error occurred.');
|
||||
}
|
||||
} catch (err) {
|
||||
modalTitle.textContent = 'Error';
|
||||
modalTitle.style.color = '#dc3545';
|
||||
modalBody.textContent = err.message;
|
||||
modalCancelBtn.textContent = 'Close';
|
||||
modalCancelBtn.style.display = 'inline-block';
|
||||
modalCancelBtn.onclick = () => {
|
||||
modal.style.display = 'none';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
loadBgpTables();
|
||||
const modal = document.getElementById('confirmationModal');
|
||||
const cancelBtn = document.getElementById('modalCancelBtn');
|
||||
cancelBtn.addEventListener('click', () => modal.style.display = 'none');
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'confirmationModal') {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
document.body.addEventListener('click', (event) => {
|
||||
if (event.target.classList.contains('action-btn')) {
|
||||
const { action, neighborIp } = event.target.dataset;
|
||||
showConfirmationModal(action, neighborIp);
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue