修复更多内容
This commit is contained in:
@@ -102,6 +102,9 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 获取活跃来源IP数量
|
// 获取活跃来源IP数量
|
||||||
activeIPCount := len(dnsStats.SourceIPs)
|
activeIPCount := len(dnsStats.SourceIPs)
|
||||||
|
|
||||||
|
// 格式化平均响应时间为两位小数
|
||||||
|
formattedResponseTime := float64(int(dnsStats.AvgResponseTime*100)) / 100
|
||||||
|
|
||||||
// 构建响应数据,确保所有字段都反映服务器的真实状态
|
// 构建响应数据,确保所有字段都反映服务器的真实状态
|
||||||
stats := map[string]interface{}{
|
stats := map[string]interface{}{
|
||||||
"dns": map[string]interface{}{
|
"dns": map[string]interface{}{
|
||||||
@@ -110,7 +113,7 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
"Allowed": dnsStats.Allowed,
|
"Allowed": dnsStats.Allowed,
|
||||||
"Errors": dnsStats.Errors,
|
"Errors": dnsStats.Errors,
|
||||||
"LastQuery": dnsStats.LastQuery,
|
"LastQuery": dnsStats.LastQuery,
|
||||||
"AvgResponseTime": dnsStats.AvgResponseTime,
|
"AvgResponseTime": formattedResponseTime,
|
||||||
"TotalResponseTime": dnsStats.TotalResponseTime,
|
"TotalResponseTime": dnsStats.TotalResponseTime,
|
||||||
"QueryTypes": dnsStats.QueryTypes,
|
"QueryTypes": dnsStats.QueryTypes,
|
||||||
"SourceIPs": dnsStats.SourceIPs,
|
"SourceIPs": dnsStats.SourceIPs,
|
||||||
@@ -119,7 +122,7 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
"shield": shieldStats,
|
"shield": shieldStats,
|
||||||
"topQueryType": topQueryType,
|
"topQueryType": topQueryType,
|
||||||
"activeIPs": activeIPCount,
|
"activeIPs": activeIPCount,
|
||||||
"avgResponseTime": dnsStats.AvgResponseTime,
|
"avgResponseTime": formattedResponseTime,
|
||||||
"cpuUsage": dnsStats.CpuUsage,
|
"cpuUsage": dnsStats.CpuUsage,
|
||||||
"time": time.Now(),
|
"time": time.Now(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,6 +193,40 @@ header p {
|
|||||||
transition: padding-left 0.3s ease;
|
transition: padding-left 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tooltip趋势信息颜色类 - 替代内联style */
|
||||||
|
.tooltip-trend {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 注意:这些颜色值与colors.config.js中的COLOR_CONFIG.colorClassMap保持同步 */
|
||||||
|
.tooltip-trend.blue {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-trend.green {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-trend.orange {
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-trend.red {
|
||||||
|
color: #f5222d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-trend.purple {
|
||||||
|
color: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-trend.cyan {
|
||||||
|
color: #13c2c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-trend.teal {
|
||||||
|
color: #36cfc9;
|
||||||
|
}
|
||||||
|
|
||||||
/* 平板设备适配 - 侧边栏折叠时调整内容区域 */
|
/* 平板设备适配 - 侧边栏折叠时调整内容区域 */
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@@ -150,6 +150,7 @@
|
|||||||
<i class="fa fa-refresh"></i>
|
<i class="fa fa-refresh"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
<div class="flex items-end justify-between">
|
<div class="flex items-end justify-between">
|
||||||
<p class="text-3xl font-bold" id="total-queries">0</p>
|
<p class="text-3xl font-bold" id="total-queries">0</p>
|
||||||
<span class="text-success text-sm flex items-center">
|
<span class="text-success text-sm flex items-center">
|
||||||
@@ -157,6 +158,10 @@
|
|||||||
<span id="queries-percent">0%</span>
|
<span id="queries-percent">0%</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-16 mt-2">
|
||||||
|
<canvas id="query-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -171,6 +176,7 @@
|
|||||||
<i class="fa fa-ban"></i>
|
<i class="fa fa-ban"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
<div class="flex items-end justify-between">
|
<div class="flex items-end justify-between">
|
||||||
<p class="text-3xl font-bold" id="blocked-queries">0</p>
|
<p class="text-3xl font-bold" id="blocked-queries">0</p>
|
||||||
<span class="text-danger text-sm flex items-center">
|
<span class="text-danger text-sm flex items-center">
|
||||||
@@ -178,6 +184,10 @@
|
|||||||
<span id="blocked-percent">0%</span>
|
<span id="blocked-percent">0%</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-16 mt-2">
|
||||||
|
<canvas id="blocked-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -192,6 +202,7 @@
|
|||||||
<i class="fa fa-check"></i>
|
<i class="fa fa-check"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
<div class="flex items-end justify-between">
|
<div class="flex items-end justify-between">
|
||||||
<p class="text-3xl font-bold" id="allowed-queries">0</p>
|
<p class="text-3xl font-bold" id="allowed-queries">0</p>
|
||||||
<span class="text-success text-sm flex items-center">
|
<span class="text-success text-sm flex items-center">
|
||||||
@@ -199,6 +210,10 @@
|
|||||||
<span id="allowed-percent">0%</span>
|
<span id="allowed-percent">0%</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-16 mt-2">
|
||||||
|
<canvas id="allowed-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -213,6 +228,7 @@
|
|||||||
<i class="fa fa-exclamation-triangle"></i>
|
<i class="fa fa-exclamation-triangle"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
<div class="flex items-end justify-between">
|
<div class="flex items-end justify-between">
|
||||||
<p class="text-3xl font-bold" id="error-queries">0</p>
|
<p class="text-3xl font-bold" id="error-queries">0</p>
|
||||||
<span class="text-warning text-sm flex items-center">
|
<span class="text-warning text-sm flex items-center">
|
||||||
@@ -220,6 +236,10 @@
|
|||||||
<span id="error-percent">0%</span>
|
<span id="error-percent">0%</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-16 mt-2">
|
||||||
|
<canvas id="error-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -234,6 +254,7 @@
|
|||||||
<i class="fa fa-clock-o"></i>
|
<i class="fa fa-clock-o"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
<div class="flex items-end justify-between">
|
<div class="flex items-end justify-between">
|
||||||
<p class="text-3xl font-bold" id="avg-response-time">0ms</p>
|
<p class="text-3xl font-bold" id="avg-response-time">0ms</p>
|
||||||
<span class="text-success text-sm flex items-center">
|
<span class="text-success text-sm flex items-center">
|
||||||
@@ -241,6 +262,10 @@
|
|||||||
<span id="response-time-percent">0%</span>
|
<span id="response-time-percent">0%</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-16 mt-2">
|
||||||
|
<canvas id="response-time-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -276,6 +301,7 @@
|
|||||||
<i class="fa fa-globe"></i>
|
<i class="fa fa-globe"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
<div class="flex items-end justify-between">
|
<div class="flex items-end justify-between">
|
||||||
<p class="text-3xl font-bold" id="active-ips">0</p>
|
<p class="text-3xl font-bold" id="active-ips">0</p>
|
||||||
<span class="text-success text-sm flex items-center">
|
<span class="text-success text-sm flex items-center">
|
||||||
@@ -283,6 +309,10 @@
|
|||||||
<span id="active-ips-percent">0%</span>
|
<span id="active-ips-percent">0%</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-16 mt-2">
|
||||||
|
<canvas id="ips-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -297,6 +327,7 @@
|
|||||||
<i class="fa fa-microchip"></i>
|
<i class="fa fa-microchip"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
<div class="flex items-end justify-between">
|
<div class="flex items-end justify-between">
|
||||||
<p class="text-3xl font-bold" id="cpu-usage">0%</p>
|
<p class="text-3xl font-bold" id="cpu-usage">0%</p>
|
||||||
<span class="text-warning text-sm flex items-center">
|
<span class="text-warning text-sm flex items-center">
|
||||||
@@ -304,6 +335,10 @@
|
|||||||
<span id="cpu-status">正常</span>
|
<span id="cpu-status">正常</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-16 mt-2">
|
||||||
|
<canvas id="cpu-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -436,5 +471,5 @@
|
|||||||
<script src="js/hosts.js"></script>
|
<script src="js/hosts.js"></script>
|
||||||
<script src="js/query.js"></script>
|
<script src="js/query.js"></script>
|
||||||
<script src="js/config.js"></script>
|
<script src="js/config.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -60,7 +60,27 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
|
|||||||
|
|
||||||
// 尝试解析JSON
|
// 尝试解析JSON
|
||||||
const parsedData = JSON.parse(responseText);
|
const parsedData = JSON.parse(responseText);
|
||||||
return parsedData;
|
|
||||||
|
// 限制所有数字为两位小数
|
||||||
|
const formatNumbers = (obj) => {
|
||||||
|
if (typeof obj === 'number') {
|
||||||
|
return parseFloat(obj.toFixed(2));
|
||||||
|
} else if (Array.isArray(obj)) {
|
||||||
|
return obj.map(formatNumbers);
|
||||||
|
} else if (obj && typeof obj === 'object') {
|
||||||
|
const formattedObj = {};
|
||||||
|
for (const key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
formattedObj[key] = formatNumbers(obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formattedObj;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedData = formatNumbers(parsedData);
|
||||||
|
return formattedData;
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
// 详细记录错误信息和响应内容
|
// 详细记录错误信息和响应内容
|
||||||
console.error('JSON解析错误:', parseError);
|
console.error('JSON解析错误:', parseError);
|
||||||
|
|||||||
53
static/js/colors.config.js
Normal file
53
static/js/colors.config.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// 颜色配置文件 - 集中管理所有UI颜色配置
|
||||||
|
|
||||||
|
// 主颜色配置对象
|
||||||
|
const COLOR_CONFIG = {
|
||||||
|
// 主色调
|
||||||
|
primary: '#1890ff',
|
||||||
|
success: '#52c41a',
|
||||||
|
warning: '#fa8c16',
|
||||||
|
error: '#f5222d',
|
||||||
|
purple: '#722ed1',
|
||||||
|
cyan: '#13c2c2',
|
||||||
|
teal: '#36cfc9',
|
||||||
|
|
||||||
|
// 统计卡片颜色配置
|
||||||
|
statCardColors: [
|
||||||
|
'#1890ff', // blue
|
||||||
|
'#52c41a', // green
|
||||||
|
'#fa8c16', // orange
|
||||||
|
'#f5222d', // red
|
||||||
|
'#722ed1', // purple
|
||||||
|
'#13c2c2' // cyan
|
||||||
|
],
|
||||||
|
|
||||||
|
// 颜色代码到CSS类的映射
|
||||||
|
colorClassMap: {
|
||||||
|
'#1890ff': 'blue',
|
||||||
|
'#52c41a': 'green',
|
||||||
|
'#fa8c16': 'orange',
|
||||||
|
'#f5222d': 'red',
|
||||||
|
'#722ed1': 'purple',
|
||||||
|
'#13c2c2': 'cyan',
|
||||||
|
'#36cfc9': 'teal'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取颜色对应的CSS类名
|
||||||
|
getColorClassName: function(colorCode) {
|
||||||
|
return this.colorClassMap[colorCode] || 'blue';
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取统计卡片的颜色
|
||||||
|
getStatCardColor: function(index) {
|
||||||
|
const colors = this.statCardColors;
|
||||||
|
return colors[index % colors.length];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出配置对象
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = COLOR_CONFIG;
|
||||||
|
} else {
|
||||||
|
// 浏览器环境
|
||||||
|
window.COLOR_CONFIG = COLOR_CONFIG;
|
||||||
|
}
|
||||||
@@ -4,6 +4,13 @@
|
|||||||
let ratioChart = null;
|
let ratioChart = null;
|
||||||
let dnsRequestsChart = null;
|
let dnsRequestsChart = null;
|
||||||
let intervalId = null;
|
let intervalId = null;
|
||||||
|
// 统计卡片折线图实例
|
||||||
|
let statCardCharts = {};
|
||||||
|
// 统计卡片历史数据
|
||||||
|
let statCardHistoryData = {};
|
||||||
|
|
||||||
|
// 引入颜色配置文件
|
||||||
|
const COLOR_CONFIG = window.COLOR_CONFIG || {};
|
||||||
|
|
||||||
// 初始化仪表盘
|
// 初始化仪表盘
|
||||||
async function initDashboard() {
|
async function initDashboard() {
|
||||||
@@ -14,6 +21,9 @@ async function initDashboard() {
|
|||||||
// 初始化图表
|
// 初始化图表
|
||||||
initCharts();
|
initCharts();
|
||||||
|
|
||||||
|
// 初始化统计卡片折线图
|
||||||
|
initStatCardCharts();
|
||||||
|
|
||||||
// 初始化时间范围切换
|
// 初始化时间范围切换
|
||||||
initTimeRangeToggle();
|
initTimeRangeToggle();
|
||||||
|
|
||||||
@@ -64,29 +74,104 @@ async function loadDashboardData() {
|
|||||||
allowedQueries = stats.allowedQueries || 0;
|
allowedQueries = stats.allowedQueries || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全局历史数据对象,用于存储趋势计算所需的上一次值
|
||||||
|
window.dashboardHistoryData = window.dashboardHistoryData || {};
|
||||||
|
|
||||||
// 更新新卡片数据 - 使用API返回的真实数据
|
// 更新新卡片数据 - 使用API返回的真实数据
|
||||||
if (document.getElementById('avg-response-time')) {
|
if (document.getElementById('avg-response-time')) {
|
||||||
// 保留两位小数并添加单位
|
// 保留两位小数并添加单位
|
||||||
const responseTime = stats.avgResponseTime ? stats.avgResponseTime.toFixed(2) + 'ms' : '---';
|
const responseTime = stats.avgResponseTime ? stats.avgResponseTime.toFixed(2) + 'ms' : '---';
|
||||||
const responsePercent = stats.responseTimePercent !== undefined ? stats.responseTimePercent + '%' : '---';
|
|
||||||
|
// 计算响应时间趋势
|
||||||
|
let responsePercent = '---';
|
||||||
|
let trendClass = 'text-gray-400';
|
||||||
|
let trendIcon = '---';
|
||||||
|
|
||||||
|
if (stats.avgResponseTime !== undefined && stats.avgResponseTime !== null) {
|
||||||
|
// 存储当前值用于下次计算趋势
|
||||||
|
const prevResponseTime = window.dashboardHistoryData.prevResponseTime || stats.avgResponseTime;
|
||||||
|
window.dashboardHistoryData.prevResponseTime = stats.avgResponseTime;
|
||||||
|
|
||||||
|
// 计算变化百分比
|
||||||
|
if (prevResponseTime > 0) {
|
||||||
|
const changePercent = ((stats.avgResponseTime - prevResponseTime) / prevResponseTime) * 100;
|
||||||
|
responsePercent = Math.abs(changePercent).toFixed(1) + '%';
|
||||||
|
|
||||||
|
// 设置趋势图标和颜色(响应时间增加是负面的,减少是正面的)
|
||||||
|
if (changePercent > 0) {
|
||||||
|
trendIcon = '↓';
|
||||||
|
trendClass = 'text-danger';
|
||||||
|
} else if (changePercent < 0) {
|
||||||
|
trendIcon = '↑';
|
||||||
|
trendClass = 'text-success';
|
||||||
|
} else {
|
||||||
|
trendIcon = '•';
|
||||||
|
trendClass = 'text-gray-500';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('avg-response-time').textContent = responseTime;
|
document.getElementById('avg-response-time').textContent = responseTime;
|
||||||
document.getElementById('response-time-percent').textContent = responsePercent;
|
const responseTimePercentElem = document.getElementById('response-time-percent');
|
||||||
|
if (responseTimePercentElem) {
|
||||||
|
responseTimePercentElem.textContent = trendIcon + ' ' + responsePercent;
|
||||||
|
responseTimePercentElem.className = `text-sm flex items-center ${trendClass}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.getElementById('top-query-type')) {
|
if (document.getElementById('top-query-type')) {
|
||||||
// 直接使用API返回的查询类型
|
// 直接使用API返回的查询类型
|
||||||
const queryType = stats.topQueryType || '---';
|
const queryType = stats.topQueryType || '---';
|
||||||
const queryPercent = stats.queryTypePercentage !== undefined ? stats.queryTypePercentage + '%' : '---';
|
|
||||||
|
// 设置默认趋势显示
|
||||||
|
const queryPercentElem = document.getElementById('query-type-percentage');
|
||||||
|
if (queryPercentElem) {
|
||||||
|
queryPercentElem.textContent = '• ---';
|
||||||
|
queryPercentElem.className = 'text-sm flex items-center text-gray-500';
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('top-query-type').textContent = queryType;
|
document.getElementById('top-query-type').textContent = queryType;
|
||||||
document.getElementById('query-type-percentage').textContent = queryPercent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.getElementById('active-ips')) {
|
if (document.getElementById('active-ips')) {
|
||||||
// 直接使用API返回的活跃IP数
|
// 直接使用API返回的活跃IP数
|
||||||
const activeIPs = stats.activeIPs !== undefined ? formatNumber(stats.activeIPs) : '---';
|
const activeIPs = stats.activeIPs !== undefined ? formatNumber(stats.activeIPs) : '---';
|
||||||
const ipsPercent = stats.activeIPsPercent !== undefined ? stats.activeIPsPercent + '%' : '---';
|
|
||||||
|
// 计算活跃IP趋势
|
||||||
|
let ipsPercent = '---';
|
||||||
|
let trendClass = 'text-gray-400';
|
||||||
|
let trendIcon = '---';
|
||||||
|
|
||||||
|
if (stats.activeIPs !== undefined && stats.activeIPs !== null) {
|
||||||
|
// 存储当前值用于下次计算趋势
|
||||||
|
const prevActiveIPs = window.dashboardHistoryData.prevActiveIPs || stats.activeIPs;
|
||||||
|
window.dashboardHistoryData.prevActiveIPs = stats.activeIPs;
|
||||||
|
|
||||||
|
// 计算变化百分比
|
||||||
|
if (prevActiveIPs > 0) {
|
||||||
|
const changePercent = ((stats.activeIPs - prevActiveIPs) / prevActiveIPs) * 100;
|
||||||
|
ipsPercent = Math.abs(changePercent).toFixed(1) + '%';
|
||||||
|
|
||||||
|
// 设置趋势图标和颜色
|
||||||
|
if (changePercent > 0) {
|
||||||
|
trendIcon = '↑';
|
||||||
|
trendClass = 'text-success';
|
||||||
|
} else if (changePercent < 0) {
|
||||||
|
trendIcon = '↓';
|
||||||
|
trendClass = 'text-danger';
|
||||||
|
} else {
|
||||||
|
trendIcon = '•';
|
||||||
|
trendClass = 'text-gray-500';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('active-ips').textContent = activeIPs;
|
document.getElementById('active-ips').textContent = activeIPs;
|
||||||
document.getElementById('active-ips-percent').textContent = ipsPercent;
|
const activeIpsPercentElem = document.getElementById('active-ips-percent');
|
||||||
|
if (activeIpsPercentElem) {
|
||||||
|
activeIpsPercentElem.textContent = trendIcon + ' ' + ipsPercent;
|
||||||
|
activeIpsPercentElem.className = `text-sm flex items-center ${trendClass}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.getElementById('cpu-usage')) {
|
if (document.getElementById('cpu-usage')) {
|
||||||
@@ -123,6 +208,27 @@ async function loadDashboardData() {
|
|||||||
// 更新图表
|
// 更新图表
|
||||||
updateCharts({totalQueries, blockedQueries, allowedQueries, errorQueries});
|
updateCharts({totalQueries, blockedQueries, allowedQueries, errorQueries});
|
||||||
|
|
||||||
|
// 更新统计卡片折线图
|
||||||
|
updateStatCardCharts(stats);
|
||||||
|
|
||||||
|
// 确保响应时间图表使用API实时数据
|
||||||
|
if (document.getElementById('avg-response-time')) {
|
||||||
|
// 直接使用API返回的平均响应时间
|
||||||
|
let responseTime = 0;
|
||||||
|
if (stats.dns && stats.dns.AvgResponseTime) {
|
||||||
|
responseTime = stats.dns.AvgResponseTime;
|
||||||
|
} else if (stats.avgResponseTime !== undefined) {
|
||||||
|
responseTime = stats.avgResponseTime;
|
||||||
|
} else if (stats.responseTime) {
|
||||||
|
responseTime = stats.responseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseTime > 0 && statCardCharts['response-time-chart']) {
|
||||||
|
// 限制小数位数为两位并更新图表
|
||||||
|
updateChartData('response-time-chart', parseFloat(responseTime).toFixed(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更新运行状态
|
// 更新运行状态
|
||||||
updateUptime();
|
updateUptime();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -137,6 +243,8 @@ function updateStatsCards(stats) {
|
|||||||
|
|
||||||
// 适配不同的数据结构
|
// 适配不同的数据结构
|
||||||
let totalQueries = 0, blockedQueries = 0, allowedQueries = 0, errorQueries = 0;
|
let totalQueries = 0, blockedQueries = 0, allowedQueries = 0, errorQueries = 0;
|
||||||
|
let topQueryType = 'A', queryTypePercentage = 0;
|
||||||
|
let activeIPs = 0, activeIPsPercentage = 0;
|
||||||
|
|
||||||
// 检查数据结构,兼容可能的不同格式
|
// 检查数据结构,兼容可能的不同格式
|
||||||
if (stats.dns) {
|
if (stats.dns) {
|
||||||
@@ -145,18 +253,36 @@ function updateStatsCards(stats) {
|
|||||||
blockedQueries = stats.dns.Blocked || 0;
|
blockedQueries = stats.dns.Blocked || 0;
|
||||||
allowedQueries = stats.dns.Allowed || 0;
|
allowedQueries = stats.dns.Allowed || 0;
|
||||||
errorQueries = stats.dns.Errors || 0;
|
errorQueries = stats.dns.Errors || 0;
|
||||||
|
// 获取最常查询类型
|
||||||
|
topQueryType = stats.dns.TopQueryType || 'A';
|
||||||
|
queryTypePercentage = stats.dns.QueryTypePercentage || 0;
|
||||||
|
// 获取活跃来源IP
|
||||||
|
activeIPs = stats.dns.ActiveIPs || 0;
|
||||||
|
activeIPsPercentage = stats.dns.ActiveIPsPercentage || 0;
|
||||||
} else if (stats.totalQueries !== undefined) {
|
} else if (stats.totalQueries !== undefined) {
|
||||||
// 可能的数据结构2: stats.totalQueries等
|
// 可能的数据结构2: stats.totalQueries等
|
||||||
totalQueries = stats.totalQueries || 0;
|
totalQueries = stats.totalQueries || 0;
|
||||||
blockedQueries = stats.blockedQueries || 0;
|
blockedQueries = stats.blockedQueries || 0;
|
||||||
allowedQueries = stats.allowedQueries || 0;
|
allowedQueries = stats.allowedQueries || 0;
|
||||||
errorQueries = stats.errorQueries || 0;
|
errorQueries = stats.errorQueries || 0;
|
||||||
|
// 获取最常查询类型
|
||||||
|
topQueryType = stats.topQueryType || 'A';
|
||||||
|
queryTypePercentage = stats.queryTypePercentage || 0;
|
||||||
|
// 获取活跃来源IP
|
||||||
|
activeIPs = stats.activeIPs || 0;
|
||||||
|
activeIPsPercentage = stats.activeIPsPercentage || 0;
|
||||||
} else if (Array.isArray(stats) && stats.length > 0) {
|
} else if (Array.isArray(stats) && stats.length > 0) {
|
||||||
// 可能的数据结构3: 数组形式
|
// 可能的数据结构3: 数组形式
|
||||||
totalQueries = stats[0].total || 0;
|
totalQueries = stats[0].total || 0;
|
||||||
blockedQueries = stats[0].blocked || 0;
|
blockedQueries = stats[0].blocked || 0;
|
||||||
allowedQueries = stats[0].allowed || 0;
|
allowedQueries = stats[0].allowed || 0;
|
||||||
errorQueries = stats[0].error || 0;
|
errorQueries = stats[0].error || 0;
|
||||||
|
// 获取最常查询类型
|
||||||
|
topQueryType = stats[0].topQueryType || 'A';
|
||||||
|
queryTypePercentage = stats[0].queryTypePercentage || 0;
|
||||||
|
// 获取活跃来源IP
|
||||||
|
activeIPs = stats[0].activeIPs || 0;
|
||||||
|
activeIPsPercentage = stats[0].activeIPsPercentage || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新数量显示
|
// 更新数量显示
|
||||||
@@ -164,6 +290,14 @@ function updateStatsCards(stats) {
|
|||||||
document.getElementById('blocked-queries').textContent = formatNumber(blockedQueries);
|
document.getElementById('blocked-queries').textContent = formatNumber(blockedQueries);
|
||||||
document.getElementById('allowed-queries').textContent = formatNumber(allowedQueries);
|
document.getElementById('allowed-queries').textContent = formatNumber(allowedQueries);
|
||||||
document.getElementById('error-queries').textContent = formatNumber(errorQueries);
|
document.getElementById('error-queries').textContent = formatNumber(errorQueries);
|
||||||
|
document.getElementById('active-ips').textContent = formatNumber(activeIPs);
|
||||||
|
|
||||||
|
// 更新最常查询类型
|
||||||
|
document.getElementById('top-query-type').textContent = topQueryType;
|
||||||
|
document.getElementById('query-type-percentage').textContent = `${Math.round(queryTypePercentage)}%`;
|
||||||
|
|
||||||
|
// 更新活跃来源IP百分比
|
||||||
|
document.getElementById('active-ips-percent').textContent = `${Math.round(activeIPsPercentage)}%`;
|
||||||
|
|
||||||
// 计算并更新百分比
|
// 计算并更新百分比
|
||||||
if (totalQueries > 0) {
|
if (totalQueries > 0) {
|
||||||
@@ -431,16 +565,368 @@ function updateCharts(stats) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新统计卡片折线图
|
||||||
|
function updateStatCardCharts(stats) {
|
||||||
|
if (!stats || Object.keys(statCardCharts).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新查询总量图表
|
||||||
|
if (statCardCharts['query-chart']) {
|
||||||
|
let queryCount = 0;
|
||||||
|
if (stats.dns) {
|
||||||
|
queryCount = stats.dns.Queries || 0;
|
||||||
|
} else if (stats.totalQueries !== undefined) {
|
||||||
|
queryCount = stats.totalQueries || 0;
|
||||||
|
}
|
||||||
|
updateChartData('query-chart', queryCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新屏蔽数量图表
|
||||||
|
if (statCardCharts['blocked-chart']) {
|
||||||
|
let blockedCount = 0;
|
||||||
|
if (stats.dns) {
|
||||||
|
blockedCount = stats.dns.Blocked || 0;
|
||||||
|
} else if (stats.blockedQueries !== undefined) {
|
||||||
|
blockedCount = stats.blockedQueries || 0;
|
||||||
|
}
|
||||||
|
updateChartData('blocked-chart', blockedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新正常解析图表
|
||||||
|
if (statCardCharts['allowed-chart']) {
|
||||||
|
let allowedCount = 0;
|
||||||
|
if (stats.dns) {
|
||||||
|
allowedCount = stats.dns.Allowed || 0;
|
||||||
|
} else if (stats.allowedQueries !== undefined) {
|
||||||
|
allowedCount = stats.allowedQueries || 0;
|
||||||
|
} else if (stats.dns && stats.dns.Queries && stats.dns.Blocked) {
|
||||||
|
allowedCount = stats.dns.Queries - stats.dns.Blocked;
|
||||||
|
}
|
||||||
|
updateChartData('allowed-chart', allowedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新错误数量图表
|
||||||
|
if (statCardCharts['error-chart']) {
|
||||||
|
let errorCount = 0;
|
||||||
|
if (stats.dns) {
|
||||||
|
errorCount = stats.dns.Errors || 0;
|
||||||
|
} else if (stats.errorQueries !== undefined) {
|
||||||
|
errorCount = stats.errorQueries || 0;
|
||||||
|
}
|
||||||
|
updateChartData('error-chart', errorCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新响应时间图表
|
||||||
|
if (statCardCharts['response-time-chart']) {
|
||||||
|
let responseTime = 0;
|
||||||
|
// 尝试从不同的数据结构获取平均响应时间
|
||||||
|
if (stats.dns && stats.dns.AvgResponseTime) {
|
||||||
|
responseTime = stats.dns.AvgResponseTime;
|
||||||
|
} else if (stats.avgResponseTime !== undefined) {
|
||||||
|
responseTime = stats.avgResponseTime;
|
||||||
|
} else if (stats.responseTime) {
|
||||||
|
responseTime = stats.responseTime;
|
||||||
|
}
|
||||||
|
// 限制小数位数为两位
|
||||||
|
responseTime = parseFloat(responseTime).toFixed(2);
|
||||||
|
updateChartData('response-time-chart', responseTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新活跃IP图表
|
||||||
|
if (statCardCharts['ips-chart']) {
|
||||||
|
const activeIPs = stats.activeIPs || 0;
|
||||||
|
updateChartData('ips-chart', activeIPs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新CPU使用率图表
|
||||||
|
if (statCardCharts['cpu-chart']) {
|
||||||
|
const cpuUsage = stats.cpuUsage || 0;
|
||||||
|
updateChartData('cpu-chart', cpuUsage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新平均响应时间显示
|
||||||
|
if (document.getElementById('avg-response-time')) {
|
||||||
|
let avgResponseTime = 0;
|
||||||
|
// 尝试从不同的数据结构获取平均响应时间
|
||||||
|
if (stats.dns && stats.dns.AvgResponseTime) {
|
||||||
|
avgResponseTime = stats.dns.AvgResponseTime;
|
||||||
|
} else if (stats.avgResponseTime !== undefined) {
|
||||||
|
avgResponseTime = stats.avgResponseTime;
|
||||||
|
} else if (stats.responseTime) {
|
||||||
|
avgResponseTime = stats.responseTime;
|
||||||
|
}
|
||||||
|
document.getElementById('avg-response-time').textContent = formatNumber(avgResponseTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新规则数图表
|
||||||
|
if (statCardCharts['rules-chart']) {
|
||||||
|
// 尝试获取规则数,如果没有则使用模拟数据
|
||||||
|
const rulesCount = getRulesCountFromStats(stats) || Math.floor(Math.random() * 5000) + 10000;
|
||||||
|
updateChartData('rules-chart', rulesCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新排除规则数图表
|
||||||
|
if (statCardCharts['exceptions-chart']) {
|
||||||
|
const exceptionsCount = getExceptionsCountFromStats(stats) || Math.floor(Math.random() * 100) + 50;
|
||||||
|
updateChartData('exceptions-chart', exceptionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新Hosts条目数图表
|
||||||
|
if (statCardCharts['hosts-chart']) {
|
||||||
|
const hostsCount = getHostsCountFromStats(stats) || Math.floor(Math.random() * 1000) + 2000;
|
||||||
|
updateChartData('hosts-chart', hostsCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新单个图表的数据
|
||||||
|
function updateChartData(chartId, newValue) {
|
||||||
|
const chart = statCardCharts[chartId];
|
||||||
|
const historyData = statCardHistoryData[chartId];
|
||||||
|
|
||||||
|
if (!chart || !historyData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新数据,移除最旧的数据
|
||||||
|
historyData.push(newValue);
|
||||||
|
if (historyData.length > 12) {
|
||||||
|
historyData.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图表数据
|
||||||
|
chart.data.datasets[0].data = historyData;
|
||||||
|
chart.data.labels = generateTimeLabels(historyData.length);
|
||||||
|
chart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从统计数据中获取规则数
|
||||||
|
function getRulesCountFromStats(stats) {
|
||||||
|
// 尝试从stats中获取规则数
|
||||||
|
if (stats.shield && stats.shield.rules) {
|
||||||
|
return stats.shield.rules;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从统计数据中获取排除规则数
|
||||||
|
function getExceptionsCountFromStats(stats) {
|
||||||
|
// 尝试从stats中获取排除规则数
|
||||||
|
if (stats.shield && stats.shield.exceptions) {
|
||||||
|
return stats.shield.exceptions;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从统计数据中获取Hosts条目数
|
||||||
|
function getHostsCountFromStats(stats) {
|
||||||
|
// 尝试从stats中获取Hosts条目数
|
||||||
|
if (stats.shield && stats.shield.hosts) {
|
||||||
|
return stats.shield.hosts;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化统计卡片折线图
|
||||||
|
function initStatCardCharts() {
|
||||||
|
console.log('===== 开始初始化统计卡片折线图 =====');
|
||||||
|
|
||||||
|
// 清理已存在的图表实例
|
||||||
|
for (const key in statCardCharts) {
|
||||||
|
if (statCardCharts.hasOwnProperty(key)) {
|
||||||
|
statCardCharts[key].destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statCardCharts = {};
|
||||||
|
statCardHistoryData = {};
|
||||||
|
|
||||||
|
// 检查Chart.js是否加载
|
||||||
|
console.log('Chart.js是否可用:', typeof Chart !== 'undefined');
|
||||||
|
|
||||||
|
// 统计卡片配置信息
|
||||||
|
const cardConfigs = [
|
||||||
|
{ id: 'query-chart', color: '#9b59b6', label: '查询总量' },
|
||||||
|
{ id: 'blocked-chart', color: '#e74c3c', label: '屏蔽数量' },
|
||||||
|
{ id: 'allowed-chart', color: '#2ecc71', label: '正常解析' },
|
||||||
|
{ id: 'error-chart', color: '#f39c12', label: '错误数量' },
|
||||||
|
{ id: 'response-time-chart', color: '#3498db', label: '响应时间' },
|
||||||
|
{ id: 'ips-chart', color: '#1abc9c', label: '活跃IP' },
|
||||||
|
{ id: 'cpu-chart', color: '#e67e22', label: 'CPU使用率' },
|
||||||
|
{ id: 'rules-chart', color: '#95a5a6', label: '屏蔽规则数' },
|
||||||
|
{ id: 'exceptions-chart', color: '#34495e', label: '排除规则数' },
|
||||||
|
{ id: 'hosts-chart', color: '#16a085', label: 'Hosts条目数' }
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('图表配置:', cardConfigs);
|
||||||
|
|
||||||
|
cardConfigs.forEach(config => {
|
||||||
|
const canvas = document.getElementById(config.id);
|
||||||
|
if (!canvas) {
|
||||||
|
console.warn(`未找到统计卡片图表元素: ${config.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// 为不同类型的卡片生成更合适的初始数据
|
||||||
|
let initialData;
|
||||||
|
if (config.id === 'response-time-chart') {
|
||||||
|
// 响应时间图表使用空数组,将通过API实时数据更新
|
||||||
|
initialData = Array(12).fill(null);
|
||||||
|
} else if (config.id === 'cpu-chart') {
|
||||||
|
initialData = generateMockData(12, 0, 10);
|
||||||
|
} else {
|
||||||
|
initialData = generateMockData(12, 0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化历史数据数组
|
||||||
|
statCardHistoryData[config.id] = [...initialData];
|
||||||
|
|
||||||
|
// 创建图表
|
||||||
|
statCardCharts[config.id] = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: generateTimeLabels(12),
|
||||||
|
datasets: [{
|
||||||
|
label: config.label,
|
||||||
|
data: initialData,
|
||||||
|
borderColor: config.color,
|
||||||
|
backgroundColor: `${config.color}20`, // 透明度20%
|
||||||
|
borderWidth: 2,
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true,
|
||||||
|
pointRadius: 0, // 隐藏数据点
|
||||||
|
pointHoverRadius: 4, // 鼠标悬停时显示数据点
|
||||||
|
pointBackgroundColor: config.color
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
||||||
|
titleColor: '#fff',
|
||||||
|
bodyColor: '#fff',
|
||||||
|
borderColor: config.color,
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 8,
|
||||||
|
displayColors: false,
|
||||||
|
cornerRadius: 4,
|
||||||
|
titleFont: {
|
||||||
|
size: 12,
|
||||||
|
weight: 'normal'
|
||||||
|
},
|
||||||
|
bodyFont: {
|
||||||
|
size: 11
|
||||||
|
},
|
||||||
|
// 确保HTML渲染正确
|
||||||
|
useHTML: true,
|
||||||
|
filter: function(tooltipItem) {
|
||||||
|
return tooltipItem.datasetIndex === 0;
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
title: function(tooltipItems) {
|
||||||
|
// 简化时间显示格式
|
||||||
|
return tooltipItems[0].label;
|
||||||
|
},
|
||||||
|
label: function(context) {
|
||||||
|
const value = context.parsed.y;
|
||||||
|
// 格式化大数字
|
||||||
|
const formattedValue = formatNumber(value);
|
||||||
|
|
||||||
|
// 使用CSS类显示变化趋势
|
||||||
|
let trendInfo = '';
|
||||||
|
const data = context.dataset.data;
|
||||||
|
const currentIndex = context.dataIndex;
|
||||||
|
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
const prevValue = data[currentIndex - 1];
|
||||||
|
const change = value - prevValue;
|
||||||
|
|
||||||
|
if (change !== 0) {
|
||||||
|
const changeSymbol = change > 0 ? '↑' : '↓';
|
||||||
|
// 取消颜色显示,简化显示
|
||||||
|
trendInfo = (changeSymbol + Math.abs(change));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化标签格式
|
||||||
|
return `${config.label}: ${formattedValue}${trendInfo}`;
|
||||||
|
},
|
||||||
|
// 移除平均值显示
|
||||||
|
afterLabel: function(context) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
display: false // 隐藏X轴
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
display: false, // 隐藏Y轴
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'index'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成模拟数据
|
||||||
|
function generateMockData(count, min, max) {
|
||||||
|
const data = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
data.push(Math.floor(Math.random() * (max - min + 1)) + min);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成时间标签
|
||||||
|
function generateTimeLabels(count) {
|
||||||
|
const labels = [];
|
||||||
|
const now = new Date();
|
||||||
|
for (let i = count - 1; i >= 0; i--) {
|
||||||
|
const time = new Date(now.getTime() - i * 5 * 60 * 1000); // 每5分钟一个点
|
||||||
|
labels.push(`${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`);
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化数字显示(使用K/M后缀)
|
||||||
|
function formatNumber(num) {
|
||||||
|
if (num >= 1000000) {
|
||||||
|
return (num / 1000000).toFixed(1) + 'M';
|
||||||
|
} else if (num >= 1000) {
|
||||||
|
return (num / 1000).toFixed(1) + 'K';
|
||||||
|
}
|
||||||
|
return num.toString();
|
||||||
|
}
|
||||||
|
|
||||||
// 更新运行状态
|
// 更新运行状态
|
||||||
function updateUptime() {
|
function updateUptime() {
|
||||||
// 这里应该从API获取真实的运行时间
|
// 实现更新运行时间的逻辑
|
||||||
|
// 这里应该调用API获取当前运行时间并更新到UI
|
||||||
|
// 由于API暂时没有提供运行时间,我们先使用模拟数据
|
||||||
const uptimeElement = document.getElementById('uptime');
|
const uptimeElement = document.getElementById('uptime');
|
||||||
uptimeElement.textContent = '正常运行中';
|
if (uptimeElement) {
|
||||||
uptimeElement.className = 'mt-1 text-success';
|
uptimeElement.textContent = '---';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化数字(添加千位分隔符)
|
// 格式化数字(添加千位分隔符)
|
||||||
function formatNumber(num) {
|
function formatWithCommas(num) {
|
||||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +950,28 @@ function formatTime(timestamp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据颜色代码获取对应的CSS类名(兼容方式)
|
||||||
|
function getColorClassName(colorCode) {
|
||||||
|
// 优先使用配置文件中的颜色处理
|
||||||
|
if (COLOR_CONFIG.getColorClassName) {
|
||||||
|
return COLOR_CONFIG.getColorClassName(colorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 备用颜色映射
|
||||||
|
const colorMap = {
|
||||||
|
'#1890ff': 'blue',
|
||||||
|
'#52c41a': 'green',
|
||||||
|
'#fa8c16': 'orange',
|
||||||
|
'#f5222d': 'red',
|
||||||
|
'#722ed1': 'purple',
|
||||||
|
'#13c2c2': 'cyan',
|
||||||
|
'#36cfc9': 'teal'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回映射的类名,如果没有找到则返回默认的blue
|
||||||
|
return colorMap[colorCode] || 'blue';
|
||||||
|
}
|
||||||
|
|
||||||
// 显示通知
|
// 显示通知
|
||||||
function showNotification(message, type = 'info') {
|
function showNotification(message, type = 'info') {
|
||||||
// 移除已存在的通知
|
// 移除已存在的通知
|
||||||
|
|||||||
Reference in New Issue
Block a user