添加95计费出入去大带宽
This commit is contained in:
@@ -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;">
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user