添加95带宽计算
This commit is contained in:
@@ -656,6 +656,14 @@ input:checked+.slider:before {
|
||||
background: var(--accent-indigo);
|
||||
}
|
||||
|
||||
.legend-p95 {
|
||||
background: var(--accent-rose);
|
||||
height: 2px;
|
||||
width: 12px;
|
||||
border-top: 1px dashed var(--accent-rose);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
padding: 12px 22px;
|
||||
height: 280px;
|
||||
@@ -700,6 +708,14 @@ input:checked+.slider:before {
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.traffic-stat-p95 .traffic-value {
|
||||
color: var(--accent-rose);
|
||||
}
|
||||
|
||||
.traffic-stat-time .traffic-value {
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
/* ---- Gauges ---- */
|
||||
.gauges-container {
|
||||
display: flex;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -7,24 +8,26 @@
|
||||
<title>数据可视化展示大屏</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<script>
|
||||
// Prevent theme flicker
|
||||
(function() {
|
||||
(function () {
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const settings = window.SITE_SETTINGS || {};
|
||||
const defaultTheme = settings.default_theme || 'dark';
|
||||
let theme = savedTheme || defaultTheme;
|
||||
|
||||
|
||||
if (theme === 'auto') {
|
||||
theme = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
|
||||
if (theme === 'light') {
|
||||
document.documentElement.classList.add('light-theme');
|
||||
}
|
||||
|
||||
|
||||
// Also apply title if available to prevent flicker
|
||||
if (settings.page_name) {
|
||||
document.title = settings.page_name;
|
||||
@@ -32,6 +35,7 @@
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Animated Background -->
|
||||
<div class="bg-grid"></div>
|
||||
@@ -47,14 +51,15 @@
|
||||
<div class="logo">
|
||||
<div id="logoIconContainer">
|
||||
<svg class="logo-icon" id="logoSvg" viewBox="0 0 32 32" fill="none">
|
||||
<rect x="2" y="2" width="28" height="28" rx="8" stroke="url(#logoGrad)" stroke-width="2.5"/>
|
||||
<path d="M8 22 L12 14 L16 18 L20 10 L24 16" stroke="url(#logoGrad)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
<circle cx="12" cy="14" r="2" fill="url(#logoGrad)"/>
|
||||
<circle cx="20" cy="10" r="2" fill="url(#logoGrad)"/>
|
||||
<rect x="2" y="2" width="28" height="28" rx="8" stroke="url(#logoGrad)" stroke-width="2.5" />
|
||||
<path d="M8 22 L12 14 L16 18 L20 10 L24 16" stroke="url(#logoGrad)" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
||||
<circle cx="12" cy="14" r="2" fill="url(#logoGrad)" />
|
||||
<circle cx="20" cy="10" r="2" fill="url(#logoGrad)" />
|
||||
<defs>
|
||||
<linearGradient id="logoGrad" x1="0" y1="0" x2="32" y2="32">
|
||||
<stop offset="0%" stop-color="#6366f1"/>
|
||||
<stop offset="100%" stop-color="#06b6d4"/>
|
||||
<stop offset="0%" stop-color="#6366f1" />
|
||||
<stop offset="100%" stop-color="#06b6d4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
@@ -75,8 +80,22 @@
|
||||
<label class="theme-switch" for="themeToggle">
|
||||
<input type="checkbox" id="themeToggle" />
|
||||
<div class="slider round">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="theme-icon sun-icon"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="theme-icon moon-icon" style="display: none;"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="theme-icon sun-icon">
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
</svg>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round" class="theme-icon moon-icon" style="display: none;">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -84,9 +103,12 @@
|
||||
<button class="btn btn-login" id="btnLogin">登录</button>
|
||||
</div>
|
||||
<button class="btn-settings" id="btnSettings" title="配置管理">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||
<path
|
||||
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z">
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -99,10 +121,10 @@
|
||||
<div class="stat-card stat-card-servers" id="cardServers">
|
||||
<div class="stat-card-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<rect x="2" y="2" width="20" height="8" rx="2"/>
|
||||
<rect x="2" y="14" width="20" height="8" rx="2"/>
|
||||
<circle cx="6" cy="6" r="1" fill="currentColor"/>
|
||||
<circle cx="6" cy="18" r="1" fill="currentColor"/>
|
||||
<rect x="2" y="2" width="20" height="8" rx="2" />
|
||||
<rect x="2" y="14" width="20" height="8" rx="2" />
|
||||
<circle cx="6" cy="6" r="1" fill="currentColor" />
|
||||
<circle cx="6" cy="18" r="1" fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
@@ -113,12 +135,16 @@
|
||||
<div class="stat-card stat-card-cpu" id="cardCpu">
|
||||
<div class="stat-card-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
||||
<rect x="9" y="9" width="6" height="6"/>
|
||||
<line x1="9" y1="2" x2="9" y2="4"/><line x1="15" y1="2" x2="15" y2="4"/>
|
||||
<line x1="9" y1="20" x2="9" y2="22"/><line x1="15" y1="20" x2="15" y2="22"/>
|
||||
<line x1="2" y1="9" x2="4" y2="9"/><line x1="2" y1="15" x2="4" y2="15"/>
|
||||
<line x1="20" y1="9" x2="22" y2="9"/><line x1="20" y1="15" x2="22" y2="15"/>
|
||||
<rect x="4" y="4" width="16" height="16" rx="2" />
|
||||
<rect x="9" y="9" width="6" height="6" />
|
||||
<line x1="9" y1="2" x2="9" y2="4" />
|
||||
<line x1="15" y1="2" x2="15" y2="4" />
|
||||
<line x1="9" y1="20" x2="9" y2="22" />
|
||||
<line x1="15" y1="20" x2="15" y2="22" />
|
||||
<line x1="2" y1="9" x2="4" y2="9" />
|
||||
<line x1="2" y1="15" x2="4" y2="15" />
|
||||
<line x1="20" y1="9" x2="22" y2="9" />
|
||||
<line x1="20" y1="15" x2="22" y2="15" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
@@ -130,8 +156,8 @@
|
||||
<div class="stat-card stat-card-mem" id="cardMem">
|
||||
<div class="stat-card-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
||||
<path d="M7 7h4v4H7zM13 7h4v4h-4zM7 13h4v4H7zM13 13h4v4h-4z"/>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
<path d="M7 7h4v4H7zM13 7h4v4h-4zM7 13h4v4H7zM13 13h4v4h-4z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
@@ -143,9 +169,9 @@
|
||||
<div class="stat-card stat-card-disk" id="cardDisk">
|
||||
<div class="stat-card-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<ellipse cx="12" cy="5" rx="9" ry="3"/>
|
||||
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
|
||||
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
|
||||
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
||||
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
||||
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
@@ -157,7 +183,7 @@
|
||||
<div class="stat-card stat-card-bandwidth" id="cardBandwidth">
|
||||
<div class="stat-card-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
|
||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-card-content">
|
||||
@@ -176,7 +202,7 @@
|
||||
<div class="chart-header-left">
|
||||
<h2 class="chart-title">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="chart-title-icon">
|
||||
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
|
||||
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
|
||||
</svg>
|
||||
网络流量趋势 (24h)
|
||||
</h2>
|
||||
@@ -185,6 +211,7 @@
|
||||
<div class="chart-legend">
|
||||
<span class="legend-item"><span class="legend-dot legend-rx"></span>接收 (RX)</span>
|
||||
<span class="legend-item"><span class="legend-dot legend-tx"></span>发送 (TX)</span>
|
||||
<span class="legend-item"><span class="legend-dot legend-p95"></span>95计费 (P95)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-body">
|
||||
@@ -199,10 +226,18 @@
|
||||
<span class="traffic-label">24h 发送总量</span>
|
||||
<span class="traffic-value" id="traffic24hTx">0 B</span>
|
||||
</div>
|
||||
<div class="traffic-stat traffic-stat-p95">
|
||||
<span class="traffic-label">95计费带宽</span>
|
||||
<span class="traffic-value" id="trafficP95">0 B/s</span>
|
||||
</div>
|
||||
<div class="traffic-stat traffic-stat-total">
|
||||
<span class="traffic-label">24h 总流量</span>
|
||||
<span class="traffic-value" id="traffic24hTotal">0 B</span>
|
||||
</div>
|
||||
<div class="traffic-stat traffic-stat-time">
|
||||
<span class="traffic-label">当前时间</span>
|
||||
<span class="traffic-value" id="footerTime">00:00:00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -213,10 +248,10 @@
|
||||
<div class="chart-card-header">
|
||||
<h2 class="chart-title">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="chart-title-icon">
|
||||
<rect x="2" y="2" width="20" height="8" rx="2"/>
|
||||
<rect x="2" y="14" width="20" height="8" rx="2"/>
|
||||
<circle cx="6" cy="6" r="1" fill="currentColor"/>
|
||||
<circle cx="6" cy="18" r="1" fill="currentColor"/>
|
||||
<rect x="2" y="2" width="20" height="8" rx="2" />
|
||||
<rect x="2" y="14" width="20" height="8" rx="2" />
|
||||
<circle cx="6" cy="6" r="1" fill="currentColor" />
|
||||
<circle cx="6" cy="18" r="1" fill="currentColor" />
|
||||
</svg>
|
||||
服务器详情
|
||||
</h2>
|
||||
@@ -312,7 +347,8 @@
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 15px;">
|
||||
<label for="defaultThemeInput">默认主题</label>
|
||||
<select id="defaultThemeInput" style="padding: 10px 14px; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary);">
|
||||
<select id="defaultThemeInput"
|
||||
style="padding: 10px 14px; background: var(--bg-input); border: 1px solid var(--border-color); border-radius: var(--radius-sm); color: var(--text-primary);">
|
||||
<option value="dark">默认夜间模式</option>
|
||||
<option value="light">默认白天模式</option>
|
||||
<option value="auto">跟随浏览器/系统</option>
|
||||
@@ -346,8 +382,11 @@
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" placeholder="请输入密码" required>
|
||||
</div>
|
||||
<div id="loginError" style="color: var(--accent-rose); font-size: 0.8rem; margin-top: 10px; display: none;"></div>
|
||||
<button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 24px; background: var(--gradient-primary); color: white; border: none; padding: 12px; border-radius: 8px; cursor: pointer;">登 录</button>
|
||||
<div id="loginError" style="color: var(--accent-rose); font-size: 0.8rem; margin-top: 10px; display: none;">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"
|
||||
style="width: 100%; margin-top: 24px; background: var(--gradient-primary); color: white; border: none; padding: 12px; border-radius: 8px; cursor: pointer;">登
|
||||
录</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -357,4 +396,5 @@
|
||||
<script src="/js/chart.js"></script>
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
@@ -25,6 +25,7 @@
|
||||
traffic24hRx: document.getElementById('traffic24hRx'),
|
||||
traffic24hTx: document.getElementById('traffic24hTx'),
|
||||
traffic24hTotal: document.getElementById('traffic24hTotal'),
|
||||
trafficP95: document.getElementById('trafficP95'),
|
||||
networkCanvas: document.getElementById('networkCanvas'),
|
||||
serverTableBody: document.getElementById('serverTableBody'),
|
||||
btnSettings: document.getElementById('btnSettings'),
|
||||
@@ -58,7 +59,8 @@
|
||||
closeLoginModal: document.getElementById('closeLoginModal'),
|
||||
loginForm: document.getElementById('loginForm'),
|
||||
loginError: document.getElementById('loginError'),
|
||||
gaugesTime: document.getElementById('gaugesTime')
|
||||
gaugesTime: document.getElementById('gaugesTime'),
|
||||
footerTime: document.getElementById('footerTime')
|
||||
};
|
||||
|
||||
// ---- State ----
|
||||
@@ -120,7 +122,7 @@
|
||||
// Start data fetching
|
||||
fetchMetrics();
|
||||
fetchNetworkHistory();
|
||||
|
||||
|
||||
// Site settings
|
||||
if (window.SITE_SETTINGS) {
|
||||
applySiteSettings(window.SITE_SETTINGS);
|
||||
@@ -128,14 +130,14 @@
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const currentTheme = savedTheme || window.SITE_SETTINGS.default_theme || 'dark';
|
||||
updateThemeIcons(currentTheme);
|
||||
|
||||
|
||||
// Still populate inputs
|
||||
dom.pageNameInput.value = window.SITE_SETTINGS.page_name || '';
|
||||
dom.siteTitleInput.value = window.SITE_SETTINGS.title || '';
|
||||
dom.logoUrlInput.value = window.SITE_SETTINGS.logo_url || '';
|
||||
dom.defaultThemeInput.value = window.SITE_SETTINGS.default_theme || 'dark';
|
||||
}
|
||||
|
||||
|
||||
loadSiteSettings();
|
||||
|
||||
setInterval(fetchMetrics, REFRESH_INTERVAL);
|
||||
@@ -155,7 +157,7 @@
|
||||
if (theme === 'auto') {
|
||||
actualTheme = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
|
||||
const isLight = actualTheme === 'light';
|
||||
dom.themeToggle.checked = isLight;
|
||||
document.documentElement.classList.toggle('light-theme', isLight);
|
||||
@@ -259,8 +261,12 @@
|
||||
}
|
||||
|
||||
function updateGaugesTime() {
|
||||
const clockStr = formatClock();
|
||||
if (dom.gaugesTime) {
|
||||
dom.gaugesTime.textContent = formatClock();
|
||||
dom.gaugesTime.textContent = clockStr;
|
||||
}
|
||||
if (dom.footerTime) {
|
||||
dom.footerTime.textContent = clockStr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +393,9 @@
|
||||
const response = await fetch('/api/metrics/network-history');
|
||||
const data = await response.json();
|
||||
networkChart.setData(data);
|
||||
if (dom.trafficP95 && networkChart.p95) {
|
||||
dom.trafficP95.textContent = formatBandwidth(networkChart.p95);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching network history:', err);
|
||||
}
|
||||
@@ -419,7 +428,7 @@
|
||||
try {
|
||||
const response = await fetch('/api/settings');
|
||||
const settings = await response.json();
|
||||
|
||||
|
||||
window.SITE_SETTINGS = settings; // Cache it globally
|
||||
|
||||
// Update inputs
|
||||
@@ -427,7 +436,7 @@
|
||||
dom.siteTitleInput.value = settings.title || '';
|
||||
dom.logoUrlInput.value = settings.logo_url || '';
|
||||
dom.defaultThemeInput.value = settings.default_theme || 'dark';
|
||||
|
||||
|
||||
// Apply to UI
|
||||
applySiteSettings(settings);
|
||||
|
||||
@@ -457,7 +466,7 @@
|
||||
if (settings.title) {
|
||||
dom.logoText.textContent = settings.title;
|
||||
}
|
||||
|
||||
|
||||
// Logo Icon
|
||||
if (settings.logo_url) {
|
||||
dom.logoIconContainer.innerHTML = `<img src="${escapeHtml(settings.logo_url)}" alt="Logo" class="logo-icon-img">`;
|
||||
|
||||
@@ -47,6 +47,18 @@ class AreaChart {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
// Calculate P95 (95th percentile)
|
||||
// Common standard: 95th percentile of the peak (max of rx/tx or sum)
|
||||
// We'll use max(rx, tx) at each point which is common for billing
|
||||
const combined = data.rx.map((r, i) => Math.max(r || 0, data.tx[i] || 0));
|
||||
if (combined.length > 0) {
|
||||
const sorted = [...combined].sort((a, b) => a - b);
|
||||
const p95Idx = Math.floor(sorted.length * 0.95);
|
||||
this.p95 = sorted[p95Idx];
|
||||
} else {
|
||||
this.p95 = null;
|
||||
}
|
||||
|
||||
this.animProgress = 0;
|
||||
this.animate();
|
||||
}
|
||||
@@ -173,6 +185,35 @@ class AreaChart {
|
||||
this.drawArea(ctx, rx, getX, getY, chartH, p,
|
||||
'rgba(6, 182, 212, 0.25)', 'rgba(6, 182, 212, 0.02)',
|
||||
'#06b6d4', len);
|
||||
|
||||
// Draw P95 line
|
||||
if (this.p95 && this.animProgress === 1) {
|
||||
const p95Y = getY(this.p95);
|
||||
// Only draw if within visible range
|
||||
if (p95Y >= p.top && p95Y <= p.top + chartH) {
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.setLineDash([6, 4]);
|
||||
ctx.strokeStyle = 'rgba(244, 63, 94, 0.85)'; // --accent-rose
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.moveTo(p.left, p95Y);
|
||||
ctx.lineTo(p.left + chartW, p95Y);
|
||||
ctx.stroke();
|
||||
|
||||
// P95 label background
|
||||
const label = '95计费: ' + (window.formatBandwidth ? window.formatBandwidth(this.p95) : this.p95.toFixed(2));
|
||||
ctx.font = 'bold 11px "JetBrains Mono", monospace';
|
||||
const metrics = ctx.measureText(label);
|
||||
ctx.fillStyle = 'rgba(244, 63, 94, 0.15)';
|
||||
ctx.fillRect(p.left + 8, p95Y - 20, metrics.width + 12, 18);
|
||||
|
||||
// P95 label text
|
||||
ctx.fillStyle = '#f43f5e';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(label, p.left + 14, p95Y - 7);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawArea(ctx, values, getX, getY, chartH, p, fillColorTop, fillColorBottom, strokeColor, len) {
|
||||
|
||||
Reference in New Issue
Block a user