添加95计费出入去大带宽

This commit is contained in:
CN-JS-HuiBai
2026-04-09 12:21:41 +08:00
parent 2eae34bb96
commit 6aa8ba5fbc
5 changed files with 1030 additions and 1028 deletions

View File

@@ -536,6 +536,7 @@
<option value="tx">仅统计上行 (TX)</option> <option value="tx">仅统计上行 (TX)</option>
<option value="rx">仅统计下行 (RX)</option> <option value="rx">仅统计下行 (RX)</option>
<option value="both">统计上行+下行 (Sum)</option> <option value="both">统计上行+下行 (Sum)</option>
<option value="max">出入取大 (Max)</option>
</select> </select>
</div> </div>
<div class="form-group" style="margin-top: 15px;"> <div class="form-group" style="margin-top: 15px;">

View File

@@ -928,22 +928,7 @@
} }
lastMapDataHash = dataFingerprint; lastMapDataHash = dataFingerprint;
// 1. Pre-build coord lookup for O(1) route lookup // 1. Prepare geoData for scatter points
const serverCoords = new Map();
servers.forEach(s => {
if (s.lat && s.lng) {
const coords = [shiftLng(s.lng), s.lat];
const city = (s.city || '').toLowerCase().trim();
const country = (s.country || '').toLowerCase().trim();
const countryName = (s.countryName || '').toLowerCase().trim();
if (city) serverCoords.set(city, coords);
if (country) serverCoords.set(country, coords);
if (countryName) serverCoords.set(countryName, coords);
}
});
// 2. Prepare geoData for scatter points
const geoData = servers const geoData = servers
.filter(s => s.lat && s.lng) .filter(s => s.lat && s.lng)
.map(s => ({ .map(s => ({
@@ -978,61 +963,37 @@
if (currentLatencies && currentLatencies.length > 0) { if (currentLatencies && currentLatencies.length > 0) {
const countryCoords = { const countryCoords = {
'china': [116.4074, 39.9042], 'china': [116.4074, 39.9042],
'中国': [116.4074, 39.9042],
'beijing': [116.4074, 39.9042], 'beijing': [116.4074, 39.9042],
'北京': [116.4074, 39.9042],
'shanghai': [121.4737, 31.2304], 'shanghai': [121.4737, 31.2304],
'上海': [121.4737, 31.2304],
'hong kong': [114.1694, 22.3193], 'hong kong': [114.1694, 22.3193],
'香港': [114.1694, 22.3193],
'taiwan': [120.9605, 23.6978], 'taiwan': [120.9605, 23.6978],
'台湾': [120.9605, 23.6978],
'united states': [-95.7129, 37.0902], 'united states': [-95.7129, 37.0902],
'美国': [-95.7129, 37.0902],
'us seattle': [-122.3321, 47.6062], 'us seattle': [-122.3321, 47.6062],
'seattle': [-122.3321, 47.6062], 'seattle': [-122.3321, 47.6062],
'西雅图': [-122.3321, 47.6062],
'us chicago': [-87.6298, 41.8781], 'us chicago': [-87.6298, 41.8781],
'chicago': [-87.6298, 41.8781], 'chicago': [-87.6298, 41.8781],
'芝加哥': [-87.6298, 41.8781],
'news york': [-74.0060, 40.7128], 'news york': [-74.0060, 40.7128],
'new york corp': [-74.0060, 40.7128], 'new york corp': [-74.0060, 40.7128],
'new york': [-74.0060, 40.7128], 'new york': [-74.0060, 40.7128],
'纽约': [-74.0060, 40.7128],
'san francisco': [-122.4194, 37.7749], 'san francisco': [-122.4194, 37.7749],
'旧金山': [-122.4194, 37.7749],
'los angeles': [-118.2437, 34.0522], 'los angeles': [-118.2437, 34.0522],
'洛杉矶': [-118.2437, 34.0522],
'japan': [138.2529, 36.2048], 'japan': [138.2529, 36.2048],
'日本': [138.2529, 36.2048],
'tokyo': [139.6917, 35.6895], 'tokyo': [139.6917, 35.6895],
'东京': [139.6917, 35.6895],
'singapore': [103.8198, 1.3521], 'singapore': [103.8198, 1.3521],
'新加坡': [103.8198, 1.3521],
'germany': [10.4515, 51.1657], 'germany': [10.4515, 51.1657],
'德国': [10.4515, 51.1657],
'frankfurt': [8.6821, 50.1109], 'frankfurt': [8.6821, 50.1109],
'法兰克福': [8.6821, 50.1109],
'united kingdom': [-3.436, 55.3781], 'united kingdom': [-3.436, 55.3781],
'英国': [-3.436, 55.3781],
'london': [-0.1276, 51.5074], 'london': [-0.1276, 51.5074],
'伦敦': [-0.1276, 51.5074],
'france': [2.2137, 46.2276], 'france': [2.2137, 46.2276],
'法国': [2.2137, 46.2276],
'paris': [2.3522, 48.8566], 'paris': [2.3522, 48.8566],
'巴黎': [2.3522, 48.8566],
'south korea': [127.7669, 35.9078], 'south korea': [127.7669, 35.9078],
'korea': [127.7669, 35.9078], 'korea': [127.7669, 35.9078],
'韩国': [127.7669, 35.9078], 'seoul': [126.9780, 37.5665]
'seoul': [126.9780, 37.5665],
'首尔': [126.9780, 37.5665]
}; };
const getShiftedCoords = (name) => { const getShiftedCoords = (name) => {
const lower = (name || '').toLowerCase().trim(); const lower = (name || '').toLowerCase().trim();
if (countryCoords[lower]) return [shiftLng(countryCoords[lower][0]), countryCoords[lower][1]]; if (countryCoords[lower]) return [shiftLng(countryCoords[lower][0]), countryCoords[lower][1]];
const sCoord = serverCoords.get(lower);
if (sCoord) return sCoord;
return null; return null;
}; };
@@ -1107,11 +1068,11 @@
formatter: (params) => { formatter: (params) => {
const r = params.data.meta; const r = params.data.meta;
if (!r) return ''; if (!r) return '';
const latVal = (r.latency !== null && r.latency !== undefined) ? `${r.latency.toFixed(2)} ms` : '测量中...'; const latVal = (r.latency !== null && r.latency !== undefined) ? `${r.latency.toFixed(2)} ms` : 'Measuring...';
return ` return `
<div style="padding: 4px;"> <div style="padding: 4px;">
<div style="font-weight: 700;">${r.source}${r.dest}</div> <div style="font-weight: 700;">${r.source}${r.dest}</div>
<div style="font-size: 0.75rem; color: var(--accent-indigo); margin-top: 4px;">延时: ${latVal}</div> <div style="font-size: 0.75rem; color: var(--accent-indigo); margin-top: 4px;">Latency: ${latVal}</div>
</div> </div>
`; `;
} }
@@ -1551,6 +1512,8 @@
{ key: 'networkTrend', label: '网络流量趋势 (24h)', value: '' } { key: 'networkTrend', label: '网络流量趋势 (24h)', value: '' }
]; ];
const types = { tx: '上行', rx: '下行', both: '上行+下行', max: '出入取大' };
// Render normal metrics // Render normal metrics
dom.detailMetricsList.innerHTML = metrics.map(m => ` dom.detailMetricsList.innerHTML = metrics.map(m => `
<div class="metric-item" id="metric-${m.key}"> <div class="metric-item" id="metric-${m.key}">
@@ -1597,7 +1560,7 @@
<span class="traffic-value" id="stat-${m.key}-tx">0 B</span> <span class="traffic-value" id="stat-${m.key}-tx">0 B</span>
</div> </div>
<div class="traffic-stat traffic-stat-p95"> <div class="traffic-stat traffic-stat-p95">
<span class="traffic-label">95计费 (上行)</span> <span class="traffic-label" id="label-${m.key}-p95">95计费 (${types[window.SITE_SETTINGS?.p95_type || 'tx'] || '上行'})</span>
<span class="traffic-value" id="stat-${m.key}-p95">0 B/s</span> <span class="traffic-value" id="stat-${m.key}-p95">0 B/s</span>
</div> </div>
<div class="traffic-stat traffic-stat-total"> <div class="traffic-stat traffic-stat-total">
@@ -1715,6 +1678,12 @@
if (txEl) txEl.textContent = formatBytes(stats.txTotal); if (txEl) txEl.textContent = formatBytes(stats.txTotal);
if (p95El) p95El.textContent = formatBandwidth(stats.p95); if (p95El) p95El.textContent = formatBandwidth(stats.p95);
if (totalEl) totalEl.textContent = formatBytes(stats.total); if (totalEl) totalEl.textContent = formatBytes(stats.total);
const p95Label = document.getElementById(`label-${metricKey}-p95`);
if (p95Label) {
const types = { tx: '上行', rx: '下行', both: '上行+下行', max: '出入取大' };
p95Label.textContent = `95计费 (${types[window.SITE_SETTINGS?.p95_type || 'tx'] || '上行'})`;
}
} }
} }
@@ -1818,8 +1787,14 @@
dom.legendP95.classList.toggle('disabled', !networkChart.showP95); dom.legendP95.classList.toggle('disabled', !networkChart.showP95);
} }
if (dom.p95LabelText) { if (dom.p95LabelText) {
const types = { tx: '上行', rx: '下行', both: '上行+下行' }; const types = { tx: '上行', rx: '下行', both: '上行+下行', max: '出入取大' };
dom.p95LabelText.textContent = types[networkChart.p95Type] || '上行'; dom.p95LabelText.textContent = types[networkChart.p95Type] || '上行';
// Also update the static label in the chart footer
const trafficP95Label = document.querySelector('.chart-card-wide .traffic-stat-p95 .traffic-label');
if (trafficP95Label) {
trafficP95Label.textContent = `95计费 (${dom.p95LabelText.textContent})`;
}
} }
networkChart.draw(); networkChart.draw();
} }
@@ -1907,8 +1882,14 @@
if (settings.p95_type !== undefined) { if (settings.p95_type !== undefined) {
networkChart.p95Type = settings.p95_type; networkChart.p95Type = settings.p95_type;
if (dom.p95LabelText) { if (dom.p95LabelText) {
const types = { tx: '上行', rx: '下行', both: '上行+下行' }; const types = { tx: '上行', rx: '下行', both: '上行+下行', max: '出入取大' };
dom.p95LabelText.textContent = types[settings.p95_type] || '上行'; dom.p95LabelText.textContent = types[settings.p95_type] || '上行';
// Also update the static label in the chart footer
const trafficP95Label = document.querySelector('.chart-card-wide .traffic-stat-p95 .traffic-label');
if (trafficP95Label) {
trafficP95Label.textContent = `95计费 (${dom.p95LabelText.textContent})`;
}
} }
} }
networkChart.draw(); networkChart.draw();

