修复BUG
This commit is contained in:
@@ -476,8 +476,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-row" style="margin-top: 10px; align-items: flex-end;">
|
<div class="form-row" style="margin-top: 10px; align-items: flex-end;">
|
||||||
<div class="form-group" style="flex: 2;">
|
<div class="form-group" style="flex: 2;">
|
||||||
<label>Blackbox 目标 (instance_name)</label>
|
<label>Blackbox 探测目标 (IP 或 域名)</label>
|
||||||
<input type="text" id="routeTargetInput" placeholder="例:China-USA-Latency">
|
<input type="text" id="routeTargetInput" placeholder="例:1.1.1.1 或 google.com">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-actions" style="padding-bottom: 0; display: flex; gap: 8px;">
|
<div class="form-actions" style="padding-bottom: 0; display: flex; gap: 8px;">
|
||||||
<button class="btn btn-add" id="btnAddRoute" style="padding: 10px 24px;">添加线路</button>
|
<button class="btn btn-add" id="btnAddRoute" style="padding: 10px 24px;">添加线路</button>
|
||||||
|
|||||||
@@ -568,12 +568,36 @@
|
|||||||
// Fetch map data
|
// Fetch map data
|
||||||
const resp = await fetch('https://cdn.jsdelivr.net/npm/echarts@4.9.0/map/json/world.json');
|
const resp = await fetch('https://cdn.jsdelivr.net/npm/echarts@4.9.0/map/json/world.json');
|
||||||
const worldJSON = await resp.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);
|
echarts.registerMap('world', worldJSON);
|
||||||
|
|
||||||
myMap2D = echarts.init(dom.globeContainer);
|
myMap2D = echarts.init(dom.globeContainer);
|
||||||
|
|
||||||
const isLight = document.documentElement.classList.contains('light-theme');
|
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 = {
|
const option = {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
@@ -601,9 +625,11 @@
|
|||||||
geo: {
|
geo: {
|
||||||
map: 'world',
|
map: 'world',
|
||||||
roam: true,
|
roam: true,
|
||||||
|
center: [165, 20], // Centered in Pacific
|
||||||
|
zoom: 1.1,
|
||||||
emphasis: {
|
emphasis: {
|
||||||
label: { show: false },
|
label: { show: false },
|
||||||
itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' }
|
itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' }
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
areaColor: isLight ? '#eef0f5' : '#1a1d2d',
|
areaColor: isLight ? '#eef0f5' : '#1a1d2d',
|
||||||
@@ -611,7 +637,7 @@
|
|||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' }
|
itemStyle: { areaColor: isLight ? '#f0f2f5' : '#2d334d' }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
series: [{
|
series: [{
|
||||||
@@ -658,11 +684,14 @@
|
|||||||
function updateMap2D(servers) {
|
function updateMap2D(servers) {
|
||||||
if (!myMap2D) return;
|
if (!myMap2D) return;
|
||||||
|
|
||||||
|
// Shift longitude for Pacific-centered view
|
||||||
|
const shiftLng = (lng) => (lng < -20 ? lng + 360 : lng);
|
||||||
|
|
||||||
const geoData = servers
|
const geoData = servers
|
||||||
.filter(s => s.lat && s.lng)
|
.filter(s => s.lat && s.lng)
|
||||||
.map(s => ({
|
.map(s => ({
|
||||||
name: s.job,
|
name: s.job,
|
||||||
value: [s.lng, s.lat],
|
value: [shiftLng(s.lng), s.lat],
|
||||||
job: s.job,
|
job: s.job,
|
||||||
city: s.city,
|
city: s.city,
|
||||||
country: s.country,
|
country: s.country,
|
||||||
@@ -715,13 +744,21 @@
|
|||||||
(sv.country || '').toLowerCase() === lowerName ||
|
(sv.country || '').toLowerCase() === lowerName ||
|
||||||
(sv.city || '').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;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
currentLatencies.forEach((route, index) => {
|
currentLatencies.forEach((route, index) => {
|
||||||
const startCoords = getCoords(route.source);
|
const startCoords = getShiftedCoords(route.source);
|
||||||
const endCoords = getCoords(route.dest);
|
const endCoords = getShiftedCoords(route.dest);
|
||||||
|
|
||||||
if (startCoords && endCoords) {
|
if (startCoords && endCoords) {
|
||||||
finalSeries.push({
|
finalSeries.push({
|
||||||
|
|||||||
@@ -31,23 +31,33 @@ async function pollLatency() {
|
|||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const response = await axios.get(probeUrl, { timeout: 5000 });
|
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 latency = null;
|
||||||
|
let success = false;
|
||||||
const lines = response.data.split('\n');
|
const lines = response.data.split('\n');
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// Match "probe_duration_seconds 0.123" or "probe_duration_seconds{...} 0.123"
|
if (line.match(/^probe_success\s+1/)) {
|
||||||
const match = line.match(/^probe_duration_seconds(?:\{.*\})?\s+([\d.]+)/);
|
success = true;
|
||||||
if (match) {
|
|
||||||
latency = parseFloat(match[1]) * 1000; // to ms
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (latency === null) {
|
if (success) {
|
||||||
// Fallback to local response time if metric not found in output
|
// Try specialized metrics first for better accuracy
|
||||||
latency = duration * 1000;
|
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
|
// Save to Valkey
|
||||||
|
|||||||
Reference in New Issue
Block a user