|
@@ -3,10 +3,62 @@
|
|
|
// ================= Interceptor: Automatic Auth Bearer & 401 Handling =================
|
|
// ================= Interceptor: Automatic Auth Bearer & 401 Handling =================
|
|
|
const originalFetch = window.fetch.bind(window);
|
|
const originalFetch = window.fetch.bind(window);
|
|
|
const pendingAPIReads = new Map();
|
|
const pendingAPIReads = new Map();
|
|
|
-const telemetryHealth = { lastSuccess: 0, failures: new Map() };
|
|
|
|
|
|
|
+const telemetryHealth = { lastSuccess: 0, failures: new Map(), successes: new Map() };
|
|
|
let coreRefreshPromise = null;
|
|
let coreRefreshPromise = null;
|
|
|
let lastSummaryRefresh = 0;
|
|
let lastSummaryRefresh = 0;
|
|
|
|
|
|
|
|
|
|
+// Map endpoint paths to the views that consume them, so unrelated failures do not
|
|
|
|
|
+// hide whether the current view has fresh data.
|
|
|
|
|
+function viewUsesEndpoint(tab, path) {
|
|
|
|
|
+ const endpoints = {
|
|
|
|
|
+ dashboard: ['/system/status', '/interfaces', '/vpn/status', '/firewall/summary'],
|
|
|
|
|
+ interfaces: ['/interfaces', '/vlans', '/roles'],
|
|
|
|
|
+ firewall: ['/firewall/summary', '/firewall/filter', '/firewall/nat', '/firewall/mangle', '/firewall/address-lists'],
|
|
|
|
|
+ routes: ['/routes', '/routing/rules', '/routing/tables'],
|
|
|
|
|
+ 'routing-filters': ['/routing/filters', '/routing/filters/community-lists', '/routing/filters/as-lists'],
|
|
|
|
|
+ bgp: ['/routing/bgp/instance', '/routing/bgp/peers', '/routing/bgp/networks', '/routing/bgp/routes'],
|
|
|
|
|
+ ospf: ['/routing/ospf/instances', '/routing/ospf/areas', '/routing/ospf/templates', '/routing/ospf/neighbors', '/routing/ospf/lsa'],
|
|
|
|
|
+ rip: ['/routing/rip/instances', '/routing/rip/interfaces', '/routing/rip/neighbors-live', '/routing/rip/routes'],
|
|
|
|
|
+ bfd: ['/routing/bfd/instance', '/routing/bfd/interfaces', '/routing/bfd/peers', '/routing/bfd/sessions'],
|
|
|
|
|
+ babel: ['/routing/babel/instances', '/routing/babel/interfaces', '/routing/babel/neighbors', '/routing/babel/routes'],
|
|
|
|
|
+ radv: ['/routing/radv/instances', '/routing/radv/interfaces'],
|
|
|
|
|
+ eigrp: ['/routing/eigrp/instances', '/routing/eigrp/interfaces', '/routing/eigrp/networks', '/routing/eigrp/neighbors', '/routing/eigrp/topology'],
|
|
|
|
|
+ 'dhcp-client': ['/dhcp/clients'], 'dhcp-server': ['/dhcp-server/servers', '/dhcp-server/networks', '/dhcp-server/leases'],
|
|
|
|
|
+ bridge: ['/bridge/summary'], sdwan: ['/sdwan/groups', '/sdwan/telemetry'],
|
|
|
|
|
+ ppp: ['/vpn/ppp/secrets', '/vpn/ppp/profiles', '/vpn/ppp/active'],
|
|
|
|
|
+ aerotun: ['/vpn/aerotun/tunnels', '/vpn/aerotun/stats'], wireguard: ['/vpn/wireguard/interfaces', '/vpn/wireguard/peers'],
|
|
|
|
|
+ eoip: ['/vpn/eoip'], vxlan: ['/vpn/vxlan', '/vpn/vxlan/vteps'], pppoe: ['/vpn/pppoe/clients'],
|
|
|
|
|
+ ipsec: ['/vpn/ipsec/tunnels'], l2tp: ['/vpn/l2tp/clients'], pptp: ['/vpn/pptp/clients'],
|
|
|
|
|
+ vrrp: ['/vrrp/summary'], logs: ['/system/logs'], users: ['/users'],
|
|
|
|
|
+ services: ['/system/services'], certificates: ['/certificates'], settings: ['/dns/settings', '/dns/static', '/dns/status'],
|
|
|
|
|
+ reverse_proxy: ['/proxy/config', '/proxy/rules'], updates: ['/update/status']
|
|
|
|
|
+ };
|
|
|
|
|
+ return (endpoints[tab] || []).some(endpoint => path === '/api' + endpoint);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function renderViewDataHealth() {
|
|
|
|
|
+ const el = document.getElementById('view-data-health');
|
|
|
|
|
+ if (!el) return;
|
|
|
|
|
+ const tab = state.activeTab;
|
|
|
|
|
+ el.hidden = !['dashboard','interfaces','firewall','routes','routing-filters','bgp','ospf','rip','bfd','babel','radv','eigrp','dhcp-client','dhcp-server','bridge','sdwan','ppp','aerotun','wireguard','eoip','vxlan','pppoe','ipsec','l2tp','pptp','vrrp','logs','users','services','certificates','settings','reverse_proxy','updates'].includes(tab);
|
|
|
|
|
+ if (el.hidden) return;
|
|
|
|
|
+ const failures = [...telemetryHealth.failures].filter(([path]) => viewUsesEndpoint(tab, path));
|
|
|
|
|
+ const successes = [...telemetryHealth.successes].filter(([path]) => viewUsesEndpoint(tab, path));
|
|
|
|
|
+ if (!failures.length && !successes.length) {
|
|
|
|
|
+ el.textContent = 'View data: waiting';
|
|
|
|
|
+ el.title = 'No successful data request recorded for this view';
|
|
|
|
|
+ } else if (failures.length) {
|
|
|
|
|
+ el.textContent = 'View data: incomplete or unavailable';
|
|
|
|
|
+ el.title = failures.map(([path, error]) => `${path}: ${error}`).join('\n');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const oldest = Math.min(...successes.map(([, time]) => time));
|
|
|
|
|
+ const seconds = Math.floor((Date.now() - oldest) / 1000);
|
|
|
|
|
+ el.textContent = `Tracked data: oldest update ${seconds}s ago`;
|
|
|
|
|
+ el.title = 'Oldest successful response among tracked requests for this view';
|
|
|
|
|
+ }
|
|
|
|
|
+ el.className = `text-[10px] font-mono ${failures.length || !successes.length || Date.now() - Math.min(...successes.map(([, time]) => time)) > 60000 ? 'text-amber-400' : 'text-emerald-400'}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function renderTelemetryHealth() {
|
|
function renderTelemetryHealth() {
|
|
|
const el = document.getElementById('telemetry-health');
|
|
const el = document.getElementById('telemetry-health');
|
|
|
if (!el) return;
|
|
if (!el) return;
|
|
@@ -33,6 +85,7 @@ function stopSessionPolling() {
|
|
|
stopNATStatPolling();
|
|
stopNATStatPolling();
|
|
|
stopMangleStatPolling();
|
|
stopMangleStatPolling();
|
|
|
telemetryHealth.failures.clear();
|
|
telemetryHealth.failures.clear();
|
|
|
|
|
+ telemetryHealth.successes.clear();
|
|
|
telemetryHealth.lastSuccess = 0;
|
|
telemetryHealth.lastSuccess = 0;
|
|
|
lastSummaryRefresh = 0;
|
|
lastSummaryRefresh = 0;
|
|
|
}
|
|
}
|
|
@@ -60,11 +113,13 @@ window.fetch = async function(resource, options = {}) {
|
|
|
if (isRead) {
|
|
if (isRead) {
|
|
|
if (response.ok) {
|
|
if (response.ok) {
|
|
|
telemetryHealth.lastSuccess = Date.now();
|
|
telemetryHealth.lastSuccess = Date.now();
|
|
|
|
|
+ telemetryHealth.successes.set(url.pathname, telemetryHealth.lastSuccess);
|
|
|
telemetryHealth.failures.delete(url.pathname);
|
|
telemetryHealth.failures.delete(url.pathname);
|
|
|
} else {
|
|
} else {
|
|
|
telemetryHealth.failures.set(url.pathname, `HTTP ${response.status}; displayed values may be old`);
|
|
telemetryHealth.failures.set(url.pathname, `HTTP ${response.status}; displayed values may be old`);
|
|
|
}
|
|
}
|
|
|
renderTelemetryHealth();
|
|
renderTelemetryHealth();
|
|
|
|
|
+ renderViewDataHealth();
|
|
|
}
|
|
}
|
|
|
if (response.status === 401 && url.pathname !== '/api/auth/login' && state.currentUser && token === localStorage.getItem('debtik_token')) {
|
|
if (response.status === 401 && url.pathname !== '/api/auth/login' && state.currentUser && token === localStorage.getItem('debtik_token')) {
|
|
|
stopSessionPolling();
|
|
stopSessionPolling();
|
|
@@ -78,6 +133,7 @@ window.fetch = async function(resource, options = {}) {
|
|
|
if (isRead) {
|
|
if (isRead) {
|
|
|
telemetryHealth.failures.set(url.pathname, err.name === 'AbortError' ? 'Request timed out' : 'Connection failed; displayed values may be old');
|
|
telemetryHealth.failures.set(url.pathname, err.name === 'AbortError' ? 'Request timed out' : 'Connection failed; displayed values may be old');
|
|
|
renderTelemetryHealth();
|
|
renderTelemetryHealth();
|
|
|
|
|
+ renderViewDataHealth();
|
|
|
}
|
|
}
|
|
|
throw err;
|
|
throw err;
|
|
|
} finally {
|
|
} finally {
|
|
@@ -341,6 +397,7 @@ function navigate(tabName) {
|
|
|
stopNATStatPolling();
|
|
stopNATStatPolling();
|
|
|
stopMangleStatPolling();
|
|
stopMangleStatPolling();
|
|
|
state.activeTab = tabName;
|
|
state.activeTab = tabName;
|
|
|
|
|
+ renderViewDataHealth();
|
|
|
window.location.hash = tabName;
|
|
window.location.hash = tabName;
|
|
|
|
|
|
|
|
// Update nav links styling
|
|
// Update nav links styling
|
|
@@ -564,7 +621,7 @@ async function refreshCoreData(isManual = false) {
|
|
|
if (state.activeTab === 'vlans') renderVlansList();
|
|
if (state.activeTab === 'vlans') renderVlansList();
|
|
|
if (state.activeTab === 'firewall') await loadFirewall(false);
|
|
if (state.activeTab === 'firewall') await loadFirewall(false);
|
|
|
if (state.activeTab === 'routes') await loadRoutes(false);
|
|
if (state.activeTab === 'routes') await loadRoutes(false);
|
|
|
- if (state.activeTab === 'dynamic-routing' || state.activeTab === 'bgp' || state.activeTab === 'ospf' || state.activeTab === 'rip' || state.activeTab === 'bfd' || state.activeTab === 'babel' || state.activeTab === 'radv') await loadDynamicRouting(false);
|
|
|
|
|
|
|
+ if (state.activeTab === 'dynamic-routing' || state.activeTab === 'bgp' || state.activeTab === 'ospf' || state.activeTab === 'rip' || state.activeTab === 'bfd' || state.activeTab === 'babel' || state.activeTab === 'radv' || state.activeTab === 'eigrp' || state.activeTab === 'routing-filters') await loadDynamicRouting(false);
|
|
|
if (state.activeTab === 'ppp') await loadPPP(false);
|
|
if (state.activeTab === 'ppp') await loadPPP(false);
|
|
|
if (state.activeTab === 'eoip') await loadEoIP(false);
|
|
if (state.activeTab === 'eoip') await loadEoIP(false);
|
|
|
if (state.activeTab === 'vxlan') await loadVXLAN(false);
|
|
if (state.activeTab === 'vxlan') await loadVXLAN(false);
|
|
@@ -9852,7 +9909,11 @@ function switchDynSubTab(subTab) {
|
|
|
|
|
|
|
|
async function loadDynamicRouting(showToastMsg = false) {
|
|
async function loadDynamicRouting(showToastMsg = false) {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ const routingTab = state.activeTab === 'dynamic-routing' ? 'bgp' : state.activeTab;
|
|
|
|
|
+ const routingViews = new Set(['bgp', 'ospf', 'rip', 'bfd', 'babel', 'radv', 'eigrp', 'routing-filters']);
|
|
|
const fetchSafeJson = async (url) => {
|
|
const fetchSafeJson = async (url) => {
|
|
|
|
|
+ const match = url.match(/\/routing\/(bgp|ospf|rip|bfd|babel|radv|eigrp)\//);
|
|
|
|
|
+ if (routingViews.has(routingTab) && match && match[1] !== routingTab) return null;
|
|
|
try {
|
|
try {
|
|
|
const r = await fetch(url);
|
|
const r = await fetch(url);
|
|
|
return r.ok ? await r.json() : null;
|
|
return r.ok ? await r.json() : null;
|
|
@@ -11180,6 +11241,8 @@ async function deleteCurrentOspfInstance() {
|
|
|
closeOspfInstanceModal();
|
|
closeOspfInstanceModal();
|
|
|
showToast('Removed', 'OSPF instance deleted', 'success');
|
|
showToast('Removed', 'OSPF instance deleted', 'success');
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Error(await responseError(res));
|
|
|
}
|
|
}
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
showToast('Error', err.message, 'error');
|
|
showToast('Error', err.message, 'error');
|
|
@@ -11189,22 +11252,22 @@ async function deleteCurrentOspfInstance() {
|
|
|
async function enableSelectedOspfInstance() {
|
|
async function enableSelectedOspfInstance() {
|
|
|
const id = state.selectedOspfRow?.instances;
|
|
const id = state.selectedOspfRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}/state`, {
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}/state`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({ disabled: false })
|
|
body: JSON.stringify({ disabled: false })
|
|
|
- });
|
|
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedOspfInstance() {
|
|
async function disableSelectedOspfInstance() {
|
|
|
const id = state.selectedOspfRow?.instances;
|
|
const id = state.selectedOspfRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}/state`, {
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}/state`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({ disabled: true })
|
|
body: JSON.stringify({ disabled: true })
|
|
|
- });
|
|
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11212,7 +11275,7 @@ async function removeSelectedOspfInstance() {
|
|
|
const id = state.selectedOspfRow?.instances;
|
|
const id = state.selectedOspfRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
if (!confirm('Delete selected OSPF instance?')) return;
|
|
if (!confirm('Delete selected OSPF instance?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/instances/${encodeURIComponent(id)}`, { method: 'DELETE' })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11223,12 +11286,12 @@ async function promptCommentOspfInstance() {
|
|
|
if (!inst) return;
|
|
if (!inst) return;
|
|
|
const comment = prompt('Enter comment for OSPF instance:', inst.comment || '');
|
|
const comment = prompt('Enter comment for OSPF instance:', inst.comment || '');
|
|
|
if (comment === null) return;
|
|
if (comment === null) return;
|
|
|
- inst.comment = comment;
|
|
|
|
|
- await fetch(`${API_BASE}/routing/ospf/instances`, {
|
|
|
|
|
|
|
+ const updated = {...inst, comment};
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/instances`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
- body: JSON.stringify(inst)
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ body: JSON.stringify(updated)
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11337,6 +11400,8 @@ async function deleteCurrentOspfArea() {
|
|
|
closeOspfAreaModal();
|
|
closeOspfAreaModal();
|
|
|
showToast('Removed', 'OSPF area deleted', 'success');
|
|
showToast('Removed', 'OSPF area deleted', 'success');
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new Error(await responseError(res));
|
|
|
}
|
|
}
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
showToast('Error', err.message, 'error');
|
|
showToast('Error', err.message, 'error');
|
|
@@ -11346,22 +11411,22 @@ async function deleteCurrentOspfArea() {
|
|
|
async function enableSelectedOspfArea() {
|
|
async function enableSelectedOspfArea() {
|
|
|
const id = state.selectedOspfRow?.areas;
|
|
const id = state.selectedOspfRow?.areas;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}/state`, {
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}/state`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({ disabled: false })
|
|
body: JSON.stringify({ disabled: false })
|
|
|
- });
|
|
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedOspfArea() {
|
|
async function disableSelectedOspfArea() {
|
|
|
const id = state.selectedOspfRow?.areas;
|
|
const id = state.selectedOspfRow?.areas;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}/state`, {
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}/state`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({ disabled: true })
|
|
body: JSON.stringify({ disabled: true })
|
|
|
- });
|
|
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11369,7 +11434,7 @@ async function removeSelectedOspfArea() {
|
|
|
const id = state.selectedOspfRow?.areas;
|
|
const id = state.selectedOspfRow?.areas;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
if (!confirm('Delete selected OSPF area?')) return;
|
|
if (!confirm('Delete selected OSPF area?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/areas/${encodeURIComponent(id)}`, { method: 'DELETE' })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11380,12 +11445,12 @@ async function promptCommentOspfArea() {
|
|
|
if (!area) return;
|
|
if (!area) return;
|
|
|
const comment = prompt('Enter comment for OSPF area:', area.comment || '');
|
|
const comment = prompt('Enter comment for OSPF area:', area.comment || '');
|
|
|
if (comment === null) return;
|
|
if (comment === null) return;
|
|
|
- area.comment = comment;
|
|
|
|
|
- await fetch(`${API_BASE}/routing/ospf/areas`, {
|
|
|
|
|
|
|
+ const updated = {...area, comment};
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/areas`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
- body: JSON.stringify(area)
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ body: JSON.stringify(updated)
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11476,7 +11541,7 @@ async function deleteCurrentOspfAreaRange() {
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
if (!confirm('Delete this area range?')) return;
|
|
if (!confirm('Delete this area range?')) return;
|
|
|
try {
|
|
try {
|
|
|
- await fetch(`${API_BASE}/routing/ospf/area-ranges/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/area-ranges/${encodeURIComponent(id)}`, { method: 'DELETE' })) return;
|
|
|
closeOspfAreaRangeModal();
|
|
closeOspfAreaRangeModal();
|
|
|
showToast('Removed', 'Area range removed', 'success');
|
|
showToast('Removed', 'Area range removed', 'success');
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
@@ -11490,8 +11555,8 @@ async function enableSelectedOspfAreaRange() {
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
const r = (state.ospfAreaRanges || []).find(it => it.id === id);
|
|
const r = (state.ospfAreaRanges || []).find(it => it.id === id);
|
|
|
if (!r) return;
|
|
if (!r) return;
|
|
|
- r.disabled = false;
|
|
|
|
|
- await fetch(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(r) });
|
|
|
|
|
|
|
+ const updated = {...r, disabled: false};
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updated) })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11500,8 +11565,8 @@ async function disableSelectedOspfAreaRange() {
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
const r = (state.ospfAreaRanges || []).find(it => it.id === id);
|
|
const r = (state.ospfAreaRanges || []).find(it => it.id === id);
|
|
|
if (!r) return;
|
|
if (!r) return;
|
|
|
- r.disabled = true;
|
|
|
|
|
- await fetch(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(r) });
|
|
|
|
|
|
|
+ const updated = {...r, disabled: true};
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updated) })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11509,7 +11574,7 @@ async function removeSelectedOspfAreaRange() {
|
|
|
const id = state.selectedOspfRow?.['area-ranges'];
|
|
const id = state.selectedOspfRow?.['area-ranges'];
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
if (!confirm('Delete selected area range?')) return;
|
|
if (!confirm('Delete selected area range?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/area-ranges/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/area-ranges/${encodeURIComponent(id)}`, { method: 'DELETE' })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11520,8 +11585,8 @@ async function promptCommentOspfAreaRange() {
|
|
|
if (!r) return;
|
|
if (!r) return;
|
|
|
const comment = prompt('Enter comment for area range:', r.comment || '');
|
|
const comment = prompt('Enter comment for area range:', r.comment || '');
|
|
|
if (comment === null) return;
|
|
if (comment === null) return;
|
|
|
- r.comment = comment;
|
|
|
|
|
- await fetch(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(r) });
|
|
|
|
|
|
|
+ const updated = {...r, comment};
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/area-ranges`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updated) })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11670,7 +11735,7 @@ async function deleteCurrentOspfTemplate() {
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
if (!confirm('Delete this interface template?')) return;
|
|
if (!confirm('Delete this interface template?')) return;
|
|
|
try {
|
|
try {
|
|
|
- await fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' })) return;
|
|
|
closeOspfTemplateModal();
|
|
closeOspfTemplateModal();
|
|
|
showToast('Removed', 'Interface template deleted', 'success');
|
|
showToast('Removed', 'Interface template deleted', 'success');
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
@@ -11682,22 +11747,22 @@ async function deleteCurrentOspfTemplate() {
|
|
|
async function enableSelectedOspfTemplate() {
|
|
async function enableSelectedOspfTemplate() {
|
|
|
const id = state.selectedOspfRow?.templates;
|
|
const id = state.selectedOspfRow?.templates;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({ disabled: false })
|
|
body: JSON.stringify({ disabled: false })
|
|
|
- });
|
|
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedOspfTemplate() {
|
|
async function disableSelectedOspfTemplate() {
|
|
|
const id = state.selectedOspfRow?.templates;
|
|
const id = state.selectedOspfRow?.templates;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
body: JSON.stringify({ disabled: true })
|
|
body: JSON.stringify({ disabled: true })
|
|
|
- });
|
|
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11705,7 +11770,7 @@ async function removeSelectedOspfTemplate() {
|
|
|
const id = state.selectedOspfRow?.templates;
|
|
const id = state.selectedOspfRow?.templates;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
if (!confirm('Delete selected interface template?')) return;
|
|
if (!confirm('Delete selected interface template?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11716,12 +11781,12 @@ async function promptCommentOspfTemplate() {
|
|
|
if (!tmpl) return;
|
|
if (!tmpl) return;
|
|
|
const comment = prompt('Enter comment for template:', tmpl.comment || '');
|
|
const comment = prompt('Enter comment for template:', tmpl.comment || '');
|
|
|
if (comment === null) return;
|
|
if (comment === null) return;
|
|
|
- tmpl.comment = comment;
|
|
|
|
|
- await fetch(`${API_BASE}/routing/ospf/templates`, {
|
|
|
|
|
|
|
+ const updated = {...tmpl, comment};
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/templates`, {
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
- body: JSON.stringify(tmpl)
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ body: JSON.stringify(updated)
|
|
|
|
|
+ })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11812,7 +11877,7 @@ async function deleteCurrentOspfStaticNbr() {
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
if (!confirm('Delete this static neighbor?')) return;
|
|
if (!confirm('Delete this static neighbor?')) return;
|
|
|
try {
|
|
try {
|
|
|
- await fetch(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}`, { method: 'DELETE' })) return;
|
|
|
closeOspfStaticNbrModal();
|
|
closeOspfStaticNbrModal();
|
|
|
showToast('Removed', 'Static neighbor removed', 'success');
|
|
showToast('Removed', 'Static neighbor removed', 'success');
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
@@ -11824,14 +11889,14 @@ async function deleteCurrentOspfStaticNbr() {
|
|
|
async function enableSelectedOspfStaticNbr() {
|
|
async function enableSelectedOspfStaticNbr() {
|
|
|
const id = state.selectedOspfRow?.['static-nbrs'];
|
|
const id = state.selectedOspfRow?.['static-nbrs'];
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedOspfStaticNbr() {
|
|
async function disableSelectedOspfStaticNbr() {
|
|
|
const id = state.selectedOspfRow?.['static-nbrs'];
|
|
const id = state.selectedOspfRow?.['static-nbrs'];
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11839,7 +11904,7 @@ async function removeSelectedOspfStaticNbr() {
|
|
|
const id = state.selectedOspfRow?.['static-nbrs'];
|
|
const id = state.selectedOspfRow?.['static-nbrs'];
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
if (!confirm('Delete selected static neighbor?')) return;
|
|
if (!confirm('Delete selected static neighbor?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/static-neighbors/${encodeURIComponent(id)}`, { method: 'DELETE' })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11850,8 +11915,8 @@ async function promptCommentOspfStaticNbr() {
|
|
|
if (!n) return;
|
|
if (!n) return;
|
|
|
const comment = prompt('Enter comment for static neighbor:', n.comment || '');
|
|
const comment = prompt('Enter comment for static neighbor:', n.comment || '');
|
|
|
if (comment === null) return;
|
|
if (comment === null) return;
|
|
|
- n.comment = comment;
|
|
|
|
|
- await fetch(`${API_BASE}/routing/ospf/static-neighbors`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(n) });
|
|
|
|
|
|
|
+ const updated = {...n, comment};
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/ospf/static-neighbors`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updated) })) return;
|
|
|
loadDynamicRouting();
|
|
loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -11860,8 +11925,8 @@ function openAddOspfInterfaceModal() { openOspfTemplateModal(); }
|
|
|
function openEditOspfInterfaceModal(id) { openEditOspfTemplateModal(id); }
|
|
function openEditOspfInterfaceModal(id) { openEditOspfTemplateModal(id); }
|
|
|
function closeOspfInterfaceModal() { closeOspfTemplateModal(); }
|
|
function closeOspfInterfaceModal() { closeOspfTemplateModal(); }
|
|
|
function submitOspfInterface(e) { submitOspfTemplate(e); }
|
|
function submitOspfInterface(e) { submitOspfTemplate(e); }
|
|
|
-function deleteOspfInterface(id) {
|
|
|
|
|
- fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' }).then(() => loadDynamicRouting());
|
|
|
|
|
|
|
+async function deleteOspfInterface(id) {
|
|
|
|
|
+ if (await routingMutation(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}`, { method: 'DELETE' })) await loadDynamicRouting();
|
|
|
}
|
|
}
|
|
|
function toggleOspfInterfaceState(id, disabled) {
|
|
function toggleOspfInterfaceState(id, disabled) {
|
|
|
fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
|
|
fetch(`${API_BASE}/routing/ospf/templates/${encodeURIComponent(id)}/state`, {
|
|
@@ -13578,7 +13643,55 @@ async function toggleASListState(id, disabled) {
|
|
|
// TROUBLESHOOT & DIAGNOSTIC TOOLS (/tool)
|
|
// TROUBLESHOOT & DIAGNOSTIC TOOLS (/tool)
|
|
|
// ==========================================
|
|
// ==========================================
|
|
|
|
|
|
|
|
|
|
+async function loadNetworkOverview() {
|
|
|
|
|
+ const container = document.getElementById('tools-overview');
|
|
|
|
|
+ const timestamp = document.getElementById('tools-overview-time');
|
|
|
|
|
+ if (!container || !timestamp) return;
|
|
|
|
|
+ timestamp.textContent = 'Refreshing overview...';
|
|
|
|
|
+ const current = {};
|
|
|
|
|
+ container._overviewRequest = current;
|
|
|
|
|
+ const requests = [
|
|
|
|
|
+ ['Interfaces', '/interfaces'], ['System', '/system/status'], ['Routes', '/routes'],
|
|
|
|
|
+ ['DNS', '/dns/status'], ['WireGuard peers', '/vpn/wireguard/peers'], ['AeroTUN', '/vpn/aerotun/tunnels']
|
|
|
|
|
+ ];
|
|
|
|
|
+ const results = await Promise.all(requests.map(async ([label, path]) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(API_BASE + path);
|
|
|
|
|
+ if (!response.ok) throw new Error(await responseError(response));
|
|
|
|
|
+ return {label, data: await response.json()};
|
|
|
|
|
+ } catch (error) { return {label, error: error.message || 'Request failed'}; }
|
|
|
|
|
+ }));
|
|
|
|
|
+ // A later refresh supersedes an earlier response.
|
|
|
|
|
+ if (container._overviewRequest !== current) return;
|
|
|
|
|
+ container.replaceChildren();
|
|
|
|
|
+ for (const result of results) {
|
|
|
|
|
+ const card = document.createElement('div');
|
|
|
|
|
+ card.className = 'rounded border border-slate-700 bg-slate-950/50 p-3';
|
|
|
|
|
+ const title = document.createElement('div');
|
|
|
|
|
+ title.className = 'text-xs font-bold text-slate-200 mb-1';
|
|
|
|
|
+ title.textContent = result.label;
|
|
|
|
|
+ const detail = document.createElement('div');
|
|
|
|
|
+ detail.className = 'text-xs text-slate-400 whitespace-pre-wrap break-words';
|
|
|
|
|
+ detail.textContent = result.error ? `Unavailable: ${result.error}` : formatNetworkOverview(result.label, result.data);
|
|
|
|
|
+ card.append(title, detail); container.append(card);
|
|
|
|
|
+ }
|
|
|
|
|
+ timestamp.textContent = `Updated ${new Date().toLocaleTimeString()} · each section reports its own errors`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatNetworkOverview(label, data) {
|
|
|
|
|
+ if (label === 'System') return `Default gateway: ${data.default_gateway || 'not reported'}`;
|
|
|
|
|
+ if (label === 'DNS') return `Active resolvers: ${(data.active_servers || []).join(', ') || 'none reported'}\nDNS service: ${data.service_running ? 'running' : 'not running'}`;
|
|
|
|
|
+ if (!Array.isArray(data)) return 'Unexpected response';
|
|
|
|
|
+ if (label === 'Routes') return `${data.length} routes reported. Open Routing for details.`;
|
|
|
|
|
+ if (!data.length) return 'None reported';
|
|
|
|
|
+ if (label === 'Interfaces') return data.map(i => `${i.name}: ${i.operstate || i.state || 'unknown'} · ${[...(i.ipv4 || []), ...(i.ipv6 || [])].join(', ') || 'no addresses'}`).join('\n');
|
|
|
|
|
+ if (label === 'WireGuard peers') return data.map(p => `${p.interface || '?'} / ${p.name || p.public_key || '?'}: ${p.disabled ? 'disabled' : 'enabled'} · last handshake ${p.latest_handshake || 'not reported'}`).join('\n');
|
|
|
|
|
+ if (label === 'AeroTUN') return data.map(t => `${t.name || '?'}: ${t.disabled ? 'disabled' : t.connected ? 'connected' : t.running ? 'running, not connected' : 'stopped'}`).join('\n');
|
|
|
|
|
+ return 'Unavailable';
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function loadToolsView() {
|
|
function loadToolsView() {
|
|
|
|
|
+ loadNetworkOverview();
|
|
|
populateToolsInterfaces();
|
|
populateToolsInterfaces();
|
|
|
switchToolsSubTab(state.activeToolsSubTab || 'ping');
|
|
switchToolsSubTab(state.activeToolsSubTab || 'ping');
|
|
|
}
|
|
}
|
|
@@ -19674,6 +19787,17 @@ async function deleteSDWANMember(groupId, iface) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ==============================================================================
|
|
// ==============================================================================
|
|
|
|
|
+async function routingMutation(url, options) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(url, options);
|
|
|
|
|
+ if (!response.ok) throw new Error(await responseError(response));
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ showToast('Routing change failed', error.message || 'Request failed', 'error');
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// DYNAMIC ROUTING: RIP MODULE
|
|
// DYNAMIC ROUTING: RIP MODULE
|
|
|
// ==============================================================================
|
|
// ==============================================================================
|
|
|
state.ripInstances = [];
|
|
state.ripInstances = [];
|
|
@@ -19923,21 +20047,21 @@ async function submitRipInstance(e) {
|
|
|
async function enableSelectedRipInstance() {
|
|
async function enableSelectedRipInstance() {
|
|
|
const id = state.selectedRipRow?.instances;
|
|
const id = state.selectedRipRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/rip/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/rip/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedRipInstance() {
|
|
async function disableSelectedRipInstance() {
|
|
|
const id = state.selectedRipRow?.instances;
|
|
const id = state.selectedRipRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/rip/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/rip/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedRipInstance() {
|
|
async function removeSelectedRipInstance() {
|
|
|
const id = state.selectedRipRow?.instances;
|
|
const id = state.selectedRipRow?.instances;
|
|
|
if (!id || !confirm('Are you sure you want to delete this RIP instance?')) return;
|
|
if (!id || !confirm('Are you sure you want to delete this RIP instance?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/rip/instances/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/rip/instances/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedRipRow.instances = null;
|
|
state.selectedRipRow.instances = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -20032,21 +20156,21 @@ async function submitRipInterface(e) {
|
|
|
async function enableSelectedRipInterface() {
|
|
async function enableSelectedRipInterface() {
|
|
|
const id = state.selectedRipRow?.interfaces;
|
|
const id = state.selectedRipRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/rip/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/rip/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedRipInterface() {
|
|
async function disableSelectedRipInterface() {
|
|
|
const id = state.selectedRipRow?.interfaces;
|
|
const id = state.selectedRipRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/rip/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/rip/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedRipInterface() {
|
|
async function removeSelectedRipInterface() {
|
|
|
const id = state.selectedRipRow?.interfaces;
|
|
const id = state.selectedRipRow?.interfaces;
|
|
|
if (!id || !confirm('Are you sure you want to delete this RIP interface template?')) return;
|
|
if (!id || !confirm('Are you sure you want to delete this RIP interface template?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/rip/interfaces/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/rip/interfaces/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedRipRow.interfaces = null;
|
|
state.selectedRipRow.interfaces = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -20319,21 +20443,21 @@ async function submitBfdInterface(e) {
|
|
|
async function enableSelectedBfdInterface() {
|
|
async function enableSelectedBfdInterface() {
|
|
|
const id = state.selectedBfdRow?.interfaces;
|
|
const id = state.selectedBfdRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/bfd/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/bfd/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedBfdInterface() {
|
|
async function disableSelectedBfdInterface() {
|
|
|
const id = state.selectedBfdRow?.interfaces;
|
|
const id = state.selectedBfdRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/bfd/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/bfd/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedBfdInterface() {
|
|
async function removeSelectedBfdInterface() {
|
|
|
const id = state.selectedBfdRow?.interfaces;
|
|
const id = state.selectedBfdRow?.interfaces;
|
|
|
if (!id || !confirm('Are you sure you want to remove this BFD interface template?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this BFD interface template?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/bfd/interfaces/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/bfd/interfaces/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedBfdRow.interfaces = null;
|
|
state.selectedBfdRow.interfaces = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -20411,7 +20535,7 @@ async function enableSelectedBfdPeer() {
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
const p = (state.bfdNeighbors || []).find(n => n.id === id);
|
|
const p = (state.bfdNeighbors || []).find(n => n.id === id);
|
|
|
if (!p) return;
|
|
if (!p) return;
|
|
|
- await fetch(`${API_BASE}/routing/bfd/peers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...p, disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/bfd/peers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...p, disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -20420,14 +20544,14 @@ async function disableSelectedBfdPeer() {
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
const p = (state.bfdNeighbors || []).find(n => n.id === id);
|
|
const p = (state.bfdNeighbors || []).find(n => n.id === id);
|
|
|
if (!p) return;
|
|
if (!p) return;
|
|
|
- await fetch(`${API_BASE}/routing/bfd/peers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...p, disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/bfd/peers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...p, disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedBfdPeer() {
|
|
async function removeSelectedBfdPeer() {
|
|
|
const id = state.selectedBfdRow?.peers;
|
|
const id = state.selectedBfdRow?.peers;
|
|
|
if (!id || !confirm('Are you sure you want to remove this BFD peer?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this BFD peer?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/bfd/peers/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/bfd/peers/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedBfdRow.peers = null;
|
|
state.selectedBfdRow.peers = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -20709,21 +20833,21 @@ async function submitBabelInstance(e) {
|
|
|
async function enableSelectedBabelInstance() {
|
|
async function enableSelectedBabelInstance() {
|
|
|
const id = state.selectedBabelRow?.instances;
|
|
const id = state.selectedBabelRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/babel/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/babel/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedBabelInstance() {
|
|
async function disableSelectedBabelInstance() {
|
|
|
const id = state.selectedBabelRow?.instances;
|
|
const id = state.selectedBabelRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/babel/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/babel/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedBabelInstance() {
|
|
async function removeSelectedBabelInstance() {
|
|
|
const id = state.selectedBabelRow?.instances;
|
|
const id = state.selectedBabelRow?.instances;
|
|
|
if (!id || !confirm('Are you sure you want to remove this Babel instance?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this Babel instance?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/babel/instances/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/babel/instances/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedBabelRow.instances = null;
|
|
state.selectedBabelRow.instances = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -20838,21 +20962,21 @@ async function submitBabelInterface(e) {
|
|
|
async function enableSelectedBabelInterface() {
|
|
async function enableSelectedBabelInterface() {
|
|
|
const id = state.selectedBabelRow?.interfaces;
|
|
const id = state.selectedBabelRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/babel/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/babel/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedBabelInterface() {
|
|
async function disableSelectedBabelInterface() {
|
|
|
const id = state.selectedBabelRow?.interfaces;
|
|
const id = state.selectedBabelRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/babel/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/babel/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedBabelInterface() {
|
|
async function removeSelectedBabelInterface() {
|
|
|
const id = state.selectedBabelRow?.interfaces;
|
|
const id = state.selectedBabelRow?.interfaces;
|
|
|
if (!id || !confirm('Are you sure you want to remove this Babel interface template?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this Babel interface template?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/babel/interfaces/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/babel/interfaces/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedBabelRow.interfaces = null;
|
|
state.selectedBabelRow.interfaces = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -21022,21 +21146,21 @@ async function submitRAdvInstance(e) {
|
|
|
async function enableSelectedRadvInstance() {
|
|
async function enableSelectedRadvInstance() {
|
|
|
const id = state.selectedRadvRow?.instances;
|
|
const id = state.selectedRadvRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/radv/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/radv/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedRadvInstance() {
|
|
async function disableSelectedRadvInstance() {
|
|
|
const id = state.selectedRadvRow?.instances;
|
|
const id = state.selectedRadvRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/radv/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/radv/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedRadvInstance() {
|
|
async function removeSelectedRadvInstance() {
|
|
|
const id = state.selectedRadvRow?.instances;
|
|
const id = state.selectedRadvRow?.instances;
|
|
|
if (!id || !confirm('Are you sure you want to remove this RAdv instance?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this RAdv instance?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/radv/instances/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/radv/instances/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedRadvRow.instances = null;
|
|
state.selectedRadvRow.instances = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -21143,21 +21267,21 @@ async function submitRAdvInterface(e) {
|
|
|
async function enableSelectedRadvInterface() {
|
|
async function enableSelectedRadvInterface() {
|
|
|
const id = state.selectedRadvRow?.interfaces;
|
|
const id = state.selectedRadvRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/radv/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/radv/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedRadvInterface() {
|
|
async function disableSelectedRadvInterface() {
|
|
|
const id = state.selectedRadvRow?.interfaces;
|
|
const id = state.selectedRadvRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/radv/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/radv/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedRadvInterface() {
|
|
async function removeSelectedRadvInterface() {
|
|
|
const id = state.selectedRadvRow?.interfaces;
|
|
const id = state.selectedRadvRow?.interfaces;
|
|
|
if (!id || !confirm('Are you sure you want to remove this RAdv interface?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this RAdv interface?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/radv/interfaces/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/radv/interfaces/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedRadvRow.interfaces = null;
|
|
state.selectedRadvRow.interfaces = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -21365,21 +21489,21 @@ async function submitEigrpInstance(e) {
|
|
|
async function enableSelectedEigrpInstance() {
|
|
async function enableSelectedEigrpInstance() {
|
|
|
const id = state.selectedEigrpRow?.instances;
|
|
const id = state.selectedEigrpRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/eigrp/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/eigrp/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedEigrpInstance() {
|
|
async function disableSelectedEigrpInstance() {
|
|
|
const id = state.selectedEigrpRow?.instances;
|
|
const id = state.selectedEigrpRow?.instances;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/eigrp/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/eigrp/instances/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedEigrpInstance() {
|
|
async function removeSelectedEigrpInstance() {
|
|
|
const id = state.selectedEigrpRow?.instances;
|
|
const id = state.selectedEigrpRow?.instances;
|
|
|
if (!id || !confirm('Are you sure you want to remove this EIGRP instance?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this EIGRP instance?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/eigrp/instances/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/eigrp/instances/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedEigrpRow.instances = null;
|
|
state.selectedEigrpRow.instances = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -21453,7 +21577,7 @@ async function submitEigrpNetwork(e) {
|
|
|
async function removeSelectedEigrpNetwork() {
|
|
async function removeSelectedEigrpNetwork() {
|
|
|
const id = state.selectedEigrpRow?.networks;
|
|
const id = state.selectedEigrpRow?.networks;
|
|
|
if (!id || !confirm('Are you sure you want to remove this EIGRP network?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this EIGRP network?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/eigrp/networks/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/eigrp/networks/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedEigrpRow.networks = null;
|
|
state.selectedEigrpRow.networks = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
@@ -21590,21 +21714,21 @@ async function submitEigrpInterface(e) {
|
|
|
async function enableSelectedEigrpInterface() {
|
|
async function enableSelectedEigrpInterface() {
|
|
|
const id = state.selectedEigrpRow?.interfaces;
|
|
const id = state.selectedEigrpRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/eigrp/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/eigrp/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: false }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function disableSelectedEigrpInterface() {
|
|
async function disableSelectedEigrpInterface() {
|
|
|
const id = state.selectedEigrpRow?.interfaces;
|
|
const id = state.selectedEigrpRow?.interfaces;
|
|
|
if (!id) return;
|
|
if (!id) return;
|
|
|
- await fetch(`${API_BASE}/routing/eigrp/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/eigrp/interfaces/${id}/state`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ disabled: true }) })) return;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function removeSelectedEigrpInterface() {
|
|
async function removeSelectedEigrpInterface() {
|
|
|
const id = state.selectedEigrpRow?.interfaces;
|
|
const id = state.selectedEigrpRow?.interfaces;
|
|
|
if (!id || !confirm('Are you sure you want to remove this EIGRP interface?')) return;
|
|
if (!id || !confirm('Are you sure you want to remove this EIGRP interface?')) return;
|
|
|
- await fetch(`${API_BASE}/routing/eigrp/interfaces/${id}`, { method: 'DELETE' });
|
|
|
|
|
|
|
+ if (!await routingMutation(`${API_BASE}/routing/eigrp/interfaces/${id}`, { method: 'DELETE' })) return;
|
|
|
state.selectedEigrpRow.interfaces = null;
|
|
state.selectedEigrpRow.interfaces = null;
|
|
|
await loadDynamicRouting(false);
|
|
await loadDynamicRouting(false);
|
|
|
}
|
|
}
|