增加显示IP地理位置的功能

This commit is contained in:
Alex Yang
2025-11-30 12:46:22 +08:00
parent dadfd4c78d
commit ed719255b7
7 changed files with 254 additions and 64 deletions

View File

@@ -942,14 +942,14 @@
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">屏蔽规则</th>
</tr>
</thead>
<tbody id="logs-table-body">
<tr>
<td colspan="5" class="py-8 text-center text-gray-500 border-b border-gray-100">
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
<div>暂无查询日志</div>
</td>
</tr>
</tbody>
<tbody id="logs-table-body">
<tr>
<td colspan="5" class="py-8 text-center text-gray-500 border-b border-gray-100">
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
<div>暂无查询日志</div>
</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -6,8 +6,8 @@ let dnsRequestsChart = null;
let detailedDnsRequestsChart = null; // 详细DNS请求趋势图表(浮窗)
let queryTypeChart = null; // 解析类型统计饼图
let intervalId = null;
let wsConnection = null;
let wsReconnectTimer = null;
let dashboardWsConnection = null;
let dashboardWsReconnectTimer = null;
// 存储统计卡片图表实例
let statCardCharts = {};
// 存储统计卡片历史数据
@@ -53,22 +53,22 @@ function connectWebSocket() {
console.log('正在连接WebSocket:', wsUrl);
// 创建WebSocket连接
wsConnection = new WebSocket(wsUrl);
dashboardWsConnection = new WebSocket(wsUrl);
// 连接打开事件
wsConnection.onopen = function() {
dashboardWsConnection.onopen = function() {
console.log('WebSocket连接已建立');
showNotification('数据更新成功', 'success');
// 清除重连计时器
if (wsReconnectTimer) {
clearTimeout(wsReconnectTimer);
wsReconnectTimer = null;
if (dashboardWsReconnectTimer) {
clearTimeout(dashboardWsReconnectTimer);
dashboardWsReconnectTimer = null;
}
};
// 接收消息事件
wsConnection.onmessage = function(event) {
dashboardWsConnection.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
@@ -82,16 +82,16 @@ function connectWebSocket() {
};
// 连接关闭事件
wsConnection.onclose = function(event) {
dashboardWsConnection.onclose = function(event) {
console.warn('WebSocket连接已关闭代码:', event.code);
wsConnection = null;
dashboardWsConnection = null;
// 设置重连
setupReconnect();
};
// 连接错误事件
wsConnection.onerror = function(error) {
dashboardWsConnection.onerror = function(error) {
console.error('WebSocket连接错误:', error);
};
@@ -104,14 +104,14 @@ function connectWebSocket() {
// 设置重连逻辑
function setupReconnect() {
if (wsReconnectTimer) {
if (dashboardWsReconnectTimer) {
return; // 已经有重连计时器在运行
}
const reconnectDelay = 5000; // 5秒后重连
console.log(`将在${reconnectDelay}ms后尝试重新连接WebSocket`);
wsReconnectTimer = setTimeout(() => {
dashboardWsReconnectTimer = setTimeout(() => {
connectWebSocket();
}, reconnectDelay);
}
@@ -362,15 +362,15 @@ function fallbackToIntervalRefresh() {
// 清理资源
function cleanupResources() {
// 清除WebSocket连接
if (wsConnection) {
wsConnection.close();
wsConnection = null;
if (dashboardWsConnection) {
dashboardWsConnection.close();
dashboardWsConnection = null;
}
// 清除重连计时器
if (wsReconnectTimer) {
clearTimeout(wsReconnectTimer);
wsReconnectTimer = null;
if (dashboardWsReconnectTimer) {
clearTimeout(dashboardWsReconnectTimer);
dashboardWsReconnectTimer = null;
}
// 清除定时刷新

View File

@@ -10,6 +10,14 @@ let logsChart = null;
let currentSortField = '';
let currentSortDirection = 'desc'; // 默认降序
// IP地理位置缓存
let ipGeolocationCache = {};
const GEOLOCATION_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 缓存有效期24小时
// WebSocket连接和重连计时器
let logsWsConnection = null;
let logsWsReconnectTimer = null;
// 初始化查询日志页面
function initLogsPage() {
console.log('初始化查询日志页面');
@@ -36,15 +44,15 @@ function initLogsPage() {
// 清理资源
function cleanupLogsResources() {
// 清除WebSocket连接
if (wsConnection) {
wsConnection.close();
wsConnection = null;
if (logsWsConnection) {
logsWsConnection.close();
logsWsConnection = null;
}
// 清除重连计时器
if (wsReconnectTimer) {
clearTimeout(wsReconnectTimer);
wsReconnectTimer = null;
if (logsWsReconnectTimer) {
clearTimeout(logsWsReconnectTimer);
logsWsReconnectTimer = null;
}
}
@@ -190,9 +198,14 @@ function updateSortIcons() {
// 加载日志统计数据
function loadLogsStats() {
fetch('/api/logs/stats')
.then(response => response.json())
// 使用封装的apiRequest函数进行API调用
apiRequest('/logs/stats')
.then(data => {
if (data && data.error) {
console.error('加载日志统计数据失败:', data.error);
return;
}
// 更新统计卡片
document.getElementById('logs-total-queries').textContent = data.totalQueries;
document.getElementById('logs-avg-response-time').textContent = data.avgResponseTime.toFixed(2) + 'ms';
@@ -216,32 +229,50 @@ function loadLogs() {
}
// 构建请求URL
let url = `/api/logs/query?limit=${logsPerPage}&offset=${(currentPage - 1) * logsPerPage}`;
let endpoint = `/logs/query?limit=${logsPerPage}&offset=${(currentPage - 1) * logsPerPage}`;
// 添加过滤条件
if (currentFilter) {
url += `&result=${currentFilter}`;
endpoint += `&result=${currentFilter}`;
}
// 添加搜索条件
if (currentSearch) {
url += `&search=${encodeURIComponent(currentSearch)}`;
endpoint += `&search=${encodeURIComponent(currentSearch)}`;
}
// 添加排序条件
if (currentSortField) {
url += `&sort=${currentSortField}&direction=${currentSortDirection}`;
endpoint += `&sort=${currentSortField}&direction=${currentSortDirection}`;
}
fetch(url)
.then(response => response.json())
// 使用封装的apiRequest函数进行API调用
apiRequest(endpoint)
.then(data => {
if (data && data.error) {
console.error('加载日志详情失败:', data.error);
// 隐藏加载状态
if (loadingEl) {
loadingEl.classList.add('hidden');
}
return;
}
// 加载日志总数
return fetch('/api/logs/count').then(response => response.json()).then(countData => {
return apiRequest('/logs/count').then(countData => {
return { logs: data, count: countData.count };
});
})
.then(result => {
if (!result || !result.logs) {
console.error('加载日志详情失败: 无效的响应数据');
// 隐藏加载状态
if (loadingEl) {
loadingEl.classList.add('hidden');
}
return;
}
const logs = result.logs;
const totalLogs = result.count;
@@ -358,7 +389,10 @@ function updateLogsTable(logs) {
<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">
<div class="font-medium">${log.ClientIP}</div>
<div class="text-xs text-gray-500 mt-1">${log.Location || '未知 未知'}</div>
</td>
<td class="py-3 px-4 text-sm">
<div class="font-medium">${log.Domain}</div>
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span></div>
@@ -396,9 +430,13 @@ function initLogsChart() {
if (!ctx) return;
// 获取24小时统计数据
fetch('/api/hourly-stats')
.then(response => response.json())
apiRequest('/hourly-stats')
.then(data => {
if (data && data.error) {
console.error('初始化日志图表失败:', data.error);
return;
}
// 创建图表
logsChart = new Chart(ctx, {
type: 'line',
@@ -446,24 +484,29 @@ function initLogsChart() {
function updateLogsChart(range) {
if (!logsChart) return;
let url = '';
let endpoint = '';
switch (range) {
case '24h':
url = '/api/hourly-stats';
endpoint = '/hourly-stats';
break;
case '7d':
url = '/api/daily-stats';
endpoint = '/daily-stats';
break;
case '30d':
url = '/api/monthly-stats';
endpoint = '/monthly-stats';
break;
default:
url = '/api/hourly-stats';
endpoint = '/hourly-stats';
}
fetch(url)
.then(response => response.json())
// 使用封装的apiRequest函数进行API调用
apiRequest(endpoint)
.then(data => {
if (data && data.error) {
console.error('更新日志图表失败:', data.error);
return;
}
// 更新图表数据
logsChart.data.labels = data.labels;
logsChart.data.datasets[0].data = data.data;
@@ -484,15 +527,15 @@ function connectLogsWebSocket() {
console.log('正在连接WebSocket:', wsUrl);
// 创建WebSocket连接
wsConnection = new WebSocket(wsUrl);
logsWsConnection = new WebSocket(wsUrl);
// 连接打开事件
wsConnection.onopen = function() {
logsWsConnection.onopen = function() {
console.log('WebSocket连接已建立');
};
// 接收消息事件
wsConnection.onmessage = function(event) {
logsWsConnection.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
@@ -507,16 +550,16 @@ function connectLogsWebSocket() {
};
// 连接关闭事件
wsConnection.onclose = function(event) {
logsWsConnection.onclose = function(event) {
console.warn('WebSocket连接已关闭代码:', event.code);
wsConnection = null;
logsWsConnection = null;
// 设置重连
setupLogsReconnect();
};
// 连接错误事件
wsConnection.onerror = function(error) {
logsWsConnection.onerror = function(error) {
console.error('WebSocket连接错误:', error);
};
@@ -527,14 +570,14 @@ function connectLogsWebSocket() {
// 设置重连逻辑
function setupLogsReconnect() {
if (wsReconnectTimer) {
if (logsWsReconnectTimer) {
return; // 已经有重连计时器在运行
}
const reconnectDelay = 5000; // 5秒后重连
console.log(`将在${reconnectDelay}ms后尝试重新连接WebSocket`);
wsReconnectTimer = setTimeout(() => {
logsWsReconnectTimer = setTimeout(() => {
connectLogsWebSocket();
}, reconnectDelay);
}

View File

@@ -161,7 +161,11 @@
})
.then(response => {
if (!response.ok) {
throw new Error('登录失败');
if (response.status === 401) {
throw new Error('未知用户名或密码');
} else {
throw new Error('登录失败');
}
}
return response.json();
})
@@ -170,7 +174,11 @@
// 登录成功,重定向到主页
window.location.href = '/';
} else {
throw new Error(data.error || '登录失败');
if (data.error === '用户名或密码错误') {
throw new Error('未知用户名或密码');
} else {
throw new Error(data.error || '登录失败');
}
}
})
.catch(error => {