@@ -860,6 +1028,7 @@
+
diff --git a/static/js/logs.js b/static/js/logs.js
new file mode 100644
index 0000000..74b5229
--- /dev/null
+++ b/static/js/logs.js
@@ -0,0 +1,359 @@
+// logs.js - 查询日志页面功能
+
+// 全局变量
+let currentPage = 1;
+let totalPages = 1;
+let logsPerPage = 20;
+let currentFilter = '';
+let currentSearch = '';
+let logsChart = null;
+
+// 初始化查询日志页面
+function initLogsPage() {
+ console.log('初始化查询日志页面');
+
+ // 加载日志统计数据
+ loadLogsStats();
+
+ // 加载日志详情
+ loadLogs();
+
+ // 初始化图表
+ initLogsChart();
+
+ // 绑定事件
+ bindLogsEvents();
+}
+
+// 绑定事件
+function bindLogsEvents() {
+ // 搜索按钮
+ const searchBtn = document.getElementById('logs-search-btn');
+ if (searchBtn) {
+ searchBtn.addEventListener('click', () => {
+ currentSearch = document.getElementById('logs-search').value;
+ currentPage = 1;
+ loadLogs();
+ });
+ }
+
+ // 搜索框回车事件
+ const searchInput = document.getElementById('logs-search');
+ if (searchInput) {
+ searchInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ currentSearch = searchInput.value;
+ currentPage = 1;
+ loadLogs();
+ }
+ });
+ }
+
+ // 结果过滤
+ const resultFilter = document.getElementById('logs-result-filter');
+ if (resultFilter) {
+ resultFilter.addEventListener('change', () => {
+ currentFilter = resultFilter.value;
+ currentPage = 1;
+ loadLogs();
+ });
+ }
+
+ // 分页按钮
+ const prevBtn = document.getElementById('logs-prev-page');
+ const nextBtn = document.getElementById('logs-next-page');
+
+ if (prevBtn) {
+ prevBtn.addEventListener('click', () => {
+ if (currentPage > 1) {
+ currentPage--;
+ loadLogs();
+ }
+ });
+ }
+
+ if (nextBtn) {
+ nextBtn.addEventListener('click', () => {
+ if (currentPage < totalPages) {
+ currentPage++;
+ loadLogs();
+ }
+ });
+ }
+
+ // 时间范围切换
+ const timeRangeBtns = document.querySelectorAll('.time-range-btn');
+ timeRangeBtns.forEach(btn => {
+ btn.addEventListener('click', () => {
+ // 更新按钮样式
+ timeRangeBtns.forEach(b => {
+ b.classList.remove('bg-primary', 'text-white');
+ b.classList.add('bg-gray-200', 'text-gray-700');
+ });
+ btn.classList.remove('bg-gray-200', 'text-gray-700');
+ btn.classList.add('bg-primary', 'text-white');
+
+ // 更新图表
+ const range = btn.getAttribute('data-range');
+ updateLogsChart(range);
+ });
+ });
+}
+
+// 加载日志统计数据
+function loadLogsStats() {
+ fetch('/api/logs/stats')
+ .then(response => response.json())
+ .then(data => {
+ // 更新统计卡片
+ document.getElementById('logs-total-queries').textContent = data.totalQueries;
+ document.getElementById('logs-avg-response-time').textContent = data.avgResponseTime.toFixed(2) + 'ms';
+ document.getElementById('logs-active-ips').textContent = data.activeIPs;
+
+ // 计算屏蔽率
+ const blockRate = data.totalQueries > 0 ? (data.blockedQueries / data.totalQueries * 100).toFixed(1) : '0';
+ document.getElementById('logs-block-rate').textContent = blockRate + '%';
+ })
+ .catch(error => {
+ console.error('加载日志统计数据失败:', error);
+ });
+}
+
+// 加载日志详情
+function loadLogs() {
+ // 显示加载状态
+ const loadingEl = document.getElementById('logs-loading');
+ if (loadingEl) {
+ loadingEl.classList.remove('hidden');
+ }
+
+ // 构建请求URL
+ let url = `/api/logs/query?limit=${logsPerPage}&offset=${(currentPage - 1) * logsPerPage}`;
+
+ // 添加过滤条件
+ if (currentFilter) {
+ url += `&result=${currentFilter}`;
+ }
+
+ // 添加搜索条件
+ if (currentSearch) {
+ url += `&search=${encodeURIComponent(currentSearch)}`;
+ }
+
+ fetch(url)
+ .then(response => response.json())
+ .then(data => {
+ // 加载日志总数
+ return fetch('/api/logs/count').then(response => response.json()).then(countData => {
+ return { logs: data, count: countData.count };
+ });
+ })
+ .then(result => {
+ const logs = result.logs;
+ const totalLogs = result.count;
+
+ // 计算总页数
+ totalPages = Math.ceil(totalLogs / logsPerPage);
+
+ // 更新日志表格
+ updateLogsTable(logs);
+
+ // 更新分页信息
+ updateLogsPagination();
+
+ // 隐藏加载状态
+ if (loadingEl) {
+ loadingEl.classList.add('hidden');
+ }
+ })
+ .catch(error => {
+ console.error('加载日志详情失败:', error);
+
+ // 隐藏加载状态
+ if (loadingEl) {
+ loadingEl.classList.add('hidden');
+ }
+ });
+}
+
+// 更新日志表格
+function updateLogsTable(logs) {
+ const tableBody = document.getElementById('logs-table-body');
+ if (!tableBody) return;
+
+ // 清空表格
+ tableBody.innerHTML = '';
+
+ if (logs.length === 0) {
+ // 显示空状态
+ const emptyRow = document.createElement('tr');
+ emptyRow.innerHTML = `
+
+
+ 暂无查询日志
+ |
+ `;
+ tableBody.appendChild(emptyRow);
+ return;
+ }
+
+ // 填充表格
+ logs.forEach(log => {
+ 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', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ });
+
+ // 结果样式
+ let resultClass = '';
+ let resultText = '';
+ switch (log.Result) {
+ case 'allowed':
+ resultClass = 'text-success';
+ resultText = '允许';
+ break;
+ case 'blocked':
+ resultClass = 'text-danger';
+ resultText = '屏蔽';
+ break;
+ case 'error':
+ resultClass = 'text-warning';
+ resultText = '错误';
+ break;
+ }
+
+ // 构建行内容
+ row.innerHTML = `
+
${formattedTime} |
+
${log.ClientIP} |
+
${log.Domain} |
+
${log.QueryType} |
+
${resultText} |
+
${log.ResponseTime}ms |
+
${log.BlockRule || '-'} |
+ `;
+
+ tableBody.appendChild(row);
+ });
+}
+
+// 更新分页信息
+function updateLogsPagination() {
+ // 更新页码显示
+ document.getElementById('logs-current-page').textContent = currentPage;
+ document.getElementById('logs-total-pages').textContent = totalPages;
+
+ // 更新按钮状态
+ const prevBtn = document.getElementById('logs-prev-page');
+ const nextBtn = document.getElementById('logs-next-page');
+
+ if (prevBtn) {
+ prevBtn.disabled = currentPage === 1;
+ }
+
+ if (nextBtn) {
+ nextBtn.disabled = currentPage === totalPages;
+ }
+}
+
+// 初始化日志图表
+function initLogsChart() {
+ const ctx = document.getElementById('logs-trend-chart');
+ if (!ctx) return;
+
+ // 获取24小时统计数据
+ fetch('/api/hourly-stats')
+ .then(response => response.json())
+ .then(data => {
+ // 创建图表
+ logsChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: data.labels,
+ datasets: [{
+ label: '查询数',
+ data: data.data,
+ borderColor: '#3b82f6',
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
+ tension: 0.4,
+ fill: true
+ }]
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ display: true,
+ position: 'top'
+ },
+ tooltip: {
+ mode: 'index',
+ intersect: false
+ }
+ },
+ scales: {
+ y: {
+ beginAtZero: true,
+ ticks: {
+ precision: 0
+ }
+ }
+ }
+ }
+ });
+ })
+ .catch(error => {
+ console.error('初始化日志图表失败:', error);
+ });
+}
+
+// 更新日志图表
+function updateLogsChart(range) {
+ if (!logsChart) return;
+
+ let url = '';
+ switch (range) {
+ case '24h':
+ url = '/api/hourly-stats';
+ break;
+ case '7d':
+ url = '/api/daily-stats';
+ break;
+ case '30d':
+ url = '/api/monthly-stats';
+ break;
+ default:
+ url = '/api/hourly-stats';
+ }
+
+ fetch(url)
+ .then(response => response.json())
+ .then(data => {
+ // 更新图表数据
+ logsChart.data.labels = data.labels;
+ logsChart.data.datasets[0].data = data.data;
+ logsChart.update();
+ })
+ .catch(error => {
+ console.error('更新日志图表失败:', error);
+ });
+}
+
+// 定期更新日志数据
+setInterval(() => {
+ // 只有在查询日志页面时才更新
+ if (window.location.hash === '#logs') {
+ loadLogsStats();
+ loadLogs();
+ }
+}, 30000); // 每30秒更新一次
diff --git a/static/js/main.js b/static/js/main.js
index 2f6bb6e..7f8760f 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -9,6 +9,7 @@ function setupNavigation() {
document.getElementById('shield-content'),
document.getElementById('hosts-content'),
document.getElementById('query-content'),
+ document.getElementById('logs-content'),
document.getElementById('config-content')
];
const pageTitle = document.getElementById('page-title');
@@ -28,6 +29,8 @@ function setupNavigation() {
initShieldPage();
} else if (target === 'hosts' && typeof initHostsPage === 'function') {
initHostsPage();
+ } else if (target === 'logs' && typeof initLogsPage === 'function') {
+ initLogsPage();
}
});
});