Update static/js/looking_glass.js

This commit is contained in:
Blackwhitebear8 2025-08-14 12:54:25 +02:00
parent 5ecbe2e5b7
commit cdd414b8a0

View file

@ -7,10 +7,8 @@ document.addEventListener('DOMContentLoaded', function() {
const outputConsole = document.getElementById('output-console');
const visualizerContainer = document.getElementById('visualizer-container');
const networkContainer = document.getElementById('mynetwork');
const alertModal = new bootstrap.Modal(document.getElementById('alertModal'));
const alertModalBody = document.getElementById('alertModalBody');
const formError = document.getElementById('form-error');
const mainLocationSelector = document.getElementById('main-location-selector');
const locationsDropdownMenu = document.getElementById('locations-dropdown-menu');
const locationNameDisplay = document.getElementById('location-name-display');
const facilityNameDisplay = document.getElementById('facility-name-display');
const mapLinkBtn = document.getElementById('map-link-btn');
@ -55,46 +53,82 @@ document.addEventListener('DOMContentLoaded', function() {
const target = document.getElementById('target').value.trim();
const method = methodSelect.value;
if (!target) {
showModalAlert('Please enter a target.');
return;
}
let isValid = false;
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;
}
formError.textContent = '';
outputConsole.innerHTML = 'Output will appear here...';
visualizerContainer.style.display = 'none';
if (network) {
network.destroy();
networkContainer.innerHTML = '';
}
setLoadingState(true);
if (method === 'visualize') {
handleVisualize(target);
} else if (method === 'bgp_raw') {
handleRawBgpLookup(target);
} else {
handleCommand(method, target);
}
});
mainLocationSelector.addEventListener('change', updateLocationInfo);
locationsDropdownMenu.addEventListener('click', function(e) {
if (e.target.matches('a.dropdown-item')) {
e.preventDefault();
mainLocationSelector.value = e.target.dataset.location;
mainLocationSelector.dispatchEvent(new Event('change'));
function highlightBGPOutput(text) {
if (!text) return '';
text = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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) {
visualizerContainer.style.display = 'none';
@ -108,25 +142,31 @@ document.addEventListener('DOMContentLoaded', function() {
body: JSON.stringify({ method, target })
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText);
}
const contentType = response.headers.get("content-type");
const reader = response.body.getReader();
const decoder = new TextDecoder();
if (contentType && contentType.indexOf("application/json") !== -1) {
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();
if (done) break;
outputConsole.textContent += decoder.decode(value, { stream: true });
outputConsole.scrollTop = outputConsole.scrollHeight;
while (true) {
const { value, done } = await reader.read();
if (done) break;
outputConsole.textContent += decoder.decode(value, { stream: true });
outputConsole.scrollTop = outputConsole.scrollHeight;
}
} else {
throw new Error('Received an unexpected response from the server.');
}
} catch (error) {
outputConsole.textContent = `--- ERROR ---\n${error.message}`;
formError.textContent = error.message;
} finally {
setLoadingState(false);
}
@ -148,20 +188,22 @@ document.addEventListener('DOMContentLoaded', function() {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ip_address: target })
})
.then(response => response.json().then(data => ({ ok: response.ok, data })))
.then(({ ok, data }) => {
if (!ok || data.error) {
throw new Error(data.error || 'Failed to retrieve visualization data.');
.then(response => response.json())
.then(data => {
if (data.error) {
formError.textContent = data.error;
visualizerContainer.style.display = 'none';
return;
}
if (data.not_found) {
formError.textContent = `Route information not found for: ${data.target}`;
visualizerContainer.style.display = 'none';
showModalAlert(`Route information not found for: ${data.target}`);
return;
}
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`;
network = new vis.Network(networkContainer, data, {
@ -180,8 +222,7 @@ document.addEventListener('DOMContentLoaded', function() {
})
.catch(error => {
visualizerContainer.style.display = 'none';
outputConsole.style.display = 'block';
outputConsole.textContent = `Visualization Error: ${error.message}`;
formError.textContent = `An unexpected error occurred: ${error.message}`;
})
.finally(() => {
loader.style.display = 'none';
@ -189,28 +230,13 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
function showModalAlert(message) {
alertModalBody.textContent = message;
alertModal.show();
}
function populateLocationSelectors() {
mainLocationSelector.innerHTML = '';
locationsDropdownMenu.innerHTML = '';
for (const locationName in allLocationsData) {
const option = document.createElement('option');
option.value = locationName;
option.textContent = locationName;
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);
}
}