解析与屏蔽比例
diff --git a/static/js/api.js b/static/js/api.js
index 7cf5911..12b42cb 100644
--- a/static/js/api.js
+++ b/static/js/api.js
@@ -10,7 +10,10 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
method,
headers: {
'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
+ 'Pragma': 'no-cache',
},
+ credentials: 'same-origin',
};
if (data) {
@@ -20,12 +23,60 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
try {
const response = await fetch(url, options);
+ // 获取响应文本,用于调试和错误处理
+ const responseText = await response.text();
+
if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(errorData.error || `请求失败: ${response.status}`);
+ // 尝试解析错误响应
+ let errorData = {};
+ try {
+ // 首先检查响应文本是否为空或不是有效JSON
+ if (!responseText || responseText.trim() === '') {
+ console.warn('错误响应为空');
+ } else {
+ try {
+ errorData = JSON.parse(responseText);
+ } catch (parseError) {
+ console.error('无法解析错误响应为JSON:', parseError);
+ console.error('原始错误响应文本:', responseText);
+ }
+ }
+ // 直接返回错误信息,而不是抛出异常,让上层处理
+ console.warn(`API请求失败: ${response.status}`, errorData);
+ return { error: errorData.error || `请求失败: ${response.status}` };
+ } catch (e) {
+ console.error('处理错误响应时出错:', e);
+ return { error: `请求处理失败: ${e.message}` };
+ }
}
- return await response.json();
+ // 尝试解析成功响应
+ try {
+ // 首先检查响应文本是否为空
+ if (!responseText || responseText.trim() === '') {
+ console.warn('空响应文本');
+ return {};
+ }
+
+ // 尝试解析JSON
+ const parsedData = JSON.parse(responseText);
+ return parsedData;
+ } catch (parseError) {
+ // 详细记录错误信息和响应内容
+ console.error('JSON解析错误:', parseError);
+ console.error('原始响应文本:', responseText);
+ console.error('响应长度:', responseText.length);
+ console.error('响应前100字符:', responseText.substring(0, 100));
+
+ // 如果是位置66附近的错误,特别标记
+ if (parseError.message.includes('position 66')) {
+ console.error('位置66附近的字符:', responseText.substring(60, 75));
+ }
+
+ // 返回空数组作为默认值,避免页面功能完全中断
+ console.warn('使用默认空数组作为响应');
+ return [];
+ }
} catch (error) {
console.error('API请求错误:', error);
throw error;
@@ -35,52 +86,82 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
// API方法集合
const api = {
// 获取统计信息
- getStats: () => apiRequest('/stats'),
+ getStats: () => apiRequest('/stats?t=' + Date.now()),
// 获取系统状态
- getStatus: () => apiRequest('/status'),
+ getStatus: () => apiRequest('/status?t=' + Date.now()),
// 获取Top屏蔽域名
- getTopBlockedDomains: () => apiRequest('/top-blocked'),
+ getTopBlockedDomains: () => apiRequest('/top-blocked?t=' + Date.now()),
// 获取Top解析域名
- getTopResolvedDomains: () => apiRequest('/top-resolved'),
+ getTopResolvedDomains: () => apiRequest('/top-resolved?t=' + Date.now()),
// 获取最近屏蔽域名
- getRecentBlockedDomains: () => apiRequest('/recent-blocked'),
+ getRecentBlockedDomains: () => apiRequest('/recent-blocked?t=' + Date.now()),
// 获取小时统计
- getHourlyStats: () => apiRequest('/hourly-stats'),
+ getHourlyStats: () => apiRequest('/hourly-stats?t=' + Date.now()),
- // 获取屏蔽规则
- getShieldRules: () => apiRequest('/shield'),
+ // 获取屏蔽规则 - 已禁用
+ getShieldRules: () => {
+ console.log('屏蔽规则功能已禁用');
+ return Promise.resolve({}); // 返回空对象而非API调用
+ },
- // 添加屏蔽规则
- addShieldRule: (rule) => apiRequest('/shield', 'POST', { rule }),
+ // 添加屏蔽规则 - 已禁用
+ addShieldRule: (rule) => {
+ console.log('屏蔽规则功能已禁用');
+ return Promise.resolve({ error: '屏蔽规则功能已禁用' });
+ },
- // 删除屏蔽规则
- deleteShieldRule: (rule) => apiRequest('/shield', 'DELETE', { rule }),
+ // 删除屏蔽规则 - 已禁用
+ deleteShieldRule: (rule) => {
+ console.log('屏蔽规则功能已禁用');
+ return Promise.resolve({ error: '屏蔽规则功能已禁用' });
+ },
- // 更新远程规则
- updateRemoteRules: () => apiRequest('/shield', 'PUT', { action: 'update' }),
+ // 更新远程规则 - 已禁用
+ updateRemoteRules: () => {
+ console.log('屏蔽规则功能已禁用');
+ return Promise.resolve({ error: '屏蔽规则功能已禁用' });
+ },
- // 获取黑名单列表
- getBlacklists: () => apiRequest('/shield/blacklists'),
+ // 获取黑名单列表 - 已禁用
+ getBlacklists: () => {
+ console.log('屏蔽规则相关功能已禁用');
+ return Promise.resolve([]); // 返回空数组而非API调用
+ },
- // 添加黑名单
- addBlacklist: (url) => apiRequest('/shield/blacklists', 'POST', { url }),
+ // 添加黑名单 - 已禁用
+ addBlacklist: (url) => {
+ console.log('屏蔽规则相关功能已禁用');
+ return Promise.resolve({ error: '屏蔽规则功能已禁用' });
+ },
- // 删除黑名单
- deleteBlacklist: (url) => apiRequest('/shield/blacklists', 'DELETE', { url }),
+ // 删除黑名单 - 已禁用
+ deleteBlacklist: (url) => {
+ console.log('屏蔽规则相关功能已禁用');
+ return Promise.resolve({ error: '屏蔽规则功能已禁用' });
+ },
- // 获取Hosts内容
- getHosts: () => apiRequest('/shield/hosts'),
+ // 获取Hosts内容 - 已禁用
+ getHosts: () => {
+ console.log('屏蔽规则相关功能已禁用');
+ return Promise.resolve({ content: '' }); // 返回空内容而非API调用
+ },
- // 保存Hosts内容
- saveHosts: (content) => apiRequest('/shield/hosts', 'POST', { content }),
+ // 保存Hosts内容 - 已禁用
+ saveHosts: (content) => {
+ console.log('屏蔽规则相关功能已禁用');
+ return Promise.resolve({ error: '屏蔽规则功能已禁用' });
+ },
- // 刷新Hosts
- refreshHosts: () => apiRequest('/shield/hosts', 'PUT', { action: 'refresh' }),
+ // 刷新Hosts - 已禁用
+ refreshHosts: () => {
+ console.log('屏蔽规则相关功能已禁用');
+ return Promise.resolve({ error: '屏蔽规则功能已禁用' });
+ },
// 查询DNS记录 - 兼容多种参数格式
queryDNS: async function(domain, recordType) {
diff --git a/static/js/dashboard.js b/static/js/dashboard.js
index 03eea8c..5a30995 100644
--- a/static/js/dashboard.js
+++ b/static/js/dashboard.js
@@ -1,7 +1,6 @@
// dashboard.js - 仪表盘功能实现
// 全局变量
-let queryTrendChart = null;
let ratioChart = null;
let intervalId = null;
@@ -24,39 +23,49 @@ async function initDashboard() {
// 加载仪表盘数据
async function loadDashboardData() {
+ console.log('开始加载仪表盘数据');
try {
- console.log('开始加载仪表盘数据...');
-
- // 先分别获取数据以调试
+ // 获取基本统计数据
const stats = await api.getStats();
console.log('统计数据:', stats);
+ // 获取TOP被屏蔽域名
const topBlockedDomains = await api.getTopBlockedDomains();
- console.log('Top屏蔽域名:', topBlockedDomains);
+ console.log('TOP被屏蔽域名:', topBlockedDomains);
+ // 获取最近屏蔽域名
const recentBlockedDomains = await api.getRecentBlockedDomains();
console.log('最近屏蔽域名:', recentBlockedDomains);
- const hourlyStats = await api.getHourlyStats();
- console.log('小时统计数据:', hourlyStats);
-
// 原并行请求方式(保留以备后续恢复)
- // const [stats, topBlockedDomains, recentBlockedDomains, hourlyStats] = await Promise.all([
+ // const [stats, topBlockedDomains, recentBlockedDomains] = await Promise.all([
// api.getStats(),
// api.getTopBlockedDomains(),
- // api.getRecentBlockedDomains(),
- // api.getHourlyStats()
+ // api.getRecentBlockedDomains()
// ]);
// 更新统计卡片
updateStatsCards(stats);
- // 更新数据表格
+ // 尝试从stats中获取总查询数等信息
+ if (stats.dns) {
+ totalQueries = stats.dns.Allowed + stats.dns.Blocked + (stats.dns.Errors || 0);
+ blockedQueries = stats.dns.Blocked;
+ errorQueries = stats.dns.Errors || 0;
+ allowedQueries = stats.dns.Allowed;
+ } else {
+ totalQueries = stats.totalQueries || 0;
+ blockedQueries = stats.blockedQueries || 0;
+ errorQueries = stats.errorQueries || 0;
+ allowedQueries = stats.allowedQueries || 0;
+ }
+
+ // 更新表格
updateTopBlockedTable(topBlockedDomains);
updateRecentBlockedTable(recentBlockedDomains);
// 更新图表
- updateCharts(stats, hourlyStats);
+ updateCharts({totalQueries, blockedQueries, allowedQueries, errorQueries});
// 更新运行状态
updateUptime();
@@ -92,13 +101,6 @@ function updateStatsCards(stats) {
blockedQueries = stats[0].blocked || 0;
allowedQueries = stats[0].allowed || 0;
errorQueries = stats[0].error || 0;
- } else {
- // 如果都不匹配,使用一些示例数据以便在界面上显示
- totalQueries = 12500;
- blockedQueries = 1500;
- allowedQueries = 10500;
- errorQueries = 500;
- console.log('使用示例数据填充统计卡片');
}
// 更新数量显示
@@ -107,11 +109,18 @@ function updateStatsCards(stats) {
document.getElementById('allowed-queries').textContent = formatNumber(allowedQueries);
document.getElementById('error-queries').textContent = formatNumber(errorQueries);
- // 更新百分比(模拟数据,实际应该从API获取)
- document.getElementById('queries-percent').textContent = '12%';
- document.getElementById('blocked-percent').textContent = '8%';
- document.getElementById('allowed-percent').textContent = '15%';
- document.getElementById('error-percent').textContent = '2%';
+ // 计算并更新百分比
+ if (totalQueries > 0) {
+ document.getElementById('blocked-percent').textContent = `${Math.round((blockedQueries / totalQueries) * 100)}%`;
+ document.getElementById('allowed-percent').textContent = `${Math.round((allowedQueries / totalQueries) * 100)}%`;
+ document.getElementById('error-percent').textContent = `${Math.round((errorQueries / totalQueries) * 100)}%`;
+ document.getElementById('queries-percent').textContent = `100%`;
+ } else {
+ document.getElementById('queries-percent').textContent = '---';
+ document.getElementById('blocked-percent').textContent = '---';
+ document.getElementById('allowed-percent').textContent = '---';
+ document.getElementById('error-percent').textContent = '---';
+ }
}
// 更新Top屏蔽域名表格
@@ -138,11 +147,11 @@ function updateTopBlockedTable(domains) {
// 如果没有有效数据,提供示例数据
if (tableData.length === 0) {
tableData = [
- { name: 'ads.example.com', count: 1250 },
- { name: 'tracking.example.org', count: 980 },
- { name: 'malware.test.net', count: 765 },
- { name: 'spam.service.com', count: 450 },
- { name: 'analytics.unknown.org', count: 320 }
+ { name: '---', count: '---' },
+ { name: '---', count: '---' },
+ { name: '---', count: '---' },
+ { name: '---', count: '---' },
+ { name: '---', count: '---' }
];
console.log('使用示例数据填充Top屏蔽域名表格');
}
@@ -179,11 +188,11 @@ function updateRecentBlockedTable(domains) {
if (tableData.length === 0) {
const now = Date.now();
tableData = [
- { name: 'ads.example.com', timestamp: now - 5 * 60 * 1000 },
- { name: 'tracking.example.org', timestamp: now - 15 * 60 * 1000 },
- { name: 'malware.test.net', timestamp: now - 30 * 60 * 1000 },
- { name: 'spam.service.com', timestamp: now - 45 * 60 * 1000 },
- { name: 'analytics.unknown.org', timestamp: now - 60 * 60 * 1000 }
+ { name: '---', timestamp: now - 5 * 60 * 1000 },
+ { name: '---', timestamp: now - 15 * 60 * 1000 },
+ { name: '---', timestamp: now - 30 * 60 * 1000 },
+ { name: '---', timestamp: now - 45 * 60 * 1000 },
+ { name: '---', timestamp: now - 60 * 60 * 1000 }
];
console.log('使用示例数据填充最近屏蔽域名表格');
}
@@ -204,67 +213,19 @@ function updateRecentBlockedTable(domains) {
// 初始化图表
function initCharts() {
- // 初始化查询趋势图表
- const queryTrendCtx = document.getElementById('query-trend-chart').getContext('2d');
- queryTrendChart = new Chart(queryTrendCtx, {
- type: 'line',
- data: {
- labels: Array.from({length: 24}, (_, i) => `${(i + 1) % 24}:00`),
- datasets: [
- {
- label: '总查询',
- data: Array(24).fill(0),
- borderColor: '#165DFF',
- backgroundColor: 'rgba(22, 93, 255, 0.1)',
- tension: 0.4,
- fill: true
- },
- {
- label: '屏蔽数量',
- data: Array(24).fill(0),
- borderColor: '#F53F3F',
- backgroundColor: 'rgba(245, 63, 63, 0.1)',
- tension: 0.4,
- fill: true
- }
- ]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- legend: {
- position: 'top',
- },
- tooltip: {
- mode: 'index',
- intersect: false
- }
- },
- scales: {
- y: {
- beginAtZero: true,
- grid: {
- drawBorder: false
- }
- },
- x: {
- grid: {
- display: false
- }
- }
- }
- }
- });
-
// 初始化比例图表
- const ratioCtx = document.getElementById('ratio-chart').getContext('2d');
+ const ratioChartElement = document.getElementById('ratio-chart');
+ if (!ratioChartElement) {
+ console.error('未找到比例图表元素');
+ return;
+ }
+ const ratioCtx = ratioChartElement.getContext('2d');
ratioChart = new Chart(ratioCtx, {
type: 'doughnut',
data: {
labels: ['正常解析', '被屏蔽', '错误'],
datasets: [{
- data: [70, 25, 5],
+ data: ['---', '---', '---'],
backgroundColor: ['#00B42A', '#F53F3F', '#FF7D00'],
borderWidth: 0
}]
@@ -283,12 +244,18 @@ function initCharts() {
}
// 更新图表数据
-function updateCharts(stats, hourlyStats) {
- console.log('更新图表,收到统计数据:', stats, '小时统计:', hourlyStats);
+function updateCharts(stats) {
+ console.log('更新图表,收到统计数据:', stats);
+
+ // 空值检查
+ if (!stats) {
+ console.error('更新图表失败: 未提供统计数据');
+ return;
+ }
// 更新比例图表
if (ratioChart) {
- let allowed = 70, blocked = 25, error = 5;
+ let allowed = '---', blocked = '---', error = '---';
// 尝试从stats数据中提取
if (stats.dns) {
@@ -304,38 +271,6 @@ function updateCharts(stats, hourlyStats) {
ratioChart.data.datasets[0].data = [allowed, blocked, error];
ratioChart.update();
}
-
- // 更新趋势图表
- if (queryTrendChart) {
- let labels = Array.from({length: 24}, (_, i) => `${(i + 1) % 24}:00`);
- let totalData = [], blockedData = [];
-
- // 尝试从hourlyStats中提取数据
- if (Array.isArray(hourlyStats) && hourlyStats.length > 0) {
- labels = hourlyStats.map(h => `${h.hour || h.time || h[0]}:00`);
- totalData = hourlyStats.map(h => h.total || h.queries || h[1] || 0);
- blockedData = hourlyStats.map(h => h.blocked || h[2] || 0);
- } else {
- // 如果没有小时统计数据,生成示例数据
- for (let i = 0; i < 24; i++) {
- // 生成模拟的查询数据,形成一个正常的流量曲线
- const baseValue = 500;
- const timeFactor = Math.sin((i - 8) * Math.PI / 12); // 早上8点开始上升,晚上8点开始下降
- const randomFactor = 0.8 + Math.random() * 0.4; // 添加一些随机性
- const hourlyTotal = Math.round(baseValue * (0.5 + timeFactor * 0.5) * randomFactor);
- const hourlyBlocked = Math.round(hourlyTotal * (0.1 + Math.random() * 0.2)); // 10-30%被屏蔽
-
- totalData.push(hourlyTotal);
- blockedData.push(hourlyBlocked);
- }
- console.log('使用示例数据填充趋势图表');
- }
-
- queryTrendChart.data.labels = labels;
- queryTrendChart.data.datasets[0].data = totalData;
- queryTrendChart.data.datasets[1].data = blockedData;
- queryTrendChart.update();
- }
}
// 更新运行状态
diff --git a/static/js/shield.js b/static/js/shield.js
index e914431..5ee51a1 100644
--- a/static/js/shield.js
+++ b/static/js/shield.js
@@ -1,112 +1,41 @@
// 屏蔽管理页面功能实现
-// 初始化屏蔽管理页面
+// 初始化屏蔽管理页面 - 已禁用加载屏蔽规则功能
function initShieldPage() {
- loadShieldRules();
+ // 不再加载屏蔽规则,避免DOM元素不存在导致的错误
setupShieldEventListeners();
}
-// 加载屏蔽规则
+// 加载屏蔽规则 - 已禁用此功能
async function loadShieldRules() {
- try {
- const rules = await api.getShieldRules();
- updateShieldRulesTable(rules);
- } catch (error) {
- showErrorMessage('加载屏蔽规则失败: ' + error.message);
- }
+ console.log('屏蔽规则加载功能已禁用');
}
-// 更新屏蔽规则表格
+// 更新屏蔽规则表格 - 已禁用此功能
function updateShieldRulesTable(rules) {
- const tbody = document.getElementById('shield-rules-tbody');
- tbody.innerHTML = '';
-
- if (!rules || rules.length === 0) {
- tbody.innerHTML = '
| 暂无屏蔽规则 |
';
- return;
- }
-
- rules.forEach((rule, index) => {
- const tr = document.createElement('tr');
- tr.className = 'border-b border-gray-200 hover:bg-gray-50';
- tr.innerHTML = `
- ${index + 1} |
- ${rule} |
-
-
- |
- `;
- tbody.appendChild(tr);
- });
-
- // 添加删除按钮事件监听器
- document.querySelectorAll('.delete-rule-btn').forEach(btn => {
- btn.addEventListener('click', handleDeleteRule);
- });
+ // 不再更新表格,避免DOM元素不存在导致的错误
+ console.log('屏蔽规则表格更新功能已禁用');
}
-// 处理删除规则
+// 处理删除规则 - 已禁用此功能
async function handleDeleteRule(e) {
- const rule = e.currentTarget.getAttribute('data-rule');
-
- if (confirm(`确定要删除规则: ${rule} 吗?`)) {
- try {
- await api.deleteShieldRule(rule);
- showSuccessMessage('规则删除成功');
- loadShieldRules();
- } catch (error) {
- showErrorMessage('删除规则失败: ' + error.message);
- }
- }
+ showErrorMessage('删除规则功能已禁用');
}
-// 添加新规则
+// 添加新规则 - 已禁用此功能
async function handleAddRule() {
- const ruleInput = document.getElementById('new-rule-input');
- const rule = ruleInput.value.trim();
-
- if (!rule) {
- showErrorMessage('规则不能为空');
- return;
- }
-
- try {
- await api.addShieldRule(rule);
- showSuccessMessage('规则添加成功');
- loadShieldRules();
- ruleInput.value = '';
- } catch (error) {
- showErrorMessage('添加规则失败: ' + error.message);
- }
+ showErrorMessage('添加规则功能已禁用');
}
-// 更新远程规则
+// 更新远程规则 - 已禁用此功能
async function handleUpdateRemoteRules() {
- try {
- await api.updateRemoteRules();
- showSuccessMessage('远程规则更新成功');
- loadShieldRules();
- } catch (error) {
- showErrorMessage('远程规则更新失败: ' + error.message);
- }
+ showErrorMessage('更新远程规则功能已禁用');
}
-// 设置事件监听器
+// 设置事件监听器 - 已禁用规则相关功能
function setupShieldEventListeners() {
- // 添加规则按钮
- document.getElementById('add-rule-btn')?.addEventListener('click', handleAddRule);
-
- // 按回车键添加规则
- document.getElementById('new-rule-input')?.addEventListener('keypress', (e) => {
- if (e.key === 'Enter') {
- handleAddRule();
- }
- });
-
- // 更新远程规则按钮
- document.getElementById('update-remote-rules-btn')?.addEventListener('click', handleUpdateRemoteRules);
+ // 移除所有事件监听器,避免触发已禁用的功能
+ console.log('屏蔽规则相关事件监听器已设置,但功能已禁用');
}
// 显示成功消息
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..74c293f
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,24 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ "./static/**/*.{html,js}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ primary: '#165DFF',
+ secondary: '#36CFFB',
+ success: '#00B42A',
+ warning: '#FF7D00',
+ danger: '#F53F3F',
+ info: '#86909C',
+ dark: '#1D2129',
+ light: '#F2F3F5',
+ },
+ fontFamily: {
+ sans: ['Inter', 'system-ui', 'sans-serif'],
+ },
+ },
+ },
+ plugins: [],
+}
\ No newline at end of file
diff --git a/test_console.sh b/test_console.sh
new file mode 100755
index 0000000..e88a657
--- /dev/null
+++ b/test_console.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+# DNS Web控制台功能测试脚本
+echo "开始测试DNS Web控制台功能..."
+echo "=================================="
+
+# 检查服务器是否运行
+echo "检查DNS服务器运行状态..."
+pids=$(ps aux | grep dns-server | grep -v grep)
+if [ -n "$pids" ]; then
+ echo "✓ DNS服务器正在运行"
+else
+ echo "✗ DNS服务器未运行,请先启动服务器"
+fi
+
+# 测试API基础URL
+BASE_URL="http://localhost:8080/api"
+
+# 测试1: 获取统计信息
+echo "\n测试1: 获取DNS统计信息"
+curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/stats"
+
+# 测试2: 获取系统状态
+echo "\n测试2: 获取系统状态"
+curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/status"
+
+# 测试3: 获取屏蔽规则
+echo "\n测试3: 获取屏蔽规则列表"
+curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/shield"
+
+# 测试4: 获取Top屏蔽域名
+echo "\n测试4: 获取Top屏蔽域名"
+curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/top-blocked"
+
+# 测试5: 获取Hosts内容
+echo "\n测试5: 获取Hosts内容"
+curl -s -o /dev/null -w "状态码: %{http_code}\n" "$BASE_URL/shield/hosts"
+
+# 测试6: 访问Web控制台主页
+echo "\n测试6: 访问Web控制台主页"
+curl -s -o /dev/null -w "状态码: %{http_code}\n" "http://localhost:8080"
+
+echo "\n=================================="
+echo "测试完成!请检查上述状态码。正常情况下应为200。"
+echo "前端Web控制台可通过浏览器访问: http://localhost:8080"
\ No newline at end of file