'use strict'; document.addEventListener('DOMContentLoaded', function() { const lgForm = document.getElementById('lg-form'); const executeBtn = document.getElementById('execute-btn'); const methodSelect = document.getElementById('method'); const outputConsole = document.getElementById('output-console'); const visualizerContainer = document.getElementById('visualizer-container'); const networkContainer = document.getElementById('mynetwork'); const formError = document.getElementById('form-error'); const mainLocationSelector = document.getElementById('main-location-selector'); const locationNameDisplay = document.getElementById('location-name-display'); const facilityNameDisplay = document.getElementById('facility-name-display'); const mapLinkBtn = document.getElementById('map-link-btn'); const peeringdbLinkBtn = document.getElementById('peeringdb-link-btn'); const lgIpv4Input = document.getElementById('lg-ipv4'); const lgIpv6Input = document.getElementById('lg-ipv6'); const clientIpv4Input = document.getElementById('client-ipv4'); const clientIpv6Input = document.getElementById('client-ipv6'); const iperfInInput = document.getElementById('iperf-in'); const iperfOutInput = document.getElementById('iperf-out'); const speedtestLinksContainer = document.getElementById('speedtest-links'); let network = null; let allLocationsData = {}; let clientIpApiUrls = {}; fetch('/api/locations') .then(response => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return response.json(); }) .then(config => { if (config.error) { throw new Error(config.error); } allLocationsData = config.locations; clientIpApiUrls = config.client_ip_api; populateLocationSelectors(); updateLocationInfo(); fetchClientIPs(); }) .catch(error => { console.error('Fatal Error: Could not fetch initial configuration:', error); lgForm.innerHTML = `
Could not load application configuration. Please check the server logs and your .env file.
`; }); lgForm.addEventListener('submit', async function(event) { event.preventDefault(); const target = document.getElementById('target').value.trim(); const method = methodSelect.value; 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); function highlightBGPOutput(text) { if (!text) return ''; text = text.replace(/&/g, "&").replace(//g, ">"); text = text.replace(/\b215085\b/g, '215085'); text = text.replace(/\b\d{4,6}\b/g, match => { if (match === '215085') return `215085`; return `${match}`; }); text = text.split('\n').map(line => { const lowerLine = line.toLowerCase(); if (lowerLine.includes('best') || lowerLine.includes('table entry') || lowerLine.includes('multipath')) { return `${line}`; } return line; }).join('\n'); return text.replace(/\n/g, '
'); } 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'; outputConsole.style.display = 'block'; outputConsole.textContent = `Executing ${method} on ${target}...\n\nThis may take a few moments. Please wait.`; try { const response = await fetch('/api/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ method, 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.textContent = ''; } } else if (response.ok && contentType && contentType.indexOf("text/plain") !== -1) { const reader = response.body.getReader(); const decoder = new TextDecoder(); outputConsole.textContent = ''; 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) { formError.textContent = error.message; } finally { setLoadingState(false); } } function handleVisualize(target) { outputConsole.style.display = 'none'; visualizerContainer.style.display = 'block'; const loader = document.getElementById('visualizer-loader'); if (network) { network.destroy(); } networkContainer.innerHTML = ''; loader.style.display = 'block'; fetch('/api/visualize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ip_address: target }) }) .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'; return; } const pathCount = data.path_count || 0; const dynamicHeight = Math.max(600, 200 + (pathCount * 150)); networkContainer.style.height = `${dynamicHeight}px`; network = new vis.Network(networkContainer, data, { layout: { hierarchical: false }, edges: { arrows: { to: { enabled: true, scaleFactor: 0.7 } }, smooth: { enabled: true, type: "cubicBezier", forceDirection: "horizontal", roundness: 0.85 } }, nodes: { shape: 'box', margin: 15, font: { size: 14, color: '#343a40', multi: 'html', align: 'left' }, borderWidth: 2 }, physics: { enabled: false } }); }) .catch(error => { visualizerContainer.style.display = 'none'; formError.textContent = `An unexpected error occurred: ${error.message}`; }) .finally(() => { loader.style.display = 'none'; setLoadingState(false); }); } function populateLocationSelectors() { mainLocationSelector.innerHTML = ''; for (const locationName in allLocationsData) { const option = document.createElement('option'); option.value = locationName; option.textContent = locationName; mainLocationSelector.appendChild(option); } } function updateLocationInfo() { const selectedLocationName = mainLocationSelector.value; const locationInfo = allLocationsData[selectedLocationName]; if (!locationInfo) return; locationNameDisplay.value = selectedLocationName; facilityNameDisplay.value = locationInfo.facility || 'N/A'; mapLinkBtn.href = `https://www.openstreetmap.org/search?query=${encodeURIComponent(selectedLocationName)}`; peeringdbLinkBtn.href = locationInfo.peeringdb_url || '#'; peeringdbLinkBtn.classList.toggle('disabled', !locationInfo.peeringdb_url); lgIpv4Input.value = locationInfo.ipv4 || 'N/A'; lgIpv6Input.value = locationInfo.ipv6 || 'N/A'; iperfInInput.value = locationInfo.iperf_in || 'N/A'; iperfOutInput.value = locationInfo.iperf_out || 'N/A'; speedtestLinksContainer.innerHTML = ''; if (locationInfo.speedtest_url_base && locationInfo.speedtest_files) { locationInfo.speedtest_files.forEach(fileName => { const link = document.createElement('a'); link.href = locationInfo.speedtest_url_base + fileName; link.textContent = fileName.replace('.bin', '').toUpperCase(); link.className = 'btn btn-sm btn-outline-secondary'; link.target = '_blank'; speedtestLinksContainer.appendChild(link); }); } } function fetchClientIPs() { clientIpv4Input.value = 'Detecting...'; clientIpv6Input.value = 'Detecting...'; if (clientIpApiUrls && clientIpApiUrls.v4) { fetch(clientIpApiUrls.v4) .then(response => response.json()) .then(data => { clientIpv4Input.value = data.ip || 'Unavailable'; }) .catch(() => { clientIpv4Input.value = 'Unavailable'; }); } else { clientIpv4Input.value = 'Not Configured'; } if (clientIpApiUrls && clientIpApiUrls.v6) { fetch(clientIpApiUrls.v6) .then(response => response.json()) .then(data => { clientIpv6Input.value = data.ip || 'Unavailable'; }) .catch(() => { clientIpv6Input.value = 'Unavailable'; }); } else { clientIpv6Input.value = 'Not Configured'; } } function setLoadingState(isLoading) { executeBtn.disabled = isLoading; executeBtn.innerHTML = isLoading ? ` Executing...` : 'Execute'; } }); window.copyToClipboard = function(elementId) { const input = document.getElementById(elementId); if (!input) return; const copyButton = input.nextElementSibling; input.select(); input.setSelectionRange(0, 99999); try { document.execCommand('copy'); if (copyButton) { const originalText = copyButton.textContent; copyButton.textContent = 'Copied!'; setTimeout(() => { copyButton.textContent = originalText; }, 2000); } } catch (err) { console.error('Failed to copy text: ', err); } }