修复BUG
This commit is contained in:
@@ -476,8 +476,8 @@
|
||||
</div>
|
||||
<div class="form-row" style="margin-top: 10px; align-items: flex-end;">
|
||||
<div class="form-group" style="flex: 2;">
|
||||
<label>Blackbox 目标 (instance_name)</label>
|
||||
<input type="text" id="routeTargetInput" placeholder="例:China-USA-Latency">
|
||||
<label>Blackbox 探测目标 (IP 或 域名)</label>
|
||||
<input type="text" id="routeTargetInput" placeholder="例:1.1.1.1 或 google.com">
|
||||
</div>
|
||||
<div class="form-actions" style="padding-bottom: 0; display: flex; gap: 8px;">
|
||||
<button class="btn btn-add" id="btnAddRoute" style="padding: 10px 24px;">添加线路</button>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user