From 5046d3a002eb82e7000d86e863989708104d3eff Mon Sep 17 00:00:00 2001 From: Blackwhitebear8 Date: Wed, 13 Aug 2025 17:13:26 +0200 Subject: [PATCH] Update static/js/pages/bgp.js --- static/js/pages/bgp.js | 197 +++++++++++++++++++++++++---------------- 1 file changed, 120 insertions(+), 77 deletions(-) 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 ? ` - - - ` : ''} + `; 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