添加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="rx">仅统计下行 (RX)</option>
<option value="both">统计上行+下行 (Sum)</option>
<option value="max">出入取大 (Max)</option>
</select>
</div>
<div class="form-group" style="margin-top: 15px;">

View File

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

View File

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

View File

@@ -1096,19 +1096,24 @@ app.get('/api/metrics/server-history', async (req, res) => {
if (!instance || !job || !source || !metric) {
return res.status(400).json({ error: 'instance, job, source, and metric are required' });
}
try {
const [rows] = await db.query('SELECT url FROM prometheus_sources WHERE name = ?', [source]);
if (rows.length === 0) return res.status(404).json({ error: 'Source not found' });
const sourceUrl = rows[0].url;
const data = await prometheusService.getServerHistory(sourceUrl, instance, job, metric, range, start, end);
// Fetch p95Type from settings for networkTrend stats
let p95Type = 'tx';
try {
const [settingsRows] = await db.query('SELECT p95_type FROM site_settings WHERE id = 1');
if (settingsRows.length > 0) p95Type = settingsRows[0].p95_type;
} catch (e) {}
const data = await prometheusService.getServerHistory(sourceUrl, instance, job, metric, range, start, end, p95Type);
res.json(data);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// SPA fallback
app.get('*', (req, res, next) => {

View File

@@ -626,7 +626,7 @@ async function getServerDetails(baseUrl, instance, job) {
/**
* 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 node = resolveToken(instance);
@@ -681,9 +681,22 @@ async function getServerHistory(baseUrl, instance, job, metric, range = '1h', st
txTotal += (tx[i] || 0) * duration;
}
const sortedTx = [...tx].sort((a, b) => a - b);
const p95Idx = Math.floor(sortedTx.length * 0.95);
const p95 = sortedTx.length > 0 ? sortedTx[p95Idx] : 0;
// Calculate P95 based on p95Type
let combined = [];
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 {
timestamps,