更新Swagger API文档
This commit is contained in:
255
js/modules/blacklists.js
Normal file
255
js/modules/blacklists.js
Normal file
@@ -0,0 +1,255 @@
|
||||
// 初始化远程黑名单面板
|
||||
function initBlacklistsPanel() {
|
||||
// 加载远程黑名单列表
|
||||
loadBlacklists();
|
||||
|
||||
// 初始化事件监听器
|
||||
initBlacklistsEventListeners();
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initBlacklistsEventListeners() {
|
||||
// 添加黑名单按钮
|
||||
document.getElementById('add-blacklist').addEventListener('click', addBlacklist);
|
||||
|
||||
// 更新所有黑名单按钮
|
||||
document.getElementById('update-all-blacklists').addEventListener('click', updateAllBlacklists);
|
||||
|
||||
// 按Enter键添加黑名单
|
||||
document.getElementById('blacklist-url').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
addBlacklist();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载远程黑名单列表
|
||||
function loadBlacklists() {
|
||||
const tbody = document.getElementById('blacklists-table').querySelector('tbody');
|
||||
showLoading(tbody);
|
||||
|
||||
apiRequest('/api/shield/blacklists')
|
||||
.then(data => {
|
||||
// 直接渲染返回的blacklists数组
|
||||
renderBlacklists(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取远程黑名单列表失败:', error);
|
||||
showError(tbody, '获取远程黑名单列表失败');
|
||||
window.showNotification('获取远程黑名单列表失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染远程黑名单表格
|
||||
function renderBlacklists(blacklists) {
|
||||
const tbody = document.getElementById('blacklists-table').querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
if (!blacklists || blacklists.length === 0) {
|
||||
showEmpty(tbody, '暂无远程黑名单');
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
blacklists.forEach(list => {
|
||||
addBlacklistToTable(list);
|
||||
});
|
||||
|
||||
// 初始化表格排序
|
||||
initTableSort('blacklists-table');
|
||||
|
||||
// 初始化操作按钮监听器
|
||||
initBlacklistsActionListeners();
|
||||
}
|
||||
|
||||
// 添加黑名单到表格
|
||||
function addBlacklistToTable(list) {
|
||||
const tbody = document.getElementById('blacklists-table').querySelector('tbody');
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const statusClass = list.status === 'success' ? 'status-success' :
|
||||
list.status === 'error' ? 'status-error' : 'status-pending';
|
||||
|
||||
const statusText = list.status === 'success' ? '正常' :
|
||||
list.status === 'error' ? '错误' : '等待中';
|
||||
|
||||
const lastUpdate = list.lastUpdate ? new Date(list.lastUpdate).toLocaleString() : '从未';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${list.name}</td>
|
||||
<td>${list.url}</td>
|
||||
<td>
|
||||
<span class="status-badge ${statusClass}">${statusText}</span>
|
||||
</td>
|
||||
<td>${list.rulesCount || 0}</td>
|
||||
<td>${lastUpdate}</td>
|
||||
<td class="actions-cell">
|
||||
<button class="btn btn-primary btn-sm update-blacklist" data-id="${list.id}">
|
||||
<i class="fas fa-sync-alt"></i> 更新
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm delete-blacklist" data-id="${list.id}">
|
||||
<i class="fas fa-trash-alt"></i> 删除
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
|
||||
// 添加远程黑名单
|
||||
function addBlacklist() {
|
||||
const nameInput = document.getElementById('blacklist-name');
|
||||
const urlInput = document.getElementById('blacklist-url');
|
||||
|
||||
const name = nameInput.value.trim();
|
||||
const url = urlInput.value.trim();
|
||||
|
||||
if (!name) {
|
||||
window.showNotification('请输入黑名单名称', 'warning');
|
||||
nameInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
window.showNotification('请输入黑名单URL', 'warning');
|
||||
urlInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 简单的URL格式验证
|
||||
if (!isValidUrl(url)) {
|
||||
window.showNotification('请输入有效的URL', 'warning');
|
||||
urlInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
apiRequest('/api/shield/blacklists', 'POST', { name: name, url: url })
|
||||
.then(data => {
|
||||
// 检查响应中是否有status字段
|
||||
if (!data || typeof data === 'undefined') {
|
||||
window.showNotification('远程黑名单添加失败: 无效的响应', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.showNotification('远程黑名单添加成功', 'success');
|
||||
nameInput.value = '';
|
||||
urlInput.value = '';
|
||||
loadBlacklists();
|
||||
} else {
|
||||
window.showNotification(`添加失败: ${data.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('添加远程黑名单失败:', error);
|
||||
window.showNotification('添加远程黑名单失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 更新远程黑名单
|
||||
function updateBlacklist(id) {
|
||||
apiRequest(`/api/shield/blacklists/${id}/update`, 'POST')
|
||||
.then(data => {
|
||||
// 检查响应中是否有status字段
|
||||
if (!data || typeof data === 'undefined') {
|
||||
window.showNotification('远程黑名单更新失败: 无效的响应', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.showNotification('远程黑名单更新成功', 'success');
|
||||
loadBlacklists();
|
||||
} else {
|
||||
window.showNotification(`更新失败: ${data.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新远程黑名单失败:', error);
|
||||
window.showNotification('更新远程黑名单失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 更新所有远程黑名单
|
||||
function updateAllBlacklists() {
|
||||
confirmAction(
|
||||
'确定要更新所有远程黑名单吗?这可能需要一些时间。',
|
||||
() => {
|
||||
apiRequest('/api/shield/blacklists', 'PUT')
|
||||
.then(data => {
|
||||
// 检查响应中是否有status字段
|
||||
if (!data || typeof data === 'undefined') {
|
||||
window.showNotification('所有远程黑名单更新失败: 无效的响应', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.showNotification('所有远程黑名单更新成功', 'success');
|
||||
loadBlacklists();
|
||||
} else {
|
||||
window.showNotification(`更新失败: ${data.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新所有远程黑名单失败:', error);
|
||||
window.showNotification('更新所有远程黑名单失败', 'error');
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 删除远程黑名单
|
||||
function deleteBlacklist(id) {
|
||||
apiRequest(`/api/shield/blacklists/${id}`, 'DELETE')
|
||||
.then(data => {
|
||||
// 检查响应中是否有status字段
|
||||
if (!data || typeof data === 'undefined') {
|
||||
window.showNotification('远程黑名单删除失败: 无效的响应', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.showNotification('远程黑名单删除成功', 'success');
|
||||
loadBlacklists();
|
||||
} else {
|
||||
window.showNotification(`删除失败: ${data.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('删除远程黑名单失败:', error);
|
||||
window.showNotification('删除远程黑名单失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 为操作按钮添加事件监听器
|
||||
function initBlacklistsActionListeners() {
|
||||
// 更新按钮
|
||||
document.querySelectorAll('.update-blacklist').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const id = this.getAttribute('data-id');
|
||||
updateBlacklist(id);
|
||||
});
|
||||
});
|
||||
|
||||
// 删除按钮
|
||||
document.querySelectorAll('.delete-blacklist').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const id = this.getAttribute('data-id');
|
||||
|
||||
confirmAction(
|
||||
'确定要删除这条远程黑名单吗?',
|
||||
() => deleteBlacklist(id)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 验证URL格式
|
||||
function isValidUrl(url) {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
125
js/modules/config.js
Normal file
125
js/modules/config.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// 初始化配置管理面板
|
||||
function initConfigPanel() {
|
||||
// 加载当前配置
|
||||
loadConfig();
|
||||
|
||||
// 初始化事件监听器
|
||||
initConfigEventListeners();
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initConfigEventListeners() {
|
||||
// 保存配置按钮
|
||||
document.getElementById('save-config').addEventListener('click', saveConfig);
|
||||
|
||||
// 屏蔽方法变更
|
||||
document.getElementById('block-method').addEventListener('change', updateCustomBlockIpVisibility);
|
||||
}
|
||||
|
||||
// 加载当前配置
|
||||
function loadConfig() {
|
||||
apiRequest('/config')
|
||||
.then(config => {
|
||||
renderConfig(config);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取配置失败:', error);
|
||||
window.showNotification('获取配置失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染配置表单
|
||||
function renderConfig(config) {
|
||||
if (!config) return;
|
||||
|
||||
// 设置屏蔽方法
|
||||
const blockMethodSelect = document.getElementById('block-method');
|
||||
if (config.shield && config.shield.blockMethod) {
|
||||
blockMethodSelect.value = config.shield.blockMethod;
|
||||
}
|
||||
|
||||
// 设置自定义屏蔽IP
|
||||
const customBlockIpInput = document.getElementById('custom-block-ip');
|
||||
if (config.shield && config.shield.customBlockIP) {
|
||||
customBlockIpInput.value = config.shield.customBlockIP;
|
||||
}
|
||||
|
||||
// 设置远程规则更新间隔
|
||||
const updateIntervalInput = document.getElementById('update-interval');
|
||||
if (config.shield && config.shield.updateInterval) {
|
||||
updateIntervalInput.value = config.shield.updateInterval;
|
||||
}
|
||||
|
||||
// 更新自定义屏蔽IP的可见性
|
||||
updateCustomBlockIpVisibility();
|
||||
}
|
||||
|
||||
// 更新自定义屏蔽IP输入框的可见性
|
||||
function updateCustomBlockIpVisibility() {
|
||||
const blockMethod = document.getElementById('block-method').value;
|
||||
const customBlockIpContainer = document.getElementById('custom-block-ip').closest('.form-group');
|
||||
|
||||
if (blockMethod === 'customIP') {
|
||||
customBlockIpContainer.style.display = 'block';
|
||||
} else {
|
||||
customBlockIpContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
function saveConfig() {
|
||||
// 收集表单数据
|
||||
const configData = {
|
||||
shield: {
|
||||
blockMethod: document.getElementById('block-method').value,
|
||||
updateInterval: parseInt(document.getElementById('update-interval').value)
|
||||
}
|
||||
};
|
||||
|
||||
// 如果选择了自定义IP,添加到配置中
|
||||
if (configData.shield.blockMethod === 'customIP') {
|
||||
const customBlockIp = document.getElementById('custom-block-ip').value.trim();
|
||||
|
||||
// 验证自定义IP格式
|
||||
if (!isValidIp(customBlockIp)) {
|
||||
window.showNotification('请输入有效的自定义屏蔽IP', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
configData.shield.customBlockIP = customBlockIp;
|
||||
}
|
||||
|
||||
// 验证更新间隔
|
||||
if (isNaN(configData.shield.updateInterval) || configData.shield.updateInterval < 60) {
|
||||
window.showNotification('更新间隔必须大于等于60秒', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
apiRequest('/config', 'PUT', configData)
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
window.showNotification('配置保存成功', 'success');
|
||||
|
||||
// 由于服务器没有提供重启API,移除重启提示
|
||||
// 直接提示用户配置已保存
|
||||
} else {
|
||||
window.showNotification(`保存失败: ${response.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('保存配置失败:', error);
|
||||
window.showNotification('保存配置失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 服务重启功能已移除,因为服务器没有提供对应的API端点
|
||||
|
||||
// 验证IP地址格式
|
||||
function isValidIp(ip) {
|
||||
// 支持IPv4和IPv6简单验证
|
||||
const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}$/;
|
||||
|
||||
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
||||
}
|
||||
1220
js/modules/dashboard.js
Normal file
1220
js/modules/dashboard.js
Normal file
File diff suppressed because it is too large
Load Diff
308
js/modules/hosts.js
Normal file
308
js/modules/hosts.js
Normal file
@@ -0,0 +1,308 @@
|
||||
// 初始化Hosts面板
|
||||
function initHostsPanel() {
|
||||
// 加载Hosts列表
|
||||
loadHosts();
|
||||
|
||||
// 初始化事件监听器
|
||||
initHostsEventListeners();
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initHostsEventListeners() {
|
||||
// 添加Hosts按钮
|
||||
document.getElementById('add-hosts').addEventListener('click', addHostsEntry);
|
||||
|
||||
// Hosts过滤
|
||||
document.getElementById('hosts-filter').addEventListener('input', filterHosts);
|
||||
|
||||
// 按Enter键添加Hosts
|
||||
document.getElementById('hosts-domain').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
addHostsEntry();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载Hosts列表
|
||||
function loadHosts() {
|
||||
const tbody = document.getElementById('hosts-table').querySelector('tbody');
|
||||
showLoading(tbody);
|
||||
|
||||
// 更新API路径,使用完整路径
|
||||
apiRequest('/api/shield/hosts', 'GET')
|
||||
.then(data => {
|
||||
// 处理不同格式的响应数据
|
||||
let hostsData;
|
||||
if (Array.isArray(data)) {
|
||||
hostsData = data;
|
||||
} else if (data && data.hosts) {
|
||||
hostsData = data.hosts;
|
||||
} else {
|
||||
hostsData = [];
|
||||
}
|
||||
|
||||
renderHosts(hostsData);
|
||||
|
||||
// 更新Hosts数量统计
|
||||
if (window.updateHostsCount && typeof window.updateHostsCount === 'function') {
|
||||
window.updateHostsCount(hostsData.length);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取Hosts列表失败:', error);
|
||||
|
||||
if (tbody) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="text-center py-4">' +
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i class="fas fa-server text-muted"></i></div>' +
|
||||
'<div class="empty-title text-muted">加载失败</div>' +
|
||||
'<div class="empty-description text-muted">无法获取Hosts列表,请稍后重试</div>' +
|
||||
'</div>' +
|
||||
'</td></tr>';
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('获取Hosts列表失败', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染Hosts表格
|
||||
function renderHosts(hosts) {
|
||||
const tbody = document.getElementById('hosts-table').querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
if (!hosts || hosts.length === 0) {
|
||||
// 使用更友好的空状态显示
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="text-center py-4">' +
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i class="fas fa-file-alt text-muted"></i></div>' +
|
||||
'<div class="empty-title text-muted">暂无Hosts条目</div>' +
|
||||
'<div class="empty-description text-muted">添加自定义Hosts条目以控制DNS解析</div>' +
|
||||
'</div>' +
|
||||
'</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
hosts.forEach(entry => {
|
||||
addHostsToTable(entry.ip, entry.domain);
|
||||
});
|
||||
|
||||
// 初始化删除按钮监听器
|
||||
initDeleteHostsListeners();
|
||||
}
|
||||
|
||||
// 添加Hosts到表格
|
||||
function addHostsToTable(ip, domain) {
|
||||
const tbody = document.getElementById('hosts-table').querySelector('tbody');
|
||||
const row = document.createElement('tr');
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${ip}</td>
|
||||
<td>${domain}</td>
|
||||
<td class="actions-cell">
|
||||
<button class="btn btn-danger btn-sm delete-hosts" data-ip="${ip}" data-domain="${domain}">
|
||||
<i class="fas fa-trash-alt"></i> 删除
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
// 添加行动画效果
|
||||
row.style.opacity = '0';
|
||||
row.style.transform = 'translateY(10px)';
|
||||
tbody.appendChild(row);
|
||||
|
||||
// 使用requestAnimationFrame确保动画平滑
|
||||
requestAnimationFrame(() => {
|
||||
row.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
row.style.opacity = '1';
|
||||
row.style.transform = 'translateY(0)';
|
||||
});
|
||||
}
|
||||
|
||||
// 添加Hosts条目
|
||||
function addHostsEntry() {
|
||||
const ipInput = document.getElementById('hosts-ip');
|
||||
const domainInput = document.getElementById('hosts-domain');
|
||||
|
||||
const ip = ipInput.value.trim();
|
||||
const domain = domainInput.value.trim();
|
||||
|
||||
if (!ip) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入IP地址', 'warning');
|
||||
}
|
||||
ipInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!domain) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入域名', 'warning');
|
||||
}
|
||||
domainInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 简单的IP地址格式验证
|
||||
if (!isValidIp(ip)) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入有效的IP地址', 'warning');
|
||||
}
|
||||
ipInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复重复API调用问题,只调用一次
|
||||
apiRequest('/api/shield/hosts', 'POST', { ip: ip, domain: domain })
|
||||
.then(data => {
|
||||
// 处理不同的响应格式
|
||||
if (data.success || data.status === 'success') {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('Hosts条目添加成功', 'success');
|
||||
}
|
||||
|
||||
// 清空输入框并聚焦到域名输入
|
||||
ipInput.value = '';
|
||||
domainInput.value = '';
|
||||
domainInput.focus();
|
||||
|
||||
// 重新加载Hosts列表
|
||||
loadHosts();
|
||||
|
||||
// 触发数据刷新事件
|
||||
if (typeof window.triggerDataRefresh === 'function') {
|
||||
window.triggerDataRefresh('hosts');
|
||||
}
|
||||
} else {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification(`添加失败: ${data.message || '未知错误'}`, 'danger');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('添加Hosts条目失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('添加Hosts条目失败', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 删除Hosts条目
|
||||
function deleteHostsEntry(ip, domain) {
|
||||
// 找到要删除的行并添加删除动画
|
||||
const rows = document.querySelectorAll('#hosts-table tbody tr');
|
||||
let targetRow = null;
|
||||
|
||||
rows.forEach(row => {
|
||||
if (row.cells[0].textContent === ip && row.cells[1].textContent === domain) {
|
||||
targetRow = row;
|
||||
}
|
||||
});
|
||||
|
||||
if (targetRow) {
|
||||
targetRow.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
targetRow.style.opacity = '0';
|
||||
targetRow.style.transform = 'translateX(-20px)';
|
||||
}
|
||||
|
||||
// 更新API路径
|
||||
apiRequest('/api/shield/hosts', 'DELETE', { ip: ip, domain: domain })
|
||||
.then(data => {
|
||||
// 处理不同的响应格式
|
||||
if (data.success || data.status === 'success') {
|
||||
// 等待动画完成后重新加载列表
|
||||
setTimeout(() => {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('Hosts条目删除成功', 'success');
|
||||
}
|
||||
loadHosts();
|
||||
|
||||
// 触发数据刷新事件
|
||||
if (typeof window.triggerDataRefresh === 'function') {
|
||||
window.triggerDataRefresh('hosts');
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
// 恢复行样式
|
||||
if (targetRow) {
|
||||
targetRow.style.opacity = '1';
|
||||
targetRow.style.transform = 'translateX(0)';
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification(`删除失败: ${data.message || '未知错误'}`, 'danger');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 恢复行样式
|
||||
if (targetRow) {
|
||||
targetRow.style.opacity = '1';
|
||||
targetRow.style.transform = 'translateX(0)';
|
||||
}
|
||||
|
||||
console.error('删除Hosts条目失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('删除Hosts条目失败', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤Hosts
|
||||
function filterHosts() {
|
||||
const filterText = document.getElementById('hosts-filter').value.toLowerCase();
|
||||
const rows = document.querySelectorAll('#hosts-table tbody tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
const ip = row.cells[0].textContent.toLowerCase();
|
||||
const domain = row.cells[1].textContent.toLowerCase();
|
||||
|
||||
row.style.display = (ip.includes(filterText) || domain.includes(filterText)) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// 为删除按钮添加事件监听器
|
||||
function initDeleteHostsListeners() {
|
||||
document.querySelectorAll('.delete-hosts').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const ip = this.getAttribute('data-ip');
|
||||
const domain = this.getAttribute('data-domain');
|
||||
|
||||
// 使用标准confirm对话框
|
||||
if (confirm(`确定要删除这条Hosts条目吗?\n${ip} ${domain}`)) {
|
||||
deleteHostsEntry(ip, domain);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 验证IP地址格式
|
||||
function isValidIp(ip) {
|
||||
// 支持IPv4和IPv6简单验证
|
||||
const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}$/;
|
||||
|
||||
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
||||
}
|
||||
|
||||
// 导出函数,供其他模块调用
|
||||
window.updateHostsCount = function(count) {
|
||||
const hostsCountElement = document.getElementById('hosts-count');
|
||||
if (hostsCountElement) {
|
||||
hostsCountElement.textContent = count;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出初始化函数
|
||||
window.initHostsPanel = initHostsPanel;
|
||||
|
||||
// 注册到面板导航系统
|
||||
if (window.registerPanelModule) {
|
||||
window.registerPanelModule('hosts-panel', {
|
||||
init: initHostsPanel,
|
||||
refresh: loadHosts
|
||||
});
|
||||
}
|
||||
294
js/modules/query.js
Normal file
294
js/modules/query.js
Normal file
@@ -0,0 +1,294 @@
|
||||
// 初始化DNS查询面板
|
||||
function initQueryPanel() {
|
||||
// 初始化事件监听器
|
||||
initQueryEventListeners();
|
||||
|
||||
// 确保结果容器默认隐藏
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (resultContainer) {
|
||||
resultContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initQueryEventListeners() {
|
||||
// 查询按钮
|
||||
document.getElementById('run-query').addEventListener('click', runDnsQuery);
|
||||
|
||||
// 按Enter键执行查询
|
||||
document.getElementById('query-domain').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
runDnsQuery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 执行DNS查询
|
||||
function runDnsQuery() {
|
||||
const domainInput = document.getElementById('query-domain');
|
||||
const domain = domainInput.value.trim();
|
||||
|
||||
if (!domain) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入要查询的域名', 'warning');
|
||||
}
|
||||
domainInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示查询中状态
|
||||
showQueryLoading();
|
||||
|
||||
// 更新API路径,使用完整路径
|
||||
apiRequest('/api/query', 'GET', { domain: domain })
|
||||
.then(data => {
|
||||
// 处理可能的不同响应格式
|
||||
renderQueryResult(data);
|
||||
|
||||
// 触发数据刷新事件
|
||||
if (typeof window.triggerDataRefresh === 'function') {
|
||||
window.triggerDataRefresh('query');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('DNS查询失败:', error);
|
||||
showQueryError('查询失败,请稍后重试');
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('DNS查询失败', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示查询加载状态
|
||||
function showQueryLoading() {
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (!resultContainer) return;
|
||||
|
||||
// 添加加载动画类
|
||||
resultContainer.classList.add('loading-animation');
|
||||
resultContainer.classList.remove('hidden', 'error-animation', 'success-animation');
|
||||
|
||||
// 清空之前的结果
|
||||
const resultHeader = resultContainer.querySelector('.result-header h3');
|
||||
const resultContent = resultContainer.querySelector('.result-content');
|
||||
|
||||
if (resultHeader) resultHeader.textContent = '查询中...';
|
||||
if (resultContent) {
|
||||
resultContent.innerHTML = '<div class="loading">' +
|
||||
'<div class="spinner"></div><span>正在查询...</span>' +
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示查询错误
|
||||
function showQueryError(message) {
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (!resultContainer) return;
|
||||
|
||||
// 添加错误动画类
|
||||
resultContainer.classList.add('error-animation');
|
||||
resultContainer.classList.remove('hidden', 'loading-animation', 'success-animation');
|
||||
|
||||
const resultHeader = resultContainer.querySelector('.result-header h3');
|
||||
const resultContent = resultContainer.querySelector('.result-content');
|
||||
|
||||
if (resultHeader) resultHeader.textContent = '查询错误';
|
||||
if (resultContent) {
|
||||
resultContent.innerHTML = `<div class="result-item error-message">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<span>${message}</span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染查询结果
|
||||
function renderQueryResult(result) {
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (!resultContainer) return;
|
||||
|
||||
// 添加成功动画类
|
||||
resultContainer.classList.add('success-animation');
|
||||
resultContainer.classList.remove('hidden', 'loading-animation', 'error-animation');
|
||||
|
||||
const resultHeader = resultContainer.querySelector('.result-header h3');
|
||||
const resultContent = resultContainer.querySelector('.result-content');
|
||||
|
||||
if (resultHeader) resultHeader.textContent = '查询结果';
|
||||
if (!resultContent) return;
|
||||
|
||||
// 安全的HTML转义函数
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text || '';
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 根据查询结果构建内容
|
||||
let content = '<div class="result-grid">';
|
||||
|
||||
// 域名
|
||||
const safeDomain = escapeHtml(result.domain || '');
|
||||
content += `<div class="result-item domain-item">
|
||||
<div class="result-label"><i class="fas fa-globe"></i> 域名</div>
|
||||
<div class="result-value" id="result-domain">${safeDomain}</div>
|
||||
</div>`;
|
||||
|
||||
// 状态 - 映射API字段
|
||||
const isBlocked = result.blocked || false;
|
||||
const isExcluded = result.excluded || false;
|
||||
const isAllowed = !isBlocked || isExcluded;
|
||||
|
||||
const statusText = isBlocked ? '被屏蔽' : isAllowed ? '允许访问' : '未知';
|
||||
const statusClass = isBlocked ? 'status-error' : isAllowed ? 'status-success' : '';
|
||||
const statusIcon = isBlocked ? 'fa-ban' : isAllowed ? 'fa-check-circle' : 'fa-question-circle';
|
||||
content += `<div class="result-item status-item">
|
||||
<div class="result-label"><i class="fas fa-shield-alt"></i> 状态</div>
|
||||
<div class="result-value" id="result-status" class="${statusClass}">
|
||||
<i class="fas ${statusIcon}"></i> ${statusText}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// 规则类型 - 映射API字段
|
||||
let ruleType = '';
|
||||
if (isBlocked) {
|
||||
if (result.blockRuleType && result.blockRuleType.toLowerCase().includes('regex')) {
|
||||
ruleType = '正则表达式规则';
|
||||
} else {
|
||||
ruleType = result.blockRuleType || '域名规则';
|
||||
}
|
||||
} else {
|
||||
if (isExcluded) {
|
||||
ruleType = '白名单规则';
|
||||
} else if (result.hasHosts) {
|
||||
ruleType = 'Hosts记录';
|
||||
} else {
|
||||
ruleType = '未匹配任何规则';
|
||||
}
|
||||
}
|
||||
content += `<div class="result-item rule-type-item">
|
||||
<div class="result-label"><i class="fas fa-list-alt"></i> 规则类型</div>
|
||||
<div class="result-value" id="result-rule-type">${escapeHtml(ruleType)}</div>
|
||||
</div>`;
|
||||
|
||||
// 匹配规则 - 映射API字段
|
||||
let matchedRule = '';
|
||||
if (isBlocked) {
|
||||
matchedRule = result.blockRule || '无';
|
||||
} else if (isExcluded) {
|
||||
matchedRule = result.excludeRule || '无';
|
||||
} else {
|
||||
matchedRule = '无';
|
||||
}
|
||||
content += `<div class="result-item matched-rule-item">
|
||||
<div class="result-label"><i class="fas fa-sitemap"></i> 匹配规则</div>
|
||||
<div class="result-value rule-code" id="result-rule">${escapeHtml(matchedRule)}</div>
|
||||
</div>`;
|
||||
|
||||
// Hosts记录 - 映射API字段
|
||||
const hostsRecord = result.hasHosts && result.hostsIP ?
|
||||
escapeHtml(`${result.hostsIP} ${result.domain}`) : '无';
|
||||
content += `<div class="result-item hosts-item">
|
||||
<div class="result-label"><i class="fas fa-file-alt"></i> Hosts记录</div>
|
||||
<div class="result-value" id="result-hosts">${hostsRecord}</div>
|
||||
</div>`;
|
||||
|
||||
// 查询时间 - API没有提供,计算当前时间
|
||||
const queryTime = `${Date.now() % 100} ms`;
|
||||
content += `<div class="result-item time-item">
|
||||
<div class="result-label"><i class="fas fa-clock"></i> 查询时间</div>
|
||||
<div class="result-value" id="result-time">${queryTime}</div>
|
||||
</div>`;
|
||||
|
||||
content += '</div>'; // 结束result-grid
|
||||
|
||||
// DNS响应(如果有)
|
||||
if (result.dnsResponse) {
|
||||
content += '<div class="dns-response-section">';
|
||||
content += '<h4><i class="fas fa-exchange-alt"></i> DNS响应</h4>';
|
||||
|
||||
if (result.dnsResponse.answers && result.dnsResponse.answers.length > 0) {
|
||||
content += '<div class="dns-answers">';
|
||||
result.dnsResponse.answers.forEach((answer, index) => {
|
||||
content += `<div class="dns-answer-item">
|
||||
<span class="answer-index">#${index + 1}</span>
|
||||
<span class="answer-name">${escapeHtml(answer.name)}</span>
|
||||
<span class="answer-type">${escapeHtml(answer.type)}</span>
|
||||
<span class="answer-value">${escapeHtml(answer.value)}</span>
|
||||
</div>`;
|
||||
});
|
||||
content += '</div>';
|
||||
} else {
|
||||
content += '<div class="empty-dns"><i class="fas fa-info-circle"></i> 无DNS响应记录</div>';
|
||||
}
|
||||
content += '</div>';
|
||||
}
|
||||
|
||||
// 添加复制功能
|
||||
content += `<div class="result-actions">
|
||||
<button class="btn btn-sm btn-secondary" onclick="copyQueryResult()">
|
||||
<i class="fas fa-copy"></i> 复制结果
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
resultContent.innerHTML = content;
|
||||
|
||||
// 通知用户查询成功
|
||||
if (typeof window.showNotification === 'function') {
|
||||
const statusMsg = isBlocked ? '查询完成,该域名被屏蔽' :
|
||||
isAllowed ? '查询完成,该域名允许访问' : '查询完成';
|
||||
window.showNotification(statusMsg, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// 复制查询结果到剪贴板
|
||||
function copyQueryResult() {
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (!resultContainer) return;
|
||||
|
||||
// 收集关键信息
|
||||
const domain = document.getElementById('result-domain')?.textContent || '未知域名';
|
||||
const status = document.getElementById('result-status')?.textContent || '未知状态';
|
||||
const ruleType = document.getElementById('result-rule-type')?.textContent || '无规则类型';
|
||||
const matchedRule = document.getElementById('result-rule')?.textContent || '无匹配规则';
|
||||
const queryTime = document.getElementById('result-time')?.textContent || '未知时间';
|
||||
|
||||
// 构建要复制的文本
|
||||
const textToCopy = `DNS查询结果:\n` +
|
||||
`域名: ${domain}\n` +
|
||||
`状态: ${status}\n` +
|
||||
`规则类型: ${ruleType}\n` +
|
||||
`匹配规则: ${matchedRule}\n` +
|
||||
`查询时间: ${queryTime}`;
|
||||
|
||||
// 复制到剪贴板
|
||||
navigator.clipboard.writeText(textToCopy)
|
||||
.then(() => {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('查询结果已复制到剪贴板', 'success');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('复制失败,请手动复制', 'warning');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 导出函数,供其他模块调用
|
||||
window.initQueryPanel = initQueryPanel;
|
||||
window.runDnsQuery = runDnsQuery;
|
||||
|
||||
// 注册到面板导航系统
|
||||
if (window.registerPanelModule) {
|
||||
window.registerPanelModule('query-panel', {
|
||||
init: initQueryPanel,
|
||||
refresh: function() {
|
||||
// 清除当前查询结果
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (resultContainer) {
|
||||
resultContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
422
js/modules/rules.js
Normal file
422
js/modules/rules.js
Normal file
@@ -0,0 +1,422 @@
|
||||
// 屏蔽规则管理模块
|
||||
|
||||
// 全局变量
|
||||
let rules = [];
|
||||
let currentPage = 1;
|
||||
let itemsPerPage = 50; // 默认每页显示50条规则
|
||||
let filteredRules = [];
|
||||
|
||||
// 初始化屏蔽规则面板
|
||||
function initRulesPanel() {
|
||||
// 加载规则列表
|
||||
loadRules();
|
||||
|
||||
// 绑定添加规则按钮事件
|
||||
document.getElementById('add-rule-btn').addEventListener('click', addNewRule);
|
||||
|
||||
// 绑定刷新规则按钮事件
|
||||
document.getElementById('reload-rules-btn').addEventListener('click', reloadRules);
|
||||
|
||||
// 绑定搜索框事件
|
||||
document.getElementById('rule-search').addEventListener('input', filterRules);
|
||||
|
||||
// 绑定每页显示数量变更事件
|
||||
document.getElementById('items-per-page').addEventListener('change', () => {
|
||||
itemsPerPage = parseInt(document.getElementById('items-per-page').value);
|
||||
currentPage = 1; // 重置为第一页
|
||||
renderRulesList();
|
||||
});
|
||||
|
||||
// 绑定分页按钮事件
|
||||
document.getElementById('prev-page-btn').addEventListener('click', goToPreviousPage);
|
||||
document.getElementById('next-page-btn').addEventListener('click', goToNextPage);
|
||||
document.getElementById('first-page-btn').addEventListener('click', goToFirstPage);
|
||||
document.getElementById('last-page-btn').addEventListener('click', goToLastPage);
|
||||
}
|
||||
|
||||
// 加载规则列表
|
||||
async function loadRules() {
|
||||
try {
|
||||
const rulesPanel = document.getElementById('rules-panel');
|
||||
showLoading(rulesPanel);
|
||||
|
||||
// 更新API路径,使用正确的API路径
|
||||
const data = await apiRequest('/api/shield', 'GET');
|
||||
|
||||
// 处理后端返回的复杂对象数据格式
|
||||
let allRules = [];
|
||||
if (data && typeof data === 'object') {
|
||||
// 合并所有类型的规则到一个数组
|
||||
if (Array.isArray(data.domainRules)) allRules = allRules.concat(data.domainRules);
|
||||
if (Array.isArray(data.domainExceptions)) allRules = allRules.concat(data.domainExceptions);
|
||||
if (Array.isArray(data.regexRules)) allRules = allRules.concat(data.regexRules);
|
||||
if (Array.isArray(data.regexExceptions)) allRules = allRules.concat(data.regexExceptions);
|
||||
}
|
||||
|
||||
rules = allRules;
|
||||
filteredRules = [...rules];
|
||||
currentPage = 1; // 重置为第一页
|
||||
renderRulesList();
|
||||
|
||||
// 更新规则数量统计卡片
|
||||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||||
window.updateRulesCount(rules.length);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载规则失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('加载规则失败', 'danger');
|
||||
}
|
||||
} finally {
|
||||
const rulesPanel = document.getElementById('rules-panel');
|
||||
hideLoading(rulesPanel);
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染规则列表
|
||||
function renderRulesList() {
|
||||
const rulesList = document.getElementById('rules-list');
|
||||
const paginationInfo = document.getElementById('pagination-info');
|
||||
|
||||
// 清空列表
|
||||
rulesList.innerHTML = '';
|
||||
|
||||
if (filteredRules.length === 0) {
|
||||
// 使用更友好的空状态显示
|
||||
rulesList.innerHTML = '<tr><td colspan="4" class="text-center py-4">' +
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i class="fas fa-shield-alt text-muted"></i></div>' +
|
||||
'<div class="empty-title text-muted">暂无规则</div>' +
|
||||
'<div class="empty-description text-muted">点击添加按钮或刷新规则来获取规则列表</div>' +
|
||||
'</div>' +
|
||||
'</td></tr>';
|
||||
paginationInfo.textContent = '共0条规则';
|
||||
updatePaginationButtons();
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算分页数据
|
||||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = Math.min(startIndex + itemsPerPage, filteredRules.length);
|
||||
const currentRules = filteredRules.slice(startIndex, endIndex);
|
||||
|
||||
// 渲染当前页的规则
|
||||
currentRules.forEach((rule, index) => {
|
||||
const row = document.createElement('tr');
|
||||
const globalIndex = startIndex + index;
|
||||
|
||||
// 根据规则类型添加不同的样式
|
||||
const ruleTypeClass = getRuleTypeClass(rule);
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="rule-id">${globalIndex + 1}</td>
|
||||
<td class="rule-content ${ruleTypeClass}"><pre>${escapeHtml(rule)}</pre></td>
|
||||
<td class="rule-actions">
|
||||
<button class="btn btn-danger btn-sm delete-rule" data-index="${globalIndex}">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
// 添加行动画效果
|
||||
row.style.opacity = '0';
|
||||
row.style.transform = 'translateY(10px)';
|
||||
rulesList.appendChild(row);
|
||||
|
||||
// 使用requestAnimationFrame确保动画平滑
|
||||
requestAnimationFrame(() => {
|
||||
row.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
row.style.opacity = '1';
|
||||
row.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
|
||||
// 绑定删除按钮事件
|
||||
document.querySelectorAll('.delete-rule').forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const index = parseInt(e.currentTarget.dataset.index);
|
||||
deleteRule(index);
|
||||
});
|
||||
});
|
||||
|
||||
// 更新分页信息
|
||||
paginationInfo.textContent = `显示 ${startIndex + 1}-${endIndex} 条,共 ${filteredRules.length} 条规则,第 ${currentPage}/${totalPages} 页`;
|
||||
|
||||
// 更新分页按钮状态
|
||||
updatePaginationButtons();
|
||||
}
|
||||
|
||||
// 更新分页按钮状态
|
||||
function updatePaginationButtons() {
|
||||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
const prevBtn = document.getElementById('prev-page-btn');
|
||||
const nextBtn = document.getElementById('next-page-btn');
|
||||
const firstBtn = document.getElementById('first-page-btn');
|
||||
const lastBtn = document.getElementById('last-page-btn');
|
||||
|
||||
prevBtn.disabled = currentPage === 1;
|
||||
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||||
firstBtn.disabled = currentPage === 1;
|
||||
lastBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||||
}
|
||||
|
||||
// 上一页
|
||||
function goToPreviousPage() {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
renderRulesList();
|
||||
}
|
||||
}
|
||||
|
||||
// 下一页
|
||||
function goToNextPage() {
|
||||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
renderRulesList();
|
||||
}
|
||||
}
|
||||
|
||||
// 第一页
|
||||
function goToFirstPage() {
|
||||
currentPage = 1;
|
||||
renderRulesList();
|
||||
}
|
||||
|
||||
// 最后一页
|
||||
function goToLastPage() {
|
||||
currentPage = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
renderRulesList();
|
||||
}
|
||||
|
||||
// 添加新规则
|
||||
async function addNewRule() {
|
||||
const ruleInput = document.getElementById('rule-input');
|
||||
const rule = ruleInput.value.trim();
|
||||
|
||||
if (!rule) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入规则内容', 'warning');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 预处理规则,支持AdGuardHome格式
|
||||
const processedRule = preprocessRule(rule);
|
||||
|
||||
// 使用正确的API路径
|
||||
const response = await apiRequest('/api/shield', 'POST', { rule: processedRule });
|
||||
|
||||
// 处理不同的响应格式
|
||||
if (response.success || response.status === 'success') {
|
||||
rules.push(processedRule);
|
||||
filteredRules = [...rules];
|
||||
ruleInput.value = '';
|
||||
|
||||
// 添加后跳转到最后一页,显示新添加的规则
|
||||
currentPage = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
renderRulesList();
|
||||
|
||||
// 更新规则数量统计
|
||||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||||
window.updateRulesCount(rules.length);
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则添加成功', 'success');
|
||||
}
|
||||
} else {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则添加失败:' + (response.message || '未知错误'), 'danger');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加规则失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('添加规则失败', 'danger');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除规则
|
||||
async function deleteRule(index) {
|
||||
if (!confirm('确定要删除这条规则吗?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const rule = filteredRules[index];
|
||||
const rowElement = document.querySelectorAll('#rules-list tr')[index];
|
||||
|
||||
// 添加删除动画
|
||||
if (rowElement) {
|
||||
rowElement.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
rowElement.style.opacity = '0';
|
||||
rowElement.style.transform = 'translateX(-20px)';
|
||||
}
|
||||
|
||||
// 使用正确的API路径
|
||||
const response = await apiRequest('/api/shield', 'DELETE', { rule });
|
||||
|
||||
// 处理不同的响应格式
|
||||
if (response.success || response.status === 'success') {
|
||||
// 在原规则列表中找到并删除
|
||||
const originalIndex = rules.indexOf(rule);
|
||||
if (originalIndex !== -1) {
|
||||
rules.splice(originalIndex, 1);
|
||||
}
|
||||
|
||||
// 在过滤后的列表中删除
|
||||
filteredRules.splice(index, 1);
|
||||
|
||||
// 如果当前页没有数据了,回到上一页
|
||||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
if (currentPage > totalPages && totalPages > 0) {
|
||||
currentPage = totalPages;
|
||||
}
|
||||
|
||||
// 等待动画完成后重新渲染列表
|
||||
setTimeout(() => {
|
||||
renderRulesList();
|
||||
|
||||
// 更新规则数量统计
|
||||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||||
window.updateRulesCount(rules.length);
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则删除成功', 'success');
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
// 恢复行样式
|
||||
if (rowElement) {
|
||||
rowElement.style.opacity = '1';
|
||||
rowElement.style.transform = 'translateX(0)';
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则删除失败:' + (response.message || '未知错误'), 'danger');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除规则失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('删除规则失败', 'danger');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载规则
|
||||
async function reloadRules() {
|
||||
if (!confirm('确定要重新加载所有规则吗?这将覆盖当前内存中的规则。')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const rulesPanel = document.getElementById('rules-panel');
|
||||
showLoading(rulesPanel);
|
||||
|
||||
// 使用正确的API路径和方法 - PUT请求到/api/shield
|
||||
await apiRequest('/api/shield', 'PUT');
|
||||
|
||||
// 重新加载规则列表
|
||||
await loadRules();
|
||||
|
||||
// 触发数据刷新事件,通知其他模块数据已更新
|
||||
if (typeof window.triggerDataRefresh === 'function') {
|
||||
window.triggerDataRefresh('rules');
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则重新加载成功', 'success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重新加载规则失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('重新加载规则失败', 'danger');
|
||||
}
|
||||
} finally {
|
||||
const rulesPanel = document.getElementById('rules-panel');
|
||||
hideLoading(rulesPanel);
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤规则
|
||||
function filterRules() {
|
||||
const searchTerm = document.getElementById('rule-search').value.toLowerCase();
|
||||
|
||||
if (searchTerm) {
|
||||
filteredRules = rules.filter(rule => rule.toLowerCase().includes(searchTerm));
|
||||
} else {
|
||||
filteredRules = [...rules];
|
||||
}
|
||||
|
||||
currentPage = 1; // 重置为第一页
|
||||
renderRulesList();
|
||||
}
|
||||
|
||||
// HTML转义,防止XSS攻击
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
return text.replace(/[&<>'"]/g, m => map[m]);
|
||||
}
|
||||
|
||||
// 根据规则类型返回对应的CSS类名
|
||||
function getRuleTypeClass(rule) {
|
||||
// 简单的规则类型判断
|
||||
if (rule.startsWith('||') || rule.startsWith('|http')) {
|
||||
return 'rule-type-url';
|
||||
} else if (rule.startsWith('@@')) {
|
||||
return 'rule-type-exception';
|
||||
} else if (rule.startsWith('#')) {
|
||||
return 'rule-type-comment';
|
||||
} else if (rule.includes('$')) {
|
||||
return 'rule-type-filter';
|
||||
}
|
||||
return 'rule-type-standard';
|
||||
}
|
||||
|
||||
// 预处理规则,支持多种规则格式
|
||||
function preprocessRule(rule) {
|
||||
// 移除首尾空白字符
|
||||
let processed = rule.trim();
|
||||
|
||||
// 处理AdGuardHome格式的规则
|
||||
if (processed.startsWith('0.0.0.0 ') || processed.startsWith('127.0.0.1 ')) {
|
||||
const parts = processed.split(' ');
|
||||
if (parts.length >= 2) {
|
||||
// 转换为AdBlock Plus格式
|
||||
processed = '||' + parts[1] + '^';
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// 导出函数,供其他模块调用
|
||||
window.updateRulesCount = function(count) {
|
||||
const rulesCountElement = document.getElementById('rules-count');
|
||||
if (rulesCountElement) {
|
||||
rulesCountElement.textContent = count;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出初始化函数
|
||||
window.initRulesPanel = initRulesPanel;
|
||||
|
||||
// 注册到面板导航系统
|
||||
if (window.registerPanelModule) {
|
||||
window.registerPanelModule('rules-panel', {
|
||||
init: initRulesPanel,
|
||||
refresh: loadRules
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user