function filterTable(searchInputId, tableId) { const filter = document.getElementById(searchInputId).value.toUpperCase(); 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")) { if ((cell.textContent || cell.innerText).toUpperCase().includes(filter)) { rows[i].style.display = ""; break; } } } } function sortTable(tableId, columnIndex, th) { 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 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 { rows.sort((a, b) => { const aTxt = a.cells[columnIndex].textContent.trim(); const bTxt = b.cells[columnIndex].textContent.trim(); const aNum = parseFloat(aTxt.replace(/[^0-9.-]/g, "")); const bNum = parseFloat(bTxt.replace(/[^0-9.-]/g, "")); const numeric = !isNaN(aNum) && !isNaN(bNum); const cmp = numeric ? aNum - bNum : aTxt.localeCompare(bTxt, undefined, { numeric: true }); return newState === "asc" ? cmp : -cmp; }); } rows.forEach(r => tbody.appendChild(r)); } async function loadBgpTables() { 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 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); activatePopoversAndTooltips(); } catch (e) { showError(ipv4Sum, ipv4Body, "IPv4"); showError(ipv6Sum, ipv6Body, "IPv6"); } } 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}
`; } function renderPeers(tbody, peers, bfdPeersSet) { tbody.innerHTML = ""; if (!peers.length) { 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 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} ${displayState} ${displayPrefixSent} ${displayDescription} ${hasBfd ? `` : ''} `; tbody.appendChild(tr); }); } function activatePopoversAndTooltips() { const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); popoverTriggerList.map(el => new bootstrap.Popover(el)); const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(el => new bootstrap.Tooltip(el)); } function showError(sumEl, bodyEl, label) { sumEl.textContent = `Error fetching ${label} data.`; bodyEl.innerHTML = `Error retrieving data.`; } function refreshBGPTable() { ["ipv4Search", "ipv6Search"].forEach(id => (document.getElementById(id).value = "")); toggleRefresh(true); loadBgpTables().finally(() => toggleRefresh(false)); } function toggleRefresh(loading) { document.getElementById("refreshIcon").classList.toggle("d-none", loading); document.getElementById("refreshSpinner").classList.toggle("d-none", !loading); document.getElementById("refreshIcon2").classList.toggle("d-none", loading); document.getElementById("refreshSpinner2").classList.toggle("d-none", !loading); } 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); } }); });