修复更多内容

This commit is contained in:
Alex Yang
2025-11-25 23:37:22 +08:00
parent 4d53b13220
commit d6e9cc990b
6 changed files with 709 additions and 56 deletions

View File

@@ -102,6 +102,9 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
// 获取活跃来源IP数量
activeIPCount := len(dnsStats.SourceIPs)
// 格式化平均响应时间为两位小数
formattedResponseTime := float64(int(dnsStats.AvgResponseTime*100)) / 100
// 构建响应数据,确保所有字段都反映服务器的真实状态
stats := map[string]interface{}{
"dns": map[string]interface{}{
@@ -110,7 +113,7 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
"Allowed": dnsStats.Allowed,
"Errors": dnsStats.Errors,
"LastQuery": dnsStats.LastQuery,
"AvgResponseTime": dnsStats.AvgResponseTime,
"AvgResponseTime": formattedResponseTime,
"TotalResponseTime": dnsStats.TotalResponseTime,
"QueryTypes": dnsStats.QueryTypes,
"SourceIPs": dnsStats.SourceIPs,
@@ -119,7 +122,7 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
"shield": shieldStats,
"topQueryType": topQueryType,
"activeIPs": activeIPCount,
"avgResponseTime": dnsStats.AvgResponseTime,
"avgResponseTime": formattedResponseTime,
"cpuUsage": dnsStats.CpuUsage,
"time": time.Now(),
}

View File

@@ -193,6 +193,40 @@ header p {
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) {
.content {

View File

@@ -150,12 +150,17 @@
<i class="fa fa-refresh"></i>
</div>
</div>
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="total-queries">0</p>
<span class="text-success text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="queries-percent">0%</span>
</span>
<div class="mb-2">
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="total-queries">0</p>
<span class="text-success text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="queries-percent">0%</span>
</span>
</div>
<div class="h-16 mt-2">
<canvas id="query-chart"></canvas>
</div>
</div>
</div>
</div>
@@ -171,12 +176,17 @@
<i class="fa fa-ban"></i>
</div>
</div>
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="blocked-queries">0</p>
<span class="text-danger text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="blocked-percent">0%</span>
</span>
<div class="mb-2">
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="blocked-queries">0</p>
<span class="text-danger text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="blocked-percent">0%</span>
</span>
</div>
<div class="h-16 mt-2">
<canvas id="blocked-chart"></canvas>
</div>
</div>
</div>
</div>
@@ -192,12 +202,17 @@
<i class="fa fa-check"></i>
</div>
</div>
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="allowed-queries">0</p>
<span class="text-success text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="allowed-percent">0%</span>
</span>
<div class="mb-2">
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="allowed-queries">0</p>
<span class="text-success text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="allowed-percent">0%</span>
</span>
</div>
<div class="h-16 mt-2">
<canvas id="allowed-chart"></canvas>
</div>
</div>
</div>
</div>
@@ -213,12 +228,17 @@
<i class="fa fa-exclamation-triangle"></i>
</div>
</div>
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="error-queries">0</p>
<span class="text-warning text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="error-percent">0%</span>
</span>
<div class="mb-2">
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="error-queries">0</p>
<span class="text-warning text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="error-percent">0%</span>
</span>
</div>
<div class="h-16 mt-2">
<canvas id="error-chart"></canvas>
</div>
</div>
</div>
</div>
@@ -234,12 +254,17 @@
<i class="fa fa-clock-o"></i>
</div>
</div>
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="avg-response-time">0ms</p>
<span class="text-success text-sm flex items-center">
<i class="fa fa-arrow-down mr-1"></i>
<span id="response-time-percent">0%</span>
</span>
<div class="mb-2">
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="avg-response-time">0ms</p>
<span class="text-success text-sm flex items-center">
<i class="fa fa-arrow-down mr-1"></i>
<span id="response-time-percent">0%</span>
</span>
</div>
<div class="h-16 mt-2">
<canvas id="response-time-chart"></canvas>
</div>
</div>
</div>
</div>
@@ -276,12 +301,17 @@
<i class="fa fa-globe"></i>
</div>
</div>
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="active-ips">0</p>
<span class="text-success text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="active-ips-percent">0%</span>
</span>
<div class="mb-2">
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="active-ips">0</p>
<span class="text-success text-sm flex items-center">
<i class="fa fa-arrow-up mr-1"></i>
<span id="active-ips-percent">0%</span>
</span>
</div>
<div class="h-16 mt-2">
<canvas id="ips-chart"></canvas>
</div>
</div>
</div>
</div>
@@ -297,12 +327,17 @@
<i class="fa fa-microchip"></i>
</div>
</div>
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="cpu-usage">0%</p>
<span class="text-warning text-sm flex items-center">
<i class="fa fa-bolt mr-1"></i>
<span id="cpu-status">正常</span>
</span>
<div class="mb-2">
<div class="flex items-end justify-between">
<p class="text-3xl font-bold" id="cpu-usage">0%</p>
<span class="text-warning text-sm flex items-center">
<i class="fa fa-bolt mr-1"></i>
<span id="cpu-status">正常</span>
</span>
</div>
<div class="h-16 mt-2">
<canvas id="cpu-chart"></canvas>
</div>
</div>
</div>
</div>
@@ -436,5 +471,5 @@
<script src="js/hosts.js"></script>
<script src="js/query.js"></script>
<script src="js/config.js"></script>
</body>
</body>
</html>

View File

@@ -60,7 +60,27 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
// 尝试解析JSON
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) {
// 详细记录错误信息和响应内容
console.error('JSON解析错误:', parseError);

View 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;
}

View File

@@ -4,6 +4,13 @@
let ratioChart = null;
let dnsRequestsChart = null;
let intervalId = null;
// 统计卡片折线图实例
let statCardCharts = {};
// 统计卡片历史数据
let statCardHistoryData = {};
// 引入颜色配置文件
const COLOR_CONFIG = window.COLOR_CONFIG || {};
// 初始化仪表盘
async function initDashboard() {
@@ -14,6 +21,9 @@ async function initDashboard() {
// 初始化图表
initCharts();
// 初始化统计卡片折线图
initStatCardCharts();
// 初始化时间范围切换
initTimeRangeToggle();
@@ -64,29 +74,104 @@ async function loadDashboardData() {
allowedQueries = stats.allowedQueries || 0;
}
// 全局历史数据对象,用于存储趋势计算所需的上一次值
window.dashboardHistoryData = window.dashboardHistoryData || {};
// 更新新卡片数据 - 使用API返回的真实数据
if (document.getElementById('avg-response-time')) {
// 保留两位小数并添加单位
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('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')) {
// 直接使用API返回的查询类型
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('query-type-percentage').textContent = queryPercent;
}
if (document.getElementById('active-ips')) {
// 直接使用API返回的活跃IP数
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-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')) {
@@ -123,6 +208,27 @@ async function loadDashboardData() {
// 更新图表
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();
} catch (error) {
@@ -137,6 +243,8 @@ function updateStatsCards(stats) {
// 适配不同的数据结构
let totalQueries = 0, blockedQueries = 0, allowedQueries = 0, errorQueries = 0;
let topQueryType = 'A', queryTypePercentage = 0;
let activeIPs = 0, activeIPsPercentage = 0;
// 检查数据结构,兼容可能的不同格式
if (stats.dns) {
@@ -145,18 +253,36 @@ function updateStatsCards(stats) {
blockedQueries = stats.dns.Blocked || 0;
allowedQueries = stats.dns.Allowed || 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) {
// 可能的数据结构2: stats.totalQueries等
totalQueries = stats.totalQueries || 0;
blockedQueries = stats.blockedQueries || 0;
allowedQueries = stats.allowedQueries || 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) {
// 可能的数据结构3: 数组形式
totalQueries = stats[0].total || 0;
blockedQueries = stats[0].blocked || 0;
allowedQueries = stats[0].allowed || 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('allowed-queries').textContent = formatNumber(allowedQueries);
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) {
@@ -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() {
// 这里应该从API获取真实的运行时间
// 实现更新运行时间的逻辑
// 这里应该调用API获取当前运行时间并更新到UI
// 由于API暂时没有提供运行时间我们先使用模拟数据
const uptimeElement = document.getElementById('uptime');
uptimeElement.textContent = '正常运行中';
uptimeElement.className = 'mt-1 text-success';
if (uptimeElement) {
uptimeElement.textContent = '---';
}
}
// 格式化数字(添加千位分隔符)
function formatNumber(num) {
function formatWithCommas(num) {
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') {
// 移除已存在的通知