web增加屏蔽管理和DNS查询功能

This commit is contained in:
Alex Yang
2025-11-27 19:58:16 +08:00
parent 79eddf7fb2
commit 7dd31c8f5a
3 changed files with 897 additions and 293 deletions

View File

@@ -635,12 +635,187 @@
</div> </div>
<!-- 其他页面内容(初始隐藏) --> <!-- 其他页面内容(初始隐藏) -->
<div id="shield-content" class="hidden"> <div id="shield-content" class="hidden space-y-6">
<!-- 屏蔽管理页面内容 --> <!-- 屏蔽规则统计信息 -->
<div class="bg-white rounded-lg p-6 card-shadow"> <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>
<!-- 这里将添加屏蔽规则管理相关内容 --> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<p>远程规则管理页面内容待实现</p> <div class="bg-blue-50 p-4 rounded-lg">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-500">域名规则</h4>
<i class="fa fa-list text-blue-500"></i>
</div>
<p class="text-2xl font-bold" id="domain-rules-count">0</p>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-500">域名例外</h4>
<i class="fa fa-check-circle text-green-500"></i>
</div>
<p class="text-2xl font-bold" id="domain-exceptions-count">0</p>
</div>
<div class="bg-purple-50 p-4 rounded-lg">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-500">正则规则</h4>
<i class="fa fa-code text-purple-500"></i>
</div>
<p class="text-2xl font-bold" id="regex-rules-count">0</p>
</div>
<div class="bg-yellow-50 p-4 rounded-lg">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-500">正则例外</h4>
<i class="fa fa-exclamation-circle text-yellow-500"></i>
</div>
<p class="text-2xl font-bold" id="regex-exceptions-count">0</p>
</div>
<div class="bg-red-50 p-4 rounded-lg">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-500">Hosts规则</h4>
<i class="fa fa-file-text text-red-500"></i>
</div>
<p class="text-2xl font-bold" id="hosts-rules-count">0</p>
</div>
<div class="bg-indigo-50 p-4 rounded-lg">
<div class="flex items-center justify-between mb-2">
<h4 class="text-sm font-medium text-gray-500">黑名单数量</h4>
<i class="fa fa-ban text-indigo-500"></i>
</div>
<p class="text-2xl font-bold" id="blacklist-count">0</p>
</div>
</div>
</div>
<!-- 本地规则管理 -->
<div class="bg-white rounded-lg p-6 card-shadow">
<div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-semibold">本地规则管理</h3>
<button id="add-rule-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
<i class="fa fa-plus mr-2"></i>添加规则
</button>
</div>
<!-- 添加规则表单 -->
<div id="add-rule-form" class="mb-6 bg-gray-50 p-4 rounded-lg hidden">
<div class="flex items-center space-x-4">
<input type="text" id="new-rule" placeholder="输入规则例如example.com 或 regex:/example\.com/" class="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
<button id="save-rule-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
保存
</button>
<button id="cancel-rule-btn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors">
取消
</button>
</div>
</div>
<!-- 规则列表 -->
<div class="overflow-x-auto">
<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-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
</tr>
</thead>
<tbody id="rules-table-body">
<tr>
<td colspan="2" class="py-4 text-center text-gray-500">暂无规则</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 远程黑名单管理 -->
<div class="bg-white rounded-lg p-6 card-shadow">
<div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-semibold">远程黑名单管理</h3>
<button id="add-blacklist-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
<i class="fa fa-plus mr-2"></i>添加黑名单
</button>
</div>
<!-- 添加黑名单表单 -->
<div id="add-blacklist-form" class="mb-6 bg-gray-50 p-4 rounded-lg hidden">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label for="blacklist-name" class="block text-sm font-medium text-gray-700 mb-1">名称</label>
<input type="text" id="blacklist-name" placeholder="输入黑名单名称" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
</div>
<div>
<label for="blacklist-url" class="block text-sm font-medium text-gray-700 mb-1">URL</label>
<input type="text" id="blacklist-url" placeholder="输入黑名单URL" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
</div>
<div class="flex items-end space-x-4">
<button id="save-blacklist-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
保存
</button>
<button id="cancel-blacklist-btn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors">
取消
</button>
</div>
</div>
</div>
<!-- 黑名单列表 -->
<div class="overflow-x-auto">
<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">URL</th>
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">状态</th>
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
</tr>
</thead>
<tbody id="blacklists-table-body">
<tr>
<td colspan="4" class="py-4 text-center text-gray-500">暂无黑名单</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Hosts条目管理 -->
<div class="bg-white rounded-lg p-6 card-shadow">
<div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-semibold">Hosts条目管理</h3>
<button id="add-hosts-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
<i class="fa fa-plus mr-2"></i>添加条目
</button>
</div>
<!-- 添加hosts条目表单 -->
<div id="add-hosts-form" class="mb-6 bg-gray-50 p-4 rounded-lg hidden">
<div class="flex items-center space-x-4">
<input type="text" id="hosts-ip" placeholder="IP地址" class="w-32 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
<input type="text" id="hosts-domain" placeholder="域名" class="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
<button id="save-hosts-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
保存
</button>
<button id="cancel-hosts-btn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors">
取消
</button>
</div>
</div>
<!-- Hosts列表 -->
<div class="overflow-x-auto">
<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">IP地址</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">域名</th>
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
</tr>
</thead>
<tbody id="hosts-table-body">
<tr>
<td colspan="3" class="py-4 text-center text-gray-500">暂无Hosts条目</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
@@ -662,12 +837,68 @@
</div> </div>
</div> </div>
<div id="query-content" class="hidden"> <div id="query-content" class="hidden space-y-6">
<!-- DNS查询页面内容 --> <!-- DNS查询表单 -->
<div class="bg-white rounded-lg p-6 card-shadow"> <div class="bg-white rounded-lg p-6 card-shadow">
<h3 class="text-lg font-semibold mb-6">DNS查询</h3> <h3 class="text-lg font-semibold mb-6">DNS查询</h3>
<!-- 这里将添加DNS查询相关内容 -->
<p>DNS查询页面内容待实现</p> <!-- 查询表单 -->
<div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4">
<div class="flex-1">
<input type="text" id="dns-query-domain" placeholder="输入域名例如example.com" class="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
</div>
<button id="dns-query-btn" class="px-6 py-3 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
<i class="fa fa-search mr-2"></i>查询
</button>
</div>
</div>
<!-- 查询结果展示 -->
<div id="query-result" class="bg-white rounded-lg p-6 card-shadow hidden">
<h3 class="text-lg font-semibold mb-4">查询结果</h3>
<div class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-500 mb-2">域名</h4>
<p class="text-lg font-semibold" id="result-domain">-</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-500 mb-2">状态</h4>
<p class="text-lg font-semibold" id="result-status">-</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-500 mb-2">屏蔽类型</h4>
<p class="text-lg font-semibold" id="result-type">-</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-500 mb-2">查询时间</h4>
<p class="text-lg font-semibold" id="result-time">-</p>
</div>
</div>
<!-- 详细信息 -->
<div class="bg-gray-50 p-4 rounded-lg">
<h4 class="text-sm font-medium text-gray-500 mb-2">详细信息</h4>
<pre class="bg-white p-4 rounded-md border border-gray-200 overflow-x-auto" id="result-details">-</pre>
</div>
</div>
</div>
<!-- 查询历史记录 -->
<div class="bg-white rounded-lg p-6 card-shadow">
<div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-semibold">查询历史</h3>
<button id="clear-history-btn" class="text-sm text-gray-500 hover:text-danger transition-colors">
<i class="fa fa-trash mr-1"></i>清空历史
</button>
</div>
<!-- 历史记录列表 -->
<div id="query-history" class="space-y-3">
<div class="text-center text-gray-500 py-4">
暂无查询历史
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -1,313 +1,205 @@
// DNS查询工具页面功能实现 // DNS查询页面功能实现
// 初始化查询工具页面 // 初始化查询页面
function initQueryPage() { function initQueryPage() {
console.log('初始化DNS查询页面...'); console.log('初始化DNS查询页面...');
setupQueryEventListeners(); setupQueryEventListeners();
loadQueryHistory();
// 页面加载时自动显示一些示例数据
setTimeout(() => {
const mockDomain = 'example.com';
const mockRecordType = 'A';
displayMockQueryResult(mockDomain, mockRecordType);
console.log('显示示例DNS查询数据');
}, 500);
} }
// 执行DNS查询 // 执行DNS查询
async function handleDNSQuery() { async function handleDNSQuery() {
// 尝试多种可能的DOM元素ID const domainInput = document.getElementById('dns-query-domain');
const domainInput = document.getElementById('query-domain') || document.getElementById('domain-input');
const recordTypeSelect = document.getElementById('query-record-type') || document.getElementById('record-type');
const resultDiv = document.getElementById('query-result'); const resultDiv = document.getElementById('query-result');
console.log('DOM元素查找结果:', { domainInput, recordTypeSelect, resultDiv }); if (!domainInput || !resultDiv) {
if (!domainInput || !recordTypeSelect || !resultDiv) {
console.error('找不到必要的DOM元素'); console.error('找不到必要的DOM元素');
return; return;
} }
const domain = domainInput.value.trim(); const domain = domainInput.value.trim();
const recordType = recordTypeSelect.value;
if (!domain) { if (!domain) {
showErrorMessage('请输入域名'); showErrorMessage('请输入域名');
return; return;
} }
console.log(`执行DNS查询: 域名=${domain}, 记录类型=${recordType}`); showLoading('查询中...');
// 清空之前的结果
resultDiv.innerHTML = '<div class="text-center py-4"><svg class="animate-spin mx-auto h-6 w-6 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> 查询中...</div>';
try { try {
// 检查api对象是否存在 const response = await fetch(`/api/query?domain=${encodeURIComponent(domain)}`);
if (!window.api || typeof window.api.queryDNS !== 'function') { if (!response.ok) {
console.warn('api.queryDNS不存在使用模拟数据'); throw new Error('查询失败');
const mockResult = generateMockDNSResult(domain, recordType);
displayQueryResult(mockResult, domain, recordType);
return;
} }
// 调用API适配不同的参数格式 const result = await response.json();
let result; displayQueryResult(result, domain);
try { saveQueryHistory(domain, result);
// 尝试不同的API调用方式 loadQueryHistory();
if (api.queryDNS.length === 1) { hideLoading();
result = await api.queryDNS({ domain, recordType });
} else {
result = await api.queryDNS(domain, recordType);
}
} catch (apiError) {
console.error('API调用失败使用模拟数据:', apiError);
const mockResult = generateMockDNSResult(domain, recordType);
displayQueryResult(mockResult, domain, recordType);
return;
}
console.log('DNS查询API返回结果:', result);
// 处理API返回的数据
if (!result || (Array.isArray(result) && result.length === 0) ||
(typeof result === 'object' && Object.keys(result).length === 0)) {
console.log('API返回空结果使用模拟数据');
const mockResult = generateMockDNSResult(domain, recordType);
displayQueryResult(mockResult, domain, recordType);
} else {
displayQueryResult(result, domain, recordType);
}
} catch (error) { } catch (error) {
console.error('DNS查询出错:', error); console.error('DNS查询出错:', error);
const mockResult = generateMockDNSResult(domain, recordType); showErrorMessage('查询失败,请稍后重试');
displayQueryResult(mockResult, domain, recordType); hideLoading();
resultDiv.innerHTML += `<div class="text-yellow-500 text-center py-2 text-sm">注意: 显示的是模拟数据</div>`;
} }
} }
// 显示查询结果 // 显示查询结果
function displayQueryResult(result, domain, recordType) { function displayQueryResult(result, domain) {
const resultDiv = document.getElementById('query-result');
// 适配不同的数据结构
let records = [];
if (Array.isArray(result)) {
// 如果是数组,直接使用
records = result;
} else if (typeof result === 'object' && result.length === undefined) {
// 如果是对象,尝试转换为数组
if (result.records) {
records = result.records;
} else if (result.data) {
records = result.data;
} else {
// 尝试将对象转换为记录数组
records = [result];
}
}
// 创建结果表格
let html = `
<div class="mb-4">
<h3 class="text-lg font-medium text-gray-800 mb-2">查询结果: ${domain} (${recordType})</h3>
<p class="text-sm text-gray-500 mb-3">查询时间: ${new Date().toLocaleString()}</p>
<div class="overflow-x-auto">
<table class="min-w-full bg-white rounded-lg overflow-hidden shadow-sm">
<thead class="bg-gray-50">
<tr>
<th class="py-2 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">类型</th>
<th class="py-2 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">值</th>
<th class="py-2 px-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
`;
if (records.length === 0) {
html += `
<tr>
<td colspan="3" class="py-4 text-center text-gray-500">未找到 ${domain}${recordType} 记录</td>
</tr>
`;
} else {
// 添加查询结果
records.forEach(record => {
const type = record.Type || record.type || recordType;
// 处理不同格式的值
let value;
if (record.Value) {
value = record.Value;
} else if (record.ip || record.address) {
value = record.ip || record.address;
} else if (record.target) {
value = record.target;
} else if (record.text) {
value = record.text;
} else if (record.name) {
value = record.name;
} else {
value = JSON.stringify(record);
}
// 格式化不同类型的记录值
if (type === 'MX' && (record.Preference || record.priority)) {
value = `${record.Preference || record.priority} ${value}`;
} else if (type === 'SRV') {
if (record.Priority && record.Weight && record.Port) {
value = `${record.Priority} ${record.Weight} ${record.Port} ${value}`;
}
}
const ttl = record.TTL || record.ttl || '-';
html += `
<tr class="hover:bg-gray-50 transition-colors">
<td class="py-3 px-4 text-sm font-medium text-gray-900">${type}</td>
<td class="py-3 px-4 text-sm text-gray-900 font-mono break-all">${value}</td>
<td class="py-3 px-4 text-sm text-gray-500">${ttl}</td>
</tr>
`;
});
}
html += `
</tbody>
</table>
</div>
</div>
`;
resultDiv.innerHTML = html;
}
// 生成模拟DNS查询结果
function generateMockDNSResult(domain, recordType) {
console.log('生成模拟DNS结果:', domain, recordType);
const mockData = {
'A': [
{ Type: 'A', Value: '192.168.1.1', TTL: 300 },
{ Type: 'A', Value: '192.168.1.2', TTL: 300 }
],
'AAAA': [
{ Type: 'AAAA', Value: '2001:db8::1', TTL: 300 },
{ Type: 'AAAA', Value: '2001:db8::2', TTL: 300 }
],
'MX': [
{ Type: 'MX', Value: 'mail.' + domain, Preference: 10, TTL: 3600 },
{ Type: 'MX', Value: 'mail2.' + domain, Preference: 20, TTL: 3600 }
],
'NS': [
{ Type: 'NS', Value: 'ns1.' + domain, TTL: 86400 },
{ Type: 'NS', Value: 'ns2.' + domain, TTL: 86400 }
],
'CNAME': [
{ Type: 'CNAME', Value: 'www.' + domain, TTL: 300 }
],
'TXT': [
{ Type: 'TXT', Value: 'v=spf1 include:_spf.' + domain + ' ~all', TTL: 3600 },
{ Type: 'TXT', Value: 'google-site-verification=abcdef123456', TTL: 3600 }
],
'SOA': [
{ Type: 'SOA', Value: 'ns1.' + domain + ' admin.' + domain + ' 1 3600 1800 604800 86400', TTL: 86400 }
]
};
return mockData[recordType] || [
{ Type: recordType, Value: 'No records found', TTL: '-' }
];
}
// 显示模拟查询结果
function displayMockQueryResult(domain, recordType) {
const resultDiv = document.getElementById('query-result'); const resultDiv = document.getElementById('query-result');
if (!resultDiv) return; if (!resultDiv) return;
// 显示提示信息 // 显示结果容器
resultDiv.innerHTML = ` resultDiv.classList.remove('hidden');
<div class="p-4 bg-blue-50 border border-blue-100 rounded-lg">
<div class="flex items-start"> // 解析结果
<svg class="h-5 w-5 text-blue-500 mt-0.5 mr-2" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> const status = result.blocked ? '被屏蔽' : '正常';
<div> const statusClass = result.blocked ? 'text-danger' : 'text-success';
<p class="text-sm text-blue-700">这是一个DNS查询工具示例。输入域名并选择记录类型然后点击查询按钮获取DNS记录信息。</p> const blockType = result.blocked ? result.blockType || '未知' : '正常';
const timestamp = new Date(result.timestamp).toLocaleString();
// 更新结果显示
document.getElementById('result-domain').textContent = domain;
document.getElementById('result-status').innerHTML = `<span class="${statusClass}">${status}</span>`;
document.getElementById('result-type').textContent = blockType;
document.getElementById('result-time').textContent = timestamp;
document.getElementById('result-details').textContent = JSON.stringify(result, null, 2);
}
// 保存查询历史
function saveQueryHistory(domain, result) {
// 获取现有历史记录
let history = JSON.parse(localStorage.getItem('dnsQueryHistory') || '[]');
// 创建历史记录项
const historyItem = {
domain: domain,
timestamp: new Date().toISOString(),
result: {
blocked: result.blocked,
blockType: result.blockType
}
};
// 添加到历史记录开头
history.unshift(historyItem);
// 限制历史记录数量
if (history.length > 20) {
history = history.slice(0, 20);
}
// 保存到本地存储
localStorage.setItem('dnsQueryHistory', JSON.stringify(history));
}
// 加载查询历史
function loadQueryHistory() {
const historyDiv = document.getElementById('query-history');
if (!historyDiv) return;
// 获取历史记录
const history = JSON.parse(localStorage.getItem('dnsQueryHistory') || '[]');
if (history.length === 0) {
historyDiv.innerHTML = '<div class="text-center text-gray-500 py-4">暂无查询历史</div>';
return;
}
// 生成历史记录HTML
const historyHTML = history.map(item => {
const statusClass = item.result.blocked ? 'text-danger' : 'text-success';
const statusText = item.result.blocked ? '被屏蔽' : '正常';
const blockType = item.result.blocked ? item.result.blockType : '正常';
const formattedTime = new Date(item.timestamp).toLocaleString();
return `
<div class="flex flex-col md:flex-row justify-between items-start md:items-center p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
<div class="flex-1">
<div class="flex items-center space-x-2">
<span class="font-medium">${item.domain}</span>
<span class="${statusClass} text-sm">${statusText}</span>
<span class="text-xs text-gray-500">${blockType}</span>
</div>
<div class="text-xs text-gray-500 mt-1">${formattedTime}</div>
</div> </div>
<button class="mt-2 md:mt-0 px-3 py-1 bg-primary text-white text-sm rounded-md hover:bg-primary/90 transition-colors" onclick="requeryFromHistory('${item.domain}')">
<i class="fa fa-refresh mr-1"></i>重新查询
</button>
</div> </div>
</div> `;
`; }).join('');
historyDiv.innerHTML = historyHTML;
}
// 从历史记录重新查询
function requeryFromHistory(domain) {
const domainInput = document.getElementById('dns-query-domain');
if (domainInput) {
domainInput.value = domain;
handleDNSQuery();
}
}
// 清空查询历史
function clearQueryHistory() {
if (confirm('确定要清空所有查询历史吗?')) {
localStorage.removeItem('dnsQueryHistory');
loadQueryHistory();
showSuccessMessage('查询历史已清空');
}
} }
// 设置事件监听器 // 设置事件监听器
function setupQueryEventListeners() { function setupQueryEventListeners() {
// 尝试多种可能的按钮ID // 查询按钮事件
const queryButtons = [ const queryBtn = document.getElementById('dns-query-btn');
document.getElementById('query-btn'), if (queryBtn) {
document.getElementById('query-button'), queryBtn.addEventListener('click', handleDNSQuery);
document.querySelector('button[type="submit"]'), }
...Array.from(document.querySelectorAll('button')).filter(btn =>
btn.textContent && btn.textContent.includes('查询')
)
].filter(Boolean);
// 绑定查询按钮事件 // 输入框回车键事件
queryButtons.forEach(button => { const domainInput = document.getElementById('dns-query-domain');
console.log('绑定查询按钮事件:', button); if (domainInput) {
button.addEventListener('click', handleDNSQuery); domainInput.addEventListener('keypress', (e) => {
});
// 尝试多种可能的输入框ID
const domainInputs = [
document.getElementById('query-domain'),
document.getElementById('domain-input'),
document.querySelector('input[id*="domain"]')
].filter(Boolean);
// 绑定回车键事件
domainInputs.forEach(input => {
console.log('绑定输入框回车事件:', input);
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
handleDNSQuery(); handleDNSQuery();
} }
}); });
}); }
// 添加示例域名按钮 // 清空历史按钮事件
const querySection = document.querySelector('#dns-query-section, #query-section'); const clearHistoryBtn = document.getElementById('clear-history-btn');
if (querySection) { if (clearHistoryBtn) {
const exampleContainer = document.createElement('div'); clearHistoryBtn.addEventListener('click', clearQueryHistory);
exampleContainer.className = 'mt-3';
exampleContainer.innerHTML = `
<p class="text-sm text-gray-500 mb-2">快速示例:</p>
<div class="flex flex-wrap gap-2">
<button class="text-xs px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-full text-gray-700 transition-colors" onclick="setExampleQuery('example.com', 'A')">example.com (A)</button>
<button class="text-xs px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-full text-gray-700 transition-colors" onclick="setExampleQuery('example.com', 'MX')">example.com (MX)</button>
<button class="text-xs px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-full text-gray-700 transition-colors" onclick="setExampleQuery('google.com', 'NS')">google.com (NS)</button>
</div>
`;
// 找到输入框容器并插入示例按钮
const inputContainer = domainInputs[0]?.parentElement;
if (inputContainer && inputContainer.nextElementSibling) {
inputContainer.parentNode.insertBefore(exampleContainer, inputContainer.nextElementSibling);
} else if (querySection.lastChild) {
querySection.appendChild(exampleContainer);
}
} }
} }
// 设置示例查询 // 显示加载状态
function setExampleQuery(domain, recordType) { function showLoading(message = '加载中...') {
const domainInput = document.getElementById('query-domain') || document.getElementById('domain-input'); // 移除现有加载状态
const recordTypeSelect = document.getElementById('query-record-type') || document.getElementById('record-type'); hideLoading();
if (domainInput) domainInput.value = domain; // 创建加载状态元素
if (recordTypeSelect) recordTypeSelect.value = recordType; const loading = document.createElement('div');
loading.className = 'loading-overlay fixed inset-0 bg-black/50 flex items-center justify-center z-50';
loading.innerHTML = `
<div class="bg-white rounded-lg p-6 shadow-lg flex items-center space-x-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<span class="text-gray-700 font-medium">${message}</span>
</div>
`;
// 自动执行查询 document.body.appendChild(loading);
handleDNSQuery(); }
// 隐藏加载状态
function hideLoading() {
const loading = document.querySelector('.loading-overlay');
if (loading) {
loading.remove();
}
} }
// 显示成功消息 // 显示成功消息
@@ -330,7 +222,7 @@ function showNotification(message, type = 'info') {
// 创建新通知 // 创建新通知
const notification = document.createElement('div'); const notification = document.createElement('div');
notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`; notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ease-in-out translate-y-0 opacity-0`;
// 设置通知样式 // 设置通知样式
if (type === 'success') { if (type === 'success') {
@@ -341,7 +233,13 @@ function showNotification(message, type = 'info') {
notification.classList.add('bg-blue-500', 'text-white'); notification.classList.add('bg-blue-500', 'text-white');
} }
notification.textContent = message; notification.innerHTML = `
<div class="flex items-center space-x-2">
<i class="fa ${type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : 'fa-info-circle'}"></i>
<span>${message}</span>
</div>
`;
document.body.appendChild(notification); document.body.appendChild(notification);
// 显示通知 // 显示通知
@@ -366,3 +264,13 @@ if (document.readyState === 'loading') {
} else { } else {
initQueryPage(); initQueryPage();
} }
// 当切换到DNS查询页面时重新加载数据
document.addEventListener('DOMContentLoaded', () => {
// 监听hash变化当切换到DNS查询页面时重新加载数据
window.addEventListener('hashchange', () => {
if (window.location.hash === '#query') {
initQueryPage();
}
});
});

View File

@@ -1,41 +1,464 @@
// 屏蔽管理页面功能实现 // 屏蔽管理页面功能实现
// 初始化屏蔽管理页面 - 已禁用加载屏蔽规则功能 // 初始化屏蔽管理页面
function initShieldPage() { function initShieldPage() {
// 不再加载屏蔽规则避免DOM元素不存在导致的错误 // 加载屏蔽规则统计信息
loadShieldStats();
// 加载本地规则
loadLocalRules();
// 加载远程黑名单
loadRemoteBlacklists();
// 加载hosts条目
loadHostsEntries();
// 设置事件监听器
setupShieldEventListeners(); setupShieldEventListeners();
} }
// 加载屏蔽规则 - 已禁用此功能 // 加载屏蔽规则统计信息
async function loadShieldRules() { async function loadShieldStats() {
console.log('屏蔽规则加载功能已禁用'); showLoading('加载屏蔽规则统计信息...');
try {
const response = await fetch('/api/shield');
if (!response.ok) {
throw new Error('Failed to load shield stats');
}
const stats = await response.json();
// 更新统计信息
document.getElementById('domain-rules-count').textContent = stats.domainRulesCount || 0;
document.getElementById('domain-exceptions-count').textContent = stats.domainExceptionsCount || 0;
document.getElementById('regex-rules-count').textContent = stats.regexRulesCount || 0;
document.getElementById('regex-exceptions-count').textContent = stats.regexExceptionsCount || 0;
document.getElementById('hosts-rules-count').textContent = stats.hostsRulesCount || 0;
document.getElementById('blacklist-count').textContent = stats.blacklistCount || 0;
hideLoading();
} catch (error) {
console.error('Error loading shield stats:', error);
showErrorMessage('加载屏蔽规则统计信息失败');
hideLoading();
}
} }
// 更新屏蔽规则表格 - 已禁用此功能 // 加载本地规则
function updateShieldRulesTable(rules) { async function loadLocalRules() {
// 不再更新表格避免DOM元素不存在导致的错误 showLoading('加载本地规则...');
console.log('屏蔽规则表格更新功能已禁用'); try {
const response = await fetch('/api/shield');
if (!response.ok) {
throw new Error('Failed to load local rules');
}
// 注意当前API不返回完整规则列表这里只是示例
// 实际实现需要后端提供获取本地规则的API
const rules = [];
updateRulesTable(rules);
hideLoading();
} catch (error) {
console.error('Error loading local rules:', error);
showErrorMessage('加载本地规则失败');
hideLoading();
}
} }
// 处理删除规则 - 已禁用此功能 // 更新规则表格
function updateRulesTable(rules) {
const tbody = document.getElementById('rules-table-body');
if (rules.length === 0) {
tbody.innerHTML = '<tr><td colspan="2" class="py-4 text-center text-gray-500">暂无规则</td></tr>';
return;
}
tbody.innerHTML = rules.map(rule => `
<tr class="border-b border-gray-200">
<td class="py-3 px-4">${rule}</td>
<td class="py-3 px-4 text-right">
<button class="delete-rule-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm" data-rule="${rule}">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
`).join('');
// 重新绑定删除事件
document.querySelectorAll('.delete-rule-btn').forEach(btn => {
btn.addEventListener('click', handleDeleteRule);
});
}
// 处理删除规则
async function handleDeleteRule(e) { async function handleDeleteRule(e) {
showErrorMessage('删除规则功能已禁用'); const rule = e.target.closest('.delete-rule-btn').dataset.rule;
showLoading('删除规则中...');
try {
const response = await fetch('/api/shield', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ rule })
});
if (!response.ok) {
throw new Error('Failed to delete rule');
}
showSuccessMessage('规则删除成功');
// 重新加载规则
loadLocalRules();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error deleting rule:', error);
showErrorMessage('删除规则失败');
hideLoading();
}
} }
// 添加新规则 - 已禁用此功能 // 添加新规则
async function handleAddRule() { async function handleAddRule() {
showErrorMessage('添加规则功能已禁用'); const rule = document.getElementById('new-rule').value.trim();
if (!rule) {
showErrorMessage('规则不能为空');
return;
}
showLoading('添加规则中...');
try {
const response = await fetch('/api/shield', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ rule })
});
if (!response.ok) {
throw new Error('Failed to add rule');
}
showSuccessMessage('规则添加成功');
// 清空输入框
document.getElementById('new-rule').value = '';
// 隐藏表单
document.getElementById('add-rule-form').classList.add('hidden');
// 重新加载规则
loadLocalRules();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error adding rule:', error);
showErrorMessage('添加规则失败');
hideLoading();
}
} }
// 更新远程规则 - 已禁用此功能 // 加载远程黑名单
async function handleUpdateRemoteRules() { async function loadRemoteBlacklists() {
showErrorMessage('更新远程规则功能已禁用'); showLoading('加载远程黑名单...');
try {
const response = await fetch('/api/shield/blacklists');
if (!response.ok) {
throw new Error('Failed to load remote blacklists');
}
const blacklists = await response.json();
updateBlacklistsTable(blacklists);
hideLoading();
} catch (error) {
console.error('Error loading remote blacklists:', error);
showErrorMessage('加载远程黑名单失败');
hideLoading();
}
} }
// 设置事件监听器 - 已禁用规则相关功能 // 更新黑名单表格
function updateBlacklistsTable(blacklists) {
const tbody = document.getElementById('blacklists-table-body');
if (blacklists.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="py-4 text-center text-gray-500">暂无黑名单</td></tr>';
return;
}
tbody.innerHTML = blacklists.map(blacklist => `
<tr class="border-b border-gray-200">
<td class="py-3 px-4">${blacklist.Name}</td>
<td class="py-3 px-4 truncate max-w-xs">${blacklist.URL}</td>
<td class="py-3 px-4 text-center">
<span class="inline-block w-3 h-3 rounded-full ${blacklist.Enabled ? 'bg-success' : 'bg-gray-300'}"></span>
</td>
<td class="py-3 px-4 text-right space-x-2">
<button class="update-blacklist-btn px-3 py-1 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors text-sm" data-url="${blacklist.URL}">
<i class="fa fa-refresh"></i>
</button>
<button class="delete-blacklist-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm" data-url="${blacklist.URL}">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
`).join('');
// 重新绑定事件
document.querySelectorAll('.update-blacklist-btn').forEach(btn => {
btn.addEventListener('click', handleUpdateBlacklist);
});
document.querySelectorAll('.delete-blacklist-btn').forEach(btn => {
btn.addEventListener('click', handleDeleteBlacklist);
});
}
// 处理更新单个黑名单
async function handleUpdateBlacklist(e) {
const url = e.target.closest('.update-blacklist-btn').dataset.url;
showLoading('更新黑名单中...');
try {
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}/update`, {
method: 'POST'
});
if (!response.ok) {
throw new Error('Failed to update blacklist');
}
showSuccessMessage('黑名单更新成功');
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error updating blacklist:', error);
showErrorMessage('更新黑名单失败');
hideLoading();
}
}
// 处理删除黑名单
async function handleDeleteBlacklist(e) {
const url = e.target.closest('.delete-blacklist-btn').dataset.url;
showLoading('删除黑名单中...');
try {
const response = await fetch(`/api/shield/blacklists/${encodeURIComponent(url)}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Failed to delete blacklist');
}
showSuccessMessage('黑名单删除成功');
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error deleting blacklist:', error);
showErrorMessage('删除黑名单失败');
hideLoading();
}
}
// 处理添加黑名单
async function handleAddBlacklist() {
const name = document.getElementById('blacklist-name').value.trim();
const url = document.getElementById('blacklist-url').value.trim();
if (!name || !url) {
showErrorMessage('名称和URL不能为空');
return;
}
showLoading('添加黑名单中...');
try {
const response = await fetch('/api/shield/blacklists', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, url })
});
if (!response.ok) {
throw new Error('Failed to add blacklist');
}
showSuccessMessage('黑名单添加成功');
// 清空输入框
document.getElementById('blacklist-name').value = '';
document.getElementById('blacklist-url').value = '';
// 隐藏表单
document.getElementById('add-blacklist-form').classList.add('hidden');
// 重新加载黑名单
loadRemoteBlacklists();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error adding blacklist:', error);
showErrorMessage('添加黑名单失败');
hideLoading();
}
}
// 加载hosts条目
async function loadHostsEntries() {
showLoading('加载Hosts条目...');
try {
const response = await fetch('/api/shield/hosts');
if (!response.ok) {
throw new Error('Failed to load hosts entries');
}
const data = await response.json();
updateHostsTable(data.hosts || []);
hideLoading();
} catch (error) {
console.error('Error loading hosts entries:', error);
showErrorMessage('加载Hosts条目失败');
hideLoading();
}
}
// 更新hosts表格
function updateHostsTable(hosts) {
const tbody = document.getElementById('hosts-table-body');
if (hosts.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="py-4 text-center text-gray-500">暂无Hosts条目</td></tr>';
return;
}
tbody.innerHTML = hosts.map(entry => `
<tr class="border-b border-gray-200">
<td class="py-3 px-4">${entry.ip}</td>
<td class="py-3 px-4">${entry.domain}</td>
<td class="py-3 px-4 text-right">
<button class="delete-hosts-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm" data-domain="${entry.domain}">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
`).join('');
// 重新绑定删除事件
document.querySelectorAll('.delete-hosts-btn').forEach(btn => {
btn.addEventListener('click', handleDeleteHostsEntry);
});
}
// 处理删除hosts条目
async function handleDeleteHostsEntry(e) {
const domain = e.target.closest('.delete-hosts-btn').dataset.domain;
showLoading('删除Hosts条目...');
try {
const response = await fetch('/api/shield/hosts', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ domain })
});
if (!response.ok) {
throw new Error('Failed to delete hosts entry');
}
showSuccessMessage('Hosts条目删除成功');
// 重新加载hosts条目
loadHostsEntries();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error deleting hosts entry:', error);
showErrorMessage('删除Hosts条目失败');
hideLoading();
}
}
// 处理添加hosts条目
async function handleAddHostsEntry() {
const ip = document.getElementById('hosts-ip').value.trim();
const domain = document.getElementById('hosts-domain').value.trim();
if (!ip || !domain) {
showErrorMessage('IP地址和域名不能为空');
return;
}
showLoading('添加Hosts条目...');
try {
const response = await fetch('/api/shield/hosts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ip, domain })
});
if (!response.ok) {
throw new Error('Failed to add hosts entry');
}
showSuccessMessage('Hosts条目添加成功');
// 清空输入框
document.getElementById('hosts-ip').value = '';
document.getElementById('hosts-domain').value = '';
// 隐藏表单
document.getElementById('add-hosts-form').classList.add('hidden');
// 重新加载hosts条目
loadHostsEntries();
// 重新加载统计信息
loadShieldStats();
hideLoading();
} catch (error) {
console.error('Error adding hosts entry:', error);
showErrorMessage('添加Hosts条目失败');
hideLoading();
}
}
// 设置事件监听器
function setupShieldEventListeners() { function setupShieldEventListeners() {
// 移除所有事件监听器,避免触发已禁用的功能 // 本地规则管理事件
console.log('屏蔽规则相关事件监听器已设置,但功能已禁用'); document.getElementById('add-rule-btn').addEventListener('click', () => {
document.getElementById('add-rule-form').classList.toggle('hidden');
});
document.getElementById('save-rule-btn').addEventListener('click', handleAddRule);
document.getElementById('cancel-rule-btn').addEventListener('click', () => {
document.getElementById('add-rule-form').classList.add('hidden');
document.getElementById('new-rule').value = '';
});
// 远程黑名单管理事件
document.getElementById('add-blacklist-btn').addEventListener('click', () => {
document.getElementById('add-blacklist-form').classList.toggle('hidden');
});
document.getElementById('save-blacklist-btn').addEventListener('click', handleAddBlacklist);
document.getElementById('cancel-blacklist-btn').addEventListener('click', () => {
document.getElementById('add-blacklist-form').classList.add('hidden');
document.getElementById('blacklist-name').value = '';
document.getElementById('blacklist-url').value = '';
});
// Hosts条目管理事件
document.getElementById('add-hosts-btn').addEventListener('click', () => {
document.getElementById('add-hosts-form').classList.toggle('hidden');
});
document.getElementById('save-hosts-btn').addEventListener('click', handleAddHostsEntry);
document.getElementById('cancel-hosts-btn').addEventListener('click', () => {
document.getElementById('add-hosts-form').classList.add('hidden');
document.getElementById('hosts-ip').value = '';
document.getElementById('hosts-domain').value = '';
});
} }
// 显示成功消息 // 显示成功消息
@@ -48,6 +471,32 @@ function showErrorMessage(message) {
showNotification(message, 'error'); showNotification(message, 'error');
} }
// 显示加载状态
function showLoading(message = '加载中...') {
// 移除现有加载状态
hideLoading();
// 创建加载状态元素
const loading = document.createElement('div');
loading.className = 'loading-overlay fixed inset-0 bg-black/50 flex items-center justify-center z-50';
loading.innerHTML = `
<div class="bg-white rounded-lg p-6 shadow-lg flex items-center space-x-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<span class="text-gray-700 font-medium">${message}</span>
</div>
`;
document.body.appendChild(loading);
}
// 隐藏加载状态
function hideLoading() {
const loading = document.querySelector('.loading-overlay');
if (loading) {
loading.remove();
}
}
// 显示通知 // 显示通知
function showNotification(message, type = 'info') { function showNotification(message, type = 'info') {
// 移除现有通知 // 移除现有通知
@@ -58,7 +507,7 @@ function showNotification(message, type = 'info') {
// 创建新通知 // 创建新通知
const notification = document.createElement('div'); const notification = document.createElement('div');
notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`; notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ease-in-out translate-y-0 opacity-0`;
// 设置通知样式 // 设置通知样式
if (type === 'success') { if (type === 'success') {
@@ -69,7 +518,13 @@ function showNotification(message, type = 'info') {
notification.classList.add('bg-blue-500', 'text-white'); notification.classList.add('bg-blue-500', 'text-white');
} }
notification.textContent = message; notification.innerHTML = `
<div class="flex items-center space-x-2">
<i class="fa ${type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : 'fa-info-circle'}"></i>
<span>${message}</span>
</div>
`;
document.body.appendChild(notification); document.body.appendChild(notification);
// 显示通知 // 显示通知
@@ -94,3 +549,13 @@ if (document.readyState === 'loading') {
} else { } else {
initShieldPage(); initShieldPage();
} }
// 当切换到屏蔽管理页面时重新加载数据
document.addEventListener('DOMContentLoaded', () => {
// 监听hash变化当切换到屏蔽管理页面时重新加载数据
window.addEventListener('hashchange', () => {
if (window.location.hash === '#shield') {
initShieldPage();
}
});
});