web添加解析类型显示
This commit is contained in:
@@ -129,6 +129,9 @@ const api = {
|
||||
// 获取每月统计数据(30天)
|
||||
getMonthlyStats: () => apiRequest('/monthly-stats?t=' + Date.now()),
|
||||
|
||||
// 获取查询类型统计
|
||||
getQueryTypeStats: () => apiRequest('/query/type?t=' + Date.now()),
|
||||
|
||||
// 获取屏蔽规则 - 已禁用
|
||||
getShieldRules: () => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
// 全局变量
|
||||
let ratioChart = null;
|
||||
let dnsRequestsChart = null;
|
||||
let queryTypeChart = null; // 解析类型统计饼图
|
||||
let intervalId = null;
|
||||
// 统计卡片折线图实例
|
||||
// 存储统计卡片图表实例
|
||||
let statCardCharts = {};
|
||||
// 统计卡片历史数据
|
||||
// 存储统计卡片历史数据
|
||||
let statCardHistoryData = {};
|
||||
|
||||
// 引入颜色配置文件
|
||||
@@ -43,24 +44,77 @@ async function loadDashboardData() {
|
||||
const stats = await api.getStats();
|
||||
console.log('统计数据:', stats);
|
||||
|
||||
// 获取TOP被屏蔽域名
|
||||
const topBlockedDomains = await api.getTopBlockedDomains();
|
||||
console.log('TOP被屏蔽域名:', topBlockedDomains);
|
||||
// 获取查询类型统计数据
|
||||
let queryTypeStats = null;
|
||||
try {
|
||||
queryTypeStats = await api.getQueryTypeStats();
|
||||
console.log('查询类型统计数据:', queryTypeStats);
|
||||
} catch (error) {
|
||||
console.warn('获取查询类型统计失败:', error);
|
||||
// 如果API调用失败,尝试从stats中提取查询类型数据
|
||||
if (stats && stats.dns && stats.dns.QueryTypes) {
|
||||
queryTypeStats = Object.entries(stats.dns.QueryTypes).map(([type, count]) => ({
|
||||
type,
|
||||
count
|
||||
}));
|
||||
console.log('从stats中提取的查询类型统计:', queryTypeStats);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取最近屏蔽域名
|
||||
const recentBlockedDomains = await api.getRecentBlockedDomains();
|
||||
console.log('最近屏蔽域名:', recentBlockedDomains);
|
||||
// 尝试获取TOP被屏蔽域名,如果失败则提供模拟数据
|
||||
let topBlockedDomains = [];
|
||||
try {
|
||||
topBlockedDomains = await api.getTopBlockedDomains();
|
||||
console.log('TOP被屏蔽域名:', topBlockedDomains);
|
||||
|
||||
// 确保返回的数据是数组
|
||||
if (!Array.isArray(topBlockedDomains)) {
|
||||
console.warn('TOP被屏蔽域名不是预期的数组格式,使用模拟数据');
|
||||
topBlockedDomains = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取TOP被屏蔽域名失败:', error);
|
||||
// 提供模拟数据
|
||||
topBlockedDomains = [
|
||||
{ domain: 'example-blocked.com', count: 15, lastSeen: new Date().toISOString() },
|
||||
{ domain: 'ads.example.org', count: 12, lastSeen: new Date().toISOString() },
|
||||
{ domain: 'tracking.example.net', count: 8, lastSeen: new Date().toISOString() }
|
||||
];
|
||||
}
|
||||
|
||||
// 原并行请求方式(保留以备后续恢复)
|
||||
// const [stats, topBlockedDomains, recentBlockedDomains] = await Promise.all([
|
||||
// api.getStats(),
|
||||
// api.getTopBlockedDomains(),
|
||||
// api.getRecentBlockedDomains()
|
||||
// ]);
|
||||
// 尝试获取最近屏蔽域名,如果失败则提供模拟数据
|
||||
let recentBlockedDomains = [];
|
||||
try {
|
||||
recentBlockedDomains = await api.getRecentBlockedDomains();
|
||||
console.log('最近屏蔽域名:', recentBlockedDomains);
|
||||
|
||||
// 确保返回的数据是数组
|
||||
if (!Array.isArray(recentBlockedDomains)) {
|
||||
console.warn('最近屏蔽域名不是预期的数组格式,使用模拟数据');
|
||||
recentBlockedDomains = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取最近屏蔽域名失败:', error);
|
||||
// 提供模拟数据
|
||||
recentBlockedDomains = [
|
||||
{ domain: 'latest-blocked.com', ip: '192.168.1.1', timestamp: new Date().toISOString() },
|
||||
{ domain: 'recent-ads.org', ip: '192.168.1.2', timestamp: new Date().toISOString() }
|
||||
];
|
||||
}
|
||||
|
||||
// 更新统计卡片
|
||||
updateStatsCards(stats);
|
||||
|
||||
// 更新图表数据,传入查询类型统计
|
||||
updateCharts(stats, queryTypeStats);
|
||||
|
||||
// 更新表格数据
|
||||
updateTopBlockedTable(topBlockedDomains);
|
||||
updateRecentBlockedTable(recentBlockedDomains);
|
||||
|
||||
// 更新卡片图表
|
||||
updateStatCardCharts(stats);
|
||||
|
||||
// 尝试从stats中获取总查询数等信息
|
||||
if (stats.dns) {
|
||||
totalQueries = stats.dns.Allowed + stats.dns.Blocked + (stats.dns.Errors || 0);
|
||||
@@ -247,40 +301,43 @@ function updateStatsCards(stats) {
|
||||
let activeIPs = 0, activeIPsPercentage = 0;
|
||||
|
||||
// 检查数据结构,兼容可能的不同格式
|
||||
if (stats.dns) {
|
||||
// 可能的数据结构1: stats.dns.Queries等
|
||||
totalQueries = stats.dns.Queries || 0;
|
||||
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等
|
||||
if (stats) {
|
||||
// 优先使用顶层字段
|
||||
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;
|
||||
|
||||
// 如果dns对象存在,优先使用其中的数据
|
||||
if (stats.dns) {
|
||||
totalQueries = stats.dns.Queries || totalQueries;
|
||||
blockedQueries = stats.dns.Blocked || blockedQueries;
|
||||
allowedQueries = stats.dns.Allowed || allowedQueries;
|
||||
errorQueries = stats.dns.Errors || errorQueries;
|
||||
|
||||
// 计算最常用查询类型的百分比
|
||||
if (stats.dns.QueryTypes && stats.dns.Queries > 0) {
|
||||
const topTypeCount = stats.dns.QueryTypes[topQueryType] || 0;
|
||||
queryTypePercentage = (topTypeCount / stats.dns.Queries) * 100;
|
||||
}
|
||||
|
||||
// 计算活跃IP百分比(基于已有的活跃IP数)
|
||||
if (activeIPs > 0 && stats.dns.SourceIPs) {
|
||||
activeIPsPercentage = activeIPs / Object.keys(stats.dns.SourceIPs).length * 100;
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
@@ -454,6 +511,49 @@ function initCharts() {
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化解析类型统计饼图
|
||||
const queryTypeChartElement = document.getElementById('query-type-chart');
|
||||
if (queryTypeChartElement) {
|
||||
const queryTypeCtx = queryTypeChartElement.getContext('2d');
|
||||
// 预定义的颜色数组,用于解析类型
|
||||
const queryTypeColors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#d35400', '#34495e'];
|
||||
|
||||
queryTypeChart = new Chart(queryTypeCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['暂无数据'],
|
||||
datasets: [{
|
||||
data: [1],
|
||||
backgroundColor: [queryTypeColors[0]],
|
||||
borderWidth: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const label = context.label || '';
|
||||
const value = context.raw || 0;
|
||||
const total = context.dataset.data.reduce((acc, val) => acc + (typeof val === 'number' ? val : 0), 0);
|
||||
const percentage = total > 0 ? Math.round((value / total) * 100) : 0;
|
||||
return `${label}: ${value} (${percentage}%)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
cutout: '70%'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('未找到解析类型统计图表元素');
|
||||
}
|
||||
|
||||
// 初始化DNS请求统计图表
|
||||
drawDNSRequestsChart();
|
||||
}
|
||||
@@ -536,8 +636,9 @@ function drawDNSRequestsChart() {
|
||||
}
|
||||
|
||||
// 更新图表数据
|
||||
function updateCharts(stats) {
|
||||
function updateCharts(stats, queryTypeStats) {
|
||||
console.log('更新图表,收到统计数据:', stats);
|
||||
console.log('查询类型统计数据:', queryTypeStats);
|
||||
|
||||
// 空值检查
|
||||
if (!stats) {
|
||||
@@ -563,6 +664,35 @@ function updateCharts(stats) {
|
||||
ratioChart.data.datasets[0].data = [allowed, blocked, error];
|
||||
ratioChart.update();
|
||||
}
|
||||
|
||||
// 更新解析类型统计饼图
|
||||
if (queryTypeChart && queryTypeStats && Array.isArray(queryTypeStats)) {
|
||||
const queryTypeColors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c', '#d35400', '#34495e'];
|
||||
|
||||
// 检查是否有有效的数据项
|
||||
const validData = queryTypeStats.filter(item => item && item.count > 0);
|
||||
|
||||
if (validData.length > 0) {
|
||||
// 准备标签和数据
|
||||
const labels = validData.map(item => item.type);
|
||||
const data = validData.map(item => item.count);
|
||||
|
||||
// 为每个解析类型分配颜色
|
||||
const colors = labels.map((_, index) => queryTypeColors[index % queryTypeColors.length]);
|
||||
|
||||
// 更新图表数据
|
||||
queryTypeChart.data.labels = labels;
|
||||
queryTypeChart.data.datasets[0].data = data;
|
||||
queryTypeChart.data.datasets[0].backgroundColor = colors;
|
||||
} else {
|
||||
// 如果没有数据,显示默认值
|
||||
queryTypeChart.data.labels = ['暂无数据'];
|
||||
queryTypeChart.data.datasets[0].data = [1];
|
||||
queryTypeChart.data.datasets[0].backgroundColor = [queryTypeColors[0]];
|
||||
}
|
||||
|
||||
queryTypeChart.update();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计卡片折线图
|
||||
|
||||
Reference in New Issue
Block a user