Update static/js/looking_glass.js
This commit is contained in:
parent
5ecbe2e5b7
commit
cdd414b8a0
1 changed files with 95 additions and 69 deletions
|
|
@ -7,10 +7,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const outputConsole = document.getElementById('output-console');
|
const outputConsole = document.getElementById('output-console');
|
||||||
const visualizerContainer = document.getElementById('visualizer-container');
|
const visualizerContainer = document.getElementById('visualizer-container');
|
||||||
const networkContainer = document.getElementById('mynetwork');
|
const networkContainer = document.getElementById('mynetwork');
|
||||||
const alertModal = new bootstrap.Modal(document.getElementById('alertModal'));
|
const formError = document.getElementById('form-error');
|
||||||
const alertModalBody = document.getElementById('alertModalBody');
|
|
||||||
const mainLocationSelector = document.getElementById('main-location-selector');
|
const mainLocationSelector = document.getElementById('main-location-selector');
|
||||||
const locationsDropdownMenu = document.getElementById('locations-dropdown-menu');
|
|
||||||
const locationNameDisplay = document.getElementById('location-name-display');
|
const locationNameDisplay = document.getElementById('location-name-display');
|
||||||
const facilityNameDisplay = document.getElementById('facility-name-display');
|
const facilityNameDisplay = document.getElementById('facility-name-display');
|
||||||
const mapLinkBtn = document.getElementById('map-link-btn');
|
const mapLinkBtn = document.getElementById('map-link-btn');
|
||||||
|
|
@ -55,46 +53,82 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const target = document.getElementById('target').value.trim();
|
const target = document.getElementById('target').value.trim();
|
||||||
const method = methodSelect.value;
|
const method = methodSelect.value;
|
||||||
|
|
||||||
if (!target) {
|
formError.textContent = '';
|
||||||
showModalAlert('Please enter a target.');
|
outputConsole.innerHTML = 'Output will appear here...';
|
||||||
return;
|
visualizerContainer.style.display = 'none';
|
||||||
}
|
if (network) {
|
||||||
|
network.destroy();
|
||||||
let isValid = false;
|
networkContainer.innerHTML = '';
|
||||||
if (method === 'visualize') {
|
|
||||||
isValid = isValidIpOrCidr(target);
|
|
||||||
if (!isValid) {
|
|
||||||
showModalAlert('Invalid input for Visualizer. Please provide a valid IPv4/IPv6 address or CIDR prefix.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const isSingleIp = isValidIPv4(target) || isValidIPv6(target);
|
|
||||||
const isHostname = isValidFqdn(target);
|
|
||||||
isValid = isSingleIp || isHostname;
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
showModalAlert('Invalid input. For this method, please provide a valid IP address (without a prefix) or a fully qualified domain name.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoadingState(true);
|
setLoadingState(true);
|
||||||
|
|
||||||
if (method === 'visualize') {
|
if (method === 'visualize') {
|
||||||
handleVisualize(target);
|
handleVisualize(target);
|
||||||
|
} else if (method === 'bgp_raw') {
|
||||||
|
handleRawBgpLookup(target);
|
||||||
} else {
|
} else {
|
||||||
handleCommand(method, target);
|
handleCommand(method, target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mainLocationSelector.addEventListener('change', updateLocationInfo);
|
mainLocationSelector.addEventListener('change', updateLocationInfo);
|
||||||
locationsDropdownMenu.addEventListener('click', function(e) {
|
|
||||||
if (e.target.matches('a.dropdown-item')) {
|
function highlightBGPOutput(text) {
|
||||||
e.preventDefault();
|
if (!text) return '';
|
||||||
mainLocationSelector.value = e.target.dataset.location;
|
|
||||||
mainLocationSelector.dispatchEvent(new Event('change'));
|
text = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
|
||||||
|
text = text.replace(/\b215085\b/g, '<span class="asn215085-highlight">215085</span>');
|
||||||
|
|
||||||
|
text = text.replace(/\b\d{4,6}\b/g, match => {
|
||||||
|
if (match === '215085') return `<span class="asn215085-highlight">215085</span>`;
|
||||||
|
return `<span class="asn-highlight">${match}</span>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
text = text.split('\n').map(line => {
|
||||||
|
const lowerLine = line.toLowerCase();
|
||||||
|
if (lowerLine.includes('best') || lowerLine.includes('table entry') || lowerLine.includes('multipath')) {
|
||||||
|
return `<strong class="best-line">${line}</strong>`;
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return text.replace(/\n/g, '<br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRawBgpLookup(target) {
|
||||||
|
visualizerContainer.style.display = 'none';
|
||||||
|
outputConsole.style.display = 'block';
|
||||||
|
outputConsole.innerHTML = `Looking up BGP route for ${target}...\n\nThis may take a few moments. Please wait.`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/bgp_raw_lookup', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ target })
|
||||||
|
});
|
||||||
|
|
||||||
|
const contentType = response.headers.get("content-type");
|
||||||
|
|
||||||
|
if (contentType && contentType.indexOf("application/json") !== -1) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.error) {
|
||||||
|
formError.textContent = data.error;
|
||||||
|
outputConsole.innerHTML = '';
|
||||||
|
}
|
||||||
|
} else if (response.ok && contentType && contentType.indexOf("text/plain") !== -1) {
|
||||||
|
const rawText = await response.text();
|
||||||
|
outputConsole.innerHTML = highlightBGPOutput(rawText);
|
||||||
|
} else {
|
||||||
|
throw new Error('Received an unexpected response from the server.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
formError.textContent = error.message;
|
||||||
|
} finally {
|
||||||
|
setLoadingState(false);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
async function handleCommand(method, target) {
|
async function handleCommand(method, target) {
|
||||||
visualizerContainer.style.display = 'none';
|
visualizerContainer.style.display = 'none';
|
||||||
|
|
@ -108,25 +142,31 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
body: JSON.stringify({ method, target })
|
body: JSON.stringify({ method, target })
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
const contentType = response.headers.get("content-type");
|
||||||
const errorText = await response.text();
|
|
||||||
throw new Error(errorText);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
if (contentType && contentType.indexOf("application/json") !== -1) {
|
||||||
const decoder = new TextDecoder();
|
const data = await response.json();
|
||||||
|
if (data.error) {
|
||||||
|
formError.textContent = data.error;
|
||||||
|
outputConsole.textContent = '';
|
||||||
|
}
|
||||||
|
} else if (response.ok && contentType && contentType.indexOf("text/plain") !== -1) {
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
outputConsole.textContent = '';
|
||||||
|
|
||||||
outputConsole.textContent = '';
|
while (true) {
|
||||||
|
const { value, done } = await reader.read();
|
||||||
while (true) {
|
if (done) break;
|
||||||
const { value, done } = await reader.read();
|
outputConsole.textContent += decoder.decode(value, { stream: true });
|
||||||
if (done) break;
|
outputConsole.scrollTop = outputConsole.scrollHeight;
|
||||||
outputConsole.textContent += decoder.decode(value, { stream: true });
|
}
|
||||||
outputConsole.scrollTop = outputConsole.scrollHeight;
|
} else {
|
||||||
|
throw new Error('Received an unexpected response from the server.');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
outputConsole.textContent = `--- ERROR ---\n${error.message}`;
|
formError.textContent = error.message;
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingState(false);
|
setLoadingState(false);
|
||||||
}
|
}
|
||||||
|
|
@ -148,20 +188,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ ip_address: target })
|
body: JSON.stringify({ ip_address: target })
|
||||||
})
|
})
|
||||||
.then(response => response.json().then(data => ({ ok: response.ok, data })))
|
.then(response => response.json())
|
||||||
.then(({ ok, data }) => {
|
.then(data => {
|
||||||
if (!ok || data.error) {
|
if (data.error) {
|
||||||
throw new Error(data.error || 'Failed to retrieve visualization data.');
|
formError.textContent = data.error;
|
||||||
|
visualizerContainer.style.display = 'none';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.not_found) {
|
if (data.not_found) {
|
||||||
|
formError.textContent = `Route information not found for: ${data.target}`;
|
||||||
visualizerContainer.style.display = 'none';
|
visualizerContainer.style.display = 'none';
|
||||||
showModalAlert(`Route information not found for: ${data.target}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathCount = data.path_count || 0;
|
const pathCount = data.path_count || 0;
|
||||||
const dynamicHeight = Math.max(400, 200 + (pathCount * 150));
|
const dynamicHeight = Math.max(600, 200 + (pathCount * 150));
|
||||||
networkContainer.style.height = `${dynamicHeight}px`;
|
networkContainer.style.height = `${dynamicHeight}px`;
|
||||||
|
|
||||||
network = new vis.Network(networkContainer, data, {
|
network = new vis.Network(networkContainer, data, {
|
||||||
|
|
@ -180,8 +222,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
visualizerContainer.style.display = 'none';
|
visualizerContainer.style.display = 'none';
|
||||||
outputConsole.style.display = 'block';
|
formError.textContent = `An unexpected error occurred: ${error.message}`;
|
||||||
outputConsole.textContent = `Visualization Error: ${error.message}`;
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loader.style.display = 'none';
|
loader.style.display = 'none';
|
||||||
|
|
@ -189,28 +230,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showModalAlert(message) {
|
|
||||||
alertModalBody.textContent = message;
|
|
||||||
alertModal.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function populateLocationSelectors() {
|
function populateLocationSelectors() {
|
||||||
mainLocationSelector.innerHTML = '';
|
mainLocationSelector.innerHTML = '';
|
||||||
locationsDropdownMenu.innerHTML = '';
|
|
||||||
for (const locationName in allLocationsData) {
|
for (const locationName in allLocationsData) {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = locationName;
|
option.value = locationName;
|
||||||
option.textContent = locationName;
|
option.textContent = locationName;
|
||||||
mainLocationSelector.appendChild(option);
|
mainLocationSelector.appendChild(option);
|
||||||
|
|
||||||
const li = document.createElement('li');
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.className = 'dropdown-item';
|
|
||||||
a.href = '#';
|
|
||||||
a.textContent = locationName;
|
|
||||||
a.dataset.location = locationName;
|
|
||||||
li.appendChild(a);
|
|
||||||
locationsDropdownMenu.appendChild(li);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue