diff --git a/public/index.html b/public/index.html index 8636350..1423d67 100644 --- a/public/index.html +++ b/public/index.html @@ -476,8 +476,8 @@
- - + +
diff --git a/public/js/app.js b/public/js/app.js index e32dac6..2cfafa6 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -568,12 +568,36 @@ // Fetch map data const resp = await fetch('https://cdn.jsdelivr.net/npm/echarts@4.9.0/map/json/world.json'); const worldJSON = await resp.json(); + + // Transform to Pacific-centered: shift longitudes < -20 to 340-360 range + // This puts America on the right of Asia + worldJSON.features.forEach(feature => { + if (feature.geometry.type === 'Polygon') { + feature.geometry.coordinates.forEach(ring => { + ring.forEach(coords => { + if (coords[0] < -20) coords[0] += 360; + }); + }); + } else if (feature.geometry.type === 'MultiPolygon') { + feature.geometry.coordinates.forEach(polygon => { + polygon.forEach(ring => { + ring.forEach(coords => { + if (coords[0] < -20) coords[0] += 360; + }); + }); + }); + } + }); + echarts.registerMap('world', worldJSON); myMap2D = echarts.init(dom.globeContainer); const isLight = document.documentElement.classList.contains('light-theme'); + // Helper to transform app coordinates to shifted map coordinates + const shiftLng = (lng) => (lng < -20 ? lng + 360 : lng); + const option = { backgroundColor: 'transparent', tooltip: { @@ -601,9 +625,11 @@ geo: { map: 'world', roam: true, + center: [165, 20], // Centered in Pacific + zoom: 1.1, emphasis: { - label: { show: false }, - itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' } + label: { show: false }, + itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' } }, itemStyle: { areaColor: isLight ? '#eef0f5' : '#1a1d2d', @@ -611,7 +637,7 @@ borderWidth: 1 }, select: { - itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' } + itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' } } }, series: [{ @@ -658,11 +684,14 @@ function updateMap2D(servers) { if (!myMap2D) return; + // Shift longitude for Pacific-centered view + const shiftLng = (lng) => (lng < -20 ? lng + 360 : lng); + const geoData = servers .filter(s => s.lat && s.lng) .map(s => ({ name: s.job, - value: [s.lng, s.lat], + value: [shiftLng(s.lng), s.lat], job: s.job, city: s.city, country: s.country, @@ -715,13 +744,21 @@ (sv.country || '').toLowerCase() === lowerName || (sv.city || '').toLowerCase() === lowerName ); - if (s && s.lng && s.lat) return [s.lng, s.lat]; + if (s && s.lng && s.lat) return [shiftLng(s.lng), s.lat]; + return null; + }; + + const getShiftedCoords = (name) => { + const c = countryCoords[(name || '').toLowerCase().trim()]; + if (c) return [shiftLng(c[0]), c[1]]; + const raw = getCoords(name); + if (raw) return [shiftLng(raw[0]), raw[1]]; return null; }; currentLatencies.forEach((route, index) => { - const startCoords = getCoords(route.source); - const endCoords = getCoords(route.dest); + const startCoords = getShiftedCoords(route.source); + const endCoords = getShiftedCoords(route.dest); if (startCoords && endCoords) { finalSeries.push({ diff --git a/server/latency-service.js b/server/latency-service.js index 4e544c6..7a94d11 100644 --- a/server/latency-service.js +++ b/server/latency-service.js @@ -31,23 +31,33 @@ async function pollLatency() { const startTime = Date.now(); const response = await axios.get(probeUrl, { timeout: 5000 }); - const duration = (Date.now() - startTime) / 1000; // Fallback to local timing if parsing fails - // Parse prometheus text format for probe_duration_seconds + // 1. Parse prometheus text format for success and specific metrics let latency = null; + let success = false; const lines = response.data.split('\n'); + for (const line of lines) { - // Match "probe_duration_seconds 0.123" or "probe_duration_seconds{...} 0.123" - const match = line.match(/^probe_duration_seconds(?:\{.*\})?\s+([\d.]+)/); - if (match) { - latency = parseFloat(match[1]) * 1000; // to ms + if (line.match(/^probe_success\s+1/)) { + success = true; break; } } - if (latency === null) { - // Fallback to local response time if metric not found in output - latency = duration * 1000; + if (success) { + // Try specialized metrics first for better accuracy + const priorityMetrics = ['probe_icmp_duration_seconds', 'probe_http_duration_seconds', 'probe_duration_seconds']; + for (const metricName of priorityMetrics) { + const regex = new RegExp(`^${metricName}(?:\\{.*\\})?\\s+([\\d.]+)`); + for (const line of lines) { + const match = line.match(regex); + if (match) { + latency = parseFloat(match[1]) * 1000; // to ms + break; + } + } + if (latency !== null) break; + } } // Save to Valkey