|
@@ -44,6 +44,16 @@ let state = {
|
|
|
|
|
|
|
|
const API_BASE = '/api';
|
|
const API_BASE = '/api';
|
|
|
|
|
|
|
|
|
|
+function escapeHtml(str) {
|
|
|
|
|
+ if (str === null || str === undefined) return '';
|
|
|
|
|
+ return String(str)
|
|
|
|
|
+ .replace(/&/g, '&')
|
|
|
|
|
+ .replace(/</g, '<')
|
|
|
|
|
+ .replace(/>/g, '>')
|
|
|
|
|
+ .replace(/"/g, '"')
|
|
|
|
|
+ .replace(/'/g, ''');
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// Initialize on page load
|
|
// Initialize on page load
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
const hash = window.location.hash.replace('#', '') || 'dashboard';
|
|
const hash = window.location.hash.replace('#', '') || 'dashboard';
|
|
@@ -3384,7 +3394,7 @@ function renderGeoCountries() {
|
|
|
</td>
|
|
</td>
|
|
|
<td class="py-2.5 px-3 text-right space-x-2 font-sans whitespace-nowrap">
|
|
<td class="py-2.5 px-3 text-right space-x-2 font-sans whitespace-nowrap">
|
|
|
<button onclick="updateGeoCountry('${c.code}')" class="text-xs text-cyan-400 hover:text-cyan-300 font-medium" title="Re-download latest country IP blocks">Sync</button>
|
|
<button onclick="updateGeoCountry('${c.code}')" class="text-xs text-cyan-400 hover:text-cyan-300 font-medium" title="Re-download latest country IP blocks">Sync</button>
|
|
|
- <button onclick="viewGeoSubnets('${c.code}', '${c.name.replace(/'/g, "\\'")}')" class="text-xs text-slate-400 hover:text-white" title="Preview Subnet Ranges">View</button>
|
|
|
|
|
|
|
+ <button onclick="viewGeoSubnets('${c.code}')" class="text-xs text-slate-400 hover:text-white" title="Preview Subnet Ranges">View</button>
|
|
|
</td>
|
|
</td>
|
|
|
`;
|
|
`;
|
|
|
tbody.appendChild(tr);
|
|
tbody.appendChild(tr);
|
|
@@ -3556,6 +3566,10 @@ async function submitGeoIntervalSettings(e) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function viewGeoSubnets(code, name) {
|
|
async function viewGeoSubnets(code, name) {
|
|
|
|
|
+ if (!name) {
|
|
|
|
|
+ const found = (state.geoCountries || []).find(x => x.code === code);
|
|
|
|
|
+ name = found ? found.name : code;
|
|
|
|
|
+ }
|
|
|
document.getElementById('geo-subnets-title').innerText = `${name} (${code}) Subnets`;
|
|
document.getElementById('geo-subnets-title').innerText = `${name} (${code}) Subnets`;
|
|
|
document.getElementById('geo-subnets-subtitle').innerText = `Kernel IPSet: mk_geo_${code}`;
|
|
document.getElementById('geo-subnets-subtitle').innerText = `Kernel IPSet: mk_geo_${code}`;
|
|
|
const listEl = document.getElementById('geo-subnets-list');
|
|
const listEl = document.getElementById('geo-subnets-list');
|
|
@@ -5112,7 +5126,7 @@ function switchDynSubTab(subTab) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function loadDynamicRouting(showToastMsg = true) {
|
|
|
|
|
|
|
+async function loadDynamicRouting(showToastMsg = false) {
|
|
|
try {
|
|
try {
|
|
|
const [sumRes, bgpInstRes, bgpPeersRes, bgpNetsRes, bgpRoutesRes, ospfInstRes, ospfIfacesRes, ospfAreasRes, ospfNbrsRes, ospfRoutesRes] = await Promise.all([
|
|
const [sumRes, bgpInstRes, bgpPeersRes, bgpNetsRes, bgpRoutesRes, ospfInstRes, ospfIfacesRes, ospfAreasRes, ospfNbrsRes, ospfRoutesRes] = await Promise.all([
|
|
|
fetch(`${API_BASE}/routing/dynamic/summary`),
|
|
fetch(`${API_BASE}/routing/dynamic/summary`),
|
|
@@ -5138,16 +5152,18 @@ async function loadDynamicRouting(showToastMsg = true) {
|
|
|
if (ospfNbrsRes.ok) state.ospfNeighbors = await ospfNbrsRes.json();
|
|
if (ospfNbrsRes.ok) state.ospfNeighbors = await ospfNbrsRes.json();
|
|
|
if (ospfRoutesRes.ok) state.ospfRoutes = await ospfRoutesRes.json();
|
|
if (ospfRoutesRes.ok) state.ospfRoutes = await ospfRoutesRes.json();
|
|
|
|
|
|
|
|
- renderBgpStatusPill();
|
|
|
|
|
- renderBgpPeers();
|
|
|
|
|
- renderBgpNetworks();
|
|
|
|
|
- renderBgpRoutes();
|
|
|
|
|
- renderOspfStatusPill();
|
|
|
|
|
- renderOspfInterfaces();
|
|
|
|
|
- renderOspfAreas();
|
|
|
|
|
- renderOspfNeighbors();
|
|
|
|
|
- renderOspfRoutes();
|
|
|
|
|
- updateBadges();
|
|
|
|
|
|
|
+ try { renderBgpStatusPill(); } catch(e) { console.error('renderBgpStatusPill error:', e); }
|
|
|
|
|
+ try { renderBgpPeers(); } catch(e) { console.error('renderBgpPeers error:', e); }
|
|
|
|
|
+ try { renderBgpNetworks(); } catch(e) { console.error('renderBgpNetworks error:', e); }
|
|
|
|
|
+ try { renderBgpRoutes(); } catch(e) { console.error('renderBgpRoutes error:', e); }
|
|
|
|
|
+ try { renderOspfStatusPill(); } catch(e) { console.error('renderOspfStatusPill error:', e); }
|
|
|
|
|
+ try { renderOspfInterfaces(); } catch(e) { console.error('renderOspfInterfaces error:', e); }
|
|
|
|
|
+ try { renderOspfAreas(); } catch(e) { console.error('renderOspfAreas error:', e); }
|
|
|
|
|
+ try { renderOspfNeighbors(); } catch(e) { console.error('renderOspfNeighbors error:', e); }
|
|
|
|
|
+ try { renderOspfRoutes(); } catch(e) { console.error('renderOspfRoutes error:', e); }
|
|
|
|
|
+ try { updateBadges(); } catch(e) { console.error('updateBadges error:', e); }
|
|
|
|
|
+
|
|
|
|
|
+ if (showToastMsg) showToast('Refreshed', 'Dynamic routing telemetry updated', 'info');
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
console.error('Failed to load dynamic routing:', err);
|
|
console.error('Failed to load dynamic routing:', err);
|
|
|
if (showToastMsg) showToast('Error', 'Failed to load dynamic routing telemetry', 'error');
|
|
if (showToastMsg) showToast('Error', 'Failed to load dynamic routing telemetry', 'error');
|
|
@@ -5172,6 +5188,7 @@ function renderBgpStatusPill() {
|
|
|
|
|
|
|
|
function renderBgpPeers() {
|
|
function renderBgpPeers() {
|
|
|
const tbody = document.getElementById('bgp-peers-table-body');
|
|
const tbody = document.getElementById('bgp-peers-table-body');
|
|
|
|
|
+ if (!tbody) return;
|
|
|
const empty = document.getElementById('bgp-peers-empty');
|
|
const empty = document.getElementById('bgp-peers-empty');
|
|
|
const countBadge = document.getElementById('bgp-peers-count-badge');
|
|
const countBadge = document.getElementById('bgp-peers-count-badge');
|
|
|
const navBadge = document.getElementById('dyn-badge-bgp-peers');
|
|
const navBadge = document.getElementById('dyn-badge-bgp-peers');
|
|
@@ -5182,10 +5199,10 @@ function renderBgpPeers() {
|
|
|
|
|
|
|
|
if (peers.length === 0) {
|
|
if (peers.length === 0) {
|
|
|
tbody.innerHTML = '';
|
|
tbody.innerHTML = '';
|
|
|
- empty.classList.remove('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.remove('hidden');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- empty.classList.add('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.add('hidden');
|
|
|
|
|
|
|
|
tbody.innerHTML = peers.map(p => {
|
|
tbody.innerHTML = peers.map(p => {
|
|
|
let flagA = !p.disabled ? '<span class="text-cyan-400 font-bold" title="Active">A</span>' : '';
|
|
let flagA = !p.disabled ? '<span class="text-cyan-400 font-bold" title="Active">A</span>' : '';
|
|
@@ -5234,6 +5251,7 @@ function renderBgpPeers() {
|
|
|
|
|
|
|
|
function renderBgpNetworks() {
|
|
function renderBgpNetworks() {
|
|
|
const tbody = document.getElementById('bgp-nets-table-body');
|
|
const tbody = document.getElementById('bgp-nets-table-body');
|
|
|
|
|
+ if (!tbody) return;
|
|
|
const empty = document.getElementById('bgp-nets-empty');
|
|
const empty = document.getElementById('bgp-nets-empty');
|
|
|
const countBadge = document.getElementById('bgp-nets-count-badge');
|
|
const countBadge = document.getElementById('bgp-nets-count-badge');
|
|
|
const navBadge = document.getElementById('dyn-badge-bgp-nets');
|
|
const navBadge = document.getElementById('dyn-badge-bgp-nets');
|
|
@@ -5244,10 +5262,10 @@ function renderBgpNetworks() {
|
|
|
|
|
|
|
|
if (nets.length === 0) {
|
|
if (nets.length === 0) {
|
|
|
tbody.innerHTML = '';
|
|
tbody.innerHTML = '';
|
|
|
- empty.classList.remove('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.remove('hidden');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- empty.classList.add('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.add('hidden');
|
|
|
|
|
|
|
|
tbody.innerHTML = nets.map(n => `
|
|
tbody.innerHTML = nets.map(n => `
|
|
|
<tr class="hover:bg-slate-800/40 transition">
|
|
<tr class="hover:bg-slate-800/40 transition">
|
|
@@ -5269,6 +5287,7 @@ function renderBgpNetworks() {
|
|
|
|
|
|
|
|
function renderBgpRoutes() {
|
|
function renderBgpRoutes() {
|
|
|
const tbody = document.getElementById('bgp-routes-table-body');
|
|
const tbody = document.getElementById('bgp-routes-table-body');
|
|
|
|
|
+ if (!tbody) return;
|
|
|
const empty = document.getElementById('bgp-routes-empty');
|
|
const empty = document.getElementById('bgp-routes-empty');
|
|
|
const countBadge = document.getElementById('bgp-routes-count-badge');
|
|
const countBadge = document.getElementById('bgp-routes-count-badge');
|
|
|
const navBadge = document.getElementById('dyn-badge-bgp-routes');
|
|
const navBadge = document.getElementById('dyn-badge-bgp-routes');
|
|
@@ -5279,10 +5298,10 @@ function renderBgpRoutes() {
|
|
|
|
|
|
|
|
if (routes.length === 0) {
|
|
if (routes.length === 0) {
|
|
|
tbody.innerHTML = '';
|
|
tbody.innerHTML = '';
|
|
|
- empty.classList.remove('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.remove('hidden');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- empty.classList.add('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.add('hidden');
|
|
|
|
|
|
|
|
tbody.innerHTML = routes.map(r => `
|
|
tbody.innerHTML = routes.map(r => `
|
|
|
<tr class="hover:bg-slate-800/40 transition">
|
|
<tr class="hover:bg-slate-800/40 transition">
|
|
@@ -5314,6 +5333,7 @@ function renderOspfStatusPill() {
|
|
|
|
|
|
|
|
function renderOspfInterfaces() {
|
|
function renderOspfInterfaces() {
|
|
|
const tbody = document.getElementById('ospf-ifaces-table-body');
|
|
const tbody = document.getElementById('ospf-ifaces-table-body');
|
|
|
|
|
+ if (!tbody) return;
|
|
|
const empty = document.getElementById('ospf-ifaces-empty');
|
|
const empty = document.getElementById('ospf-ifaces-empty');
|
|
|
const countBadge = document.getElementById('ospf-ifaces-count-badge');
|
|
const countBadge = document.getElementById('ospf-ifaces-count-badge');
|
|
|
const navBadge = document.getElementById('dyn-badge-ospf-ifaces');
|
|
const navBadge = document.getElementById('dyn-badge-ospf-ifaces');
|
|
@@ -5324,10 +5344,10 @@ function renderOspfInterfaces() {
|
|
|
|
|
|
|
|
if (ifaces.length === 0) {
|
|
if (ifaces.length === 0) {
|
|
|
tbody.innerHTML = '';
|
|
tbody.innerHTML = '';
|
|
|
- empty.classList.remove('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.remove('hidden');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- empty.classList.add('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.add('hidden');
|
|
|
|
|
|
|
|
tbody.innerHTML = ifaces.map(i => {
|
|
tbody.innerHTML = ifaces.map(i => {
|
|
|
let flagA = !i.disabled ? '<span class="text-cyan-400 font-bold" title="Active">A</span>' : '';
|
|
let flagA = !i.disabled ? '<span class="text-cyan-400 font-bold" title="Active">A</span>' : '';
|
|
@@ -5363,6 +5383,7 @@ function renderOspfInterfaces() {
|
|
|
|
|
|
|
|
function renderOspfAreas() {
|
|
function renderOspfAreas() {
|
|
|
const tbody = document.getElementById('ospf-areas-table-body');
|
|
const tbody = document.getElementById('ospf-areas-table-body');
|
|
|
|
|
+ if (!tbody) return;
|
|
|
const countBadge = document.getElementById('ospf-areas-count-badge');
|
|
const countBadge = document.getElementById('ospf-areas-count-badge');
|
|
|
const navBadge = document.getElementById('dyn-badge-ospf-areas');
|
|
const navBadge = document.getElementById('dyn-badge-ospf-areas');
|
|
|
|
|
|
|
@@ -5387,6 +5408,7 @@ function renderOspfAreas() {
|
|
|
|
|
|
|
|
function renderOspfNeighbors() {
|
|
function renderOspfNeighbors() {
|
|
|
const tbody = document.getElementById('ospf-nbrs-table-body');
|
|
const tbody = document.getElementById('ospf-nbrs-table-body');
|
|
|
|
|
+ if (!tbody) return;
|
|
|
const empty = document.getElementById('ospf-nbrs-empty');
|
|
const empty = document.getElementById('ospf-nbrs-empty');
|
|
|
const countBadge = document.getElementById('ospf-nbrs-count-badge');
|
|
const countBadge = document.getElementById('ospf-nbrs-count-badge');
|
|
|
const navBadge = document.getElementById('dyn-badge-ospf-nbrs');
|
|
const navBadge = document.getElementById('dyn-badge-ospf-nbrs');
|
|
@@ -5397,10 +5419,10 @@ function renderOspfNeighbors() {
|
|
|
|
|
|
|
|
if (nbrs.length === 0) {
|
|
if (nbrs.length === 0) {
|
|
|
tbody.innerHTML = '';
|
|
tbody.innerHTML = '';
|
|
|
- empty.classList.remove('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.remove('hidden');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- empty.classList.add('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.add('hidden');
|
|
|
|
|
|
|
|
tbody.innerHTML = nbrs.map(n => {
|
|
tbody.innerHTML = nbrs.map(n => {
|
|
|
let stateBadge = '';
|
|
let stateBadge = '';
|
|
@@ -5425,6 +5447,7 @@ function renderOspfNeighbors() {
|
|
|
|
|
|
|
|
function renderOspfRoutes() {
|
|
function renderOspfRoutes() {
|
|
|
const tbody = document.getElementById('ospf-routes-table-body');
|
|
const tbody = document.getElementById('ospf-routes-table-body');
|
|
|
|
|
+ if (!tbody) return;
|
|
|
const empty = document.getElementById('ospf-routes-empty');
|
|
const empty = document.getElementById('ospf-routes-empty');
|
|
|
const countBadge = document.getElementById('ospf-routes-count-badge');
|
|
const countBadge = document.getElementById('ospf-routes-count-badge');
|
|
|
const navBadge = document.getElementById('dyn-badge-ospf-routes');
|
|
const navBadge = document.getElementById('dyn-badge-ospf-routes');
|
|
@@ -5435,10 +5458,10 @@ function renderOspfRoutes() {
|
|
|
|
|
|
|
|
if (routes.length === 0) {
|
|
if (routes.length === 0) {
|
|
|
tbody.innerHTML = '';
|
|
tbody.innerHTML = '';
|
|
|
- empty.classList.remove('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.remove('hidden');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- empty.classList.add('hidden');
|
|
|
|
|
|
|
+ if (empty) empty.classList.add('hidden');
|
|
|
|
|
|
|
|
tbody.innerHTML = routes.map(r => `
|
|
tbody.innerHTML = routes.map(r => `
|
|
|
<tr class="hover:bg-slate-800/40 transition">
|
|
<tr class="hover:bg-slate-800/40 transition">
|