270 lines
8.8 KiB
JavaScript
270 lines
8.8 KiB
JavaScript
// dashboard.js - 仪表盘页面功能
|
|
|
|
// 加载仪表盘数据
|
|
async function loadDashboardData() {
|
|
try {
|
|
// 并行加载所有需要的数据
|
|
const [statsData, topBlockedData, recentBlockedData, hourlyStatsData] = await Promise.all([
|
|
api.getStats(),
|
|
api.getTopBlockedDomains(),
|
|
api.getRecentBlockedDomains(),
|
|
api.getHourlyStats()
|
|
]);
|
|
|
|
// 更新统计卡片
|
|
updateStatsCards(statsData);
|
|
|
|
// 更新表格数据
|
|
updateTopBlockedTable(topBlockedData);
|
|
updateRecentBlockedTable(recentBlockedData);
|
|
|
|
// 更新图表
|
|
updateQueryTrendChart(hourlyStatsData);
|
|
updateRatioChart(statsData);
|
|
|
|
} catch (error) {
|
|
console.error('加载仪表盘数据失败:', error);
|
|
showErrorMessage('数据加载失败,请刷新页面重试');
|
|
}
|
|
}
|
|
|
|
// 更新统计卡片
|
|
function updateStatsCards(data) {
|
|
const dnsStats = data.dns;
|
|
|
|
// 更新总查询数
|
|
document.getElementById('total-queries').textContent = formatNumber(dnsStats.Queries);
|
|
|
|
// 更新屏蔽数量
|
|
document.getElementById('blocked-queries').textContent = formatNumber(dnsStats.Blocked);
|
|
|
|
// 更新正常解析数量
|
|
document.getElementById('allowed-queries').textContent = formatNumber(dnsStats.Allowed);
|
|
|
|
// 更新错误数量
|
|
document.getElementById('error-queries').textContent = formatNumber(dnsStats.Errors);
|
|
|
|
// 计算百分比(简化计算,实际可能需要与历史数据比较)
|
|
if (dnsStats.Queries > 0) {
|
|
const blockedPercent = Math.round((dnsStats.Blocked / dnsStats.Queries) * 100);
|
|
const allowedPercent = Math.round((dnsStats.Allowed / dnsStats.Queries) * 100);
|
|
const errorPercent = Math.round((dnsStats.Errors / dnsStats.Queries) * 100);
|
|
|
|
document.getElementById('blocked-percent').textContent = `${blockedPercent}%`;
|
|
document.getElementById('allowed-percent').textContent = `${allowedPercent}%`;
|
|
document.getElementById('error-percent').textContent = `${errorPercent}%`;
|
|
document.getElementById('queries-percent').textContent = '100%';
|
|
}
|
|
}
|
|
|
|
// 更新最常屏蔽域名表格
|
|
function updateTopBlockedTable(data) {
|
|
const tableBody = document.getElementById('top-blocked-table');
|
|
tableBody.innerHTML = '';
|
|
|
|
if (data.length === 0) {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `<td colspan="2" class="py-4 text-center text-gray-500">暂无数据</td>`;
|
|
tableBody.appendChild(row);
|
|
return;
|
|
}
|
|
|
|
data.forEach(item => {
|
|
const row = document.createElement('tr');
|
|
row.className = 'border-b border-gray-100 hover:bg-gray-50';
|
|
row.innerHTML = `
|
|
<td class="py-3 px-4 text-sm text-gray-800">${item.domain}</td>
|
|
<td class="py-3 px-4 text-sm text-gray-800 text-right">${formatNumber(item.count)}</td>
|
|
`;
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
// 更新最近屏蔽域名表格
|
|
function updateRecentBlockedTable(data) {
|
|
const tableBody = document.getElementById('recent-blocked-table');
|
|
tableBody.innerHTML = '';
|
|
|
|
if (data.length === 0) {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `<td colspan="2" class="py-4 text-center text-gray-500">暂无数据</td>`;
|
|
tableBody.appendChild(row);
|
|
return;
|
|
}
|
|
|
|
data.forEach(item => {
|
|
const row = document.createElement('tr');
|
|
row.className = 'border-b border-gray-100 hover:bg-gray-50';
|
|
row.innerHTML = `
|
|
<td class="py-3 px-4 text-sm text-gray-800">${item.domain}</td>
|
|
<td class="py-3 px-4 text-sm text-gray-500 text-right">${item.time}</td>
|
|
`;
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
// 更新查询趋势图表
|
|
function updateQueryTrendChart(data) {
|
|
const ctx = document.getElementById('query-trend-chart').getContext('2d');
|
|
|
|
// 创建图表
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.labels,
|
|
datasets: [{
|
|
label: '查询数量',
|
|
data: data.data,
|
|
borderColor: '#165DFF',
|
|
backgroundColor: 'rgba(22, 93, 255, 0.1)',
|
|
borderWidth: 2,
|
|
tension: 0.3,
|
|
fill: true,
|
|
pointBackgroundColor: '#FFFFFF',
|
|
pointBorderColor: '#165DFF',
|
|
pointBorderWidth: 2,
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
padding: 12,
|
|
cornerRadius: 8,
|
|
titleFont: {
|
|
size: 14,
|
|
weight: 'bold'
|
|
},
|
|
bodyFont: {
|
|
size: 13
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {
|
|
display: false
|
|
},
|
|
ticks: {
|
|
font: {
|
|
size: 12
|
|
}
|
|
}
|
|
},
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: 'rgba(0, 0, 0, 0.05)'
|
|
},
|
|
ticks: {
|
|
font: {
|
|
size: 12
|
|
},
|
|
callback: function(value) {
|
|
return formatNumber(value);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
interaction: {
|
|
intersect: false,
|
|
mode: 'index'
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 更新比例图表
|
|
function updateRatioChart(data) {
|
|
const dnsStats = data.dns;
|
|
const ctx = document.getElementById('ratio-chart').getContext('2d');
|
|
|
|
// 准备数据
|
|
const chartData = [dnsStats.Allowed, dnsStats.Blocked, dnsStats.Errors];
|
|
const chartColors = ['#00B42A', '#F53F3F', '#FF7D00'];
|
|
const chartLabels = ['正常解析', '屏蔽', '错误'];
|
|
|
|
// 创建图表
|
|
new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: chartLabels,
|
|
datasets: [{
|
|
data: chartData,
|
|
backgroundColor: chartColors,
|
|
borderWidth: 0,
|
|
hoverOffset: 10
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom',',\,
|
|
labels: {
|
|
padding: 20,
|
|
font: {
|
|
size: 13
|
|
}
|
|
}
|
|
},
|
|
tooltip: {
|
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
padding: 12,
|
|
cornerRadius: 8,
|
|
callbacks: {
|
|
label: function(context) {
|
|
const value = context.parsed;
|
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
const percentage = total > 0 ? Math.round((value / total) * 100) : 0;
|
|
return `${context.label}: ${formatNumber(value)} (${percentage}%)`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
cutout: '70%'
|
|
}
|
|
});
|
|
}
|
|
|
|
// 格式化数字
|
|
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 showErrorMessage(message) {
|
|
// 创建错误消息元素
|
|
const errorElement = document.createElement('div');
|
|
errorElement.className = 'fixed bottom-4 right-4 bg-danger text-white px-6 py-3 rounded-lg shadow-lg z-50 flex items-center';
|
|
errorElement.innerHTML = `
|
|
<i class="fa fa-exclamation-circle mr-2"></i>
|
|
<span>${message}</span>
|
|
`;
|
|
|
|
document.body.appendChild(errorElement);
|
|
|
|
// 3秒后自动移除
|
|
setTimeout(() => {
|
|
errorElement.classList.add('opacity-0', 'transition-opacity', 'duration-300');
|
|
setTimeout(() => {
|
|
document.body.removeChild(errorElement);
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// 定期刷新数据
|
|
setInterval(loadDashboardData, 30000); // 每30秒刷新一次
|