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