document.addEventListener('DOMContentLoaded', () => { const map = L.map('map', { zoomControl: false }).setView([52.13, 5.29], 5); L.control.zoom({ position: 'topright' }).addTo(map); const openStreetMap = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors' }); openStreetMap.addTo(map); const modal = document.getElementById('testModal'); const span = document.getElementsByClassName("close")[0]; const modalTitle = document.getElementById('modal-title'); const modalBody = document.getElementById('modal-body'); span.onclick = function() { modal.style.display = "none"; } window.onclick = function(event) { if (event.target == modal) { modal.style.display = "none"; } } const serversByLocation = {}; let markersLayer = L.layerGroup().addTo(map); const loadingStatus = document.getElementById('loading-status'); const serverCountSpan = document.getElementById('server-count'); if ("geolocation" in navigator) { navigator.geolocation.getCurrentPosition(position => { const lat = position.coords.latitude; const lon = position.coords.longitude; const userIcon = L.divIcon({ className: 'custom-div-icon', html: "
📍
", iconSize: [30, 30], iconAnchor: [15, 30] }); L.marker([lat, lon], { icon: userIcon }) .addTo(map) .openPopup(); }, error => { console.log("Geolocation access denied or failed.", error); }); } function loadServers() { fetch('/api/servers') .then(response => response.json()) .then(servers => { if (servers.length > 0) { loadingStatus.style.display = 'block'; serverCountSpan.textContent = servers.length; } if (servers.length === 0) { console.log("No servers yet, retrying..."); setTimeout(loadServers, 3000); return; } markersLayer.clearLayers(); for (const key in serversByLocation) delete serversByLocation[key]; servers.forEach(server => { if (server.lat && server.lon) { const key = `${server.lat},${server.lon}`; if (!serversByLocation[key]) { serversByLocation[key] = { lat: server.lat, lon: server.lon, city: server.SITE, country: server.COUNTRY, servers: [] }; } serversByLocation[key].servers.push(server); } }); Object.values(serversByLocation).forEach(loc => { const starIcon = L.divIcon({ className: 'custom-div-icon', html: "
★
", iconSize: [30, 30], iconAnchor: [15, 15] }); const marker = L.marker([loc.lat, loc.lon], { icon: starIcon }); const container = document.createElement('div'); const title = document.createElement('h3'); title.textContent = `${loc.city}, ${loc.country}`; container.appendChild(title); const list = document.createElement('div'); loc.servers.forEach(server => { const item = document.createElement('div'); item.className = 'server-list-item'; const info = document.createElement('span'); info.textContent = `${server.PROVIDER} (${server['IP/HOST']})`; const btn = document.createElement('button'); btn.className = 'btn'; btn.textContent = 'Select'; btn.onclick = () => openTestModal(server); item.appendChild(info); item.appendChild(btn); list.appendChild(item); }); container.appendChild(list); marker.bindPopup(container); markersLayer.addLayer(marker); }); if (servers.length < 100) { setTimeout(loadServers, 5000); } else { loadingStatus.textContent = `Loaded ${servers.length} servers.`; setTimeout(() => { loadingStatus.style.display = 'none'; }, 3000); } }) .catch(err => console.error('Error loading servers:', err)); } loadServers(); const updateBtn = document.getElementById('update-locations-btn'); if (updateBtn) { updateBtn.onclick = () => { if (!confirm("Update locations? This will fetch the latest server list and regenerate the location database. It may take a while.")) return; const icon = updateBtn.querySelector('i'); icon.classList.add('fa-spin'); updateBtn.disabled = true; fetch('/api/update-locations', { method: 'POST' }) .then(async res => { const contentType = res.headers.get('content-type') || ''; if (contentType.includes('application/json')) { const data = await res.json(); if (!res.ok) { throw new Error(data.error || `HTTP ${res.status}`); } return data; } const text = await res.text(); if (!res.ok) { throw new Error(text || `HTTP ${res.status}`); } throw new Error(text || 'Unexpected non-JSON response'); }) .then(data => { if (data.error) { alert("Error: " + data.error); } else { alert(data.message); location.reload(); } }) .catch(err => alert("Error: " + err)) .finally(() => { icon.classList.remove('fa-spin'); updateBtn.disabled = false; }); }; } function openTestModal(server) { modal.style.display = "block"; modalTitle.textContent = `Test to ${server.SITE} (${server['IP/HOST']})`; let ports = []; if (server.PORT) { if (server.PORT.includes('-')) { const parts = server.PORT.split('-'); const start = parseInt(parts[0]); const end = parseInt(parts[1]); for (let p = start; p <= end; p++) { ports.push(p); if (ports.length > 20) break; } } else if (server.PORT.includes(',')) { ports = server.PORT.split(',').map(p => parseInt(p.trim())); } else { ports = [parseInt(server.PORT)]; } } else { ports = [5201]; } let portOptions = ports.map(p => ``).join(''); modalBody.innerHTML = `
Provider: ${server.PROVIDER}
Host: ${server['IP/HOST']}

Server suggested options: ${server.OPTIONS || 'None'}
`; const startBtn = document.getElementById('start-test-btn'); const statusDiv = document.getElementById('test-status'); const outputDiv = document.getElementById('test-output'); const portSelect = document.getElementById('port-select'); const optReverse = document.getElementById('opt-reverse'); const optIPv6 = document.getElementById('opt-ipv6'); const optCustom = document.getElementById('opt-custom'); startBtn.onclick = () => { startBtn.disabled = true; statusDiv.innerHTML = ' Running test... (this may take 10-20 seconds)'; outputDiv.style.display = 'none'; outputDiv.textContent = ''; const selectedPort = parseInt(portSelect.value); let flags = ""; if (optReverse.checked) flags += " -R"; if (optIPv6.checked) flags += " -6"; if (optCustom.value) flags += " " + optCustom.value; fetch('/api/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ host: server['IP/HOST'], port: selectedPort, flags: flags.trim() }) }) .then(response => response.json()) .then(data => { startBtn.disabled = false; statusDiv.innerHTML = ''; outputDiv.style.display = 'block'; if (data.error) { outputDiv.textContent = "Error: " + data.error; outputDiv.style.color = 'red'; } else { outputDiv.textContent = data.output; outputDiv.style.color = '#00ff00'; } }) .catch(err => { startBtn.disabled = false; statusDiv.innerHTML = ''; outputDiv.style.display = 'block'; outputDiv.textContent = "Network/Server Error: " + err; outputDiv.style.color = 'red'; }); }; } });