添加重启endpoint
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5
data/shield_stats.json
Normal file
5
data/shield_stats.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"blockedDomainsCount": {},
|
||||
"resolvedDomainsCount": {},
|
||||
"lastSaved": "2025-11-27T18:23:48.562056138+08:00"
|
||||
}
|
||||
3129
data/stats.json
Normal file
3129
data/stats.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
dns-server
Executable file
BIN
dns-server
Executable file
Binary file not shown.
@@ -65,16 +65,18 @@ type Server struct {
|
||||
resolvedDomainsMutex sync.RWMutex
|
||||
resolvedDomains map[string]*BlockedDomain // 用于记录解析的域名
|
||||
clientStatsMutex sync.RWMutex
|
||||
clientStats map[string]*ClientStats // 用于记录客户端统计
|
||||
clientStats map[string]*ClientStats // 用于记录客户端统计
|
||||
hourlyStatsMutex sync.RWMutex
|
||||
hourlyStats map[string]int64 // 按小时统计屏蔽数量
|
||||
hourlyStats map[string]int64 // 按小时统计屏蔽数量
|
||||
dailyStatsMutex sync.RWMutex
|
||||
dailyStats map[string]int64 // 按天统计屏蔽数量
|
||||
dailyStats map[string]int64 // 按天统计屏蔽数量
|
||||
monthlyStatsMutex sync.RWMutex
|
||||
monthlyStats map[string]int64 // 按月统计屏蔽数量
|
||||
monthlyStats map[string]int64 // 按月统计屏蔽数量
|
||||
saveTicker *time.Ticker // 用于定时保存数据
|
||||
startTime time.Time // 服务器启动时间
|
||||
saveDone chan struct{} // 用于通知保存协程停止
|
||||
stopped bool // 服务器是否已经停止
|
||||
stoppedMutex sync.Mutex // 保护stopped标志的互斥锁
|
||||
}
|
||||
|
||||
// Stats DNS服务器统计信息
|
||||
@@ -102,9 +104,9 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
|
||||
Net: "udp",
|
||||
Timeout: time.Duration(config.Timeout) * time.Millisecond,
|
||||
},
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
startTime: time.Now(), // 记录服务器启动时间
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
startTime: time.Now(), // 记录服务器启动时间
|
||||
stats: &Stats{
|
||||
Queries: 0,
|
||||
Blocked: 0,
|
||||
@@ -123,6 +125,7 @@ func NewServer(config *config.DNSConfig, shieldConfig *config.ShieldConfig, shie
|
||||
dailyStats: make(map[string]int64),
|
||||
monthlyStats: make(map[string]int64),
|
||||
saveDone: make(chan struct{}),
|
||||
stopped: false, // 初始化为未停止状态
|
||||
}
|
||||
|
||||
// 加载已保存的统计数据
|
||||
|
||||
@@ -71,6 +71,7 @@ func (s *Server) Start() error {
|
||||
mux.HandleFunc("/api/query", s.handleQuery)
|
||||
mux.HandleFunc("/api/status", s.handleStatus)
|
||||
mux.HandleFunc("/api/config", s.handleConfig)
|
||||
mux.HandleFunc("/api/config/restart", s.handleRestart)
|
||||
// 添加统计相关接口
|
||||
mux.HandleFunc("/api/top-blocked", s.handleTopBlockedDomains)
|
||||
mux.HandleFunc("/api/top-resolved", s.handleTopResolvedDomains)
|
||||
@@ -1062,3 +1063,37 @@ func isValidIP(ip string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// handleRestart 处理重启服务请求
|
||||
func (s *Server) handleRestart(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("收到重启服务请求")
|
||||
|
||||
// 停止DNS服务器
|
||||
s.dnsServer.Stop()
|
||||
|
||||
// 重新加载屏蔽规则
|
||||
if err := s.shieldManager.LoadRules(); err != nil {
|
||||
logger.Error("重新加载屏蔽规则失败", "error", err)
|
||||
}
|
||||
|
||||
// 重新启动DNS服务器
|
||||
go func() {
|
||||
if err := s.dnsServer.Start(); err != nil {
|
||||
logger.Error("DNS服务器重启失败", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 重新启动定时更新任务
|
||||
s.shieldManager.StopAutoUpdate()
|
||||
s.shieldManager.StartAutoUpdate()
|
||||
|
||||
// 返回成功响应
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "服务已重启"})
|
||||
logger.Info("服务重启成功")
|
||||
}
|
||||
|
||||
52202
logs/dns-server.log
Normal file
52202
logs/dns-server.log
Normal file
File diff suppressed because it is too large
Load Diff
BIN
output/dns-server
Executable file
BIN
output/dns-server
Executable file
Binary file not shown.
@@ -638,9 +638,9 @@
|
||||
<div id="shield-content" class="hidden">
|
||||
<!-- 屏蔽管理页面内容 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-6">屏蔽规则管理</h3>
|
||||
<h3 class="text-lg font-semibold mb-6">远程规则管理</h3>
|
||||
<!-- 这里将添加屏蔽规则管理相关内容 -->
|
||||
<p>屏蔽管理页面内容待实现</p>
|
||||
<p>远程规则管理页面内容待实现</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1219,7 +1219,21 @@ function initTimeRangeToggle() {
|
||||
button.classList.add(...styleConfig.activeHover); // 添加选中时的浅色悬停
|
||||
|
||||
// 获取并更新当前时间范围
|
||||
const rangeValue = button.dataset.range || button.textContent.trim().replace(/[^0-9a-zA-Z]/g, '');
|
||||
let rangeValue;
|
||||
if (button.dataset.range) {
|
||||
rangeValue = button.dataset.range;
|
||||
} else {
|
||||
const btnText = button.textContent.trim();
|
||||
if (btnText.includes('24')) {
|
||||
rangeValue = '24h';
|
||||
} else if (btnText.includes('7')) {
|
||||
rangeValue = '7d';
|
||||
} else if (btnText.includes('30')) {
|
||||
rangeValue = '30d';
|
||||
} else {
|
||||
rangeValue = btnText.replace(/[^0-9a-zA-Z]/g, '');
|
||||
}
|
||||
}
|
||||
currentTimeRange = rangeValue;
|
||||
console.log('更新时间范围为:', currentTimeRange);
|
||||
}
|
||||
@@ -1233,7 +1247,7 @@ function initTimeRangeToggle() {
|
||||
// 移除自定义鼠标悬停提示效果
|
||||
});
|
||||
|
||||
// 确保默认选中第一个按钮
|
||||
// 确保默认选中第一个按钮并显示混合内容
|
||||
if (timeRangeButtons.length > 0) {
|
||||
const firstButton = timeRangeButtons[0];
|
||||
const firstStyle = buttonStyles[0];
|
||||
@@ -1241,18 +1255,24 @@ function initTimeRangeToggle() {
|
||||
// 先重置所有按钮
|
||||
timeRangeButtons.forEach((btn, index) => {
|
||||
const btnStyle = buttonStyles[index % buttonStyles.length];
|
||||
btn.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-green-500', 'bg-purple-500');
|
||||
btn.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-green-500', 'bg-purple-500', 'bg-gray-500', 'mixed-view-active');
|
||||
btn.classList.remove(...btnStyle.active);
|
||||
btn.classList.remove(...btnStyle.activeHover);
|
||||
btn.classList.add(...btnStyle.normal);
|
||||
btn.classList.add(...btnStyle.hover);
|
||||
});
|
||||
|
||||
// 然后设置第一个按钮为激活状态
|
||||
// 然后设置第一个按钮为激活状态,并标记为混合视图
|
||||
firstButton.classList.remove(...firstStyle.normal);
|
||||
firstButton.classList.remove(...firstStyle.hover);
|
||||
firstButton.classList.add('active');
|
||||
firstButton.classList.add('active', 'mixed-view-active');
|
||||
firstButton.classList.add(...firstStyle.active);
|
||||
console.log('默认选中第一个按钮:', firstButton.textContent.trim());
|
||||
firstButton.classList.add(...firstStyle.activeHover);
|
||||
console.log('默认选中第一个按钮并显示混合内容:', firstButton.textContent.trim());
|
||||
|
||||
// 设置默认显示混合内容
|
||||
isMixedView = true;
|
||||
currentTimeRange = 'mixed';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1479,28 +1499,126 @@ function initDetailedTimeRangeToggle() {
|
||||
|
||||
console.log('初始化详细图表时间范围切换,找到按钮数量:', detailedTimeRangeButtons.length);
|
||||
|
||||
detailedTimeRangeButtons.forEach((button) => {
|
||||
// 初始化详细图表的默认状态,与主图表保持一致
|
||||
detailedCurrentTimeRange = currentTimeRange;
|
||||
detailedIsMixedView = isMixedView;
|
||||
|
||||
// 定义按钮样式配置,与主视图保持一致
|
||||
const buttonStyles = [
|
||||
{ // 24小时按钮
|
||||
normal: ['bg-gray-100', 'text-gray-700'],
|
||||
hover: ['hover:bg-blue-100'],
|
||||
active: ['bg-blue-500', 'text-white'],
|
||||
activeHover: ['hover:bg-blue-400']
|
||||
},
|
||||
{ // 7天按钮
|
||||
normal: ['bg-gray-100', 'text-gray-700'],
|
||||
hover: ['hover:bg-green-100'],
|
||||
active: ['bg-green-500', 'text-white'],
|
||||
activeHover: ['hover:bg-green-400']
|
||||
},
|
||||
{ // 30天按钮
|
||||
normal: ['bg-gray-100', 'text-gray-700'],
|
||||
hover: ['hover:bg-purple-100'],
|
||||
active: ['bg-purple-500', 'text-white'],
|
||||
activeHover: ['hover:bg-purple-400']
|
||||
},
|
||||
{ // 混合视图按钮
|
||||
normal: ['bg-gray-100', 'text-gray-700'],
|
||||
hover: ['hover:bg-gray-200'],
|
||||
active: ['bg-gray-500', 'text-white'],
|
||||
activeHover: ['hover:bg-gray-400']
|
||||
}
|
||||
];
|
||||
|
||||
// 设置初始按钮状态
|
||||
detailedTimeRangeButtons.forEach((button, index) => {
|
||||
const styleConfig = buttonStyles[index % buttonStyles.length];
|
||||
|
||||
// 移除所有初始样式
|
||||
button.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-gray-200', 'text-gray-700',
|
||||
'bg-green-500', 'bg-purple-500', 'bg-gray-100', 'mixed-view-active');
|
||||
|
||||
// 设置非选中状态样式
|
||||
button.classList.add('transition-colors', 'duration-200');
|
||||
button.classList.add(...styleConfig.normal);
|
||||
button.classList.add(...styleConfig.hover);
|
||||
|
||||
// 如果是第一个按钮且当前是混合视图,设置为混合视图激活状态
|
||||
if (index === 0 && detailedIsMixedView) {
|
||||
button.classList.remove(...styleConfig.normal);
|
||||
button.classList.remove(...styleConfig.hover);
|
||||
button.classList.add('active', 'mixed-view-active');
|
||||
button.classList.add(...styleConfig.active);
|
||||
button.classList.add(...styleConfig.activeHover);
|
||||
}
|
||||
});
|
||||
|
||||
detailedTimeRangeButtons.forEach((button, index) => {
|
||||
button.addEventListener('click', () => {
|
||||
// 设置当前时间范围
|
||||
const timeRange = button.dataset.range;
|
||||
const isMixedMode = timeRange === 'mixed';
|
||||
const styleConfig = buttonStyles[index % buttonStyles.length];
|
||||
|
||||
console.log('时间范围按钮被点击:', { timeRange, isMixedMode });
|
||||
// 检查是否是再次点击已选中的按钮
|
||||
const isActive = button.classList.contains('active');
|
||||
|
||||
// 更新按钮状态
|
||||
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');
|
||||
}
|
||||
// 重置所有按钮为非选中状态
|
||||
detailedTimeRangeButtons.forEach((btn, btnIndex) => {
|
||||
const btnStyle = buttonStyles[btnIndex % buttonStyles.length];
|
||||
|
||||
// 移除所有可能的激活状态类
|
||||
btn.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-green-500', 'bg-purple-500', 'bg-gray-500', 'mixed-view-active');
|
||||
btn.classList.remove(...btnStyle.active);
|
||||
btn.classList.remove(...btnStyle.activeHover);
|
||||
|
||||
// 添加非选中状态类
|
||||
btn.classList.add(...btnStyle.normal);
|
||||
btn.classList.add(...btnStyle.hover);
|
||||
});
|
||||
|
||||
// 更新详细图表专用变量
|
||||
detailedCurrentTimeRange = timeRange;
|
||||
detailedIsMixedView = isMixedMode;
|
||||
if (isActive && index < 3) { // 再次点击已选中的时间范围按钮
|
||||
// 切换到混合视图
|
||||
detailedIsMixedView = true;
|
||||
detailedCurrentTimeRange = 'mixed';
|
||||
console.log('详细图表切换到混合视图');
|
||||
|
||||
// 设置当前按钮为特殊混合视图状态
|
||||
button.classList.remove(...styleConfig.normal);
|
||||
button.classList.remove(...styleConfig.hover);
|
||||
button.classList.add('active', 'mixed-view-active');
|
||||
button.classList.add(...styleConfig.active);
|
||||
button.classList.add(...styleConfig.activeHover);
|
||||
} else {
|
||||
// 普通选中模式
|
||||
detailedIsMixedView = false;
|
||||
|
||||
// 设置当前按钮为激活状态
|
||||
button.classList.remove(...styleConfig.normal);
|
||||
button.classList.remove(...styleConfig.hover);
|
||||
button.classList.add('active');
|
||||
button.classList.add(...styleConfig.active);
|
||||
button.classList.add(...styleConfig.activeHover);
|
||||
|
||||
// 获取并更新当前时间范围
|
||||
let rangeValue;
|
||||
if (button.dataset.range) {
|
||||
rangeValue = button.dataset.range;
|
||||
} else {
|
||||
const btnText = button.textContent.trim();
|
||||
if (btnText.includes('24')) {
|
||||
rangeValue = '24h';
|
||||
} else if (btnText.includes('7')) {
|
||||
rangeValue = '7d';
|
||||
} else if (btnText.includes('30')) {
|
||||
rangeValue = '30d';
|
||||
} else {
|
||||
rangeValue = btnText.replace(/[^0-9a-zA-Z]/g, '');
|
||||
}
|
||||
}
|
||||
detailedCurrentTimeRange = rangeValue;
|
||||
console.log('详细图表更新时间范围为:', detailedCurrentTimeRange);
|
||||
}
|
||||
|
||||
// 重新绘制详细图表
|
||||
drawDetailedDNSRequestsChart();
|
||||
});
|
||||
});
|
||||
@@ -1610,17 +1728,26 @@ function drawDetailedDNSRequestsChart() {
|
||||
});
|
||||
} else {
|
||||
// 普通视图
|
||||
// 根据详细视图时间范围选择API函数
|
||||
// 根据详细视图时间范围选择API函数和对应的颜色
|
||||
let apiFunction;
|
||||
let chartColor;
|
||||
let chartFillColor;
|
||||
|
||||
switch (detailedCurrentTimeRange) {
|
||||
case '7d':
|
||||
apiFunction = (api && api.getDailyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
chartColor = '#22c55e'; // 绿色,与混合视图中的7天数据颜色一致
|
||||
chartFillColor = 'rgba(34, 197, 94, 0.1)';
|
||||
break;
|
||||
case '30d':
|
||||
apiFunction = (api && api.getMonthlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
chartColor = '#a855f7'; // 紫色,与混合视图中的30天数据颜色一致
|
||||
chartFillColor = 'rgba(168, 85, 247, 0.1)';
|
||||
break;
|
||||
default: // 24h
|
||||
apiFunction = (api && api.getHourlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
chartColor = '#3b82f6'; // 蓝色,与混合视图中的24小时数据颜色一致
|
||||
chartFillColor = 'rgba(59, 130, 246, 0.1)';
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
@@ -1631,8 +1758,8 @@ function drawDetailedDNSRequestsChart() {
|
||||
detailedDnsRequestsChart.data.datasets = [{
|
||||
label: 'DNS请求数量',
|
||||
data: data.data,
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
borderColor: chartColor,
|
||||
backgroundColor: chartFillColor,
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}];
|
||||
@@ -1650,8 +1777,8 @@ function drawDetailedDNSRequestsChart() {
|
||||
datasets: [{
|
||||
label: 'DNS请求数量',
|
||||
data: data.data,
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
borderColor: chartColor,
|
||||
backgroundColor: chartFillColor,
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
@@ -1762,6 +1889,7 @@ function drawDNSRequestsChart() {
|
||||
|
||||
// 创建或更新图表
|
||||
if (dnsRequestsChart) {
|
||||
// 使用第一个数据集的标签,但确保每个数据集使用自己的数据
|
||||
dnsRequestsChart.data.labels = results[0].labels;
|
||||
dnsRequestsChart.data.datasets = datasets;
|
||||
dnsRequestsChart.options.plugins.legend.display = showLegend;
|
||||
@@ -1815,17 +1943,26 @@ function drawDNSRequestsChart() {
|
||||
});
|
||||
} else {
|
||||
// 普通视图
|
||||
// 根据当前时间范围选择API函数
|
||||
// 根据当前时间范围选择API函数和对应的颜色
|
||||
let apiFunction;
|
||||
let chartColor;
|
||||
let chartFillColor;
|
||||
|
||||
switch (currentTimeRange) {
|
||||
case '7d':
|
||||
apiFunction = (api && api.getDailyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
chartColor = '#22c55e'; // 绿色,与混合视图中的7天数据颜色一致
|
||||
chartFillColor = 'rgba(34, 197, 94, 0.1)';
|
||||
break;
|
||||
case '30d':
|
||||
apiFunction = (api && api.getMonthlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
chartColor = '#a855f7'; // 紫色,与混合视图中的30天数据颜色一致
|
||||
chartFillColor = 'rgba(168, 85, 247, 0.1)';
|
||||
break;
|
||||
default: // 24h
|
||||
apiFunction = (api && api.getHourlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
chartColor = '#3b82f6'; // 蓝色,与混合视图中的24小时数据颜色一致
|
||||
chartFillColor = 'rgba(59, 130, 246, 0.1)';
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
@@ -1836,8 +1973,8 @@ function drawDNSRequestsChart() {
|
||||
dnsRequestsChart.data.datasets = [{
|
||||
label: 'DNS请求数量',
|
||||
data: data.data,
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
borderColor: chartColor,
|
||||
backgroundColor: chartFillColor,
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}];
|
||||
@@ -1855,8 +1992,8 @@ function drawDNSRequestsChart() {
|
||||
datasets: [{
|
||||
label: 'DNS请求数量',
|
||||
data: data.data,
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
borderColor: chartColor,
|
||||
backgroundColor: chartFillColor,
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
|
||||
Reference in New Issue
Block a user