'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 alertModal = new bootstrap.Modal(document.getElementById('alertModal')); const alertModalBody = document.getElementById('alertModalBody'); 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'); 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; 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; } } setLoadingState(true); if (method === 'visualize') { handleVisualize(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')); } }); 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 }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(errorText); } 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; } } catch (error) { outputConsole.textContent = `--- ERROR ---\n${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 => ({ ok: response.ok, data }))) .then(({ ok, data }) => { if (!ok || data.error) { throw new Error(data.error || 'Failed to retrieve visualization data.'); } if (data.not_found) { 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)); 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'; outputConsole.style.display = 'block'; outputConsole.textContent = `Visualization Error: ${error.message}`; }) .finally(() => { loader.style.display = 'none'; setLoadingState(false); }); } 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); } } 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); } }