优化日志查询页面

This commit is contained in:
Alex Yang
2025-11-30 03:24:20 +08:00
parent fb9a62f2a7
commit 9fffac0fb7
5 changed files with 16675 additions and 37 deletions

View File

@@ -638,7 +638,7 @@ func (s *Server) GetStats() *Stats {
}
// GetQueryLogs 获取查询日志
func (s *Server) GetQueryLogs(limit, offset int) []QueryLog {
func (s *Server) GetQueryLogs(limit, offset int, sortField, sortDirection string) []QueryLog {
s.queryLogsMutex.RLock()
defer s.queryLogsMutex.RUnlock()
@@ -650,17 +650,90 @@ func (s *Server) GetQueryLogs(limit, offset int) []QueryLog {
limit = 100 // 默认返回100条日志
}
// 创建日志副本用于排序
logsCopy := make([]QueryLog, len(s.queryLogs))
copy(logsCopy, s.queryLogs)
// 排序日志
if sortField != "" {
sort.Slice(logsCopy, func(i, j int) bool {
var a, b interface{}
switch sortField {
case "time":
a = logsCopy[i].Timestamp
b = logsCopy[j].Timestamp
case "clientIp":
a = logsCopy[i].ClientIP
b = logsCopy[j].ClientIP
case "domain":
a = logsCopy[i].Domain
b = logsCopy[j].Domain
case "responseTime":
a = logsCopy[i].ResponseTime
b = logsCopy[j].ResponseTime
case "blockRule":
a = logsCopy[i].BlockRule
b = logsCopy[j].BlockRule
default:
// 默认按时间排序
a = logsCopy[i].Timestamp
b = logsCopy[j].Timestamp
}
// 根据排序方向比较
if sortDirection == "asc" {
return compareValues(a, b) < 0
}
return compareValues(a, b) > 0
})
}
// 计算返回范围
start := offset
end := offset + limit
if end > len(s.queryLogs) {
end = len(s.queryLogs)
if end > len(logsCopy) {
end = len(logsCopy)
}
if start >= len(s.queryLogs) {
if start >= len(logsCopy) {
return []QueryLog{} // 没有数据,返回空切片
}
return s.queryLogs[start:end]
return logsCopy[start:end]
}
// compareValues 比较两个值
func compareValues(a, b interface{}) int {
switch v1 := a.(type) {
case time.Time:
v2 := b.(time.Time)
if v1.Before(v2) {
return -1
}
if v1.After(v2) {
return 1
}
return 0
case string:
v2 := b.(string)
if v1 < v2 {
return -1
}
if v1 > v2 {
return 1
}
return 0
case int64:
v2 := b.(int64)
if v1 < v2 {
return -1
}
if v1 > v2 {
return 1
}
return 0
default:
return 0
}
}
// GetQueryLogsCount 获取查询日志总数

View File

@@ -1245,6 +1245,8 @@ func (s *Server) handleLogsQuery(w http.ResponseWriter, r *http.Request) {
// 获取查询参数
limit := 100 // 默认返回100条日志
offset := 0
sortField := r.URL.Query().Get("sort")
sortDirection := r.URL.Query().Get("direction")
if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
fmt.Sscanf(limitStr, "%d", &limit)
@@ -1255,7 +1257,7 @@ func (s *Server) handleLogsQuery(w http.ResponseWriter, r *http.Request) {
}
// 获取日志数据
logs := s.dnsServer.GetQueryLogs(limit, offset)
logs := s.dnsServer.GetQueryLogs(limit, offset, sortField, sortDirection)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(logs)

16466
logs/dns-server.log Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -902,11 +902,24 @@
<table class="min-w-full">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">时间</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">客户端IP</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">域名</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">查询类型</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">结果</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="time">
<div class="flex items-center">
时间
<i class="fa fa-sort ml-1 text-xs"></i>
</div>
</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="clientIp">
<div class="flex items-center">
客户端IP
<i class="fa fa-sort ml-1 text-xs"></i>
</div>
</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="domain">
<div class="flex items-center">
请求
<i class="fa fa-sort ml-1 text-xs"></i>
</div>
</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">响应时间</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">屏蔽规则</th>
</tr>

View File

@@ -7,6 +7,8 @@ let logsPerPage = 30; // 默认显示30条记录
let currentFilter = '';
let currentSearch = '';
let logsChart = null;
let currentSortField = '';
let currentSortDirection = 'desc'; // 默认降序
// 初始化查询日志页面
function initLogsPage() {
@@ -129,6 +131,51 @@ function bindLogsEvents() {
updateLogsChart(range);
});
});
// 排序按钮事件
const sortHeaders = document.querySelectorAll('th[data-sort]');
sortHeaders.forEach(header => {
header.addEventListener('click', () => {
const sortField = header.getAttribute('data-sort');
// 如果点击的是当前排序字段,则切换排序方向
if (sortField === currentSortField) {
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
} else {
// 否则,设置新的排序字段,默认降序
currentSortField = sortField;
currentSortDirection = 'desc';
}
// 更新排序图标
updateSortIcons();
// 重新加载日志
currentPage = 1;
loadLogs();
});
});
}
// 更新排序图标
function updateSortIcons() {
const sortHeaders = document.querySelectorAll('th[data-sort]');
sortHeaders.forEach(header => {
const sortField = header.getAttribute('data-sort');
const icon = header.querySelector('i');
// 重置所有图标
icon.className = 'fa fa-sort ml-1 text-xs';
// 设置当前排序字段的图标
if (sortField === currentSortField) {
if (currentSortDirection === 'asc') {
icon.className = 'fa fa-sort-asc ml-1 text-xs';
} else {
icon.className = 'fa fa-sort-desc ml-1 text-xs';
}
}
});
}
// 加载日志统计数据
@@ -171,6 +218,11 @@ function loadLogs() {
url += `&search=${encodeURIComponent(currentSearch)}`;
}
// 添加排序条件
if (currentSortField) {
url += `&sort=${currentSortField}&direction=${currentSortDirection}`;
}
fetch(url)
.then(response => response.json())
.then(data => {
@@ -219,7 +271,7 @@ function updateLogsTable(logs) {
// 显示空状态
const emptyRow = document.createElement('tr');
emptyRow.innerHTML = `
<td colspan="7" class="py-8 text-center text-gray-500">
<td colspan="5" class="py-8 text-center text-gray-500">
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
<div>暂无查询日志</div>
</td>
@@ -233,42 +285,74 @@ function updateLogsTable(logs) {
const row = document.createElement('tr');
row.className = 'border-b border-gray-100 hover:bg-gray-50 transition-colors';
// 格式化时间
// 格式化时间 - 两行显示,第一行显示时间,第二行显示日期
const time = new Date(log.Timestamp);
const formattedTime = time.toLocaleString('zh-CN', {
const formattedDate = time.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
day: '2-digit'
});
const formattedTime = time.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// 结果样式
let resultClass = '';
let resultText = '';
// 根据结果添加不同的背景色
let rowClass = '';
switch (log.Result) {
case 'allowed':
resultClass = 'text-success';
resultText = '允许';
break;
case 'blocked':
resultClass = 'text-danger';
resultText = '屏蔽';
rowClass = 'bg-red-50'; // 淡红色填充
break;
case 'error':
resultClass = 'text-warning';
resultText = '错误';
case 'allowed':
// 检查是否是规则允许项目
if (log.BlockRule && log.BlockRule.includes('allow')) {
rowClass = 'bg-green-50'; // 规则允许项目用淡绿色填充
} else {
rowClass = ''; // 允许的不填充
}
break;
default:
rowClass = '';
}
// 构建行内容
// 添加行背景色
if (rowClass) {
row.classList.add(rowClass);
}
// 添加被屏蔽或允许显示,并增加颜色
let statusText = '';
let statusClass = '';
switch (log.Result) {
case 'blocked':
statusText = '被屏蔽';
statusClass = 'text-danger';
break;
case 'allowed':
statusText = '允许';
statusClass = 'text-success';
break;
case 'error':
statusText = '错误';
statusClass = 'text-warning';
break;
default:
statusText = '';
statusClass = '';
}
// 构建行内容 - 两行显示,时间列显示时间和日期,请求列显示域名和类型状态
row.innerHTML = `
<td class="py-3 px-4 text-sm">${formattedTime}</td>
<td class="py-3 px-4">
<div class="text-sm font-medium">${formattedTime}</div>
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
</td>
<td class="py-3 px-4 text-sm">${log.ClientIP}</td>
<td class="py-3 px-4 text-sm font-medium">${log.Domain}</td>
<td class="py-3 px-4 text-sm">${log.QueryType}</td>
<td class="py-3 px-4 text-sm"><span class="${resultClass}">${resultText}</span></td>
<td class="py-3 px-4">
<div class="text-sm font-medium">${log.Domain}</div>
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span></div>
</td>
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>
`;