点击展开按钮悬浮窗显示详细24小时/7天/30天详细信息
This commit is contained in:
59660
dns-server.log
Normal file
59660
dns-server.log
Normal file
File diff suppressed because it is too large
Load Diff
BIN
dns_server
BIN
dns_server
Binary file not shown.
1574
index.html
1574
index.html
File diff suppressed because it is too large
Load Diff
69
main.go
69
main.go
@@ -16,12 +16,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 解析命令行参数
|
// 命令行参数解析
|
||||||
configPath := flag.String("config", "config.json", "配置文件路径")
|
var configFile string
|
||||||
|
var daemonMode bool
|
||||||
|
flag.StringVar(&configFile, "config", "config.json", "配置文件路径")
|
||||||
|
flag.BoolVar(&daemonMode, "daemon", false, "以守护进程模式运行")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
// 如果是守护进程模式,创建守护进程
|
||||||
|
if daemonMode {
|
||||||
|
if err := daemonize(); err != nil {
|
||||||
|
log.Fatalf("创建守护进程失败: %v", err)
|
||||||
|
}
|
||||||
|
// 父进程退出
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
cfg, err := config.LoadConfig(*configPath)
|
var cfg *config.Config
|
||||||
|
var err error
|
||||||
|
cfg, err = config.LoadConfig(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("加载配置失败: %v", err)
|
log.Fatalf("加载配置失败: %v", err)
|
||||||
}
|
}
|
||||||
@@ -61,14 +75,51 @@ func main() {
|
|||||||
logger.Info(fmt.Sprintf("DNS服务器已启动,监听端口: %d", cfg.DNS.Port))
|
logger.Info(fmt.Sprintf("DNS服务器已启动,监听端口: %d", cfg.DNS.Port))
|
||||||
logger.Info(fmt.Sprintf("HTTP控制台已启动,监听端口: %d", cfg.HTTP.Port))
|
logger.Info(fmt.Sprintf("HTTP控制台已启动,监听端口: %d", cfg.HTTP.Port))
|
||||||
|
|
||||||
// 等待退出信号
|
// 监听信号
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-sigChan
|
<-sigCh
|
||||||
|
|
||||||
logger.Info("正在关闭服务...")
|
// 清理资源
|
||||||
|
log.Println("正在关闭服务...")
|
||||||
dnsServer.Stop()
|
dnsServer.Stop()
|
||||||
httpServer.Stop()
|
httpServer.Stop()
|
||||||
shieldManager.StopAutoUpdate()
|
shieldManager.StopAutoUpdate()
|
||||||
logger.Info("所有服务已关闭")
|
// 守护进程模式下不需要删除PID文件
|
||||||
|
|
||||||
|
log.Println("服务已关闭")
|
||||||
|
}
|
||||||
|
|
||||||
|
// daemonize 创建守护进程
|
||||||
|
func daemonize() error {
|
||||||
|
// 使用更简单的方式创建守护进程:直接在当前进程中进行守护化处理
|
||||||
|
// 1. 重定向标准输入、输出、错误
|
||||||
|
nullFile, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("打开/dev/null失败: %w", err)
|
||||||
|
}
|
||||||
|
defer nullFile.Close()
|
||||||
|
|
||||||
|
// 重定向文件描述符
|
||||||
|
err = syscall.Dup2(int(nullFile.Fd()), int(os.Stdin.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("重定向stdin失败: %w", err)
|
||||||
|
}
|
||||||
|
err = syscall.Dup2(int(nullFile.Fd()), int(os.Stdout.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("重定向stdout失败: %w", err)
|
||||||
|
}
|
||||||
|
err = syscall.Dup2(int(nullFile.Fd()), int(os.Stderr.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("重定向stderr失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 创建新的会话和进程组
|
||||||
|
_, err = syscall.Setsid()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("创建新会话失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("守护进程已启动")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -511,10 +511,41 @@
|
|||||||
|
|
||||||
<!-- 图表和数据表格 -->
|
<!-- 图表和数据表格 -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<!-- DNS请求趋势图表 -->
|
<!-- 三个图表在同一行显示 -->
|
||||||
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-3">
|
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-1 md:col-span-1">
|
||||||
|
<h3 class="text-lg font-semibold mb-6">解析与屏蔽比例</h3>
|
||||||
|
<div class="h-64 flex items-center justify-center">
|
||||||
|
<canvas id="ratio-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-1 md:col-span-1">
|
||||||
|
<h3 class="text-lg font-semibold mb-6">解析类型统计</h3>
|
||||||
|
<div class="h-64 flex items-center justify-center">
|
||||||
|
<canvas id="query-type-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-1 md:col-span-1">
|
||||||
<div class="flex items-center justify-between mb-6">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h3 class="text-lg font-semibold">DNS请求趋势</h3>
|
<h3 class="text-lg font-semibold">DNS请求趋势</h3>
|
||||||
|
<!-- 展开按钮 -->
|
||||||
|
<button id="expand-chart-btn" class="p-2 rounded-full bg-primary/10 text-primary hover:bg-primary/20 transition-colors" title="展开详细图表">
|
||||||
|
<i class="fa fa-expand"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="h-64">
|
||||||
|
<canvas id="dns-requests-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详细图表浮窗 -->
|
||||||
|
<div id="chart-modal" class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 hidden">
|
||||||
|
<div class="bg-white rounded-lg w-full max-w-5xl max-h-[90vh] overflow-hidden">
|
||||||
|
<div class="flex items-center justify-between p-6 border-b border-gray-200">
|
||||||
|
<h3 class="text-xl font-semibold">DNS请求趋势详细图表</h3>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
<!-- 时间范围切换按钮 -->
|
<!-- 时间范围切换按钮 -->
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="mixed">混合视图</button>
|
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="mixed">混合视图</button>
|
||||||
@@ -522,26 +553,17 @@
|
|||||||
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="7d">7天</button>
|
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="7d">7天</button>
|
||||||
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="30d">30天</button>
|
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="30d">30天</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- 关闭按钮 -->
|
||||||
<div class="h-80">
|
<button id="close-modal-btn" class="p-2 rounded-full bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors">
|
||||||
<canvas id="dns-requests-chart"></canvas>
|
<i class="fa fa-times"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
<!-- 解析与屏蔽比例 -->
|
<div class="h-[600px]">
|
||||||
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-3">
|
<canvas id="detailed-dns-requests-chart"></canvas>
|
||||||
<h3 class="text-lg font-semibold mb-6">解析与屏蔽比例</h3>
|
|
||||||
<div class="h-80 flex items-center justify-center">
|
|
||||||
<canvas id="ratio-chart"></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 解析类型统计 -->
|
|
||||||
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-3">
|
|
||||||
<h3 class="text-lg font-semibold mb-6">解析类型统计</h3>
|
|
||||||
<div class="h-80 flex items-center justify-center">
|
|
||||||
<canvas id="query-type-chart"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// 全局变量
|
// 全局变量
|
||||||
let ratioChart = null;
|
let ratioChart = null;
|
||||||
let dnsRequestsChart = null;
|
let dnsRequestsChart = null;
|
||||||
|
let detailedDnsRequestsChart = null; // 详细DNS请求趋势图表(浮窗)
|
||||||
let queryTypeChart = null; // 解析类型统计饼图
|
let queryTypeChart = null; // 解析类型统计饼图
|
||||||
let intervalId = null;
|
let intervalId = null;
|
||||||
let wsConnection = null;
|
let wsConnection = null;
|
||||||
@@ -939,6 +940,219 @@ function initTimeRangeToggle() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化展开按钮功能
|
||||||
|
function initExpandButton() {
|
||||||
|
const expandBtn = document.getElementById('expand-chart-btn');
|
||||||
|
const chartModal = document.getElementById('chart-modal');
|
||||||
|
const closeBtn = document.getElementById('close-modal-btn');
|
||||||
|
|
||||||
|
if (!expandBtn || !chartModal || !closeBtn) {
|
||||||
|
console.error('未找到展开按钮或浮窗元素');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 展开按钮点击事件
|
||||||
|
expandBtn.addEventListener('click', () => {
|
||||||
|
console.log('展开按钮被点击');
|
||||||
|
chartModal.classList.remove('hidden');
|
||||||
|
// 初始化详细图表
|
||||||
|
drawDetailedDNSRequestsChart();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭按钮点击事件
|
||||||
|
closeBtn.addEventListener('click', () => {
|
||||||
|
console.log('关闭浮窗');
|
||||||
|
chartModal.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击浮窗外部关闭
|
||||||
|
chartModal.addEventListener('click', (event) => {
|
||||||
|
if (event.target === chartModal) {
|
||||||
|
chartModal.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化详细图表的时间范围切换
|
||||||
|
initDetailedTimeRangeToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化详细图表的时间范围切换
|
||||||
|
function initDetailedTimeRangeToggle() {
|
||||||
|
const timeRangeBtns = document.querySelectorAll('.time-range-btn[data-range]');
|
||||||
|
|
||||||
|
if (!timeRangeBtns.length) {
|
||||||
|
console.warn('未找到详细图表的时间范围按钮');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详细图表的当前时间范围
|
||||||
|
let detailedCurrentTimeRange = '24h';
|
||||||
|
let detailedIsMixedView = false;
|
||||||
|
|
||||||
|
// 为所有时间范围按钮添加点击事件
|
||||||
|
timeRangeBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const range = btn.getAttribute('data-range');
|
||||||
|
|
||||||
|
if (range === 'mixed') {
|
||||||
|
detailedIsMixedView = !detailedIsMixedView;
|
||||||
|
} else {
|
||||||
|
detailedCurrentTimeRange = range;
|
||||||
|
detailedIsMixedView = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新按钮样式
|
||||||
|
updateTimeRangeButtonStyles(timeRangeBtns, range, detailedIsMixedView);
|
||||||
|
|
||||||
|
// 重新绘制详细图表
|
||||||
|
drawDetailedDNSRequestsChart(detailedCurrentTimeRange, detailedIsMixedView);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新时间范围按钮样式
|
||||||
|
function updateTimeRangeButtonStyles(buttons, activeRange, isMixedView) {
|
||||||
|
buttons.forEach(btn => {
|
||||||
|
const btnRange = btn.getAttribute('data-range');
|
||||||
|
|
||||||
|
if (btnRange === activeRange && (btnRange !== 'mixed' || isMixedView)) {
|
||||||
|
btn.classList.add('bg-primary', 'text-white');
|
||||||
|
btn.classList.remove('bg-gray-200', 'text-gray-700');
|
||||||
|
} else {
|
||||||
|
btn.classList.remove('bg-primary', 'text-white');
|
||||||
|
btn.classList.add('bg-gray-200', 'text-gray-700');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制详细DNS请求图表
|
||||||
|
function drawDetailedDNSRequestsChart(timeRange = '24h', isMixedView = false) {
|
||||||
|
const chartElement = document.getElementById('detailed-dns-requests-chart');
|
||||||
|
if (!chartElement) {
|
||||||
|
console.error('未找到详细DNS请求图表元素');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartContext = chartElement.getContext('2d');
|
||||||
|
|
||||||
|
// 这里可以复用或修改现有的drawDNSRequestsChart函数的逻辑
|
||||||
|
// 为详细图表使用与原始图表相同的数据获取逻辑
|
||||||
|
let apiFunction;
|
||||||
|
let count;
|
||||||
|
|
||||||
|
if (isMixedView) {
|
||||||
|
// 混合视图 - 不同时间范围的数据
|
||||||
|
apiFunction = () => Promise.resolve({
|
||||||
|
labels: ['0h', '6h', '12h', '18h', '24h', '48h', '72h', '96h', '120h', '144h', '168h'],
|
||||||
|
data: generateMockData(11, 1000, 5000)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 普通视图 - 基于时间范围
|
||||||
|
if (timeRange === '24h') {
|
||||||
|
count = 24;
|
||||||
|
apiFunction = () => Promise.resolve({
|
||||||
|
labels: generateTimeLabels(count),
|
||||||
|
data: generateMockData(count, 100, 500)
|
||||||
|
});
|
||||||
|
} else if (timeRange === '7d') {
|
||||||
|
count = 7;
|
||||||
|
apiFunction = () => Promise.resolve({
|
||||||
|
labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
||||||
|
data: generateMockData(count, 1000, 5000)
|
||||||
|
});
|
||||||
|
} else if (timeRange === '30d') {
|
||||||
|
count = 30;
|
||||||
|
apiFunction = () => Promise.resolve({
|
||||||
|
labels: Array(count).fill(''),
|
||||||
|
data: generateMockData(count, 10000, 50000)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据并绘制图表
|
||||||
|
if (apiFunction) {
|
||||||
|
apiFunction().then(data => {
|
||||||
|
// 创建或更新图表
|
||||||
|
if (detailedDnsRequestsChart) {
|
||||||
|
detailedDnsRequestsChart.data.labels = data.labels;
|
||||||
|
detailedDnsRequestsChart.data.datasets = [{
|
||||||
|
label: 'DNS请求数量',
|
||||||
|
data: data.data,
|
||||||
|
borderColor: '#3b82f6',
|
||||||
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
}];
|
||||||
|
detailedDnsRequestsChart.options.plugins.legend.display = false;
|
||||||
|
// 使用平滑过渡动画更新图表
|
||||||
|
detailedDnsRequestsChart.update({
|
||||||
|
duration: 800,
|
||||||
|
easing: 'easeInOutQuart'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
detailedDnsRequestsChart = new Chart(chartContext, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: data.labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'DNS请求数量',
|
||||||
|
data: data.data,
|
||||||
|
borderColor: '#3b82f6',
|
||||||
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: {
|
||||||
|
duration: 800,
|
||||||
|
easing: 'easeInOutQuart'
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('绘制详细DNS请求图表失败:', error);
|
||||||
|
// 错误处理:使用空数据
|
||||||
|
const count = timeRange === '24h' ? 24 : (timeRange === '7d' ? 7 : 30);
|
||||||
|
const emptyData = {
|
||||||
|
labels: Array(count).fill(''),
|
||||||
|
data: Array(count).fill(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (detailedDnsRequestsChart) {
|
||||||
|
detailedDnsRequestsChart.data.labels = emptyData.labels;
|
||||||
|
detailedDnsRequestsChart.data.datasets[0].data = emptyData.data;
|
||||||
|
detailedDnsRequestsChart.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化图表
|
// 初始化图表
|
||||||
function initCharts() {
|
function initCharts() {
|
||||||
// 初始化比例图表
|
// 初始化比例图表
|
||||||
@@ -969,9 +1183,27 @@ function initCharts() {
|
|||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
boxWidth: 12, // 减小图例框的宽度
|
||||||
|
font: {
|
||||||
|
size: 11 // 减小字体大小
|
||||||
|
},
|
||||||
|
padding: 10 // 减小内边距
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cutout: '70%'
|
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: '75%' // 增加中心空白区域比例,使环形更适合小容器
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1003,6 +1235,13 @@ function initCharts() {
|
|||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
boxWidth: 12, // 减小图例框的宽度
|
||||||
|
font: {
|
||||||
|
size: 11 // 减小字体大小
|
||||||
|
},
|
||||||
|
padding: 10 // 减小内边距
|
||||||
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
@@ -1016,7 +1255,7 @@ function initCharts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cutout: '70%'
|
cutout: '75%' // 增加中心空白区域比例,使环形更适合小容器
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -1025,6 +1264,299 @@ function initCharts() {
|
|||||||
|
|
||||||
// 初始化DNS请求统计图表
|
// 初始化DNS请求统计图表
|
||||||
drawDNSRequestsChart();
|
drawDNSRequestsChart();
|
||||||
|
|
||||||
|
// 初始化展开按钮功能
|
||||||
|
initExpandButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化展开按钮事件
|
||||||
|
function initExpandButton() {
|
||||||
|
const expandBtn = document.getElementById('expand-chart-btn');
|
||||||
|
const chartModal = document.getElementById('chart-modal');
|
||||||
|
const closeModalBtn = document.getElementById('close-modal-btn'); // 修复ID匹配
|
||||||
|
|
||||||
|
// 添加调试日志
|
||||||
|
console.log('初始化展开按钮功能:', { expandBtn, chartModal, closeModalBtn });
|
||||||
|
|
||||||
|
if (expandBtn && chartModal && closeModalBtn) {
|
||||||
|
// 展开按钮点击事件
|
||||||
|
expandBtn.addEventListener('click', () => {
|
||||||
|
console.log('展开按钮被点击');
|
||||||
|
// 显示浮窗
|
||||||
|
chartModal.classList.remove('hidden');
|
||||||
|
|
||||||
|
// 初始化或更新详细图表
|
||||||
|
drawDetailedDNSRequestsChart();
|
||||||
|
|
||||||
|
// 初始化浮窗中的时间范围切换
|
||||||
|
initDetailedTimeRangeToggle();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭按钮点击事件
|
||||||
|
closeModalBtn.addEventListener('click', () => {
|
||||||
|
console.log('关闭按钮被点击');
|
||||||
|
chartModal.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击遮罩层关闭浮窗(使用chartModal作为遮罩层)
|
||||||
|
chartModal.addEventListener('click', (e) => {
|
||||||
|
// 检查点击目标是否是遮罩层本身(即最外层div)
|
||||||
|
if (e.target === chartModal) {
|
||||||
|
console.log('点击遮罩层关闭');
|
||||||
|
chartModal.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ESC键关闭浮窗
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && !chartModal.classList.contains('hidden')) {
|
||||||
|
console.log('ESC键关闭浮窗');
|
||||||
|
chartModal.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('无法找到必要的DOM元素');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化详细图表的时间范围切换
|
||||||
|
function initDetailedTimeRangeToggle() {
|
||||||
|
const detailedTimeRangeButtons = document.querySelectorAll('.time-range-btn');
|
||||||
|
|
||||||
|
console.log('初始化详细图表时间范围切换,找到按钮数量:', detailedTimeRangeButtons.length);
|
||||||
|
|
||||||
|
detailedTimeRangeButtons.forEach((button) => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
// 设置当前时间范围
|
||||||
|
const timeRange = button.dataset.range;
|
||||||
|
const isMixedMode = timeRange === 'mixed';
|
||||||
|
|
||||||
|
console.log('时间范围按钮被点击:', { timeRange, isMixedMode });
|
||||||
|
|
||||||
|
// 更新按钮状态
|
||||||
|
detailedTimeRangeButtons.forEach((btn) => {
|
||||||
|
if (btn === button) {
|
||||||
|
btn.classList.add('bg-blue-500', 'text-white');
|
||||||
|
btn.classList.remove('bg-gray-200', 'text-gray-700');
|
||||||
|
} else {
|
||||||
|
btn.classList.remove('bg-blue-500', 'text-white');
|
||||||
|
btn.classList.add('bg-gray-200', 'text-gray-700');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新详细图表
|
||||||
|
currentTimeRange = timeRange;
|
||||||
|
isMixedView = isMixedMode;
|
||||||
|
drawDetailedDNSRequestsChart();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制详细的DNS请求趋势图表
|
||||||
|
function drawDetailedDNSRequestsChart() {
|
||||||
|
console.log('绘制详细DNS请求趋势图表,时间范围:', currentTimeRange, '混合视图:', isMixedView);
|
||||||
|
|
||||||
|
const ctx = document.getElementById('detailed-dns-requests-chart');
|
||||||
|
if (!ctx) {
|
||||||
|
console.error('未找到详细DNS请求图表元素');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartContext = ctx.getContext('2d');
|
||||||
|
|
||||||
|
// 混合视图配置
|
||||||
|
const datasetsConfig = [
|
||||||
|
{ label: '24小时', api: (api && api.getHourlyStats) || (() => Promise.resolve({ labels: [], data: [] })), color: '#3b82f6', fillColor: 'rgba(59, 130, 246, 0.1)' },
|
||||||
|
{ label: '7天', api: (api && api.getDailyStats) || (() => Promise.resolve({ labels: [], data: [] })), color: '#22c55e', fillColor: 'rgba(34, 197, 94, 0.1)' },
|
||||||
|
{ label: '30天', api: (api && api.getMonthlyStats) || (() => Promise.resolve({ labels: [], data: [] })), color: '#a855f7', fillColor: 'rgba(168, 85, 247, 0.1)' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 检查是否为混合视图
|
||||||
|
if (isMixedView || currentTimeRange === 'mixed') {
|
||||||
|
console.log('渲染混合视图详细图表');
|
||||||
|
|
||||||
|
// 显示图例
|
||||||
|
const showLegend = true;
|
||||||
|
|
||||||
|
// 获取所有时间范围的数据
|
||||||
|
Promise.all(datasetsConfig.map(config =>
|
||||||
|
config.api().catch(error => {
|
||||||
|
console.error(`获取${config.label}数据失败:`, error);
|
||||||
|
// 返回空数据
|
||||||
|
const count = config.label === '24小时' ? 24 : (config.label === '7天' ? 7 : 30);
|
||||||
|
return {
|
||||||
|
labels: Array(count).fill(''),
|
||||||
|
data: Array(count).fill(0)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
)).then(results => {
|
||||||
|
// 创建数据集
|
||||||
|
const datasets = results.map((data, index) => ({
|
||||||
|
label: datasetsConfig[index].label,
|
||||||
|
data: data.data,
|
||||||
|
borderColor: datasetsConfig[index].color,
|
||||||
|
backgroundColor: datasetsConfig[index].fillColor,
|
||||||
|
tension: 0.4,
|
||||||
|
fill: false,
|
||||||
|
borderWidth: 2
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 创建或更新图表
|
||||||
|
if (detailedDnsRequestsChart) {
|
||||||
|
detailedDnsRequestsChart.data.labels = results[0].labels;
|
||||||
|
detailedDnsRequestsChart.data.datasets = datasets;
|
||||||
|
detailedDnsRequestsChart.options.plugins.legend.display = showLegend;
|
||||||
|
// 使用平滑过渡动画更新图表
|
||||||
|
detailedDnsRequestsChart.update({
|
||||||
|
duration: 800,
|
||||||
|
easing: 'easeInOutQuart'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
detailedDnsRequestsChart = new Chart(chartContext, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: results[0].labels,
|
||||||
|
datasets: datasets
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: {
|
||||||
|
duration: 800,
|
||||||
|
easing: 'easeInOutQuart'
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: showLegend,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('绘制混合视图详细图表失败:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 普通视图
|
||||||
|
// 根据当前时间范围选择API函数
|
||||||
|
let apiFunction;
|
||||||
|
switch (currentTimeRange) {
|
||||||
|
case '7d':
|
||||||
|
apiFunction = (api && api.getDailyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||||
|
break;
|
||||||
|
case '30d':
|
||||||
|
apiFunction = (api && api.getMonthlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||||
|
break;
|
||||||
|
default: // 24h
|
||||||
|
apiFunction = (api && api.getHourlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取统计数据
|
||||||
|
apiFunction().then(data => {
|
||||||
|
// 创建或更新图表
|
||||||
|
if (detailedDnsRequestsChart) {
|
||||||
|
detailedDnsRequestsChart.data.labels = data.labels;
|
||||||
|
detailedDnsRequestsChart.data.datasets = [{
|
||||||
|
label: 'DNS请求数量',
|
||||||
|
data: data.data,
|
||||||
|
borderColor: '#3b82f6',
|
||||||
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
}];
|
||||||
|
detailedDnsRequestsChart.options.plugins.legend.display = false;
|
||||||
|
// 使用平滑过渡动画更新图表
|
||||||
|
detailedDnsRequestsChart.update({
|
||||||
|
duration: 800,
|
||||||
|
easing: 'easeInOutQuart'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
detailedDnsRequestsChart = new Chart(chartContext, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: data.labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'DNS请求数量',
|
||||||
|
data: data.data,
|
||||||
|
borderColor: '#3b82f6',
|
||||||
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||||
|
tension: 0.4,
|
||||||
|
fill: true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
animation: {
|
||||||
|
duration: 800,
|
||||||
|
easing: 'easeInOutQuart'
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'DNS请求趋势',
|
||||||
|
font: {
|
||||||
|
size: 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('绘制详细DNS请求图表失败:', error);
|
||||||
|
// 错误处理:使用空数据
|
||||||
|
const count = currentTimeRange === '24h' ? 24 : (currentTimeRange === '7d' ? 7 : 30);
|
||||||
|
const emptyData = {
|
||||||
|
labels: Array(count).fill(''),
|
||||||
|
data: Array(count).fill(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (detailedDnsRequestsChart) {
|
||||||
|
detailedDnsRequestsChart.data.labels = emptyData.labels;
|
||||||
|
detailedDnsRequestsChart.data.datasets[0].data = emptyData.data;
|
||||||
|
detailedDnsRequestsChart.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制DNS请求统计图表
|
// 绘制DNS请求统计图表
|
||||||
|
|||||||
Reference in New Issue
Block a user