diff --git a/static/js/pages/bgp.js b/static/js/pages/bgp.js
index 1b6282c..571bcd7 100644
--- a/static/js/pages/bgp.js
+++ b/static/js/pages/bgp.js
@@ -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 = `
- BGP Router ID: ${info.router_id}
- Local AS: ${info.local_as}
- VRF ID: ${info.vrf_id}
- RIB Entries: ${info.rib_entries} (using ${info.rib_memory})
- Peers: ${info.peers} (using ${info.peers_memory})
- BGP Table Version: ${info.table_version}
- `.trim();
-
- return `
-
${label} Unicast Summary
-
- ℹ️
-
- Peers: ${info.peers}
- `;
+ const popContent = `BGP Router ID: ${info.router_id}
Local AS: ${info.local_as}
VRF ID: ${info.vrf_id}
RIB Entries: ${info.rib_entries} (using ${info.rib_memory})
Peers: ${info.peers} (using ${info.peers_memory})
BGP Table Version: ${info.table_version}`.trim();
+
+ return `${label} Unicast Summary
Peers: ${info.peers}
`;
}
function renderPeers(tbody, peers, bfdPeersSet) {
@@ -94,68 +69,60 @@ function renderPeers(tbody, peers, bfdPeersSet) {
tbody.innerHTML = `| No data available. |
`;
return;
}
-
const ip_version = tbody.id.includes('ipv4') ? 'ipv4' : 'ipv6';
-
peers.forEach((p, i) => {
- const popTitle = `${p.as_number} details`;
- const popContent = `
- Messages Received: ${p.msg_received}
- Messages Sent: ${p.msg_sent}
- Inbound Queue: ${p.in_queue}
- Outbound Queue: ${p.out_queue}
- `.trim();
-
+ const popTitle = `${p.as_number} details`;
+ const popContent = `Messages Received: ${p.msg_received}
Messages Sent: ${p.msg_sent}
Inbound Queue: ${p.in_queue}
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 = `
${p.neighbor} |
${p.as_number} |
${p.up_down} |
- ${p.state_pfx_rcd} |
- ${p.prefix_sent} |
- ${p.description} |
-
-
-
+ | ${displayState} |
+ ${displayPrefixSent} |
+ ${displayDescription} |
+
+
+
+ ${hasBfd ? `` : ''}
-
-
-
- ${hasBfd ? `
-
-
- ` : ''}
+
+ Actions
+
+
|
`;
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 = `| Error retrieving data. |
`;
+ bodyEl.innerHTML = `| Error retrieving data. |
`;
}
function refreshBGPTable() {
@@ -171,4 +138,80 @@ function toggleRefresh(loading) {
document.getElementById("refreshSpinner2").classList.toggle("d-none", !loading);
}
-document.addEventListener("DOMContentLoaded", loadBgpTables);
\ No newline at end of file
+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 = `Are you sure you want to perform a ${action.replace('_', ' ').toLowerCase()} on neighbor ${neighborIp}?
`;
+ 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 = `Loading...
`;
+ 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);
+ }
+ });
+});
\ No newline at end of file