View File

@@ -93,6 +93,8 @@ class AreaChart {
combined = data.tx.map(t => t || 0); combined = data.tx.map(t => t || 0);
} else if (this.p95Type === 'rx') { } else if (this.p95Type === 'rx') {
combined = data.rx.map(r => r || 0); combined = data.rx.map(r => r || 0);
} else if (this.p95Type === 'max') {
combined = data.tx.map((t, i) => Math.max(t || 0, data.rx[i] || 0));
} else { } else {
combined = data.tx.map((t, i) => (t || 0) + (data.rx[i] || 0)); combined = data.tx.map((t, i) => (t || 0) + (data.rx[i] || 0));
} }

File diff suppressed because it is too large Load Diff

View File

@@ -626,7 +626,7 @@ async function getServerDetails(baseUrl, instance, job) {
/** /**
* Get historical metrics for a specific server (node) * Get historical metrics for a specific server (node)
*/ */
async function getServerHistory(baseUrl, instance, job, metric, range = '1h', start = null, end = null) { async function getServerHistory(baseUrl, instance, job, metric, range = '1h', start = null, end = null, p95Type = 'tx') {
const url = normalizeUrl(baseUrl); const url = normalizeUrl(baseUrl);
const node = resolveToken(instance); const node = resolveToken(instance);
@@ -681,9 +681,22 @@ async function getServerHistory(baseUrl, instance, job, metric, range = '1h', st
txTotal += (tx[i] || 0) * duration; txTotal += (tx[i] || 0) * duration;
} }
const sortedTx = [...tx].sort((a, b) => a - b); // Calculate P95 based on p95Type
const p95Idx = Math.floor(sortedTx.length * 0.95); let combined = [];
const p95 = sortedTx.length > 0 ? sortedTx[p95Idx] : 0; if (p95Type === 'rx') {
combined = [...rx];
} else if (p95Type === 'both') {
combined = tx.map((t, i) => (t || 0) + (rx[i] || 0));
} else if (p95Type === 'max') {
combined = tx.map((t, i) => Math.max(t || 0, rx[i] || 0));
} else {
// Default to tx
combined = [...tx];
}
const sorted = combined.sort((a, b) => a - b);
const p95Idx = Math.floor(sorted.length * 0.95);
const p95 = sorted.length > 0 ? sorted[p95Idx] : 0;
return { return {
timestamps, timestamps,