重大重构
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +0,0 @@
|
||||
CGO_ENABLED=1 \
|
||||
GOOS=windows \
|
||||
GOARCH=amd64 \
|
||||
CC=gcc \
|
||||
go build -o dns-server.exe main.go
|
||||
@@ -65,7 +65,7 @@
|
||||
"name": "AdGuard DNS filter",
|
||||
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt",
|
||||
"enabled": true,
|
||||
"lastUpdateTime": "2025-12-21T10:46:36.629Z"
|
||||
"lastUpdateTime": "2026-01-23T01:04:32.424Z"
|
||||
},
|
||||
{
|
||||
"name": "Adaway Default Blocklist",
|
||||
@@ -150,7 +150,7 @@
|
||||
"statsSaveInterval": 60
|
||||
},
|
||||
"gfwList": {
|
||||
"ip": "",
|
||||
"ip": "127.0.0.1",
|
||||
"content": "",
|
||||
"enabled": false
|
||||
},
|
||||
|
||||
2572
data/gflist.txt
Normal file
2572
data/gflist.txt
Normal file
File diff suppressed because it is too large
Load Diff
7552
data/gfwlist.txt
Normal file
7552
data/gfwlist.txt
Normal file
File diff suppressed because it is too large
Load Diff
12
download.sh
12
download.sh
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e -f -u -x
|
||||
|
||||
# This script syncs companies DB that we bundle with AdGuard Home. The source
|
||||
# for this database is https://github.com/AdguardTeam/companiesdb.
|
||||
#
|
||||
trackers_url='https://raw.githubusercontent.com/AdguardTeam/companiesdb/main/dist/trackers.json'
|
||||
output='./trackers.json'
|
||||
readonly trackers_url output
|
||||
|
||||
curl -o "$output" -v "$trackers_url"
|
||||
@@ -1,34 +0,0 @@
|
||||
<ul class="f-cb">
|
||||
<li><a href="http://banshi.beijing.gov.cn/" target="_blank">北京市</a></li>
|
||||
<li><a href="https://zwfw.tj.gov.cn/" target="_blank">天津市</a></li>
|
||||
<li><a href="http://www.hbzwfw.gov.cn/" target="_blank">河北省</a></li>
|
||||
<li><a href="http://www.sxzwfw.gov.cn/icity/public/index" target="_blank">山西省</a></li>
|
||||
<li><a href="http://zwfw.nmg.gov.cn" target="_blank">内蒙古自治区</a></li>
|
||||
<li><a href="http://www.lnzwfw.gov.cn" target="_blank">辽宁省</a></li>
|
||||
<li><a href="http://zwfw.jl.gov.cn/jlszwfw/" target="_blank">吉林省</a></li>
|
||||
<li><a href="http://zwfw.hlj.gov.cn/" target="_blank">黑龙江省</a></li>
|
||||
<li><a href="http://zwdt.sh.gov.cn/govPortals/index.do" target="_blank">上海市</a></li>
|
||||
<li><a href="http://www.jszwfw.gov.cn" target="_blank">江苏省</a></li>
|
||||
<li><a href="http://www.zjzwfw.gov.cn" target="_blank">浙江省</a></li>
|
||||
<li><a href="https://www.ahzwfw.gov.cn" target="_blank">安徽省</a></li>
|
||||
<li><a href="http://zwfw.fujian.gov.cn" target="_blank">福建省</a></li>
|
||||
<li><a href="http://www.jxzwfww.gov.cn/" target="_blank">江西省</a></li>
|
||||
<li><a href="https://tysfrz.isdapp.shandong.gov.cn/jpaas-jis-sso-server/sso/entrance/auth-center?appMark=OWWNSJVCC&backUrl=http%3A%2F%2Fwww.shandong.gov.cn%2Fapi-gateway%2Fjpaas-juspace-web-sdywtb%2Ffront%2Fsso%2Flogin-success%3Fgotourl%3DaHR0cDovL3d3dy5zaGFuZG9uZy5nb3YuY24vY29sL2NvbDk0MDkxL2luZGV4Lmh0bWw%3D&userType=1&noLoginBackUrl=http%3A%2F%2Fwww.shandong.gov.cn%2Fcol%2Fcol94091%2Findex.html" target="_blank">山东省</a></li>
|
||||
<li><a href="http://www.hnzwfw.gov.cn" target="_blank">河南省</a></li>
|
||||
<li><a href="http://zwfw.hubei.gov.cn" target="_blank">湖北省</a></li>
|
||||
<li><a href="https://auth.zwfw.hunan.gov.cn/oauth2/authorize?client_id=sXK6HBx3QwuJqaMXqmx2fQ&response_type=redirect&redirect_uri=http://zwfw-new.hunan.gov.cn/" target="_blank">湖南省</a></li>
|
||||
<li><a href="http://www.gdzwfw.gov.cn" target="_blank">广东省</a></li>
|
||||
<li><a href="http://zwfw.gxzf.gov.cn" target="_blank">广西壮族自治区</a></li>
|
||||
<li><a href="https://wssp.hainan.gov.cn/" target="_blank">海南省</a></li>
|
||||
<li><a href="http://zwykb.cq.gov.cn/" target="_blank">重庆市</a></li>
|
||||
<li><a href="http://www.sczwfw.gov.cn" target="_blank">四川省</a></li>
|
||||
<li><a href="https://zwfw.guizhou.gov.cn/index.html" target="_blank">贵州省</a></li>
|
||||
<li><a href="https://zwfw.yn.gov.cn/portal/" target="_blank">云南省</a></li>
|
||||
<li><a href="http://www.xzzwfw.gov.cn" target="_blank">西藏自治区</a></li>
|
||||
<li><a href="https://zwfw.shaanxi.gov.cn/sx/public/index" target="_blank">陕西省</a></li>
|
||||
<li><a href="https://zwfw.gansu.gov.cn/" target="_blank">甘肃省</a></li>
|
||||
<li><a href="https://www.qhzwfw.gov.cn/" target="_blank">青海省</a></li>
|
||||
<li><a href="http://zwfw.nx.gov.cn" target="_blank">宁夏回族自治区</a></li>
|
||||
<li><a href="https://zwfw.xinjiang.gov.cn/" target="_blank">新疆维吾尔自治区</a></li>
|
||||
<li><a target="_blank" href="https://zwfw.xjbt.gov.cn">新疆生产建设兵团</a></li>
|
||||
</ul>
|
||||
@@ -440,18 +440,60 @@
|
||||
},
|
||||
"company": "广州市动景计算机科技有限公司"
|
||||
},
|
||||
"腾讯": {
|
||||
"微信": {
|
||||
"name": "微信",
|
||||
"categoryId": 7,
|
||||
"url": {
|
||||
"1": "https://wx.qq.com/",
|
||||
"2": "https://weixin.qq.com/",
|
||||
"3": "https://res.wx.qq.com/",
|
||||
"4": "dns.weixin.qq.com",
|
||||
"5": "pc.weixin.qq.com"
|
||||
"腾讯系列": {
|
||||
"微信相关": {
|
||||
"微信": {
|
||||
"name": "微信",
|
||||
"categoryId": 7,
|
||||
"url": {
|
||||
"1": "https://wx.qq.com/",
|
||||
"2": "https://weixin.qq.com/",
|
||||
"3": "https://res.wx.qq.com/",
|
||||
"4": "dns.weixin.qq.com",
|
||||
"5": "pc.weixin.qq.com"
|
||||
},
|
||||
"icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico"
|
||||
},
|
||||
"icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico"
|
||||
"微信公众号文章缩略图CDN节点": {
|
||||
"name": "微信公众号文章缩略图与广告图片分发CDN节点",
|
||||
"categoryId": 2,
|
||||
"url": "wxsnsdythumb.wxs.qq.com",
|
||||
"icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico"
|
||||
},
|
||||
"微信公众号动态内容主CDN节点": {
|
||||
"name": "微信公众号动态内容主CDN/文章正文图片素材分发",
|
||||
"categoryId": 2,
|
||||
"url": "wxsnsdy.wxs.qq.com",
|
||||
"icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico"
|
||||
},
|
||||
"微信公众号内容备用CDN节点": {
|
||||
"name": "微信公众号内容备用CDN/图片资源访问分流服务",
|
||||
"categoryId": 2,
|
||||
"url": "snsdy.tc.qq.com",
|
||||
"icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico"
|
||||
},
|
||||
"微信生态视频缩略图CDN节点": {
|
||||
"name": "微信生态视频缩略图CDN/公众号视频号封面截图分发",
|
||||
"categoryId": 2,
|
||||
"url": "vweixinthumb.tc.qq.com",
|
||||
"icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico"
|
||||
}
|
||||
},
|
||||
"腾讯视频相关": {
|
||||
"腾讯视频图片缓存CDN节点": {
|
||||
"name": "腾讯视频封面、海报、截图等静态图片CDN分发节点",
|
||||
"categoryId": 2,
|
||||
"url": "imgcache.tc.qq.com",
|
||||
"icon": "https://v.qq.com/favicon.ico"
|
||||
}
|
||||
},
|
||||
"QQ音乐相关": {
|
||||
"QQ音乐音频资源CDN节点": {
|
||||
"name": "QQ音乐音频资源与封面图片CDN分发节点",
|
||||
"categoryId": 2,
|
||||
"url": "tsmusic.tc.qq.com",
|
||||
"icon": "https://y.qq.com/favicon.ico"
|
||||
}
|
||||
},
|
||||
"腾讯会议核心代理服务": {
|
||||
"name": "腾讯会议核心代理服务",
|
||||
|
||||
@@ -14312,6 +14312,13 @@
|
||||
"companyId": "tencent",
|
||||
"source": "AdGuard"
|
||||
},
|
||||
"weixin":{
|
||||
"name": "微信广告",
|
||||
"categoryId": 12,
|
||||
"url": "https://wxsnsdythumb.wxs.qq.com",
|
||||
"companyId": "tencent",
|
||||
"source": "本地"
|
||||
},
|
||||
"qrius": {
|
||||
"name": "Qrius",
|
||||
"categoryId": 7,
|
||||
|
||||
@@ -34,7 +34,7 @@ let errorQueries = 0;
|
||||
// 初始化仪表盘
|
||||
async function initDashboard() {
|
||||
try {
|
||||
console.log('页面打开时强制刷新数据...');
|
||||
|
||||
|
||||
// 优先加载初始数据,确保页面显示最新信息
|
||||
await loadDashboardData();
|
||||
@@ -63,14 +63,12 @@ function connectWebSocket() {
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${wsProtocol}//${window.location.host}/ws/stats`;
|
||||
|
||||
console.log('正在连接WebSocket:', wsUrl);
|
||||
|
||||
// 创建WebSocket连接
|
||||
dashboardWsConnection = new WebSocket(wsUrl);
|
||||
|
||||
// 连接打开事件
|
||||
dashboardWsConnection.onopen = function() {
|
||||
console.log('WebSocket连接已建立');
|
||||
showNotification('数据更新成功', 'success');
|
||||
|
||||
// 清除重连计时器
|
||||
@@ -86,7 +84,6 @@ function connectWebSocket() {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'initial_data' || data.type === 'stats_update') {
|
||||
console.log('收到实时数据更新');
|
||||
processRealTimeData(data.data);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -122,7 +119,6 @@ function setupReconnect() {
|
||||
}
|
||||
|
||||
const reconnectDelay = 5000; // 5秒后重连
|
||||
console.log(`将在${reconnectDelay}ms后尝试重新连接WebSocket`);
|
||||
|
||||
dashboardWsReconnectTimer = setTimeout(() => {
|
||||
connectWebSocket();
|
||||
@@ -139,7 +135,6 @@ function processRealTimeData(stats) {
|
||||
lastProcessedTime = now;
|
||||
|
||||
try {
|
||||
console.log('收到实时数据:', stats);
|
||||
|
||||
// 确保stats是有效的对象
|
||||
if (!stats || typeof stats !== 'object') {
|
||||
@@ -179,15 +174,6 @@ function processRealTimeData(stats) {
|
||||
allowedQueries = Number(stats.allowedQueries) || 0;
|
||||
}
|
||||
|
||||
console.log('实时数据处理完成,更新后的值:', {
|
||||
totalQueries,
|
||||
allowedQueries,
|
||||
blockedQueries,
|
||||
errorQueries
|
||||
});
|
||||
|
||||
|
||||
|
||||
if (document.getElementById('top-query-type')) {
|
||||
const queryType = stats.topQueryType || '---';
|
||||
document.getElementById('top-query-type').textContent = queryType;
|
||||
@@ -472,7 +458,6 @@ function cleanupResources() {
|
||||
|
||||
// 更新统计卡片
|
||||
function updateStatsCards(stats) {
|
||||
console.log('更新统计卡片,收到数据:', stats);
|
||||
|
||||
// 适配不同的数据结构
|
||||
// 保存当前显示的值,用于在数据缺失时保留
|
||||
@@ -876,7 +861,6 @@ function updateStatsCards(stats) {
|
||||
|
||||
// 更新Top屏蔽域名表格
|
||||
async function updateTopBlockedTable(domains) {
|
||||
console.log('更新Top屏蔽域名表格,收到数据:', domains);
|
||||
const tableBody = document.getElementById('top-blocked-table');
|
||||
|
||||
let tableData = [];
|
||||
@@ -902,7 +886,6 @@ async function updateTopBlockedTable(domains) {
|
||||
{ name: '---.---.---', count: '---' },
|
||||
{ name: '---.---.---', count: '---' }
|
||||
];
|
||||
console.log('使用示例数据填充Top屏蔽域名表格');
|
||||
}
|
||||
|
||||
// 计算总拦截次数
|
||||
@@ -985,12 +968,10 @@ async function updateTopBlockedTable(domains) {
|
||||
|
||||
// 更新最近屏蔽域名表格
|
||||
function updateRecentBlockedTable(domains) {
|
||||
console.log('更新最近屏蔽域名表格,收到数据:', domains);
|
||||
const tableBody = document.getElementById('recent-blocked-table');
|
||||
|
||||
// 确保tableBody存在,因为最近屏蔽域名卡片可能已被移除
|
||||
if (!tableBody) {
|
||||
console.log('未找到recent-blocked-table元素,跳过更新');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1015,7 +996,6 @@ function updateRecentBlockedTable(domains) {
|
||||
{ name: '---.---.---', timestamp: now - 45 * 60 * 1000, type: '追踪' },
|
||||
{ name: '---.---.---', timestamp: now - 60 * 60 * 1000, type: '恶意' }
|
||||
];
|
||||
console.log('使用示例数据填充最近屏蔽域名表格');
|
||||
}
|
||||
|
||||
let html = '';
|
||||
@@ -1217,7 +1197,6 @@ function isPrivateIP(ip) {
|
||||
|
||||
// 更新TOP客户端表格
|
||||
async function updateTopClientsTable(clients) {
|
||||
console.log('更新TOP客户端表格,收到数据:', clients);
|
||||
const tableBody = document.getElementById('top-clients-table');
|
||||
|
||||
// 确保tableBody存在
|
||||
@@ -1260,7 +1239,6 @@ async function updateTopClientsTable(clients) {
|
||||
{ ip: '---.---.---', count: '---' },
|
||||
{ ip: '---.---.---', count: '---' }
|
||||
];
|
||||
console.log('使用示例数据填充TOP客户端表格');
|
||||
}
|
||||
|
||||
|
||||
@@ -1291,7 +1269,6 @@ async function updateTopClientsTable(clients) {
|
||||
|
||||
// 更新请求域名排行表格
|
||||
async function updateTopDomainsTable(domains) {
|
||||
console.log('更新请求域名排行表格,收到数据:', domains);
|
||||
const tableBody = document.getElementById('top-domains-table');
|
||||
|
||||
// 确保tableBody存在
|
||||
@@ -1325,7 +1302,6 @@ async function updateTopDomainsTable(domains) {
|
||||
{ name: 'twitter.com', count: 35 },
|
||||
{ name: 'youtube.com', count: 30 }
|
||||
];
|
||||
console.log('使用示例数据填充请求域名排行表格');
|
||||
}
|
||||
|
||||
// 计算总请求次数
|
||||
@@ -1415,7 +1391,6 @@ let detailedCurrentTimeRange = '24h'; // 详细图表当前时间范围
|
||||
|
||||
// 初始化时间范围切换
|
||||
function initTimeRangeToggle() {
|
||||
console.log('初始化时间范围切换');
|
||||
// 查找所有可能的时间范围按钮类名
|
||||
const allTimeRangeButtons = document.querySelectorAll('.time-range-btn, .time-range-button, .timerange-btn, button[data-range]');
|
||||
|
||||
@@ -1426,7 +1401,6 @@ function initTimeRangeToggle() {
|
||||
return !chartModal || !chartModal.contains(button);
|
||||
});
|
||||
|
||||
console.log('找到时间范围按钮数量:', timeRangeButtons.length, '排除了图表模态框内的按钮');
|
||||
|
||||
if (timeRangeButtons.length === 0) {
|
||||
console.warn('未找到时间范围按钮,请检查HTML中的类名');
|
||||
@@ -1471,13 +1445,11 @@ function initTimeRangeToggle() {
|
||||
|
||||
// 移除鼠标悬停提示
|
||||
|
||||
console.log('为按钮设置初始样式:', button.textContent.trim(), '索引:', index, '类名:', Array.from(button.classList).join(', '));
|
||||
|
||||
button.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
console.log('点击按钮:', button.textContent.trim(), '索引:', index);
|
||||
|
||||
// 重置所有按钮为非选中状态
|
||||
timeRangeButtons.forEach((btn, btnIndex) => {
|
||||
@@ -1520,7 +1492,6 @@ function initTimeRangeToggle() {
|
||||
}
|
||||
}
|
||||
currentTimeRange = rangeValue;
|
||||
console.log('更新时间范围为:', currentTimeRange);
|
||||
|
||||
// 重新加载数据
|
||||
loadDashboardData();
|
||||
@@ -1554,7 +1525,6 @@ function initTimeRangeToggle() {
|
||||
defaultButton.classList.add('active');
|
||||
defaultButton.classList.add(...defaultStyle.active);
|
||||
defaultButton.classList.add(...defaultStyle.activeHover);
|
||||
console.log('默认选中24小时按钮:', defaultButton.textContent.trim());
|
||||
|
||||
// 设置默认时间范围为24小时
|
||||
currentTimeRange = '24h';
|
||||
@@ -1877,12 +1847,10 @@ function initExpandButton() {
|
||||
const closeModalBtn = document.getElementById('close-modal-btn'); // 修复ID匹配
|
||||
|
||||
// 添加调试日志
|
||||
console.log('初始化展开按钮功能:', { expandBtn, chartModal, closeModalBtn });
|
||||
|
||||
if (expandBtn && chartModal && closeModalBtn) {
|
||||
// 展开按钮点击事件
|
||||
expandBtn.addEventListener('click', () => {
|
||||
console.log('展开按钮被点击');
|
||||
// 显示浮窗
|
||||
chartModal.classList.remove('hidden');
|
||||
|
||||
@@ -1902,7 +1870,6 @@ function initExpandButton() {
|
||||
|
||||
// 关闭按钮点击事件
|
||||
closeModalBtn.addEventListener('click', () => {
|
||||
console.log('关闭按钮被点击');
|
||||
chartModal.classList.add('hidden');
|
||||
});
|
||||
|
||||
@@ -1910,7 +1877,6 @@ function initExpandButton() {
|
||||
chartModal.addEventListener('click', (e) => {
|
||||
// 检查点击目标是否是遮罩层本身(即最外层div)
|
||||
if (e.target === chartModal) {
|
||||
console.log('点击遮罩层关闭');
|
||||
chartModal.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
@@ -1918,7 +1884,6 @@ function initExpandButton() {
|
||||
// ESC键关闭浮窗
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && !chartModal.classList.contains('hidden')) {
|
||||
console.log('ESC键关闭浮窗');
|
||||
chartModal.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
@@ -1933,7 +1898,6 @@ function initDetailedTimeRangeToggle() {
|
||||
const chartModal = document.getElementById('chart-modal');
|
||||
const detailedTimeRangeButtons = chartModal ? chartModal.querySelectorAll('.time-range-btn') : [];
|
||||
|
||||
console.log('初始化详细图表时间范围切换,找到按钮数量:', detailedTimeRangeButtons.length);
|
||||
|
||||
// 初始化详细图表的默认状态,与主图表保持一致
|
||||
detailedCurrentTimeRange = currentTimeRange;
|
||||
@@ -2016,7 +1980,6 @@ function initDetailedTimeRangeToggle() {
|
||||
}
|
||||
}
|
||||
detailedCurrentTimeRange = rangeValue;
|
||||
console.log('详细图表更新时间范围为:', detailedCurrentTimeRange);
|
||||
|
||||
// 重新绘制详细图表
|
||||
drawDetailedDNSRequestsChart();
|
||||
@@ -2026,7 +1989,6 @@ function initDetailedTimeRangeToggle() {
|
||||
|
||||
// 绘制详细的DNS请求趋势图表
|
||||
function drawDetailedDNSRequestsChart() {
|
||||
console.log('绘制详细DNS请求趋势图表,时间范围:', detailedCurrentTimeRange);
|
||||
|
||||
const ctx = document.getElementById('detailed-dns-requests-chart');
|
||||
if (!ctx) {
|
||||
@@ -2270,8 +2232,6 @@ function drawDNSRequestsChart() {
|
||||
|
||||
// 更新图表数据
|
||||
function updateCharts(stats, queryTypeStats) {
|
||||
console.log('更新图表,收到统计数据:', stats);
|
||||
console.log('查询类型统计数据:', queryTypeStats);
|
||||
|
||||
// 空值检查
|
||||
if (!stats) {
|
||||
@@ -2497,7 +2457,6 @@ function getHostsCountFromStats(stats) {
|
||||
|
||||
// 初始化统计卡片折线图
|
||||
function initStatCardCharts() {
|
||||
console.log('===== 开始初始化统计卡片折线图 =====');
|
||||
|
||||
// 清理已存在的图表实例
|
||||
for (const key in statCardCharts) {
|
||||
@@ -2509,7 +2468,6 @@ function initStatCardCharts() {
|
||||
statCardHistoryData = {};
|
||||
|
||||
// 检查Chart.js是否加载
|
||||
console.log('Chart.js是否可用:', typeof Chart !== 'undefined');
|
||||
|
||||
// 统计卡片配置信息
|
||||
const cardConfigs = [
|
||||
@@ -2525,7 +2483,6 @@ function initStatCardCharts() {
|
||||
{ id: 'hosts-chart', color: '#16a085', label: 'Hosts条目数' }
|
||||
];
|
||||
|
||||
console.log('图表配置:', cardConfigs);
|
||||
|
||||
cardConfigs.forEach(config => {
|
||||
const canvas = document.getElementById(config.id);
|
||||
@@ -3011,7 +2968,6 @@ function addRetryEventListeners() {
|
||||
const retryTopClientsBtn = document.getElementById('retry-top-clients');
|
||||
if (retryTopClientsBtn) {
|
||||
retryTopClientsBtn.addEventListener('click', async () => {
|
||||
console.log('重试获取TOP客户端数据');
|
||||
const clientsData = await api.getTopClients();
|
||||
if (clientsData && !clientsData.error && Array.isArray(clientsData) && clientsData.length > 0) {
|
||||
// 使用真实数据
|
||||
@@ -3030,7 +2986,6 @@ function addRetryEventListeners() {
|
||||
const retryTopDomainsBtn = document.getElementById('retry-top-domains');
|
||||
if (retryTopDomainsBtn) {
|
||||
retryTopDomainsBtn.addEventListener('click', async () => {
|
||||
console.log('重试获取TOP域名数据');
|
||||
const domainsData = await api.getTopDomains();
|
||||
if (domainsData && !domainsData.error && Array.isArray(domainsData) && domainsData.length > 0) {
|
||||
// 使用真实数据
|
||||
@@ -3068,7 +3023,6 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
});// 重写loadDashboardData函数,修复语法错误
|
||||
async function loadDashboardData() {
|
||||
console.log('开始加载仪表盘数据');
|
||||
try {
|
||||
// 并行获取所有数据,提高加载效率
|
||||
const [stats, queryTypeStatsResult, topBlockedDomainsResult, recentBlockedDomainsResult, topClientsResult] = await Promise.all([
|
||||
@@ -3093,26 +3047,22 @@ async function loadDashboardData() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('统计数据:', stats);
|
||||
|
||||
// 处理查询类型统计数据
|
||||
let queryTypeStats = null;
|
||||
if (queryTypeStatsResult) {
|
||||
console.log('查询类型统计数据:', queryTypeStatsResult);
|
||||
queryTypeStats = queryTypeStatsResult;
|
||||
} else if (stats.dns && stats.dns.QueryTypes) {
|
||||
queryTypeStats = Object.entries(stats.dns.QueryTypes).map(([type, count]) => ({
|
||||
type,
|
||||
count
|
||||
}));
|
||||
console.log('从stats中提取的查询类型统计:', queryTypeStats);
|
||||
}
|
||||
|
||||
// 处理TOP被屏蔽域名
|
||||
let topBlockedDomains = [];
|
||||
if (topBlockedDomainsResult && Array.isArray(topBlockedDomainsResult)) {
|
||||
topBlockedDomains = topBlockedDomainsResult;
|
||||
console.log('TOP被屏蔽域名:', topBlockedDomains);
|
||||
} else {
|
||||
topBlockedDomains = [
|
||||
{ domain: 'example-blocked.com', count: 15, lastSeen: new Date().toISOString() },
|
||||
@@ -3125,7 +3075,6 @@ async function loadDashboardData() {
|
||||
let recentBlockedDomains = [];
|
||||
if (recentBlockedDomainsResult && Array.isArray(recentBlockedDomainsResult)) {
|
||||
recentBlockedDomains = recentBlockedDomainsResult;
|
||||
console.log('最近屏蔽域名:', recentBlockedDomains);
|
||||
} else {
|
||||
recentBlockedDomains = [
|
||||
{ domain: '---.---.---', ip: '---.---.---.---', timestamp: new Date().toISOString() },
|
||||
@@ -3145,7 +3094,6 @@ async function loadDashboardData() {
|
||||
let topClients = [];
|
||||
if (topClientsResult && !topClientsResult.error && Array.isArray(topClientsResult) && topClientsResult.length > 0) {
|
||||
topClients = topClientsResult;
|
||||
console.log('TOP客户端:', topClients);
|
||||
} else {
|
||||
console.warn('获取TOP客户端失败或数据无效,使用模拟数据');
|
||||
topClients = [
|
||||
@@ -3162,7 +3110,6 @@ async function loadDashboardData() {
|
||||
let topDomains = [];
|
||||
try {
|
||||
const domainsData = await api.getTopDomains();
|
||||
console.log('TOP域名:', domainsData);
|
||||
|
||||
if (domainsData && !domainsData.error && Array.isArray(domainsData) && domainsData.length > 0) {
|
||||
topDomains = domainsData;
|
||||
|
||||
@@ -7,7 +7,7 @@ let logsPerPage = 30; // 默认显示30条记录
|
||||
let currentFilter = '';
|
||||
let currentSearch = '';
|
||||
let logsChart = null;
|
||||
let currentSortField = '';
|
||||
let currentSortField = 'timestamp'; // 默认按时间排序,显示最新记录
|
||||
let currentSortDirection = 'desc'; // 默认降序
|
||||
|
||||
// IP地理位置缓存(检查是否已经存在,避免重复声明)
|
||||
@@ -165,15 +165,12 @@ async function loadTrackersDatabase() {
|
||||
|
||||
// 加载域名信息数据库
|
||||
async function loadDomainInfoDatabase() {
|
||||
console.log('开始加载域名信息数据库');
|
||||
|
||||
if (domainInfoLoaded) {
|
||||
console.log('域名信息数据库已加载,直接返回');
|
||||
return domainInfoDatabase;
|
||||
}
|
||||
|
||||
if (domainInfoLoading) {
|
||||
console.log('域名信息数据库正在加载中,等待完成');
|
||||
// 等待正在进行的加载完成
|
||||
while (domainInfoLoading) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
@@ -184,7 +181,6 @@ async function loadDomainInfoDatabase() {
|
||||
domainInfoLoading = true;
|
||||
|
||||
try {
|
||||
console.log('发起请求获取域名信息数据库');
|
||||
const response = await fetch('domain-info/domains/domain-info.json');
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -194,9 +190,7 @@ async function loadDomainInfoDatabase() {
|
||||
return domainInfoDatabase;
|
||||
}
|
||||
|
||||
console.log('域名信息数据库请求成功,开始解析JSON');
|
||||
domainInfoDatabase = await response.json();
|
||||
console.log('域名信息数据库解析成功,包含', Object.keys(domainInfoDatabase.domains || {}).length, '个公司');
|
||||
domainInfoLoaded = true;
|
||||
return domainInfoDatabase;
|
||||
} catch (error) {
|
||||
@@ -206,7 +200,6 @@ async function loadDomainInfoDatabase() {
|
||||
return domainInfoDatabase;
|
||||
} finally {
|
||||
domainInfoLoading = false;
|
||||
console.log('域名信息数据库加载完成');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,10 +240,8 @@ async function isDomainInTrackerDatabase(domain) {
|
||||
|
||||
// 根据域名查找对应的网站信息
|
||||
async function getDomainInfo(domain) {
|
||||
console.log('开始查找域名信息,域名:', domain);
|
||||
|
||||
if (!domainInfoDatabase || !domainInfoLoaded) {
|
||||
console.log('域名信息数据库未加载,调用loadDomainInfoDatabase');
|
||||
await loadDomainInfoDatabase();
|
||||
}
|
||||
|
||||
@@ -261,29 +252,23 @@ async function getDomainInfo(domain) {
|
||||
|
||||
// 规范化域名,移除可能的端口号
|
||||
const normalizedDomain = domain.replace(/:\d+$/, '').toLowerCase();
|
||||
console.log('规范化后的域名:', normalizedDomain);
|
||||
|
||||
// 遍历所有公司
|
||||
console.log('开始遍历公司,总公司数:', Object.keys(domainInfoDatabase.domains).length);
|
||||
for (const companyKey in domainInfoDatabase.domains) {
|
||||
if (domainInfoDatabase.domains.hasOwnProperty(companyKey)) {
|
||||
console.log('检查公司:', companyKey);
|
||||
const companyData = domainInfoDatabase.domains[companyKey];
|
||||
const companyName = companyData.company || companyKey;
|
||||
|
||||
// 遍历公司下的所有网站和类别
|
||||
for (const websiteKey in companyData) {
|
||||
if (companyData.hasOwnProperty(websiteKey) && websiteKey !== 'company') {
|
||||
console.log(' 检查网站/类别:', websiteKey);
|
||||
const website = companyData[websiteKey];
|
||||
|
||||
// 如果有URL属性,直接检查域名
|
||||
if (website.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof website.url === 'string') {
|
||||
console.log(' 检查字符串URL:', website.url);
|
||||
if (isDomainMatch(website.url, normalizedDomain, website.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: website.name,
|
||||
icon: website.icon,
|
||||
@@ -295,13 +280,10 @@ async function getDomainInfo(domain) {
|
||||
}
|
||||
// 处理对象类型的URL
|
||||
else if (typeof website.url === 'object') {
|
||||
console.log(' 检查对象类型URL,包含', Object.keys(website.url).length, '个URL');
|
||||
for (const urlKey in website.url) {
|
||||
if (website.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = website.url[urlKey];
|
||||
console.log(' 检查URL', urlKey, ':', urlValue);
|
||||
if (isDomainMatch(urlValue, normalizedDomain, website.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: website.name,
|
||||
icon: website.icon,
|
||||
@@ -315,18 +297,14 @@ async function getDomainInfo(domain) {
|
||||
}
|
||||
} else if (typeof website === 'object' && website !== null) {
|
||||
// 没有URL属性,可能是嵌套的类别
|
||||
console.log(' 发现嵌套类别,进一步检查');
|
||||
for (const nestedWebsiteKey in website) {
|
||||
if (website.hasOwnProperty(nestedWebsiteKey) && nestedWebsiteKey !== 'company') {
|
||||
console.log(' 检查嵌套网站/类别:', nestedWebsiteKey);
|
||||
const nestedWebsite = website[nestedWebsiteKey];
|
||||
|
||||
if (nestedWebsite.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof nestedWebsite.url === 'string') {
|
||||
console.log(' 检查字符串URL:', nestedWebsite.url);
|
||||
if (isDomainMatch(nestedWebsite.url, normalizedDomain, nestedWebsite.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: nestedWebsite.name,
|
||||
icon: nestedWebsite.icon,
|
||||
@@ -338,13 +316,10 @@ async function getDomainInfo(domain) {
|
||||
}
|
||||
// 处理对象类型的URL
|
||||
else if (typeof nestedWebsite.url === 'object') {
|
||||
console.log(' 检查对象类型URL,包含', Object.keys(nestedWebsite.url).length, '个URL');
|
||||
for (const urlKey in nestedWebsite.url) {
|
||||
if (nestedWebsite.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = nestedWebsite.url[urlKey];
|
||||
console.log(' 检查URL', urlKey, ':', urlValue);
|
||||
if (isDomainMatch(urlValue, normalizedDomain, nestedWebsite.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: nestedWebsite.name,
|
||||
icon: nestedWebsite.icon,
|
||||
@@ -358,18 +333,14 @@ async function getDomainInfo(domain) {
|
||||
}
|
||||
} else if (typeof nestedWebsite === 'object' && nestedWebsite !== null) {
|
||||
// 嵌套类别中的嵌套类别,递归检查
|
||||
console.log(' 发现二级嵌套类别,进一步检查');
|
||||
for (const secondNestedWebsiteKey in nestedWebsite) {
|
||||
if (nestedWebsite.hasOwnProperty(secondNestedWebsiteKey) && secondNestedWebsiteKey !== 'company') {
|
||||
console.log(' 检查二级嵌套网站:', secondNestedWebsiteKey);
|
||||
const secondNestedWebsite = nestedWebsite[secondNestedWebsiteKey];
|
||||
|
||||
if (secondNestedWebsite.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof secondNestedWebsite.url === 'string') {
|
||||
console.log(' 检查字符串URL:', secondNestedWebsite.url);
|
||||
if (isDomainMatch(secondNestedWebsite.url, normalizedDomain, secondNestedWebsite.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: secondNestedWebsite.name,
|
||||
icon: secondNestedWebsite.icon,
|
||||
@@ -381,13 +352,10 @@ async function getDomainInfo(domain) {
|
||||
}
|
||||
// 处理对象类型的URL
|
||||
else if (typeof secondNestedWebsite.url === 'object') {
|
||||
console.log(' 检查对象类型URL,包含', Object.keys(secondNestedWebsite.url).length, '个URL');
|
||||
for (const urlKey in secondNestedWebsite.url) {
|
||||
if (secondNestedWebsite.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = secondNestedWebsite.url[urlKey];
|
||||
console.log(' 检查URL', urlKey, ':', urlValue);
|
||||
if (isDomainMatch(urlValue, normalizedDomain, secondNestedWebsite.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: secondNestedWebsite.name,
|
||||
icon: secondNestedWebsite.icon,
|
||||
@@ -403,83 +371,67 @@ async function getDomainInfo(domain) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(' 嵌套网站没有URL属性且不是对象类型');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(' 网站没有URL属性');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('未找到匹配的域名信息');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查域名是否匹配
|
||||
function isDomainMatch(urlValue, targetDomain, categoryId) {
|
||||
console.log(' 开始匹配URL:', urlValue, '目标域名:', targetDomain, '类别ID:', categoryId);
|
||||
|
||||
// 规范化目标域名,去除末尾的点
|
||||
const normalizedTargetDomain = targetDomain.replace(/\.$/, '').toLowerCase();
|
||||
|
||||
try {
|
||||
// 尝试将URL值解析为完整URL
|
||||
console.log(' 尝试解析URL为完整URL');
|
||||
const url = new URL(urlValue);
|
||||
let hostname = url.hostname.toLowerCase();
|
||||
// 规范化主机名,去除末尾的点
|
||||
hostname = hostname.replace(/\.$/, '');
|
||||
console.log(' 解析成功,主机名:', hostname, '规范化目标域名:', normalizedTargetDomain);
|
||||
|
||||
// 根据类别ID选择匹配方式
|
||||
if (categoryId === 2) {
|
||||
// CDN类别,使用域名后缀匹配
|
||||
if (normalizedTargetDomain.endsWith('.' + hostname) || normalizedTargetDomain === hostname) {
|
||||
console.log(' CDN域名后缀匹配成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' CDN域名后缀不匹配');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 其他类别,使用完整域名匹配
|
||||
if (hostname === normalizedTargetDomain) {
|
||||
console.log(' 完整域名匹配成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' 完整域名不匹配');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(' 解析URL失败,将其视为纯域名处理,错误信息:', e.message);
|
||||
// 如果是纯域名而不是完整URL
|
||||
let urlDomain = urlValue.toLowerCase();
|
||||
// 规范化纯域名,去除末尾的点
|
||||
urlDomain = urlDomain.replace(/\.$/, '');
|
||||
console.log(' 处理为纯域名:', urlDomain, '规范化目标域名:', normalizedTargetDomain);
|
||||
|
||||
// 根据类别ID选择匹配方式
|
||||
if (categoryId === 2) {
|
||||
// CDN类别,使用域名后缀匹配
|
||||
if (normalizedTargetDomain.endsWith('.' + urlDomain) || normalizedTargetDomain === urlDomain) {
|
||||
console.log(' CDN域名后缀匹配成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' CDN域名后缀不匹配');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 其他类别,使用完整域名匹配
|
||||
if (urlDomain === normalizedTargetDomain) {
|
||||
console.log(' 完整域名匹配成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' 完整域名不匹配');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -488,42 +440,34 @@ function isDomainMatch(urlValue, targetDomain, categoryId) {
|
||||
|
||||
// 提取主域名
|
||||
function extractPrimaryDomain(domain) {
|
||||
console.log(' 开始提取主域名,原始域名:', domain);
|
||||
|
||||
const parts = domain.split('.');
|
||||
console.log(' 域名分割为:', parts);
|
||||
|
||||
if (parts.length <= 2) {
|
||||
console.log(' 域名长度小于等于2,直接返回:', domain);
|
||||
return domain;
|
||||
}
|
||||
|
||||
// 处理常见的三级域名
|
||||
const commonSubdomains = ['www', 'mail', 'news', 'map', 'image', 'video', 'cdn', 'api', 'blog', 'shop', 'cloud', 'docs', 'help', 'support', 'dev', 'test', 'staging'];
|
||||
console.log(' 检查是否为常见三级域名');
|
||||
|
||||
if (commonSubdomains.includes(parts[0])) {
|
||||
const result = parts.slice(1).join('.');
|
||||
console.log(' 是常见三级域名,返回:', result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 处理特殊情况,如co.uk, co.jp等
|
||||
const countryTLDs = ['co.uk', 'co.jp', 'co.kr', 'co.in', 'co.ca', 'co.au', 'co.nz', 'co.th', 'co.sg', 'co.my', 'co.id', 'co.za', 'com.cn', 'org.cn', 'net.cn', 'gov.cn', 'edu.cn'];
|
||||
console.log(' 检查是否为特殊国家TLD');
|
||||
|
||||
for (const tld of countryTLDs) {
|
||||
if (domain.endsWith('.' + tld)) {
|
||||
const mainParts = domain.split('.');
|
||||
const result = mainParts.slice(-tld.split('.').length - 1).join('.');
|
||||
console.log(' 是特殊国家TLD,返回:', result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认情况:返回最后两个部分
|
||||
const result = parts.slice(-2).join('.');
|
||||
console.log(' 默认情况,返回最后两个部分:', result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -793,7 +737,6 @@ function initResizableColumns() {
|
||||
|
||||
// 初始化查询日志页面
|
||||
function initLogsPage() {
|
||||
console.log('初始化查询日志页面');
|
||||
|
||||
// 加载日志统计数据
|
||||
loadLogsStats();
|
||||
@@ -992,6 +935,9 @@ function bindLogsEvents() {
|
||||
loadLogs();
|
||||
});
|
||||
});
|
||||
|
||||
// 初始化排序图标
|
||||
updateSortIcons();
|
||||
}
|
||||
|
||||
// 更新排序图标
|
||||
@@ -1345,7 +1291,6 @@ async function updateLogsTable(logs) {
|
||||
if (e.target.closest('button')) {
|
||||
return;
|
||||
}
|
||||
console.log('Row clicked, log object:', log);
|
||||
showLogDetailModal(log);
|
||||
});
|
||||
|
||||
@@ -1479,14 +1424,12 @@ function connectLogsWebSocket() {
|
||||
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${wsProtocol}//${window.location.host}/ws/stats`;
|
||||
|
||||
console.log('正在连接WebSocket:', wsUrl);
|
||||
|
||||
// 创建WebSocket连接
|
||||
logsWsConnection = new WebSocket(wsUrl);
|
||||
|
||||
// 连接打开事件
|
||||
logsWsConnection.onopen = function() {
|
||||
console.log('WebSocket连接已建立');
|
||||
};
|
||||
|
||||
// 接收消息事件
|
||||
@@ -1495,7 +1438,6 @@ function connectLogsWebSocket() {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === 'initial_data' || data.type === 'stats_update') {
|
||||
console.log('收到实时数据更新');
|
||||
// 只更新统计数据,不更新日志详情
|
||||
updateLogsStatsFromWebSocket(data.data);
|
||||
}
|
||||
@@ -1530,7 +1472,6 @@ function setupLogsReconnect() {
|
||||
}
|
||||
|
||||
const reconnectDelay = 5000; // 5秒后重连
|
||||
console.log(`将在${reconnectDelay}ms后尝试重新连接WebSocket`);
|
||||
|
||||
logsWsReconnectTimer = setTimeout(() => {
|
||||
connectLogsWebSocket();
|
||||
@@ -1567,17 +1508,13 @@ function updateLogsStatsFromWebSocket(stats) {
|
||||
// 拦截域名
|
||||
async function blockDomain(domain) {
|
||||
try {
|
||||
console.log(`开始拦截域名: ${domain}`);
|
||||
|
||||
// 创建拦截规则,使用AdBlock Plus格式
|
||||
const blockRule = `||${domain}^`;
|
||||
console.log(`创建的拦截规则: ${blockRule}`);
|
||||
|
||||
// 调用API添加拦截规则
|
||||
console.log(`调用API添加拦截规则,路径: /shield, 方法: POST`);
|
||||
const response = await apiRequest('/shield', 'POST', { rule: blockRule });
|
||||
|
||||
console.log(`API响应:`, response);
|
||||
|
||||
// 处理不同的响应格式
|
||||
if (response && (response.success || response.status === 'success')) {
|
||||
@@ -1665,17 +1602,13 @@ async function refreshRulesList() {
|
||||
// 放行域名
|
||||
async function unblockDomain(domain) {
|
||||
try {
|
||||
console.log(`开始放行域名: ${domain}`);
|
||||
|
||||
// 创建放行规则,使用AdBlock Plus格式
|
||||
const allowRule = `@@||${domain}^`;
|
||||
console.log(`创建的放行规则: ${allowRule}`);
|
||||
|
||||
// 调用API添加放行规则
|
||||
console.log(`调用API添加放行规则,路径: /shield, 方法: POST`);
|
||||
const response = await apiRequest('/shield', 'POST', { rule: allowRule });
|
||||
|
||||
console.log(`API响应:`, response);
|
||||
|
||||
// 处理不同的响应格式
|
||||
if (response && (response.success || response.status === 'success')) {
|
||||
@@ -1808,7 +1741,6 @@ function formatDNSString(str) {
|
||||
|
||||
// 显示日志详情弹窗
|
||||
async function showLogDetailModal(log) {
|
||||
console.log('showLogDetailModal called with log:', JSON.stringify(log, null, 2)); // 输出完整的log对象
|
||||
|
||||
if (!log) {
|
||||
console.error('No log data provided!');
|
||||
|
||||
@@ -1,488 +0,0 @@
|
||||
/* 基础样式 */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #ffffff;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 默认浅色主题样式 */
|
||||
.swagger-ui .topbar {
|
||||
background-color: #2c3e50;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.swagger-ui .topbar .topbar-wrapper .link {
|
||||
color: #ecf0f1;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.swagger-ui .info {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.swagger-ui .info .title {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.swagger-ui .info .description {
|
||||
font-size: 1rem;
|
||||
color: #555;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* 修复服务器URL输入框样式 */
|
||||
.swagger-ui .servers li input[type="text"] {
|
||||
padding: 8px 12px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 修复服务器选择区域的背景颜色和布局 */
|
||||
.swagger-ui .servers {
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 确保服务器列表容器有正确的背景色和布局 */
|
||||
.swagger-ui .servers-wrapper {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 确保整个顶部区域颜色一致和布局正确 */
|
||||
.swagger-ui .info {
|
||||
margin: 0;
|
||||
padding: 20px 16px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 确保顶部主容器颜色一致和布局正确 */
|
||||
.swagger-ui {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 确保API信息区域颜色一致和布局正确 */
|
||||
.swagger-ui .info-container {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body.dark-mode .swagger-ui .servers li label {
|
||||
color: #ffffff !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
/* 修复服务器URL输入框深色模式样式 */
|
||||
body.dark-mode .swagger-ui .servers li input[type="text"] {
|
||||
background-color: #1a202c !important;
|
||||
color: #ffffff !important;
|
||||
border-color: #4a5568 !important;
|
||||
padding: 8px 12px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* 修复服务器选择区域的深色模式背景颜色和布局 */
|
||||
body.dark-mode .swagger-ui .servers {
|
||||
background-color: #1a202c !important;
|
||||
border: none !important;
|
||||
padding: 16px !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保服务器列表容器在深色模式下也有正确的背景色和布局 */
|
||||
body.dark-mode .swagger-ui .servers-wrapper {
|
||||
background-color: #1a202c !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保整个顶部区域在深色模式下颜色一致和布局正确 */
|
||||
body.dark-mode .swagger-ui .info {
|
||||
background-color: #1a202c !important;
|
||||
margin: 0 !important;
|
||||
padding: 20px 16px !important;
|
||||
border-bottom: 1px solid #4a5568 !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 确保顶部主容器在深色模式下颜色一致和布局正确 */
|
||||
body.dark-mode .swagger-ui {
|
||||
background-color: #1a202c !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保API信息区域在深色模式下颜色一致和布局正确 */
|
||||
body.dark-mode .swagger-ui .info-container {
|
||||
background-color: #1a202c !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 修复深色模式下内容区域的布局问题 */
|
||||
body.dark-mode .swagger-ui .wrapper {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 修复深色模式下API操作块的布局 */
|
||||
body.dark-mode .swagger-ui .opblock {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 修复深色模式下过滤器的布局 */
|
||||
body.dark-mode .swagger-ui .filter {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 16px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 修复深色模式下顶部栏布局 */
|
||||
body.dark-mode .swagger-ui .topbar {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
padding: 15px 0 !important;
|
||||
}
|
||||
|
||||
/* 修复深色模式下顶部栏包装器布局 */
|
||||
body.dark-mode .swagger-ui .topbar .topbar-wrapper {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
/* 修复深色模式下响应容器布局 */
|
||||
body.dark-mode .swagger-ui .responses-inner {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 修复深色模式下操作块摘要布局 */
|
||||
body.dark-mode .swagger-ui .opblock-summary {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 确保深色模式下所有容器元素都使用box-sizing */
|
||||
body.dark-mode * {
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 增强标签标题深色模式样式 */
|
||||
body.dark-mode .swagger-ui .opblock-tag {
|
||||
color: #ffffff !important;
|
||||
background-color: #2d3748 !important;
|
||||
padding: 12px 16px !important;
|
||||
border-radius: 6px !important;
|
||||
margin-bottom: 12px !important;
|
||||
font-weight: 700 !important;
|
||||
font-size: 1.1rem !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
/* 增强标签标题(h3)深色模式样式 */
|
||||
body.dark-mode .swagger-ui .opblock-tag.h3 {
|
||||
color: #ffffff !important;
|
||||
background-color: #2d3748 !important;
|
||||
}
|
||||
|
||||
/* 增强标签部分深色模式样式 */
|
||||
body.dark-mode .swagger-ui .opblock-tag-section {
|
||||
background-color: #2d3748 !important;
|
||||
padding: 16px !important;
|
||||
border-radius: 8px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
/* 增强API描述深色模式样式 */
|
||||
body.dark-mode .swagger-ui .opblock-description-wrapper {
|
||||
color: #ffffff !important;
|
||||
background-color: #2d3748 !important;
|
||||
padding: 12px 16px !important;
|
||||
border-radius: 6px !important;
|
||||
margin-bottom: 12px !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-description-wrapper p {
|
||||
color: #ffffff !important;
|
||||
line-height: 1.5 !important;
|
||||
}
|
||||
|
||||
/* 增强stats标签描述深色模式样式 */
|
||||
body.dark-mode .swagger-ui .opblock-summary-description {
|
||||
color: #ffffff !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
/* 增强操作块标题深色模式样式 */
|
||||
body.dark-mode .swagger-ui .opblock-title_normal h4 {
|
||||
color: #ffffff !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* 增强参数部分深色模式样式 */
|
||||
body.dark-mode .swagger-ui .opblock-body {
|
||||
background-color: #2d3748 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .parameter__name {
|
||||
color: #ffffff !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .parameter__type {
|
||||
color: #ffffff !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .parameter__description {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .parameters-col_description,
|
||||
body.dark-mode .swagger-ui .parameters-col_name,
|
||||
body.dark-mode .swagger-ui .parameters-col_type {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .parameters-col_description p,
|
||||
body.dark-mode .swagger-ui .parameters-col_name p,
|
||||
body.dark-mode .swagger-ui .parameters-col_type p {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* 新增:适配API文档展开界面的所有文字元素 */
|
||||
body.dark-mode .swagger-ui .opblock-body {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .parameter__name {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .parameter__type {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .parameter__description {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .body-param-options {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .body-param-options .body-param-type {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .responses-inner {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .responses-inner h4 {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .response-container {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .response-container .response-wrapper {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .response-container .response-code {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .response-container .response-description {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .model {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .model .property {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .model .property .property-name {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .model .property .property-description {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .model .property .property-type {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .model .property .required {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .scroll-to-top {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-tag-section {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .servers-title {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .servers {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .servers li {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .servers li label {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .servers li select {
|
||||
color: #ffffff;
|
||||
background-color: #1a202c;
|
||||
border-color: #4a5568;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .auth-wrapper {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .auth-wrapper .auth-title {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .auth-wrapper .auth-list {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .auth-wrapper .auth-item {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .auth-wrapper .auth-item label {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 确保代码块内的文字也清晰可见 */
|
||||
body.dark-mode .swagger-ui pre {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui code {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 确保所有表单元素的文字颜色正确 */
|
||||
body.dark-mode .swagger-ui form {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui form label {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui select {
|
||||
color: #ffffff;
|
||||
background-color: #1a202c;
|
||||
border-color: #4a5568;
|
||||
}
|
||||
|
||||
/* 适配可能的嵌套内容 */
|
||||
body.dark-mode .swagger-ui .opblock-body .schema {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .schema .title {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .opblock-body .schema .required {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 适配可能的按钮组 */
|
||||
body.dark-mode .swagger-ui .btn-group {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 适配可能的标签 */
|
||||
body.dark-mode .swagger-ui .tag {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 适配可能的警告和提示信息 */
|
||||
body.dark-mode .swagger-ui .warning {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui .hint {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 适配可能的表格内容 */
|
||||
body.dark-mode .swagger-ui table {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui table th {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-mode .swagger-ui table td {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.topbar-controls {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.theme-toggle-btn span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DNS Server API 文档</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.18.3/swagger-ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.18.3/swagger-ui-bundle.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.18.3/swagger-ui-standalone-preset.js"></script>
|
||||
<script src="js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,62 +0,0 @@
|
||||
@layer utilities {
|
||||
.content-auto {
|
||||
content-visibility: auto;
|
||||
}
|
||||
.card-shadow {
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.sidebar-item-active {
|
||||
background-color: rgba(22, 93, 255, 0.1);
|
||||
color: #165DFF;
|
||||
border-right: 4px solid #165DFF;
|
||||
}
|
||||
}
|
||||
|
||||
/* 服务器状态组件光晕效果 */
|
||||
.glow-effect {
|
||||
animation: pulse 2s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(41, 128, 185, 0.4);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(41, 128, 185, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(41, 128, 185, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 服务器状态组件样式优化 */
|
||||
.server-status-widget {
|
||||
min-width: 170px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.server-status-widget:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
/* 加载状态样式 */
|
||||
.status-loading {
|
||||
animation: status-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 状态脉冲动画 */
|
||||
@keyframes status-pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
/* 保存按钮状态样式 */
|
||||
#save-blacklist-status {
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
9
staticbak/static/css/vendor/all.min.css
vendored
9
staticbak/static/css/vendor/all.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1,535 +0,0 @@
|
||||
{
|
||||
"timeUpdated": "2025-03-17T10:05:02.622Z",
|
||||
"categories": {
|
||||
"0": "官方网站",
|
||||
"1": "搜索引擎",
|
||||
"2": "CDN",
|
||||
"3": "新闻资讯",
|
||||
"4": "购物网站",
|
||||
"5": "社交网站",
|
||||
"6": "工具网站",
|
||||
"7": "社交工具",
|
||||
"8": "视频网站",
|
||||
"9": "混合内容",
|
||||
"10": "博客/自媒体",
|
||||
"11": "论坛/社区",
|
||||
"12": "地图",
|
||||
"13": "涉政网站",
|
||||
"14": "政府网站",
|
||||
"15": "政务网站",
|
||||
"16": "组织网站",
|
||||
"17": "事业单位",
|
||||
"18": "政务服务",
|
||||
"19": "网络邮件",
|
||||
"20": "云服务",
|
||||
"21": "网络存储",
|
||||
"22": "网络服务",
|
||||
"23": "CDN网络分发平台",
|
||||
"24": "支付平台",
|
||||
"25": "其他"
|
||||
},
|
||||
"domains": {
|
||||
"网易": {
|
||||
"网易163": {
|
||||
"name": "网易163",
|
||||
"categoryId": 3,
|
||||
"url": "http://www.163.com/",
|
||||
"icon": "https://www.163.com/favicon.ico"
|
||||
},
|
||||
"网易163免费邮": {
|
||||
"name": "网易163免费邮",
|
||||
"categoryId": 18,
|
||||
"url": "https://mail.163.com/",
|
||||
"icon": "https://mail.163.com/favicon.ico"
|
||||
},
|
||||
"网易126免费邮": {
|
||||
"name": "网易126免费邮",
|
||||
"categoryId": 18,
|
||||
"url": "https://www.126.com/",
|
||||
"icon": "https://www.126.com/favicon.ico"
|
||||
},
|
||||
"网易有道词典": {
|
||||
"name": "网易有道词典",
|
||||
"categoryId": 0,
|
||||
"url": "https://dict.youdao.com/",
|
||||
"icon": "https://dict.youdao.com/favicon.ico"
|
||||
},
|
||||
"网易uu加速器":{
|
||||
"name": "网易uu加速器",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "https://uu.163.com/",
|
||||
"2": "https://log.uu.163.com/"
|
||||
}
|
||||
},
|
||||
"company": "网易股份有限公司"
|
||||
},
|
||||
"百度": {
|
||||
"百度搜索": {
|
||||
"name": "百度搜索",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.baidu.com/",
|
||||
"icon": "https://www.baidu.com/favicon.ico"
|
||||
},
|
||||
"百度百科": {
|
||||
"name": "百度百科",
|
||||
"categoryId": 19,
|
||||
"url": "https://baike.baidu.com/",
|
||||
"icon": "https://baike.baidu.com/favicon.ico"
|
||||
},
|
||||
"百度地图": {
|
||||
"name": "百度地图",
|
||||
"categoryId": 7,
|
||||
"url": "https://map.baidu.com/",
|
||||
"icon": "https://map.baidu.com/favicon.ico"
|
||||
},
|
||||
"百度知道": {
|
||||
"name": "百度知道",
|
||||
"categoryId": 12,
|
||||
"url": "https://zhidao.baidu.com/",
|
||||
"icon": "https://zhidao.baidu.com/favicon.ico"
|
||||
},
|
||||
"company": "北京百度网讯科技有限公司"
|
||||
},
|
||||
"阿里云": {
|
||||
"阿里云": {
|
||||
"name": "阿里云",
|
||||
"categoryId": 22,
|
||||
"url": "https://www.aliyun.com/",
|
||||
"icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico"
|
||||
},
|
||||
"阿里云API服务": {
|
||||
"name": "阿里云API服务",
|
||||
"categoryId": 22,
|
||||
"url": "aliyuncs.com",
|
||||
"icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico"
|
||||
},
|
||||
"阿里云DNS": {
|
||||
"name": "阿里云DNS",
|
||||
"categoryId": 22,
|
||||
"url": {
|
||||
"1": "alidns.com",
|
||||
"2": "alibabadns.com"
|
||||
},
|
||||
"icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico"
|
||||
},
|
||||
"支付宝": {
|
||||
"name": "支付宝",
|
||||
"categoryId": 24,
|
||||
"url": "https://www.alipay.com/",
|
||||
"icon": "https://www.alipay.com/favicon.ico"
|
||||
},
|
||||
"淘宝": {
|
||||
"name": "淘宝",
|
||||
"categoryId": 4,
|
||||
"url": "https://www.taobao.com/",
|
||||
"icon": "https://www.taobao.com/favicon.ico"
|
||||
},
|
||||
"天猫": {
|
||||
"name": "天猫",
|
||||
"categoryId": 4,
|
||||
"url": "https://www.tmall.com/",
|
||||
"icon": "https://www.tmall.com/favicon.ico"
|
||||
},
|
||||
"阿里云镜像源":{
|
||||
"name": "阿里巴巴开源镜像站",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "https://mirrors.aliyun.com/"
|
||||
},
|
||||
"icon": "https://www.aliyun.com/favicon.ico"
|
||||
},
|
||||
"company": "阿里云计算有限公司/阿里巴巴集团"
|
||||
},
|
||||
"UC":{
|
||||
"UC浏览器官网": {
|
||||
"name": "UC浏览器官网",
|
||||
"categoryId": 4,
|
||||
"url": {
|
||||
"1": "www.uc.cn",
|
||||
"2": "https://www.uc.com/"
|
||||
},
|
||||
"icon": "https://www.uc.cn/favicon.ico"
|
||||
},
|
||||
"company": "广州市动景计算机科技有限公司"
|
||||
},
|
||||
"腾讯": {
|
||||
"微信": {
|
||||
"name": "微信",
|
||||
"categoryId": 7,
|
||||
"url": {
|
||||
"1": "https://wx.qq.com/",
|
||||
"2": "https://weixin.qq.com/",
|
||||
"3": "https://res.wx.qq.com/",
|
||||
"4": "dns.weixin.qq.com",
|
||||
"5": "pc.weixin.qq.com"
|
||||
},
|
||||
"icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico"
|
||||
},
|
||||
"WeChat": {
|
||||
"name": "WeChat",
|
||||
"categoryId": 7,
|
||||
"url": {
|
||||
"1":"wechat.com",
|
||||
"2": "support.wechat.com",
|
||||
"3": "www.wechat.com"
|
||||
},
|
||||
|
||||
"icon": "https://wechat.com/favicon.ico"
|
||||
},
|
||||
"微信开放平台": {
|
||||
"name": "微信开放平台",
|
||||
"categoryId": 24,
|
||||
"url": "https://open.weixin.qq.com/",
|
||||
"icon": "https://open.weixin.qq.com/favicon.ico"
|
||||
},
|
||||
"微信支付": {
|
||||
"name": "微信支付商户平台",
|
||||
"categoryId": 24,
|
||||
"url": {"1": "pay.weixin.qq.com",
|
||||
"2": "pay.wechatpay.cn",
|
||||
"3": "act.weixin.qq.com",
|
||||
"4": "api.wechatpay.cn",
|
||||
"5": "api.mch.weixin.qq.com",
|
||||
"6": "api2.mch.weixin.qq.com",
|
||||
"7": "api.wechatpay.cn",
|
||||
"8": "api2.wechatpay.cn",
|
||||
"9": "payapp.weixin.qq.com",
|
||||
"10": "payapp.wechatpay.cn",
|
||||
"11": "fraud.mch.weixin.qq.com",
|
||||
"12": "fraud.wechatpay.cn",
|
||||
"13": "action.weixin.qq.com",
|
||||
"14": "action.wechatpay.cn",
|
||||
"15": "wechatpay.cn"
|
||||
},
|
||||
|
||||
"icon": "https://gtimg.wechatpay.cn/core/favicon.ico"
|
||||
},
|
||||
"微信支付海外版": {
|
||||
"name": "微信支付海外版",
|
||||
"categoryId": 24,
|
||||
"url": {"1": "https://pay.wechatpay.global/",
|
||||
"2": "apihk.mch.weixin.qq.com",
|
||||
"3": "apius.mch.weixin.qq.com"
|
||||
},
|
||||
"icon": "https://gtimg.wechatpay.cn/core/favicon.ico"
|
||||
},
|
||||
"微信CDN": {
|
||||
"name": "微信CDN和日志等资源",
|
||||
"categoryId": 23,
|
||||
"url": {"1": "https://gtimg.wechatpay.cn/",
|
||||
"2": "log.wechatpay.cn",
|
||||
"3": "newres.wechat.com",
|
||||
"4": "res.wx.qq.com",
|
||||
"5": "sh.servicewechat.com",
|
||||
"6": "res.servicewechat.com"
|
||||
},
|
||||
"icon": "https://gtimg.wechatpay.cn/core/favicon.ico"
|
||||
},
|
||||
"腾讯下载加速网络CDN": {
|
||||
"name": "腾讯下载加速网络CDN",
|
||||
"categoryId": 23,
|
||||
"url": {"1": "dldir1v6.qq.com",
|
||||
"2": "dldir1.qq.com",
|
||||
"3": "dldir2.qq.com",
|
||||
"4": "dldir3.qq.com",
|
||||
"5": "dl.qq.com",
|
||||
"6": "dldir.tencent.com",
|
||||
"7": "pc.qq.com"
|
||||
},
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"腾讯QQ": {
|
||||
"name": "腾讯QQ",
|
||||
"categoryId": 5,
|
||||
"url": "https://im.qq.com/",
|
||||
"icon": "https://im.qq.com/favicon.ico"
|
||||
},
|
||||
"腾讯网": {
|
||||
"name": "腾讯网",
|
||||
"categoryId": 3,
|
||||
"url": "https://www.qq.com/",
|
||||
"icon": "https://www.qq.com/favicon.ico"
|
||||
},
|
||||
"腾讯3G移动门户": {
|
||||
"name": "腾讯3G移动门户",
|
||||
"categoryId": 3,
|
||||
"url": "3g.qq.com",
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"3G 移动门户CDN": {
|
||||
"name": "3G 移动门户CDN",
|
||||
"categoryId": 23,
|
||||
"url": "cdnimg.3g.qq.com",
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"腾讯": {
|
||||
"name": "腾讯",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.tencent.com/",
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"腾讯地图": {
|
||||
"name": "腾讯地图",
|
||||
"categoryId": 8,
|
||||
"url": "https://map.qq.com/",
|
||||
"icon": "https://map.qq.com/favicon.ico"
|
||||
},
|
||||
"腾讯视频": {
|
||||
"name": "腾讯视频",
|
||||
"categoryId": 8,
|
||||
"url": "https://v.qq.com/",
|
||||
"icon": "https://v.qq.com/favicon.ico"
|
||||
},
|
||||
"腾讯广告": {
|
||||
"name": "腾讯广告",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "ugdtimg.com",
|
||||
"2": "gdtimg.com",
|
||||
"3": "ad.qq.com",
|
||||
"4": "v3.gdt.qq.com",
|
||||
"5": "c3.gdt.qq.com",
|
||||
"6": "v2mi.gdt.qq.com",
|
||||
"7": "gdt.qq.com"
|
||||
},
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"腾讯Beacon事件日志上报": {
|
||||
"name": "腾讯Beacon事件日志上报",
|
||||
"categoryId": 0,
|
||||
"url": "aeventlog.beacon.qq.com",
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"company": "腾讯计算机系统有限公司"
|
||||
},
|
||||
|
||||
"微信青少年相关":{
|
||||
"微信守护平台":{
|
||||
"name": "微信守护平台",
|
||||
"categoryId": 24,
|
||||
"url": "wxguard.weixin.qq.com",
|
||||
"icon": "https://open.weixin.qq.com/favicon.ico"
|
||||
},
|
||||
"微信未成年人服务短链接":{
|
||||
"name": "微信未成年人服务短链接",
|
||||
"categoryId": 24,
|
||||
"url": {"1": "minorshort.weixin.qq.com",
|
||||
"2": "szminorshort.weixin.qq.com"
|
||||
},
|
||||
"icon": "https://open.weixin.qq.com/favicon.ico"
|
||||
},
|
||||
"深圳地区扩展短链接":{
|
||||
"name": "深圳地区扩展短链接",
|
||||
"categoryId": 24,
|
||||
"url": "szextshort.weixin.qq.com",
|
||||
"icon": "https://open.weixin.qq.com/favicon.ico"
|
||||
},
|
||||
"company": "腾讯计算机系统有限公司"
|
||||
},
|
||||
"高德地图相关": {
|
||||
"高德地图": {
|
||||
"name": "高德地图",
|
||||
"categoryId": 7,
|
||||
"url": "https://map.amap.com/",
|
||||
"icon": "https://a.amap.com/pc/static/favicon.ico"
|
||||
},
|
||||
"高德开放平台": {
|
||||
"name": "高德开放平台",
|
||||
"categoryId": 7,
|
||||
"url": "lbs.amap.com",
|
||||
"icon": "https://a.amap.com/pc/static/favicon.ico"
|
||||
},
|
||||
"高德地图API": {
|
||||
"name": "高德地图API",
|
||||
"categoryId": 7,
|
||||
"url": "https://restapi.amap.com/",
|
||||
"icon": "https://a.amap.com/pc/static/favicon.ico"
|
||||
},
|
||||
"company": "北京高德图强科技有限公司"
|
||||
},
|
||||
"微软": {
|
||||
"微软": {
|
||||
"name": "微软",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.microsoft.com/",
|
||||
"icon": "https://cdn-dynmedia-1.microsoft.com/is/content/microsoftcorp/Link-List-Icons-Microsoft-365?wid=40&hei=40"
|
||||
},
|
||||
"微软Edge": {
|
||||
"name": "微软Edge",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.microsoftedgeinsider.com/",
|
||||
"icon": "https://www.microsoftedgeinsider.com/favicon.ico"
|
||||
},
|
||||
"微软Office": {
|
||||
"name": "微软Office",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.office.com/",
|
||||
"icon": "https://www.office.com/favicon.ico"
|
||||
},
|
||||
"Azure": {
|
||||
"name": "Azure",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.azure.com/",
|
||||
"icon": "https://portal.azure.com/Content/favicon.ico"
|
||||
},
|
||||
"网络连接状态指示器(NCSI)": {
|
||||
"name": "网络连接状态指示器(NCSI)",
|
||||
"categoryId": 0,
|
||||
"url": "msftncsi.com",
|
||||
"icon": "https://www.microsoft.com/favicon.ico"
|
||||
},
|
||||
"company": "微软公司"
|
||||
},
|
||||
"字节跳动": {
|
||||
"抖音": {
|
||||
"name": "抖音",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.douyin.com/",
|
||||
"icon": "https://www.douyin.com/favicon.ico"
|
||||
},
|
||||
"今日头条": {
|
||||
"name": "今日头条",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.toutiao.com/",
|
||||
"icon": "https://www.toutiao.com/favicon.ico"
|
||||
},
|
||||
"抖音视频": {
|
||||
"name": "抖音视频",
|
||||
"categoryId": 8,
|
||||
"url": "https://v.douyin.com/",
|
||||
"icon": "https://v.douyin.com/favicon.ico"
|
||||
},
|
||||
"字节跳动API服务": {
|
||||
"name": "字节API服务",
|
||||
"categoryId": 0,
|
||||
"url": "zijieapi.com",
|
||||
"icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC"
|
||||
},
|
||||
"今日头条API服务":{
|
||||
"name": "今日头条API 服务",
|
||||
"categoryId": 0,
|
||||
"url": "toutiaoapi.com",
|
||||
"icon": "https://www.toutiao.com/favicon.ico"
|
||||
},
|
||||
"豆包": {
|
||||
"name": "豆包",
|
||||
"categoryId": 0,
|
||||
"url": "doubao.com",
|
||||
"icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png"
|
||||
},
|
||||
"company": "字节跳动有限公司"
|
||||
},
|
||||
"金山办公": {
|
||||
"金山文档": {
|
||||
"name": "金山文档",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1":"kdocs.cn",
|
||||
"2":"www.kdocs.cn",
|
||||
"3":"365.kdocs.cn",
|
||||
"4":"account.kdocs.cn"
|
||||
},
|
||||
"icon": "https://qn.cache.wpscdn.cn/kdocs/mobile/touch/apple-180.png"
|
||||
},
|
||||
"WPS Office": {
|
||||
"name": "WPS Office",
|
||||
"categoryId": 6,
|
||||
"url": {
|
||||
"1":"wps.cn",
|
||||
"2":"www.wps.cn"
|
||||
},
|
||||
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ2SURBVHgB7Va9biRFEK7qWa9XAuSByMEhr0FIzvARkEBgEUHEPQG79wKcTyL3Li/gnxdgl5TgTAQSQmeJxEJCIoIjOc/JyemSW4tbe70z28XX1T0z+zO2U6RzWe3unq6un6+/rl6iW7mV111Y7tzZImMekVAcvkm+NqUnxZy1S0jsQz49PdTF3Q82ych3WNzM1dM/Msp+S4UviQ32RHDlBm5MIgJrz9Bvs6y9e4Ida3MOvRXGnxRTyb2HgM6I/13nZDCQb98/IcNNtQC19J8JXf46InWM5npjfM/qnJ1dtVGjSOLgKkQWnMz4y+clcmgx2TiWb955QENpFsBhT/r7SDhymZMwEnfKOjbiFcSSRuO+k7F7FFk4xsdIECZ6xtxg7HqeuHFYQ583lh59Dgvn0qZXmL/CviGy/3tM9gJOoYPmehcMcaR7vE03Vj9yyNJsxlQf4xgkLrNFUCnvEzUwGZUoRDjjiB7peCzr9Gm9hflOgRokfZHdnfw1HPi9+X/IMu8CpHsznIIN3SgbqzvoOiXAMgBgd/nJ84TmibGx+pgcCTeoS9HSY5hpFnwh6vEPp/cr9jidE3/M+Vfb4ycv7hsf5mifzOQlYPFwM87X2B2qlq62kWnRhW3SOaC8sExDHFUadSt3OFsecg+9csGorgbAfw4GVOMDPef8fCJpy0bcnLcFVI5oFYORbcMpaQBD6/o+HybJYvawAVs4Kgq2Hd96ObqmjHK8h8gGgYw+yuWoGoXzrEXnE2SfCQ0zN3dUqc7e2VAyB0LjdtBSWugWASgK7G7EFNONtGRzFgWdp7JNKYxlCBYd+h4fP0+wFi/oGmp7ZK1WI/jowleyEIAXCy7Yl8UxGGyIzCwKjvXGxj5AZxAR2ElXPorbVDPbs7qmExKRcLUTR75plZkAFIVIDvQY3CYlpW3nKBQZ6VqoGUb6oOwgBPa1bHkUVJcnXwUd9hwAUlPZVyAAGZPnQl6UXBDLsqtrDdvReV6UHFKRuxH2AeZwCGTc2MkymO9JnSfyDN768+4WAggo7IWKxeH8vpRP3mo5TpQVUYPrBSttrabujI1sy8dvfIixZ76x3gajRhzPZq/+qEIUxjR9igr3NlVKqChZtk6NxgC9e9A8AUWL0hn+r+QvDPQTytLP+HiU3IiAmj8KXCh5IEWR8mN9C5xB1a3RfnFz/NGtTL0d7v39vsr5lQH4lTq4YM+U6aVxCuQD3JOpe19zuoNQwALp8qtsE6plvSvdXLWgmRnZL1isJXoSXjLu81GZUaGrL14otVrQ0Eg607oLfugakS2c61L9qf5a8i+e6O+CzLw3b1R1a3XHhZWp1zHhXy7Xr/NhrlvkI73fB2Vh0vvcr8pIdR0Xpisp49reILWbFPjncUe+qK0g83vK5tGbD12xqJQR3pPG0hrq8xZQ+JF/yvp0K7fyf5f/AKzIO6StcaNfAAAAAElFTkSuQmCC"
|
||||
},
|
||||
"WPS CDN": {
|
||||
"name": "WPS CDN",
|
||||
"categoryId": 23,
|
||||
"url": {
|
||||
"1":"wpsdns.com",
|
||||
"2":"kdocs-om.wpscdn.cn",
|
||||
"3":"qn.cache.wpscdn.cn",
|
||||
"4":"fe-static.wpscdn.cn",
|
||||
"5":"docer-ks3.wpscdn.cn",
|
||||
"6":"cloudcdn.wpscdn.cn",
|
||||
"7":"vasvip-pub.wpscdn.cn",
|
||||
"8":"ks3.wpsplus.wpscdn.cn",
|
||||
"9":"op-kdocs.wpscdn.cn",
|
||||
"10":"volcengine-cache-weboffice.wpscdn.cn",
|
||||
"11":"ac.wpscdn.cn",
|
||||
"12":"honeycomb-emergency.wpscdn.cn",
|
||||
"13":"res-honeycomb.wpscdn.cn",
|
||||
"14":"kflow.wpscdn.cn",
|
||||
"15":"personal-act.wpscdn.cn",
|
||||
"16":"official-package.wpscdn.cn"
|
||||
},
|
||||
"icon": "https://wps.cn/favicon.ico"
|
||||
},
|
||||
"company": "珠海金山办公科技有限公司"
|
||||
},
|
||||
"京东": {
|
||||
"京东": {
|
||||
"name": "京东",
|
||||
"categoryId": 4,
|
||||
"url": "https://www.jd.com/",
|
||||
"icon": "https://www.jd.com/favicon.ico"
|
||||
},
|
||||
"company": "京东"
|
||||
},
|
||||
"360": {
|
||||
"360官网": {
|
||||
"name": "360",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.360.cn/",
|
||||
"icon": "https://www.360.cn/favicon.ico"
|
||||
},
|
||||
"360搜索": {
|
||||
"name": "360搜索",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1":"https://www.so.com/",
|
||||
"2":"https://so.com/"
|
||||
},
|
||||
"icon": "https://www.so.com/favicon.ico"
|
||||
},
|
||||
"company": "奇虎360"
|
||||
},
|
||||
"绮梦之家": {
|
||||
"绮梦之家": {
|
||||
"name": "绮梦之家",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "https://www.amazehome.xyz/",
|
||||
"2": "amazehome.xyz",
|
||||
"3": "https://www.amazehome.cn/",
|
||||
"4": "amazehome.cn"
|
||||
},
|
||||
"icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png"
|
||||
},
|
||||
"company": "绮梦之家"
|
||||
},
|
||||
"南京市中医院": {
|
||||
"南京市中医院": {
|
||||
"name": "南京市中医院",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1":"https://www.njszyy.cn/",
|
||||
"2":"https://njszyy.cn/"
|
||||
},
|
||||
"icon": "#"
|
||||
},
|
||||
"company": "南京市中医院"
|
||||
},
|
||||
"软件源相关": {
|
||||
"腾讯镜像源":{
|
||||
"name": "腾讯软件源",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "https://mirrors.cloud.tencent.com/"
|
||||
},
|
||||
"icon": "https://www.tencent.com/favicon.ico",
|
||||
"company": "腾讯计算机系统有限公司"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,305 +0,0 @@
|
||||
// API模块 - 统一管理所有API调用
|
||||
|
||||
// API路径定义
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
// API请求封装
|
||||
async function apiRequest(endpoint, method = 'GET', data = null) {
|
||||
const url = `${API_BASE_URL}${endpoint}`;
|
||||
const options = {
|
||||
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) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
// 添加超时处理
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('请求超时'));
|
||||
}, 10000); // 10秒超时
|
||||
});
|
||||
|
||||
try {
|
||||
// 竞争:请求或超时
|
||||
const response = await Promise.race([fetch(url, options), timeoutPromise]);
|
||||
|
||||
// 获取响应文本,用于调试和错误处理
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
// 优化错误响应处理
|
||||
console.warn(`API请求失败: ${response.status}`);
|
||||
|
||||
// 处理401未授权错误,重定向到登录页面
|
||||
if (response.status === 401) {
|
||||
console.warn('未授权访问,重定向到登录页面');
|
||||
window.location.href = '/login';
|
||||
return { error: '未授权访问' };
|
||||
}
|
||||
|
||||
// 尝试解析JSON,但如果失败,直接使用原始文本作为错误信息
|
||||
try {
|
||||
const errorData = JSON.parse(responseText);
|
||||
return { error: errorData.error || responseText || `请求失败: ${response.status}` };
|
||||
} catch (parseError) {
|
||||
// 当响应不是有效的JSON时(如中文错误信息),直接使用原始文本
|
||||
console.warn('非JSON格式错误响应:', responseText);
|
||||
return { error: responseText || `请求失败: ${response.status}` };
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试解析成功响应
|
||||
try {
|
||||
// 首先检查响应文本是否为空
|
||||
if (!responseText || responseText.trim() === '') {
|
||||
console.warn('空响应文本');
|
||||
return null; // 返回null表示空响应
|
||||
}
|
||||
|
||||
// 尝试解析JSON
|
||||
const parsedData = JSON.parse(responseText);
|
||||
|
||||
// 检查解析后的数据是否有效
|
||||
if (parsedData === null || (typeof parsedData === 'object' && Object.keys(parsedData).length === 0)) {
|
||||
console.warn('解析后的数据为空');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 限制所有数字为两位小数
|
||||
const formatNumbers = (obj) => {
|
||||
if (typeof obj === 'number') {
|
||||
return parseFloat(obj.toFixed(2));
|
||||
} else if (Array.isArray(obj)) {
|
||||
return obj.map(formatNumbers);
|
||||
} else if (obj && typeof obj === 'object') {
|
||||
const formattedObj = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
formattedObj[key] = formatNumbers(obj[key]);
|
||||
}
|
||||
}
|
||||
return formattedObj;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
const formattedData = formatNumbers(parsedData);
|
||||
return formattedData;
|
||||
} 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));
|
||||
}
|
||||
|
||||
// 返回错误对象,让上层处理
|
||||
return { error: 'JSON解析错误' };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API请求错误:', error);
|
||||
// 返回错误对象,而不是抛出异常,让上层处理
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// API方法集合
|
||||
const api = {
|
||||
// 获取统计信息
|
||||
getStats: () => apiRequest('/stats?t=' + Date.now()),
|
||||
|
||||
// 获取系统状态
|
||||
getStatus: () => apiRequest('/status?t=' + Date.now()),
|
||||
|
||||
// 获取Top屏蔽域名
|
||||
getTopBlockedDomains: () => apiRequest('/top-blocked?t=' + Date.now()),
|
||||
|
||||
// 获取Top解析域名
|
||||
getTopResolvedDomains: () => apiRequest('/top-resolved?t=' + Date.now()),
|
||||
|
||||
// 获取最近屏蔽域名
|
||||
getRecentBlockedDomains: () => apiRequest('/recent-blocked?t=' + Date.now()),
|
||||
|
||||
// 获取TOP客户端
|
||||
getTopClients: () => apiRequest('/top-clients?t=' + Date.now()),
|
||||
|
||||
// 获取TOP域名
|
||||
getTopDomains: () => apiRequest('/top-domains?t=' + Date.now()),
|
||||
|
||||
// 获取小时统计
|
||||
getHourlyStats: () => apiRequest('/hourly-stats?t=' + Date.now()),
|
||||
|
||||
// 获取每日统计数据(7天)
|
||||
getDailyStats: () => apiRequest('/daily-stats?t=' + Date.now()),
|
||||
|
||||
// 获取每月统计数据(30天)
|
||||
getMonthlyStats: () => apiRequest('/monthly-stats?t=' + Date.now()),
|
||||
|
||||
// 获取查询类型统计
|
||||
getQueryTypeStats: () => apiRequest('/query/type?t=' + Date.now()),
|
||||
|
||||
// 获取屏蔽规则 - 已禁用
|
||||
getShieldRules: () => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({}); // 返回空对象而非API调用
|
||||
},
|
||||
|
||||
// 添加屏蔽规则 - 已禁用
|
||||
addShieldRule: (rule) => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 删除屏蔽规则 - 已禁用
|
||||
deleteShieldRule: (rule) => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 更新远程规则 - 已禁用
|
||||
updateRemoteRules: () => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 获取黑名单列表 - 已禁用
|
||||
getBlacklists: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve([]); // 返回空数组而非API调用
|
||||
},
|
||||
|
||||
// 添加黑名单 - 已禁用
|
||||
addBlacklist: (url) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 删除黑名单 - 已禁用
|
||||
deleteBlacklist: (url) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 获取Hosts内容 - 已禁用
|
||||
getHosts: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ content: '' }); // 返回空内容而非API调用
|
||||
},
|
||||
|
||||
// 保存Hosts内容 - 已禁用
|
||||
saveHosts: (content) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 刷新Hosts - 已禁用
|
||||
refreshHosts: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 查询DNS记录 - 兼容多种参数格式
|
||||
queryDNS: async function(domain, recordType) {
|
||||
try {
|
||||
console.log('执行DNS查询:', { domain, recordType });
|
||||
|
||||
// 适配参数格式
|
||||
let params;
|
||||
if (typeof domain === 'object') {
|
||||
// 当传入对象时
|
||||
params = domain;
|
||||
} else {
|
||||
// 当传入单独参数时
|
||||
params = { domain, recordType };
|
||||
}
|
||||
|
||||
// 尝试不同的API端点
|
||||
const endpoints = ['/api/dns/query', '/dns/query', '/api/query', '/query'];
|
||||
let lastError;
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
console.log(`尝试API端点: ${endpoint}`);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('DNS查询成功:', data);
|
||||
return data;
|
||||
} else {
|
||||
lastError = new Error(`HTTP error! status: ${response.status} for endpoint: ${endpoint}`);
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.log(`端点 ${endpoint} 调用失败,尝试下一个`);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有端点都失败,抛出最后一个错误
|
||||
throw lastError || new Error('所有API端点调用失败');
|
||||
} catch (error) {
|
||||
console.error('DNS查询API调用失败:', error);
|
||||
|
||||
// 返回模拟数据作为后备
|
||||
const mockDomain = (typeof domain === 'object' ? domain.domain : domain) || 'example.com';
|
||||
const mockType = (typeof domain === 'object' ? domain.recordType : recordType) || 'A';
|
||||
|
||||
const mockData = {
|
||||
'A': [
|
||||
{ Type: 'A', Value: '93.184.216.34', TTL: 172800 },
|
||||
{ Type: 'A', Value: '93.184.216.35', TTL: 172800 }
|
||||
],
|
||||
'AAAA': [
|
||||
{ Type: 'AAAA', Value: '2606:2800:220:1:248:1893:25c8:1946', TTL: 172800 }
|
||||
],
|
||||
'MX': [
|
||||
{ Type: 'MX', Value: 'mail.' + mockDomain, Preference: 10, TTL: 3600 },
|
||||
{ Type: 'MX', Value: 'mail2.' + mockDomain, Preference: 20, TTL: 3600 }
|
||||
],
|
||||
'NS': [
|
||||
{ Type: 'NS', Value: 'ns1.' + mockDomain, TTL: 86400 },
|
||||
{ Type: 'NS', Value: 'ns2.' + mockDomain, TTL: 86400 }
|
||||
],
|
||||
'CNAME': [
|
||||
{ Type: 'CNAME', Value: 'origin.' + mockDomain, TTL: 300 }
|
||||
],
|
||||
'TXT': [
|
||||
{ Type: 'TXT', Value: 'v=spf1 include:_spf.' + mockDomain + ' ~all', TTL: 3600 }
|
||||
]
|
||||
};
|
||||
|
||||
console.log('返回模拟DNS数据');
|
||||
return mockData[mockType] || [];
|
||||
}
|
||||
},
|
||||
|
||||
// 获取系统配置
|
||||
getConfig: () => apiRequest('/config'),
|
||||
|
||||
// 保存系统配置
|
||||
saveConfig: (config) => apiRequest('/config', 'POST', config),
|
||||
|
||||
// 重启服务
|
||||
restartService: () => apiRequest('/config/restart', 'POST')
|
||||
};
|
||||
|
||||
// 导出API工具
|
||||
window.api = api;
|
||||
@@ -1,317 +0,0 @@
|
||||
// 全局配置
|
||||
const API_BASE_URL = '.';
|
||||
|
||||
// DOM 加载完成后执行
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 初始化面板切换
|
||||
initPanelNavigation();
|
||||
|
||||
// 加载初始数据
|
||||
loadInitialData();
|
||||
|
||||
// 直接调用dashboard面板初始化函数,确保数据正确加载
|
||||
if (typeof initDashboardPanel === 'function') {
|
||||
initDashboardPanel();
|
||||
}
|
||||
|
||||
// 注意:实时更新现在由index.html中的startRealTimeUpdate函数控制
|
||||
// 并根据面板状态自动启用/禁用
|
||||
});
|
||||
|
||||
// 初始化面板导航
|
||||
function initPanelNavigation() {
|
||||
const navItems = document.querySelectorAll('.nav-item');
|
||||
const panels = document.querySelectorAll('.panel');
|
||||
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
// 移除所有活动类
|
||||
navItems.forEach(nav => nav.classList.remove('active'));
|
||||
panels.forEach(panel => panel.classList.remove('active'));
|
||||
|
||||
// 添加当前活动类
|
||||
this.classList.add('active');
|
||||
const target = this.getAttribute('data-target');
|
||||
document.getElementById(target).classList.add('active');
|
||||
|
||||
// 面板激活时执行相应的初始化函数
|
||||
if (window[`init${target.charAt(0).toUpperCase() + target.slice(1)}Panel`]) {
|
||||
window[`init${target.charAt(0).toUpperCase() + target.slice(1)}Panel`]();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 保留原有的通知函数作为兼容层
|
||||
// 现在主通知功能由index.html中的showNotification函数实现
|
||||
if (typeof window.showNotification === 'undefined') {
|
||||
window.showNotification = function(message, type = 'info') {
|
||||
// 创建临时通知元素
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type} show`;
|
||||
notification.innerHTML = `
|
||||
<div class="notification-content">${message}</div>
|
||||
`;
|
||||
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #333; color: white; padding: 10px 15px; border-radius: 4px; z-index: 10000;';
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 3000);
|
||||
};
|
||||
}
|
||||
|
||||
// 加载初始数据(主要用于服务器状态)
|
||||
function loadInitialData() {
|
||||
// 加载服务器状态
|
||||
fetch(`${API_BASE_URL}/api/status`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// 更新服务器状态指示器
|
||||
const statusDot = document.querySelector('.status-dot');
|
||||
const serverStatus = document.getElementById('server-status');
|
||||
|
||||
if (data && data.status === 'running') {
|
||||
statusDot.classList.add('connected');
|
||||
serverStatus.textContent = '运行中';
|
||||
} else {
|
||||
statusDot.classList.remove('connected');
|
||||
serverStatus.textContent = '离线';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取服务器状态失败:', error);
|
||||
|
||||
// 更新状态为离线
|
||||
const statusDot = document.querySelector('.status-dot');
|
||||
const serverStatus = document.getElementById('server-status');
|
||||
statusDot.classList.remove('connected');
|
||||
serverStatus.textContent = '离线';
|
||||
|
||||
// 使用新的通知功能
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('获取服务器状态失败', 'danger');
|
||||
}
|
||||
});
|
||||
|
||||
// 注意:统计数据更新现在由dashboard.js中的updateStatCards函数处理
|
||||
}
|
||||
|
||||
// 注意:统计卡片数据更新现在由dashboard.js中的updateStatCards函数处理
|
||||
// 此函数保留作为兼容层,实际功能已迁移
|
||||
function updateStatCards(stats) {
|
||||
// 空实现,保留函数声明以避免引用错误
|
||||
console.log('更新统计卡片 - 此功能现在由dashboard.js处理');
|
||||
}
|
||||
|
||||
// 注意:获取规则数量功能现在由dashboard.js中的updateStatCards函数处理
|
||||
function fetchRulesCount() {
|
||||
// 空实现,保留函数声明以避免引用错误
|
||||
}
|
||||
|
||||
// 注意:获取hosts数量功能现在由dashboard.js中的updateStatCards函数处理
|
||||
function fetchHostsCount() {
|
||||
// 空实现,保留函数声明以避免引用错误
|
||||
}
|
||||
|
||||
// 通用API请求函数 - 添加错误处理和重试机制
|
||||
function apiRequest(endpoint, method = 'GET', data = null, maxRetries = 3) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const config = {
|
||||
method,
|
||||
headers,
|
||||
timeout: 10000, // 设置超时时间为10秒
|
||||
};
|
||||
|
||||
// 处理请求URL和参数
|
||||
let url = `${API_BASE_URL}${endpoint}`;
|
||||
|
||||
if (data) {
|
||||
if (method === 'GET') {
|
||||
// 为GET请求拼接查询参数
|
||||
const params = new URLSearchParams();
|
||||
Object.keys(data).forEach(key => {
|
||||
params.append(key, data[key]);
|
||||
});
|
||||
url += `?${params.toString()}`;
|
||||
} else if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
|
||||
// 为其他方法设置body
|
||||
config.body = JSON.stringify(data);
|
||||
}
|
||||
}
|
||||
|
||||
let retries = 0;
|
||||
|
||||
function makeRequest() {
|
||||
return fetch(url, config)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// 检查响应是否完整
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
// 使用.text()先获取响应文本,处理可能的JSON解析错误
|
||||
return response.text().then(text => {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (e) {
|
||||
console.error('JSON解析错误:', e, '响应文本:', text);
|
||||
// 针对ERR_INCOMPLETE_CHUNKED_ENCODING错误进行重试
|
||||
if (retries < maxRetries) {
|
||||
retries++;
|
||||
console.warn(`请求失败,正在进行第${retries}次重试...`);
|
||||
return new Promise(resolve => setTimeout(() => resolve(makeRequest()), 1000 * retries));
|
||||
}
|
||||
throw new Error('JSON解析失败且重试次数已达上限');
|
||||
}
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('API请求错误:', error);
|
||||
|
||||
// 检查是否为网络错误或ERR_INCOMPLETE_CHUNKED_ENCODING相关错误
|
||||
if ((error.name === 'TypeError' && error.message.includes('Failed to fetch')) ||
|
||||
error.message.includes('incomplete chunked encoding')) {
|
||||
|
||||
if (retries < maxRetries) {
|
||||
retries++;
|
||||
console.warn(`网络错误,正在进行第${retries}次重试...`);
|
||||
return new Promise(resolve => setTimeout(() => resolve(makeRequest()), 1000 * retries));
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
return makeRequest();
|
||||
}
|
||||
|
||||
// 数字格式化函数
|
||||
function formatNumber(num) {
|
||||
// 显示完整数字的最大长度阈值
|
||||
const MAX_FULL_LENGTH = 5;
|
||||
|
||||
// 先获取完整数字字符串
|
||||
const fullNumStr = num.toString();
|
||||
|
||||
// 如果数字长度小于等于阈值,直接返回完整数字
|
||||
if (fullNumStr.length <= MAX_FULL_LENGTH) {
|
||||
return fullNumStr;
|
||||
}
|
||||
|
||||
// 否则使用缩写格式
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
|
||||
return fullNumStr;
|
||||
}
|
||||
|
||||
// 确认对话框函数
|
||||
function confirmAction(message, onConfirm) {
|
||||
if (confirm(message)) {
|
||||
onConfirm();
|
||||
}
|
||||
}
|
||||
|
||||
// 加载状态函数
|
||||
function showLoading(element) {
|
||||
if (element) {
|
||||
element.innerHTML = '<td colspan="100%" class="loading">加载中...</td>';
|
||||
}
|
||||
}
|
||||
|
||||
// 错误状态函数
|
||||
function showError(element, message) {
|
||||
if (element) {
|
||||
element.innerHTML = `<td colspan="100%" style="color: #e74c3c;">${message}</td>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态函数
|
||||
function showEmpty(element, message) {
|
||||
if (element) {
|
||||
element.innerHTML = `<td colspan="100%" style="color: #7f8c8d; font-style: italic;">${message}</td>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格排序功能
|
||||
function initTableSort(tableId) {
|
||||
const table = document.getElementById(tableId);
|
||||
if (!table) return;
|
||||
|
||||
const headers = table.querySelectorAll('thead th');
|
||||
headers.forEach(header => {
|
||||
header.addEventListener('click', function() {
|
||||
const columnIndex = Array.from(headers).indexOf(this);
|
||||
const isAscending = this.getAttribute('data-sort') !== 'asc';
|
||||
|
||||
// 重置所有标题
|
||||
headers.forEach(h => h.setAttribute('data-sort', ''));
|
||||
this.setAttribute('data-sort', isAscending ? 'asc' : 'desc');
|
||||
|
||||
// 排序行
|
||||
sortTable(table, columnIndex, isAscending);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 表格排序实现
|
||||
function sortTable(table, columnIndex, isAscending) {
|
||||
const tbody = table.querySelector('tbody');
|
||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
|
||||
// 排序行
|
||||
rows.sort((a, b) => {
|
||||
const aValue = a.cells[columnIndex].textContent.trim();
|
||||
const bValue = b.cells[columnIndex].textContent.trim();
|
||||
|
||||
// 尝试数字排序
|
||||
const aNum = parseFloat(aValue);
|
||||
const bNum = parseFloat(bValue);
|
||||
|
||||
if (!isNaN(aNum) && !isNaN(bNum)) {
|
||||
return isAscending ? aNum - bNum : bNum - aNum;
|
||||
}
|
||||
|
||||
// 字符串排序
|
||||
return isAscending
|
||||
? aValue.localeCompare(bValue)
|
||||
: bValue.localeCompare(aValue);
|
||||
});
|
||||
|
||||
// 重新添加行
|
||||
rows.forEach(row => tbody.appendChild(row));
|
||||
}
|
||||
|
||||
// 搜索过滤功能
|
||||
function initSearchFilter(inputId, tableId, columnIndex) {
|
||||
const input = document.getElementById(inputId);
|
||||
const table = document.getElementById(tableId);
|
||||
|
||||
if (!input || !table) return;
|
||||
|
||||
input.addEventListener('input', function() {
|
||||
const filter = this.value.toLowerCase();
|
||||
const rows = table.querySelectorAll('tbody tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
const cell = row.cells[columnIndex];
|
||||
if (cell) {
|
||||
const text = cell.textContent.toLowerCase();
|
||||
row.style.display = text.includes(filter) ? '' : 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// 颜色配置文件 - 集中管理所有UI颜色配置
|
||||
|
||||
// 主颜色配置对象
|
||||
const COLOR_CONFIG = {
|
||||
// 主色调
|
||||
primary: '#1890ff',
|
||||
success: '#52c41a',
|
||||
warning: '#fa8c16',
|
||||
error: '#f5222d',
|
||||
purple: '#722ed1',
|
||||
cyan: '#13c2c2',
|
||||
teal: '#36cfc9',
|
||||
|
||||
// 统计卡片颜色配置
|
||||
statCardColors: [
|
||||
'#1890ff', // blue
|
||||
'#52c41a', // green
|
||||
'#fa8c16', // orange
|
||||
'#f5222d', // red
|
||||
'#722ed1', // purple
|
||||
'#13c2c2' // cyan
|
||||
],
|
||||
|
||||
// 颜色代码到CSS类的映射
|
||||
colorClassMap: {
|
||||
'#1890ff': 'blue',
|
||||
'#52c41a': 'green',
|
||||
'#fa8c16': 'orange',
|
||||
'#f5222d': 'red',
|
||||
'#722ed1': 'purple',
|
||||
'#13c2c2': 'cyan',
|
||||
'#36cfc9': 'teal'
|
||||
},
|
||||
|
||||
// 获取颜色对应的CSS类名
|
||||
getColorClassName: function(colorCode) {
|
||||
return this.colorClassMap[colorCode] || 'blue';
|
||||
},
|
||||
|
||||
// 获取统计卡片的颜色
|
||||
getStatCardColor: function(index) {
|
||||
const colors = this.statCardColors;
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
};
|
||||
|
||||
// 导出配置对象
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = COLOR_CONFIG;
|
||||
} else {
|
||||
// 浏览器环境
|
||||
window.COLOR_CONFIG = COLOR_CONFIG;
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
// 配置管理页面功能实现
|
||||
|
||||
// 工具函数:安全获取DOM元素
|
||||
function getElement(id) {
|
||||
const element = document.getElementById(id);
|
||||
if (!element) {
|
||||
console.warn(`Element with id "${id}" not found`);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
// 工具函数:验证端口号
|
||||
function validatePort(port) {
|
||||
// 确保port是字符串类型
|
||||
var portStr = port;
|
||||
if (port === null || port === undefined || typeof port !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 去除前后空白并验证是否为纯数字
|
||||
portStr = port.trim();
|
||||
if (!/^\d+$/.test(portStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const num = parseInt(portStr, 10);
|
||||
return num >= 1 && num <= 65535 ? num : null;
|
||||
}
|
||||
|
||||
// 初始化配置管理页面
|
||||
function initConfigPage() {
|
||||
loadConfig();
|
||||
setupConfigEventListeners();
|
||||
}
|
||||
|
||||
// 加载系统配置
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const result = await api.getConfig();
|
||||
|
||||
// 检查API返回的错误
|
||||
if (result && result.error) {
|
||||
showErrorMessage('加载配置失败: ' + result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
populateConfigForm(result);
|
||||
} catch (error) {
|
||||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||||
showErrorMessage('加载配置失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
// 填充配置表单
|
||||
function populateConfigForm(config) {
|
||||
// 安全获取配置对象,防止未定义属性访问
|
||||
const dnsServerConfig = config.DNSServer || {};
|
||||
const httpServerConfig = config.HTTPServer || {};
|
||||
const shieldConfig = config.Shield || {};
|
||||
|
||||
// DNS配置 - 使用函数安全设置值,避免 || 操作符可能的错误处理
|
||||
setElementValue('dns-port', getSafeValue(dnsServerConfig.Port, 53));
|
||||
setElementValue('dns-upstream-servers', getSafeArray(dnsServerConfig.UpstreamServers).join(', '));
|
||||
setElementValue('dns-dnssec-upstream-servers', getSafeArray(dnsServerConfig.DNSSECUpstreamServers).join(', '));
|
||||
//setElementValue('dns-stats-file', getSafeValue(dnsServerConfig.StatsFile, 'data/stats.json'));
|
||||
setElementValue('dns-save-interval', getSafeValue(dnsServerConfig.SaveInterval, 30));
|
||||
//setElementValue('dns-cache-ttl', getSafeValue(dnsServerConfig.CacheTTL, 10));
|
||||
setElementValue('dns-enable-ipv6', getSafeValue(dnsServerConfig.EnableIPv6, false));
|
||||
// HTTP配置
|
||||
setElementValue('http-port', getSafeValue(httpServerConfig.Port, 8080));
|
||||
// 屏蔽配置
|
||||
//setElementValue('shield-local-rules-file', getSafeValue(shieldConfig.LocalRulesFile, 'data/rules.txt'));
|
||||
setElementValue('shield-update-interval', getSafeValue(shieldConfig.UpdateInterval, 3600));
|
||||
//setElementValue('shield-hosts-file', getSafeValue(shieldConfig.HostsFile, 'data/hosts.txt'));
|
||||
// 使用服务器端接受的屏蔽方法值,默认使用NXDOMAIN, 可选值: NXDOMAIN, NULL, REFUSED
|
||||
setElementValue('shield-block-method', getSafeValue(shieldConfig.BlockMethod, 'NXDOMAIN'));
|
||||
}
|
||||
|
||||
// 工具函数:安全设置元素值
|
||||
function setElementValue(elementId, value) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element && element.tagName === 'INPUT') {
|
||||
if (element.type === 'checkbox') {
|
||||
element.checked = value;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
} else if (!element) {
|
||||
console.warn(`Element with id "${elementId}" not found for setting value: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数:安全获取值,如果未定义或为null则返回默认值
|
||||
function getSafeValue(value, defaultValue) {
|
||||
// 更严格的检查,避免0、空字符串等被默认值替换
|
||||
return value === undefined || value === null ? defaultValue : value;
|
||||
}
|
||||
|
||||
// 工具函数:安全获取数组,如果不是数组则返回空数组
|
||||
function getSafeArray(value) {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
async function handleSaveConfig() {
|
||||
const formData = collectFormData();
|
||||
if (!formData) return;
|
||||
|
||||
try {
|
||||
const result = await api.saveConfig(formData);
|
||||
|
||||
// 检查API返回的错误
|
||||
if (result && result.error) {
|
||||
showErrorMessage('保存配置失败: ' + result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessMessage('配置保存成功');
|
||||
} catch (error) {
|
||||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||||
showErrorMessage('保存配置失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
// 重启服务
|
||||
async function handleRestartService() {
|
||||
if (!confirm('确定要重启DNS服务吗?重启期间服务可能会短暂不可用。')) return;
|
||||
|
||||
try {
|
||||
const result = await api.restartService();
|
||||
|
||||
// 检查API返回的错误
|
||||
if (result && result.error) {
|
||||
showErrorMessage('服务重启失败: ' + result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessMessage('服务重启成功');
|
||||
} catch (error) {
|
||||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||||
showErrorMessage('重启服务失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
// 收集表单数据并验证
|
||||
function collectFormData() {
|
||||
// 验证端口号 - 使用安全获取元素值的函数
|
||||
const dnsPortValue = getElementValue('dns-port');
|
||||
const httpPortValue = getElementValue('http-port');
|
||||
|
||||
const dnsPort = validatePort(dnsPortValue);
|
||||
const httpPort = validatePort(httpPortValue);
|
||||
|
||||
if (!dnsPort) {
|
||||
showErrorMessage('DNS端口号无效(必须是1-65535之间的整数)');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!httpPort) {
|
||||
showErrorMessage('HTTP端口号无效(必须是1-65535之间的整数)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 安全获取上游服务器列表
|
||||
const upstreamServersText = getElementValue('dns-upstream-servers');
|
||||
const upstreamServers = upstreamServersText ?
|
||||
upstreamServersText.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
|
||||
[];
|
||||
|
||||
// 安全获取DNSSEC上游服务器列表
|
||||
const dnssecUpstreamServersText = getElementValue('dns-dnssec-upstream-servers');
|
||||
const dnssecUpstreamServers = dnssecUpstreamServersText ?
|
||||
dnssecUpstreamServersText.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
|
||||
[];
|
||||
|
||||
// 安全获取并转换整数值
|
||||
const timeoutValue = getElementValue('dns-timeout');
|
||||
const timeout = timeoutValue ? parseInt(timeoutValue, 10) : 5;
|
||||
|
||||
const saveIntervalValue = getElementValue('dns-save-interval');
|
||||
const saveInterval = saveIntervalValue ? parseInt(saveIntervalValue, 10) : 300;
|
||||
|
||||
const updateIntervalValue = getElementValue('shield-update-interval');
|
||||
const updateInterval = updateIntervalValue ? parseInt(updateIntervalValue, 10) : 3600;
|
||||
|
||||
return {
|
||||
dnsserver: {
|
||||
port: dnsPort,
|
||||
upstreamServers: upstreamServers,
|
||||
dnssecUpstreamServers: dnssecUpstreamServers,
|
||||
timeout: timeout,
|
||||
saveInterval: saveInterval,
|
||||
enableIPv6: getElementValue('dns-enable-ipv6')
|
||||
},
|
||||
httpserver: {
|
||||
port: httpPort
|
||||
},
|
||||
shield: {
|
||||
updateInterval: updateInterval,
|
||||
blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 工具函数:安全获取元素值
|
||||
function getElementValue(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element && element.tagName === 'INPUT') {
|
||||
if (element.type === 'checkbox') {
|
||||
return element.checked;
|
||||
}
|
||||
return element.value;
|
||||
}
|
||||
return ''; // 默认返回空字符串
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
function setupConfigEventListeners() {
|
||||
// 保存配置按钮
|
||||
getElement('save-config-btn')?.addEventListener('click', handleSaveConfig);
|
||||
|
||||
// 重启服务按钮
|
||||
getElement('restart-service-btn')?.addEventListener('click', handleRestartService);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccessMessage(message) {
|
||||
showNotification(message, 'success');
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showErrorMessage(message) {
|
||||
showNotification(message, 'error');
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
function showNotification(message, type = 'info') {
|
||||
// 移除现有通知
|
||||
const existingNotification = document.querySelector('.notification');
|
||||
if (existingNotification) {
|
||||
existingNotification.remove();
|
||||
}
|
||||
|
||||
// 创建新通知
|
||||
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`;
|
||||
|
||||
// 设置通知样式(兼容Tailwind和原生CSS)
|
||||
notification.style.cssText += `
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
opacity: 0;
|
||||
`;
|
||||
|
||||
if (type === 'success') {
|
||||
notification.style.backgroundColor = '#10b981';
|
||||
notification.style.color = 'white';
|
||||
} else if (type === 'error') {
|
||||
notification.style.backgroundColor = '#ef4444';
|
||||
notification.style.color = 'white';
|
||||
} else {
|
||||
notification.style.backgroundColor = '#3b82f6';
|
||||
notification.style.color = 'white';
|
||||
}
|
||||
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 显示通知
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '1';
|
||||
}, 10);
|
||||
|
||||
// 3秒后隐藏通知
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initConfigPage);
|
||||
} else {
|
||||
initConfigPage();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,202 +0,0 @@
|
||||
// Hosts管理页面功能实现
|
||||
|
||||
// 初始化Hosts管理页面
|
||||
function initHostsPage() {
|
||||
// 加载Hosts规则
|
||||
loadHostsRules();
|
||||
// 设置事件监听器
|
||||
setupHostsEventListeners();
|
||||
}
|
||||
|
||||
// 加载Hosts规则
|
||||
async function loadHostsRules() {
|
||||
try {
|
||||
const response = await fetch('/api/shield/hosts');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load hosts rules');
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
// 处理API返回的数据格式
|
||||
let hostsRules = [];
|
||||
if (data && Array.isArray(data)) {
|
||||
// 直接是数组格式
|
||||
hostsRules = data;
|
||||
} else if (data && data.hosts) {
|
||||
// 包含在hosts字段中
|
||||
hostsRules = data.hosts;
|
||||
}
|
||||
|
||||
updateHostsTable(hostsRules);
|
||||
} catch (error) {
|
||||
console.error('Error loading hosts rules:', error);
|
||||
showErrorMessage('加载Hosts规则失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新Hosts表格
|
||||
function updateHostsTable(hostsRules) {
|
||||
const tbody = document.getElementById('hosts-table-body');
|
||||
|
||||
if (hostsRules.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="py-4 text-center text-gray-500">暂无Hosts条目</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = hostsRules.map(rule => {
|
||||
// 处理对象格式的规则
|
||||
const ip = rule.ip || '';
|
||||
const domain = rule.domain || '';
|
||||
|
||||
return `
|
||||
<tr class="border-b border-gray-200">
|
||||
<td class="py-3 px-4">${ip}</td>
|
||||
<td class="py-3 px-4">${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-ip="${ip}" data-domain="${domain}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// 重新绑定删除事件
|
||||
document.querySelectorAll('.delete-hosts-btn').forEach(btn => {
|
||||
btn.addEventListener('click', handleDeleteHostsRule);
|
||||
});
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
function setupHostsEventListeners() {
|
||||
// 保存Hosts按钮
|
||||
document.getElementById('save-hosts-btn').addEventListener('click', handleAddHostsRule);
|
||||
}
|
||||
|
||||
// 处理添加Hosts规则
|
||||
async function handleAddHostsRule() {
|
||||
const ip = document.getElementById('hosts-ip').value.trim();
|
||||
const domain = document.getElementById('hosts-domain').value.trim();
|
||||
|
||||
if (!ip || !domain) {
|
||||
showErrorMessage('IP地址和域名不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
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 rule');
|
||||
}
|
||||
|
||||
showSuccessMessage('Hosts规则添加成功');
|
||||
|
||||
// 清空输入框
|
||||
document.getElementById('hosts-ip').value = '';
|
||||
document.getElementById('hosts-domain').value = '';
|
||||
|
||||
// 重新加载规则
|
||||
loadHostsRules();
|
||||
} catch (error) {
|
||||
console.error('Error adding hosts rule:', error);
|
||||
showErrorMessage('添加Hosts规则失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理删除Hosts规则
|
||||
async function handleDeleteHostsRule(e) {
|
||||
const ip = e.target.closest('.delete-hosts-btn').dataset.ip;
|
||||
const domain = e.target.closest('.delete-hosts-btn').dataset.domain;
|
||||
|
||||
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 rule');
|
||||
}
|
||||
|
||||
showSuccessMessage('Hosts规则删除成功');
|
||||
|
||||
// 重新加载规则
|
||||
loadHostsRules();
|
||||
} catch (error) {
|
||||
console.error('Error deleting hosts rule:', error);
|
||||
showErrorMessage('删除Hosts规则失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccessMessage(message) {
|
||||
showNotification(message, 'success');
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showErrorMessage(message) {
|
||||
showNotification(message, 'error');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 显示通知
|
||||
function showNotification(message, type = 'info') {
|
||||
// 移除现有通知
|
||||
const existingNotification = document.querySelector('.notification');
|
||||
if (existingNotification) {
|
||||
existingNotification.remove();
|
||||
}
|
||||
|
||||
// 创建新通知
|
||||
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-all duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
|
||||
// 设置通知样式
|
||||
if (type === 'success') {
|
||||
notification.classList.add('bg-green-500', 'text-white');
|
||||
} else if (type === 'error') {
|
||||
notification.classList.add('bg-red-500', 'text-white');
|
||||
} else {
|
||||
notification.classList.add('bg-blue-500', 'text-white');
|
||||
}
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fa fa-${type === 'success' ? 'check' : type === 'error' ? 'exclamation' : 'info'}"></i>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 显示通知
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('opacity-0');
|
||||
}, 100);
|
||||
|
||||
// 3秒后隐藏通知
|
||||
setTimeout(() => {
|
||||
notification.classList.add('opacity-0');
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initHostsPage);
|
||||
} else {
|
||||
initHostsPage();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,405 +0,0 @@
|
||||
// main.js - 主脚本文件
|
||||
|
||||
// 页面导航功能
|
||||
function setupNavigation() {
|
||||
// 侧边栏菜单项
|
||||
const menuItems = document.querySelectorAll('nav a');
|
||||
const contentSections = [
|
||||
document.getElementById('dashboard-content'),
|
||||
document.getElementById('shield-content'),
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('logs-content'),
|
||||
document.getElementById('config-content')
|
||||
];
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
|
||||
menuItems.forEach((item, index) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
// 允许浏览器自动更新地址栏中的hash,不阻止默认行为
|
||||
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 移动端侧边栏切换
|
||||
const toggleSidebar = document.getElementById('toggle-sidebar');
|
||||
const closeSidebarBtn = document.getElementById('close-sidebar');
|
||||
const sidebar = document.getElementById('mobile-sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
|
||||
// 打开侧边栏函数
|
||||
function openSidebar() {
|
||||
console.log('Opening sidebar...');
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
sidebar.classList.add('translate-x-0');
|
||||
}
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.classList.remove('hidden');
|
||||
sidebarOverlay.classList.add('block');
|
||||
}
|
||||
// 防止页面滚动
|
||||
document.body.style.overflow = 'hidden';
|
||||
console.log('Sidebar opened successfully');
|
||||
}
|
||||
|
||||
// 关闭侧边栏函数
|
||||
function closeSidebar() {
|
||||
console.log('Closing sidebar...');
|
||||
if (sidebar) {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebar.classList.remove('translate-x-0');
|
||||
}
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
sidebarOverlay.classList.remove('block');
|
||||
}
|
||||
// 恢复页面滚动
|
||||
document.body.style.overflow = '';
|
||||
console.log('Sidebar closed successfully');
|
||||
}
|
||||
|
||||
// 切换侧边栏函数
|
||||
function toggleSidebarVisibility() {
|
||||
console.log('Toggling sidebar visibility...');
|
||||
console.log('Current sidebar classes:', sidebar ? sidebar.className : 'sidebar not found');
|
||||
if (sidebar && sidebar.classList.contains('-translate-x-full')) {
|
||||
console.log('Sidebar is hidden, opening...');
|
||||
openSidebar();
|
||||
} else {
|
||||
console.log('Sidebar is visible, closing...');
|
||||
closeSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定切换按钮事件
|
||||
if (toggleSidebar) {
|
||||
toggleSidebar.addEventListener('click', toggleSidebarVisibility);
|
||||
}
|
||||
|
||||
// 绑定关闭按钮事件
|
||||
if (closeSidebarBtn) {
|
||||
closeSidebarBtn.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// 绑定遮罩层点击事件
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
menuItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
// 检查是否是移动设备视图
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 添加键盘事件监听,按ESC键关闭侧边栏
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面初始化函数 - 根据当前hash值初始化对应页面
|
||||
function initPageByHash() {
|
||||
const hash = window.location.hash.substring(1);
|
||||
|
||||
// 隐藏所有内容区域
|
||||
const contentSections = [
|
||||
document.getElementById('dashboard-content'),
|
||||
document.getElementById('shield-content'),
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('logs-content'),
|
||||
document.getElementById('config-content')
|
||||
];
|
||||
|
||||
contentSections.forEach(section => {
|
||||
if (section) {
|
||||
section.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 显示当前页面内容
|
||||
const currentSection = document.getElementById(`${hash}-content`);
|
||||
if (currentSection) {
|
||||
currentSection.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 更新页面标题
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
if (pageTitle) {
|
||||
const titles = {
|
||||
'dashboard': '仪表盘',
|
||||
'shield': '屏蔽管理',
|
||||
'hosts': 'Hosts管理',
|
||||
'query': 'DNS屏蔽查询',
|
||||
'logs': '查询日志',
|
||||
'config': '系统设置'
|
||||
};
|
||||
pageTitle.textContent = titles[hash] || '仪表盘';
|
||||
}
|
||||
|
||||
// 页面特定初始化 - 使用setTimeout延迟调用,确保所有脚本文件都已加载完成
|
||||
if (hash === 'shield') {
|
||||
setTimeout(() => {
|
||||
if (typeof initShieldPage === 'function') {
|
||||
initShieldPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'hosts') {
|
||||
setTimeout(() => {
|
||||
if (typeof initHostsPage === 'function') {
|
||||
initHostsPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'logs') {
|
||||
setTimeout(() => {
|
||||
if (typeof initLogsPage === 'function') {
|
||||
initLogsPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'dashboard') {
|
||||
setTimeout(() => {
|
||||
if (typeof loadDashboardData === 'function') {
|
||||
loadDashboardData();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置导航
|
||||
setupNavigation();
|
||||
|
||||
// 初始化页面
|
||||
initPageByHash();
|
||||
|
||||
// 添加hashchange事件监听,处理浏览器前进/后退按钮
|
||||
window.addEventListener('hashchange', initPageByHash);
|
||||
|
||||
// 定期更新系统状态
|
||||
setInterval(updateSystemStatus, 5000);
|
||||
}
|
||||
|
||||
// 更新系统状态
|
||||
function updateSystemStatus() {
|
||||
fetch('/api/status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
if (uptimeElement) {
|
||||
uptimeElement.textContent = `正常运行中 | ${formatUptime(data.uptime)}`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新系统状态失败:', error);
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
if (uptimeElement) {
|
||||
uptimeElement.textContent = '连接异常';
|
||||
uptimeElement.classList.add('text-danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化运行时间
|
||||
function formatUptime(milliseconds) {
|
||||
// 简化版的格式化,实际使用时需要根据API返回的数据格式调整
|
||||
const seconds = Math.floor(milliseconds / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
if (days > 0) {
|
||||
return `${days}天${hours % 24}小时`;
|
||||
} else if (hours > 0) {
|
||||
return `${hours}小时${minutes % 60}分钟`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}分钟${seconds % 60}秒`;
|
||||
} else {
|
||||
return `${seconds}秒`;
|
||||
}
|
||||
}
|
||||
|
||||
// 账户功能 - 下拉菜单、注销和修改密码
|
||||
function setupAccountFeatures() {
|
||||
// 下拉菜单功能
|
||||
const accountDropdown = document.getElementById('account-dropdown');
|
||||
const accountMenu = document.getElementById('account-menu');
|
||||
const changePasswordBtn = document.getElementById('change-password-btn');
|
||||
const logoutBtn = document.getElementById('logout-btn');
|
||||
const changePasswordModal = document.getElementById('change-password-modal');
|
||||
const closeModalBtn = document.getElementById('close-modal-btn');
|
||||
const cancelChangePasswordBtn = document.getElementById('cancel-change-password');
|
||||
const changePasswordForm = document.getElementById('change-password-form');
|
||||
const passwordMismatch = document.getElementById('password-mismatch');
|
||||
const newPassword = document.getElementById('new-password');
|
||||
const confirmPassword = document.getElementById('confirm-password');
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
document.addEventListener('click', (e) => {
|
||||
if (accountDropdown && !accountDropdown.contains(e.target)) {
|
||||
accountMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 点击账户区域切换下拉菜单
|
||||
if (accountDropdown) {
|
||||
accountDropdown.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
accountMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// 打开修改密码模态框
|
||||
if (changePasswordBtn) {
|
||||
changePasswordBtn.addEventListener('click', () => {
|
||||
accountMenu.classList.add('hidden');
|
||||
changePasswordModal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭修改密码模态框
|
||||
function closeModal() {
|
||||
changePasswordModal.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
changePasswordForm.reset();
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 绑定关闭模态框事件
|
||||
if (closeModalBtn) {
|
||||
closeModalBtn.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
if (cancelChangePasswordBtn) {
|
||||
cancelChangePasswordBtn.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
// 点击模态框外部关闭模态框
|
||||
if (changePasswordModal) {
|
||||
changePasswordModal.addEventListener('click', (e) => {
|
||||
if (e.target === changePasswordModal) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 按ESC键关闭模态框
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && !changePasswordModal.classList.contains('hidden')) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 密码匹配验证
|
||||
if (newPassword && confirmPassword) {
|
||||
confirmPassword.addEventListener('input', () => {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
} else {
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
newPassword.addEventListener('input', () => {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
} else {
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 修改密码表单提交
|
||||
if (changePasswordForm) {
|
||||
changePasswordForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 验证密码匹配
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(changePasswordForm);
|
||||
const data = {
|
||||
currentPassword: formData.get('currentPassword'),
|
||||
newPassword: formData.get('newPassword')
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.status === 'success') {
|
||||
// 密码修改成功
|
||||
alert('密码修改成功');
|
||||
closeModal();
|
||||
} else {
|
||||
// 密码修改失败
|
||||
alert(result.error || '密码修改失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('修改密码失败:', error);
|
||||
alert('修改密码失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 注销功能
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
await fetch('/api/logout', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
// 重定向到登录页面
|
||||
window.location.href = '/login';
|
||||
} catch (error) {
|
||||
console.error('注销失败:', error);
|
||||
alert('注销失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置导航
|
||||
setupNavigation();
|
||||
|
||||
// 设置账户功能
|
||||
setupAccountFeatures();
|
||||
|
||||
// 初始化页面
|
||||
initPageByHash();
|
||||
|
||||
// 添加hashchange事件监听,处理浏览器前进/后退按钮
|
||||
window.addEventListener('hashchange', initPageByHash);
|
||||
|
||||
// 定期更新系统状态
|
||||
setInterval(updateSystemStatus, 5000);
|
||||
}
|
||||
|
||||
// 页面加载完成后执行初始化
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
@@ -1,255 +0,0 @@
|
||||
// 初始化远程黑名单面板
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
// 初始化配置管理面板
|
||||
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);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,308 +0,0 @@
|
||||
// 初始化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
|
||||
});
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
// 初始化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');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
// 屏蔽规则管理模块
|
||||
|
||||
// 全局变量
|
||||
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
|
||||
});
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
// DNS查询页面功能实现
|
||||
|
||||
// 初始化查询页面
|
||||
function initQueryPage() {
|
||||
console.log('初始化DNS查询页面...');
|
||||
setupQueryEventListeners();
|
||||
loadQueryHistory();
|
||||
}
|
||||
|
||||
// 执行DNS查询
|
||||
async function handleDNSQuery() {
|
||||
const domainInput = document.getElementById('dns-query-domain');
|
||||
const resultDiv = document.getElementById('query-result');
|
||||
|
||||
if (!domainInput || !resultDiv) {
|
||||
console.error('找不到必要的DOM元素');
|
||||
return;
|
||||
}
|
||||
|
||||
const domain = domainInput.value.trim();
|
||||
if (!domain) {
|
||||
showErrorMessage('请输入域名');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/query?domain=${encodeURIComponent(domain)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('查询失败');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
displayQueryResult(result, domain);
|
||||
saveQueryHistory(domain, result);
|
||||
loadQueryHistory();
|
||||
} catch (error) {
|
||||
console.error('DNS查询出错:', error);
|
||||
showErrorMessage('查询失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示查询结果
|
||||
function displayQueryResult(result, domain) {
|
||||
const resultDiv = document.getElementById('query-result');
|
||||
if (!resultDiv) return;
|
||||
|
||||
// 显示结果容器
|
||||
resultDiv.classList.remove('hidden');
|
||||
|
||||
// 解析结果
|
||||
const status = result.blocked ? '被屏蔽' : '正常';
|
||||
const statusClass = result.blocked ? 'text-danger' : 'text-success';
|
||||
const blockType = result.blocked ? result.blockRuleType || '未知' : '正常';
|
||||
const blockRule = result.blocked ? result.blockRule || '未知' : '无';
|
||||
const blockSource = result.blocked ? result.blocksource || '未知' : '无';
|
||||
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;
|
||||
|
||||
// 检查是否存在屏蔽规则显示元素,如果不存在则创建
|
||||
let blockRuleElement = document.getElementById('result-block-rule');
|
||||
if (!blockRuleElement) {
|
||||
// 创建屏蔽规则显示区域
|
||||
const grid = resultDiv.querySelector('.grid');
|
||||
if (grid) {
|
||||
const newGridItem = document.createElement('div');
|
||||
newGridItem.className = 'bg-gray-50 p-4 rounded-lg';
|
||||
newGridItem.innerHTML = `
|
||||
<h4 class="text-sm font-medium text-gray-500 mb-2">屏蔽规则</h4>
|
||||
<p class="text-lg font-semibold" id="result-block-rule">-</p>
|
||||
`;
|
||||
grid.appendChild(newGridItem);
|
||||
blockRuleElement = document.getElementById('result-block-rule');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新屏蔽规则显示
|
||||
if (blockRuleElement) {
|
||||
blockRuleElement.textContent = blockRule;
|
||||
}
|
||||
|
||||
// 检查是否存在屏蔽来源显示元素,如果不存在则创建
|
||||
let blockSourceElement = document.getElementById('result-block-source');
|
||||
if (!blockSourceElement) {
|
||||
// 创建屏蔽来源显示区域
|
||||
const grid = resultDiv.querySelector('.grid');
|
||||
if (grid) {
|
||||
const newGridItem = document.createElement('div');
|
||||
newGridItem.className = 'bg-gray-50 p-4 rounded-lg';
|
||||
newGridItem.innerHTML = `
|
||||
<h4 class="text-sm font-medium text-gray-500 mb-2">屏蔽来源</h4>
|
||||
<p class="text-lg font-semibold" id="result-block-source">-</p>
|
||||
`;
|
||||
grid.appendChild(newGridItem);
|
||||
blockSourceElement = document.getElementById('result-block-source');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新屏蔽来源显示
|
||||
if (blockSourceElement) {
|
||||
blockSourceElement.textContent = blockSource;
|
||||
}
|
||||
|
||||
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,
|
||||
blockRuleType: result.blockRuleType,
|
||||
blockRule: result.blockRule,
|
||||
blocksource: result.blocksource
|
||||
}
|
||||
};
|
||||
|
||||
// 添加到历史记录开头
|
||||
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.blockRuleType : '正常';
|
||||
const blockRule = item.result.blocked ? item.result.blockRule : '无';
|
||||
const blockSource = item.result.blocked ? item.result.blocksource : '无';
|
||||
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">规则: ${blockRule}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">来源: ${blockSource}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${formattedTime}</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>
|
||||
`;
|
||||
}).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() {
|
||||
// 查询按钮事件
|
||||
const queryBtn = document.getElementById('dns-query-btn');
|
||||
if (queryBtn) {
|
||||
queryBtn.addEventListener('click', handleDNSQuery);
|
||||
}
|
||||
|
||||
// 输入框回车键事件
|
||||
const domainInput = document.getElementById('dns-query-domain');
|
||||
if (domainInput) {
|
||||
domainInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleDNSQuery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 清空历史按钮事件
|
||||
const clearHistoryBtn = document.getElementById('clear-history-btn');
|
||||
if (clearHistoryBtn) {
|
||||
clearHistoryBtn.addEventListener('click', clearQueryHistory);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccessMessage(message) {
|
||||
showNotification(message, 'success');
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showErrorMessage(message) {
|
||||
showNotification(message, 'error');
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
function showNotification(message, type = 'info') {
|
||||
// 移除现有通知
|
||||
const existingNotification = document.querySelector('.notification');
|
||||
if (existingNotification) {
|
||||
existingNotification.remove();
|
||||
}
|
||||
|
||||
// 创建新通知
|
||||
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-all duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
|
||||
// 设置通知样式
|
||||
if (type === 'success') {
|
||||
notification.classList.add('bg-green-500', 'text-white');
|
||||
} else if (type === 'error') {
|
||||
notification.classList.add('bg-red-500', 'text-white');
|
||||
} else {
|
||||
notification.classList.add('bg-blue-500', 'text-white');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 显示通知
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('opacity-0');
|
||||
notification.classList.add('opacity-100');
|
||||
}, 10);
|
||||
|
||||
// 3秒后隐藏通知
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('opacity-100');
|
||||
notification.classList.add('opacity-0');
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initQueryPage);
|
||||
} else {
|
||||
initQueryPage();
|
||||
}
|
||||
|
||||
// 当切换到DNS查询页面时重新加载数据
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 监听hash变化,当切换到DNS查询页面时重新加载数据
|
||||
window.addEventListener('hashchange', () => {
|
||||
if (window.location.hash === '#query') {
|
||||
initQueryPage();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,305 +0,0 @@
|
||||
// 服务器状态组件 - 显示CPU使用率和查询统计
|
||||
|
||||
// 全局变量
|
||||
let serverStatusUpdateTimer = null;
|
||||
let previousServerData = {
|
||||
cpu: 0,
|
||||
queries: 0
|
||||
};
|
||||
|
||||
// 初始化服务器状态组件
|
||||
function initServerStatusWidget() {
|
||||
// 确保DOM元素存在
|
||||
const widget = document.getElementById('server-status-widget');
|
||||
if (!widget) return;
|
||||
|
||||
// 初始化页面类型检测
|
||||
updateWidgetDisplayByPageType();
|
||||
|
||||
// 设置页面切换事件监听
|
||||
handlePageSwitchEvents();
|
||||
|
||||
// 设置WebSocket监听(如果可用)
|
||||
setupWebSocketListeners();
|
||||
|
||||
// 立即加载一次数据
|
||||
loadServerStatusData();
|
||||
|
||||
// 设置定时更新(每5秒更新一次)
|
||||
serverStatusUpdateTimer = setInterval(loadServerStatusData, 5000);
|
||||
}
|
||||
|
||||
// 判断当前页面是否为仪表盘
|
||||
function isCurrentPageDashboard() {
|
||||
// 方法1:检查侧边栏激活状态
|
||||
const dashboardLink = document.querySelector('.sidebar a[href="#dashboard"]');
|
||||
if (dashboardLink && dashboardLink.classList.contains('active')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 方法2:检查仪表盘特有元素
|
||||
const dashboardElements = [
|
||||
'#dashboard-container',
|
||||
'.dashboard-summary',
|
||||
'#dashboard-stats'
|
||||
];
|
||||
|
||||
for (const selector of dashboardElements) {
|
||||
if (document.querySelector(selector)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 方法3:检查URL哈希值
|
||||
if (window.location.hash === '#dashboard' || window.location.hash === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据页面类型更新组件显示
|
||||
function updateWidgetDisplayByPageType() {
|
||||
const additionalStats = document.getElementById('server-additional-stats');
|
||||
if (!additionalStats) return;
|
||||
|
||||
// 如果当前页面是仪表盘,隐藏额外统计指标
|
||||
if (isCurrentPageDashboard()) {
|
||||
additionalStats.classList.add('hidden');
|
||||
} else {
|
||||
// 非仪表盘页面,显示额外统计指标
|
||||
additionalStats.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理页面切换事件
|
||||
function handlePageSwitchEvents() {
|
||||
// 监听哈希变化(导航切换)
|
||||
window.addEventListener('hashchange', updateWidgetDisplayByPageType);
|
||||
|
||||
// 监听侧边栏点击事件
|
||||
const sidebarLinks = document.querySelectorAll('.sidebar a');
|
||||
sidebarLinks.forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
// 延迟检查,确保页面已切换
|
||||
setTimeout(updateWidgetDisplayByPageType, 100);
|
||||
});
|
||||
});
|
||||
|
||||
// 监听导航菜单点击事件
|
||||
const navLinks = document.querySelectorAll('nav a');
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
setTimeout(updateWidgetDisplayByPageType, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 监控WebSocket连接状态
|
||||
function monitorWebSocketConnection() {
|
||||
// 如果存在WebSocket连接,监听消息
|
||||
if (window.socket) {
|
||||
window.socket.addEventListener('message', function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'status_update') {
|
||||
updateServerStatusWidget(data.payload);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析WebSocket消息失败:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 设置WebSocket监听器
|
||||
function setupWebSocketListeners() {
|
||||
// 如果WebSocket已经存在
|
||||
if (window.socket) {
|
||||
monitorWebSocketConnection();
|
||||
} else {
|
||||
// 监听socket初始化事件
|
||||
window.addEventListener('socketInitialized', function() {
|
||||
monitorWebSocketConnection();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 加载服务器状态数据
|
||||
async function loadServerStatusData() {
|
||||
try {
|
||||
// 使用现有的API获取系统状态
|
||||
const api = window.api || {};
|
||||
const getStatusFn = api.getStatus || function() { return Promise.resolve({}); };
|
||||
const statusData = await getStatusFn();
|
||||
if (statusData && !statusData.error) {
|
||||
updateServerStatusWidget(statusData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载服务器状态数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新服务器状态组件
|
||||
function updateServerStatusWidget(stats) {
|
||||
// 确保组件存在
|
||||
const widget = document.getElementById('server-status-widget');
|
||||
if (!widget) return;
|
||||
|
||||
// 确保stats存在
|
||||
stats = stats || {};
|
||||
|
||||
// 提取CPU使用率
|
||||
let cpuUsage = 0;
|
||||
if (stats.system && typeof stats.system.cpu === 'number') {
|
||||
cpuUsage = stats.system.cpu;
|
||||
} else if (typeof stats.cpuUsage === 'number') {
|
||||
cpuUsage = stats.cpuUsage;
|
||||
}
|
||||
|
||||
// 提取查询统计数据
|
||||
let totalQueries = 0;
|
||||
let blockedQueries = 0;
|
||||
let allowedQueries = 0;
|
||||
|
||||
if (stats.dns) {
|
||||
const allowed = typeof stats.dns.Allowed === 'number' ? stats.dns.Allowed : 0;
|
||||
const blocked = typeof stats.dns.Blocked === 'number' ? stats.dns.Blocked : 0;
|
||||
const errors = typeof stats.dns.Errors === 'number' ? stats.dns.Errors : 0;
|
||||
totalQueries = allowed + blocked + errors;
|
||||
blockedQueries = blocked;
|
||||
allowedQueries = allowed;
|
||||
} else {
|
||||
totalQueries = typeof stats.totalQueries === 'number' ? stats.totalQueries : 0;
|
||||
blockedQueries = typeof stats.blockedQueries === 'number' ? stats.blockedQueries : 0;
|
||||
allowedQueries = typeof stats.allowedQueries === 'number' ? stats.allowedQueries : 0;
|
||||
}
|
||||
|
||||
// 更新CPU使用率
|
||||
const cpuValueElement = document.getElementById('server-cpu-value');
|
||||
if (cpuValueElement) {
|
||||
cpuValueElement.textContent = cpuUsage.toFixed(1) + '%';
|
||||
}
|
||||
|
||||
const cpuBarElement = document.getElementById('server-cpu-bar');
|
||||
if (cpuBarElement) {
|
||||
cpuBarElement.style.width = Math.min(cpuUsage, 100) + '%';
|
||||
|
||||
// 根据CPU使用率改变颜色
|
||||
if (cpuUsage > 80) {
|
||||
cpuBarElement.className = 'h-full bg-danger rounded-full';
|
||||
} else if (cpuUsage > 50) {
|
||||
cpuBarElement.className = 'h-full bg-warning rounded-full';
|
||||
} else {
|
||||
cpuBarElement.className = 'h-full bg-success rounded-full';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新查询量
|
||||
const queriesValueElement = document.getElementById('server-queries-value');
|
||||
if (queriesValueElement) {
|
||||
queriesValueElement.textContent = formatNumber(totalQueries);
|
||||
}
|
||||
|
||||
// 计算查询量百分比(假设最大查询量为10000)
|
||||
const queryPercentage = Math.min((totalQueries / 10000) * 100, 100);
|
||||
const queriesBarElement = document.getElementById('server-queries-bar');
|
||||
if (queriesBarElement) {
|
||||
queriesBarElement.style.width = queryPercentage + '%';
|
||||
}
|
||||
|
||||
// 更新额外统计指标
|
||||
const totalQueriesElement = document.getElementById('server-total-queries');
|
||||
if (totalQueriesElement) {
|
||||
totalQueriesElement.textContent = formatNumber(totalQueries);
|
||||
}
|
||||
|
||||
const blockedQueriesElement = document.getElementById('server-blocked-queries');
|
||||
if (blockedQueriesElement) {
|
||||
blockedQueriesElement.textContent = formatNumber(blockedQueries);
|
||||
}
|
||||
|
||||
const allowedQueriesElement = document.getElementById('server-allowed-queries');
|
||||
if (allowedQueriesElement) {
|
||||
allowedQueriesElement.textContent = formatNumber(allowedQueries);
|
||||
}
|
||||
|
||||
// 添加光晕提示效果
|
||||
if (previousServerData.cpu !== cpuUsage || previousServerData.queries !== totalQueries) {
|
||||
addGlowEffect();
|
||||
}
|
||||
|
||||
// 更新服务器状态指示器
|
||||
const statusIndicator = document.getElementById('server-status-indicator');
|
||||
if (statusIndicator) {
|
||||
// 检查系统状态
|
||||
if (stats.system && stats.system.status === 'error') {
|
||||
statusIndicator.className = 'inline-block w-2 h-2 bg-danger rounded-full';
|
||||
} else {
|
||||
statusIndicator.className = 'inline-block w-2 h-2 bg-success rounded-full';
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前数据用于下次比较
|
||||
previousServerData = {
|
||||
cpu: cpuUsage,
|
||||
queries: totalQueries
|
||||
};
|
||||
}
|
||||
|
||||
// 添加光晕提示效果
|
||||
function addGlowEffect() {
|
||||
const widget = document.getElementById('server-status-widget');
|
||||
if (!widget) return;
|
||||
|
||||
// 添加光晕类
|
||||
widget.classList.add('glow-effect');
|
||||
|
||||
// 2秒后移除光晕
|
||||
setTimeout(function() {
|
||||
widget.classList.remove('glow-effect');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 格式化数字
|
||||
function formatNumber(num) {
|
||||
// 显示完整数字的最大长度阈值
|
||||
const MAX_FULL_LENGTH = 5;
|
||||
|
||||
// 先获取完整数字字符串
|
||||
const fullNumStr = num.toString();
|
||||
|
||||
// 如果数字长度小于等于阈值,直接返回完整数字
|
||||
if (fullNumStr.length <= MAX_FULL_LENGTH) {
|
||||
return fullNumStr;
|
||||
}
|
||||
|
||||
// 否则使用缩写格式
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
|
||||
return fullNumStr;
|
||||
}
|
||||
|
||||
// 在DOM加载完成后初始化
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
// 延迟初始化,确保页面完全加载
|
||||
setTimeout(initServerStatusWidget, 500);
|
||||
});
|
||||
|
||||
// 在页面卸载时清理资源
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (serverStatusUpdateTimer) {
|
||||
clearInterval(serverStatusUpdateTimer);
|
||||
serverStatusUpdateTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 导出函数供其他模块使用
|
||||
window.serverStatusWidget = {
|
||||
init: initServerStatusWidget,
|
||||
update: updateServerStatusWidget
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
1
staticbak/static/js/vendor/chart.umd.min.js
vendored
1
staticbak/static/js/vendor/chart.umd.min.js
vendored
File diff suppressed because one or more lines are too long
19
staticbak/static/js/vendor/tailwind.js
vendored
19
staticbak/static/js/vendor/tailwind.js
vendored
@@ -1,19 +0,0 @@
|
||||
tailwind.config = {
|
||||
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'],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS服务器控制台 - 登录</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 24px;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: #7f8c8d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #e1e5e9;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #fee;
|
||||
color: #c00;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<h1>DNS服务器控制台</h1>
|
||||
<p>请输入您的登录凭据</p>
|
||||
</div>
|
||||
|
||||
<div class="error-message" id="errorMessage"></div>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" placeholder="请输入密码" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn" id="loginBtn">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const loginBtn = document.getElementById('loginBtn');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
// 显示加载状态
|
||||
loginBtn.textContent = '登录中...';
|
||||
loginBtn.classList.add('loading');
|
||||
errorMessage.style.display = 'none';
|
||||
|
||||
// 发送登录请求
|
||||
fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
throw new Error('未知用户名或密码');
|
||||
} else {
|
||||
throw new Error('登录失败');
|
||||
}
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
// 登录成功,重定向到主页
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
if (data.error === '用户名或密码错误') {
|
||||
throw new Error('未知用户名或密码');
|
||||
} else {
|
||||
throw new Error(data.error || '登录失败');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 显示错误信息
|
||||
errorMessage.textContent = error.message;
|
||||
errorMessage.style.display = 'block';
|
||||
loginBtn.textContent = '登录';
|
||||
loginBtn.classList.remove('loading');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,261 +0,0 @@
|
||||
// 测试脚本,用于调试 getDomainInfo 函数
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 模拟浏览器环境的 console.log
|
||||
console.log = function() {
|
||||
process.stdout.write(Array.from(arguments).join(' ') + '\n');
|
||||
};
|
||||
|
||||
// 读取域名信息数据库
|
||||
const domainInfoPath = path.join(__dirname, 'static/domain-info/domains/domain-info.json');
|
||||
const domainInfoDatabase = JSON.parse(fs.readFileSync(domainInfoPath, 'utf8'));
|
||||
|
||||
// 模拟已加载的数据库
|
||||
let domainInfoLoaded = true;
|
||||
|
||||
// 检查域名是否匹配
|
||||
function isDomainMatch(urlValue, targetDomain, categoryId) {
|
||||
console.log(' 开始匹配URL:', urlValue, '目标域名:', targetDomain, '类别ID:', categoryId);
|
||||
|
||||
// 规范化目标域名,去除末尾的点
|
||||
const normalizedTargetDomain = targetDomain.replace(/\.$/, '').toLowerCase();
|
||||
|
||||
try {
|
||||
// 尝试将URL值解析为完整URL
|
||||
console.log(' 尝试解析URL为完整URL');
|
||||
const url = new URL(urlValue);
|
||||
let hostname = url.hostname.toLowerCase();
|
||||
// 规范化主机名,去除末尾的点
|
||||
hostname = hostname.replace(/\.$/, '');
|
||||
console.log(' 解析成功,主机名:', hostname, '规范化目标域名:', normalizedTargetDomain);
|
||||
|
||||
// 根据类别ID选择匹配方式
|
||||
if (categoryId === 2) {
|
||||
// CDN类别,使用域名后缀匹配
|
||||
if (normalizedTargetDomain.endsWith('.' + hostname) || normalizedTargetDomain === hostname) {
|
||||
console.log(' CDN域名后缀匹配成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' CDN域名后缀不匹配');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 其他类别,使用完整域名匹配
|
||||
if (hostname === normalizedTargetDomain) {
|
||||
console.log(' 完整域名匹配成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' 完整域名不匹配');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(' 解析URL失败,将其视为纯域名处理,错误信息:', e.message);
|
||||
// 如果是纯域名而不是完整URL
|
||||
let urlDomain = urlValue.toLowerCase();
|
||||
// 规范化纯域名,去除末尾的点
|
||||
urlDomain = urlDomain.replace(/\.$/, '');
|
||||
console.log(' 处理为纯域名:', urlDomain, '规范化目标域名:', normalizedTargetDomain);
|
||||
|
||||
// 根据类别ID选择匹配方式
|
||||
if (categoryId === 2) {
|
||||
// CDN类别,使用域名后缀匹配
|
||||
if (normalizedTargetDomain.endsWith('.' + urlDomain) || normalizedTargetDomain === urlDomain) {
|
||||
console.log(' CDN域名后缀匹配成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' CDN域名后缀不匹配');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 其他类别,使用完整域名匹配
|
||||
if (urlDomain === normalizedTargetDomain) {
|
||||
console.log(' 完整域名匹配成功');
|
||||
return true;
|
||||
} else {
|
||||
console.log(' 完整域名不匹配');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据域名查找对应的网站信息
|
||||
async function getDomainInfo(domain) {
|
||||
console.log('开始查找域名信息,域名:', domain);
|
||||
|
||||
if (!domainInfoDatabase || !domainInfoDatabase.domains) {
|
||||
console.error('域名信息数据库无效或为空');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 规范化域名,移除可能的端口号
|
||||
const normalizedDomain = domain.replace(/:\d+$/, '').toLowerCase();
|
||||
console.log('规范化后的域名:', normalizedDomain);
|
||||
|
||||
// 遍历所有公司
|
||||
console.log('开始遍历公司,总公司数:', Object.keys(domainInfoDatabase.domains).length);
|
||||
for (const companyKey in domainInfoDatabase.domains) {
|
||||
if (domainInfoDatabase.domains.hasOwnProperty(companyKey)) {
|
||||
console.log('检查公司:', companyKey);
|
||||
const companyData = domainInfoDatabase.domains[companyKey];
|
||||
const companyName = companyData.company || companyKey;
|
||||
|
||||
// 遍历公司下的所有网站和类别
|
||||
for (const websiteKey in companyData) {
|
||||
if (companyData.hasOwnProperty(websiteKey) && websiteKey !== 'company') {
|
||||
console.log(' 检查网站/类别:', websiteKey);
|
||||
const website = companyData[websiteKey];
|
||||
|
||||
// 如果有URL属性,直接检查域名
|
||||
if (website.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof website.url === 'string') {
|
||||
console.log(' 检查字符串URL:', website.url);
|
||||
if (isDomainMatch(website.url, normalizedDomain, website.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: website.name,
|
||||
icon: website.icon,
|
||||
categoryId: website.categoryId,
|
||||
categoryName: domainInfoDatabase.categories[website.categoryId] || '未知',
|
||||
company: website.company || companyName
|
||||
};
|
||||
}
|
||||
}
|
||||
// 处理对象类型的URL
|
||||
else if (typeof website.url === 'object') {
|
||||
console.log(' 检查对象类型URL,包含', Object.keys(website.url).length, '个URL');
|
||||
for (const urlKey in website.url) {
|
||||
if (website.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = website.url[urlKey];
|
||||
console.log(' 检查URL', urlKey, ':', urlValue);
|
||||
if (isDomainMatch(urlValue, normalizedDomain, website.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: website.name,
|
||||
icon: website.icon,
|
||||
categoryId: website.categoryId,
|
||||
categoryName: domainInfoDatabase.categories[website.categoryId] || '未知',
|
||||
company: website.company || companyName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof website === 'object' && website !== null) {
|
||||
// 没有URL属性,可能是嵌套的类别
|
||||
console.log(' 发现嵌套类别,进一步检查');
|
||||
for (const nestedWebsiteKey in website) {
|
||||
if (website.hasOwnProperty(nestedWebsiteKey) && nestedWebsiteKey !== 'company') {
|
||||
console.log(' 检查嵌套网站/类别:', nestedWebsiteKey);
|
||||
const nestedWebsite = website[nestedWebsiteKey];
|
||||
|
||||
if (nestedWebsite.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof nestedWebsite.url === 'string') {
|
||||
console.log(' 检查字符串URL:', nestedWebsite.url);
|
||||
if (isDomainMatch(nestedWebsite.url, normalizedDomain, nestedWebsite.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: nestedWebsite.name,
|
||||
icon: nestedWebsite.icon,
|
||||
categoryId: nestedWebsite.categoryId,
|
||||
categoryName: domainInfoDatabase.categories[nestedWebsite.categoryId] || '未知',
|
||||
company: nestedWebsite.company || companyName
|
||||
};
|
||||
}
|
||||
}
|
||||
// 处理对象类型的URL
|
||||
else if (typeof nestedWebsite.url === 'object') {
|
||||
console.log(' 检查对象类型URL,包含', Object.keys(nestedWebsite.url).length, '个URL');
|
||||
for (const urlKey in nestedWebsite.url) {
|
||||
if (nestedWebsite.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = nestedWebsite.url[urlKey];
|
||||
console.log(' 检查URL', urlKey, ':', urlValue);
|
||||
if (isDomainMatch(urlValue, normalizedDomain, nestedWebsite.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: nestedWebsite.name,
|
||||
icon: nestedWebsite.icon,
|
||||
categoryId: nestedWebsite.categoryId,
|
||||
categoryName: domainInfoDatabase.categories[nestedWebsite.categoryId] || '未知',
|
||||
company: nestedWebsite.company || companyName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof nestedWebsite === 'object' && nestedWebsite !== null) {
|
||||
// 嵌套类别中的嵌套类别,递归检查
|
||||
console.log(' 发现二级嵌套类别,进一步检查');
|
||||
for (const secondNestedWebsiteKey in nestedWebsite) {
|
||||
if (nestedWebsite.hasOwnProperty(secondNestedWebsiteKey) && secondNestedWebsiteKey !== 'company') {
|
||||
console.log(' 检查二级嵌套网站:', secondNestedWebsiteKey);
|
||||
const secondNestedWebsite = nestedWebsite[secondNestedWebsiteKey];
|
||||
|
||||
if (secondNestedWebsite.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof secondNestedWebsite.url === 'string') {
|
||||
console.log(' 检查字符串URL:', secondNestedWebsite.url);
|
||||
if (isDomainMatch(secondNestedWebsite.url, normalizedDomain, secondNestedWebsite.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: secondNestedWebsite.name,
|
||||
icon: secondNestedWebsite.icon,
|
||||
categoryId: secondNestedWebsite.categoryId,
|
||||
categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知',
|
||||
company: secondNestedWebsite.company || companyName
|
||||
};
|
||||
}
|
||||
}
|
||||
// 处理对象类型的URL
|
||||
else if (typeof secondNestedWebsite.url === 'object') {
|
||||
console.log(' 检查对象类型URL,包含', Object.keys(secondNestedWebsite.url).length, '个URL');
|
||||
for (const urlKey in secondNestedWebsite.url) {
|
||||
if (secondNestedWebsite.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = secondNestedWebsite.url[urlKey];
|
||||
console.log(' 检查URL', urlKey, ':', urlValue);
|
||||
if (isDomainMatch(urlValue, normalizedDomain, secondNestedWebsite.categoryId)) {
|
||||
console.log(' 匹配成功,返回网站信息');
|
||||
return {
|
||||
name: secondNestedWebsite.name,
|
||||
icon: secondNestedWebsite.icon,
|
||||
categoryId: secondNestedWebsite.categoryId,
|
||||
categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知',
|
||||
company: secondNestedWebsite.company || companyName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(' 嵌套网站没有URL属性且不是对象类型');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(' 网站没有URL属性');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('未找到匹配的域名信息');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 测试 mcs.doubao.com
|
||||
getDomainInfo('mcs.doubao.com').then(result => {
|
||||
console.log('\n=== 测试结果 ===');
|
||||
if (result) {
|
||||
console.log('匹配成功:', JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log('匹配失败');
|
||||
}
|
||||
});
|
||||
@@ -1,185 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"dns-server/dns"
|
||||
|
||||
miekdns "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 测试1: 内存缓存模式
|
||||
fmt.Println("=== 测试1: 内存缓存模式 ===")
|
||||
memCache := dns.NewDNSCache(60*time.Second, "memory", 100, "test_cache.json", 10*time.Second, 3600*time.Second, 60*time.Second)
|
||||
|
||||
// 设置缓存项
|
||||
msg := &miekdns.Msg{}
|
||||
msg.SetQuestion("test.com.", miekdns.TypeA)
|
||||
memCache.Set("test.com.", miekdns.TypeA, msg, 60*time.Second)
|
||||
|
||||
// 从缓存获取
|
||||
_, found := memCache.Get("test.com.", miekdns.TypeA)
|
||||
if found {
|
||||
fmt.Println("✓ 内存缓存: 成功获取缓存项")
|
||||
} else {
|
||||
fmt.Println("✗ 内存缓存: 未能获取缓存项")
|
||||
}
|
||||
|
||||
// 检查文件是否创建
|
||||
if _, err := os.Stat("test_cache.json"); os.IsNotExist(err) {
|
||||
fmt.Println("✓ 内存缓存: 没有创建缓存文件")
|
||||
} else {
|
||||
fmt.Println("✗ 内存缓存: 不应该创建缓存文件,但文件存在")
|
||||
}
|
||||
|
||||
// 测试2: 文件缓存模式
|
||||
fmt.Println("\n=== 测试2: 文件缓存模式 ===")
|
||||
// 使用独立的缓存文件
|
||||
test2CacheFile := "test_cache2.json"
|
||||
// 先删除可能存在的测试文件
|
||||
os.Remove(test2CacheFile)
|
||||
|
||||
fileCache := dns.NewDNSCache(60*time.Second, "file", 100, test2CacheFile, 1*time.Second, 3600*time.Second, 60*time.Second)
|
||||
|
||||
// 设置缓存项
|
||||
fileCache.Set("test.com.", miekdns.TypeA, msg, 60*time.Second)
|
||||
|
||||
// 等待保存到文件
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
|
||||
// 检查文件是否创建
|
||||
if _, err := os.Stat(test2CacheFile); err == nil {
|
||||
fmt.Println("✓ 文件缓存: 成功创建缓存文件")
|
||||
} else {
|
||||
fmt.Println("✗ 文件缓存: 未能创建缓存文件")
|
||||
}
|
||||
|
||||
// 测试3: 从文件加载缓存
|
||||
fmt.Println("\n=== 测试3: 从文件加载缓存 ===")
|
||||
// 创建新的缓存实例,从文件加载
|
||||
loadCache := dns.NewDNSCache(60*time.Second, "file", 100, test2CacheFile, 10*time.Second, 3600*time.Second, 60*time.Second)
|
||||
|
||||
// 从缓存获取
|
||||
_, found = loadCache.Get("test.com.", miekdns.TypeA)
|
||||
if found {
|
||||
fmt.Println("✓ 文件缓存: 成功从文件加载缓存项")
|
||||
} else {
|
||||
fmt.Println("✗ 文件缓存: 未能从文件加载缓存项")
|
||||
}
|
||||
|
||||
// 清理测试2的缓存文件
|
||||
os.Remove(test2CacheFile)
|
||||
|
||||
// 测试4: 缓存模式切换
|
||||
fmt.Println("\n=== 测试4: 缓存模式切换 ===")
|
||||
// 使用独立的缓存文件
|
||||
test4CacheFile := "test_cache4.json"
|
||||
// 先删除可能存在的测试文件
|
||||
os.Remove(test4CacheFile)
|
||||
// 创建文件缓存
|
||||
switchCache := dns.NewDNSCache(60*time.Second, "file", 100, test4CacheFile, 1*time.Second, 3600*time.Second, 60*time.Second)
|
||||
|
||||
// 设置缓存项
|
||||
switchCache.Set("switch.com.", miekdns.TypeA, msg, 60*time.Second)
|
||||
|
||||
// 检查当前缓存大小
|
||||
size := switchCache.Size()
|
||||
fmt.Printf(" 设置缓存项后,缓存大小: %d\n", size)
|
||||
|
||||
// 直接调用SaveToFile方法保存缓存
|
||||
switchCache.SaveToFile()
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(test4CacheFile); err == nil {
|
||||
fmt.Println(" 切换前: 缓存文件已存在")
|
||||
// 查看文件大小
|
||||
fileInfo, _ := os.Stat(test4CacheFile)
|
||||
fmt.Printf(" 切换前: 缓存文件大小: %d bytes\n", fileInfo.Size())
|
||||
// 读取文件内容
|
||||
content, _ := os.ReadFile(test4CacheFile)
|
||||
fmt.Printf(" 切换前: 缓存文件内容: %s\n", content)
|
||||
} else {
|
||||
fmt.Println(" 切换前: 缓存文件不存在")
|
||||
}
|
||||
|
||||
// 切换到内存模式
|
||||
switchCache.SetCacheMode("memory")
|
||||
|
||||
// 再设置一个缓存项
|
||||
switchCache.Set("switch2.com.", miekdns.TypeA, msg, 60*time.Second)
|
||||
|
||||
// 等待一段时间,检查是否继续保存
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
|
||||
// 检查文件是否仍然存在
|
||||
if _, err := os.Stat("test_cache.json"); err == nil {
|
||||
fmt.Println(" 切换后: 缓存文件仍然存在")
|
||||
// 查看文件大小
|
||||
fileInfo, _ := os.Stat("test_cache.json")
|
||||
fmt.Printf(" 切换后: 缓存文件大小: %d bytes\n", fileInfo.Size())
|
||||
// 读取文件内容
|
||||
content, _ := os.ReadFile("test_cache.json")
|
||||
fmt.Printf(" 切换后: 缓存文件内容: %s\n", content)
|
||||
} else {
|
||||
fmt.Println(" 切换后: 缓存文件不存在")
|
||||
}
|
||||
|
||||
// 查看当前时间
|
||||
fmt.Printf(" 当前时间: %v\n", time.Now())
|
||||
|
||||
// 查看缓存文件中的过期时间
|
||||
content, _ := os.ReadFile(test4CacheFile)
|
||||
var serializableCache map[string]interface{}
|
||||
json.Unmarshal(content, &serializableCache)
|
||||
if items, ok := serializableCache["items"].(map[string]interface{}); ok {
|
||||
if item, ok := items["switch.com.|A"].(map[string]interface{}); ok {
|
||||
if expiry, ok := item["expiry"].(float64); ok {
|
||||
expiryTime := time.Unix(0, int64(expiry))
|
||||
fmt.Printf(" 缓存项过期时间: %v\n", expiryTime)
|
||||
fmt.Printf(" 缓存项是否过期: %v\n", time.Now().After(expiryTime))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新的缓存实例,验证是否只保存了切换前的缓存项
|
||||
verifyCache := dns.NewDNSCache(60*time.Second, "file", 100, test4CacheFile, 10*time.Second, 3600*time.Second, 60*time.Second)
|
||||
|
||||
// 检查verifyCache的缓存大小
|
||||
verifySize := verifyCache.Size()
|
||||
fmt.Printf(" 新缓存实例大小: %d\n", verifySize)
|
||||
|
||||
// 我们无法直接访问verifyCache的内部缓存,所以我们尝试不同的域名格式
|
||||
// 尝试带点和不带点的域名
|
||||
_, found1 := verifyCache.Get("switch.com", miekdns.TypeA)
|
||||
_, found2 := verifyCache.Get("switch.com.", miekdns.TypeA)
|
||||
_, found3 := verifyCache.Get("switch.com.|A", miekdns.TypeA)
|
||||
|
||||
fmt.Printf(" 尝试获取switch.com: %v\n", found1)
|
||||
fmt.Printf(" 尝试获取switch.com.: %v\n", found2)
|
||||
fmt.Printf(" 尝试获取switch.com.|A: %v\n", found3)
|
||||
|
||||
// 检查切换前的缓存项
|
||||
_, found = verifyCache.Get("switch.com.", miekdns.TypeA)
|
||||
if found || found1 {
|
||||
fmt.Println("✓ 模式切换: 切换前的缓存项已保存到文件")
|
||||
} else {
|
||||
fmt.Println("✗ 模式切换: 切换前的缓存项未保存到文件")
|
||||
}
|
||||
|
||||
// 检查切换后的缓存项
|
||||
_, found = verifyCache.Get("switch2.com.", miekdns.TypeA)
|
||||
if !found {
|
||||
fmt.Println("✓ 模式切换: 切换后的缓存项没有保存到文件")
|
||||
} else {
|
||||
fmt.Println("✗ 模式切换: 切换后的缓存项不应该保存到文件,但文件中存在")
|
||||
}
|
||||
|
||||
// 清理测试文件
|
||||
os.Remove("test_cache.json")
|
||||
|
||||
fmt.Println("\n=== 测试完成 ===")
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"items": {
|
||||
"test.com.|A": {
|
||||
"responseBytes": "kosBAAABAAAAAAAABHRlc3QDY29tAAABAAE=",
|
||||
"expiry": 1768551008159638734,
|
||||
"hasDNSSEC": false,
|
||||
"size": 378
|
||||
}
|
||||
},
|
||||
"ttl": 60000000000,
|
||||
"maxSize": 10000,
|
||||
"cacheMode": "file",
|
||||
"cacheFilePath": "test_cache2.json"
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"items": {
|
||||
"switch.com.|A": {
|
||||
"responseBytes": "kosBAAABAAAAAAAABHRlc3QDY29tAAABAAE=",
|
||||
"expiry": 1768551010161428066,
|
||||
"hasDNSSEC": false,
|
||||
"size": 378
|
||||
}
|
||||
},
|
||||
"ttl": 60000000000,
|
||||
"maxSize": 10000,
|
||||
"cacheMode": "file",
|
||||
"cacheFilePath": "test_cache4.json"
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DNS性能测试脚本
|
||||
SERVER="127.0.0.1"
|
||||
DOMAIN="example.com"
|
||||
THREADS=10
|
||||
QUERIES=1000
|
||||
|
||||
# 创建临时文件存储进程ID
|
||||
pids=()
|
||||
|
||||
# 运行并发查询
|
||||
for ((i=1; i<=THREADS; i++)); do
|
||||
for ((j=1; j<=$((QUERIES/THREADS)); j++)); do
|
||||
dig @$SERVER $DOMAIN A +short > /dev/null &
|
||||
pids+=($!)
|
||||
done
|
||||
echo "线程 $i 已启动,将执行 $((QUERIES/THREADS)) 个查询"
|
||||
done
|
||||
|
||||
echo "所有查询已启动,等待完成..."
|
||||
|
||||
# 等待所有查询完成
|
||||
for pid in "${pids[@]}"; do
|
||||
wait $pid
|
||||
done
|
||||
|
||||
echo "所有查询已完成!"
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DNS压力测试脚本
|
||||
SERVER="127.0.0.1"
|
||||
DOMAIN="example.com"
|
||||
THREADS=20
|
||||
QUERIES=1000
|
||||
|
||||
# 创建临时文件存储进程ID
|
||||
pids=()
|
||||
|
||||
echo "开始DNS压力测试..."
|
||||
echo "服务器: $SERVER"
|
||||
echo "域名: $DOMAIN"
|
||||
echo "线程数: $THREADS"
|
||||
echo "总查询数: $QUERIES"
|
||||
echo "--------------------------------------"
|
||||
|
||||
# 记录开始时间(秒)
|
||||
start_time=$SECONDS
|
||||
|
||||
# 运行并发查询
|
||||
for ((i=1; i<=THREADS; i++)); do
|
||||
for ((j=1; j<=$((QUERIES/THREADS)); j++)); do
|
||||
dig @$SERVER $DOMAIN A +short > /dev/null &
|
||||
pids+=($!)
|
||||
done
|
||||
# 每启动5个线程暂停一下,避免系统资源瞬间耗尽
|
||||
if (( $i % 5 == 0 )); then
|
||||
echo "线程 $i 已启动,已完成 $i/$THREADS 个线程..."
|
||||
sleep 0.5
|
||||
fi
|
||||
done
|
||||
|
||||
echo "所有 $THREADS 个线程已启动,共执行 $QUERIES 个查询,等待完成..."
|
||||
|
||||
# 等待所有查询完成
|
||||
for pid in "${pids[@]}"; do
|
||||
wait $pid
|
||||
done
|
||||
|
||||
# 计算执行时间
|
||||
elapsed=$((SECONDS - start_time))
|
||||
|
||||
# 计算QPS(每秒查询数)
|
||||
if [ $elapsed -eq 0 ]; then
|
||||
elapsed=1 # 避免除以零
|
||||
fi
|
||||
qps=$((QUERIES / elapsed))
|
||||
|
||||
echo "--------------------------------------"
|
||||
echo "所有查询已完成!"
|
||||
echo "执行时间: $elapsed 秒"
|
||||
echo "QPS: $qps 次/秒"
|
||||
echo "--------------------------------------"
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DNS性能优化验证脚本
|
||||
SERVER="127.0.0.1"
|
||||
DOMAIN="example.com"
|
||||
THREADS=50
|
||||
QUERIES=5000
|
||||
|
||||
echo "========================================="
|
||||
echo "DNS服务器多线程优化验证测试"
|
||||
echo "========================================="
|
||||
echo "服务器: $SERVER"
|
||||
echo "域名: $DOMAIN"
|
||||
echo "线程数: $THREADS"
|
||||
echo "总查询数: $QUERIES"
|
||||
echo "========================================="
|
||||
|
||||
echo ""
|
||||
echo "启动DNS服务器..."
|
||||
./dns-server > /dev/null 2>&1 &
|
||||
DNS_PID=$!
|
||||
echo "DNS服务器PID: $DNS_PID"
|
||||
|
||||
sleep 5
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "开始压力测试前系统状态:"
|
||||
echo "========================================="
|
||||
echo "CPU使用率:"
|
||||
top -b -n 1 | grep "Cpu(s)" | awk '{print " " $2 "% 用户, " $4 "% 系统, " $8 "% 空闲"}'
|
||||
echo ""
|
||||
echo "内存使用情况:"
|
||||
free -h | grep -E "Mem|Swap" | awk '{print " " $1 ": " $3 "/" $2 " (" $3/$2*100 "% 使用)"}'
|
||||
echo ""
|
||||
echo "DNS服务器进程资源使用:"
|
||||
ps -p $DNS_PID -o %cpu,%mem,cmd --no-headers | awk '{print " CPU: " $1 "%, 内存: " $2 "%"}'
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "开始压力测试..."
|
||||
echo "========================================="
|
||||
|
||||
start_time=$(date +%s.%N)
|
||||
|
||||
pids=()
|
||||
for ((i=1; i<=THREADS; i++)); do
|
||||
for ((j=1; j<=$((QUERIES/THREADS)); j++)); do
|
||||
dig @$SERVER $DOMAIN A +short > /dev/null 2>&1 &
|
||||
pids+=($!)
|
||||
done
|
||||
if (( $i % 10 == 0 )); then
|
||||
echo "已启动 $i/$THREADS 个线程..."
|
||||
sleep 0.2
|
||||
fi
|
||||
done
|
||||
|
||||
echo "所有线程已启动,等待完成..."
|
||||
|
||||
for pid in "${pids[@]}"; do
|
||||
wait $pid 2>/dev/null
|
||||
done
|
||||
|
||||
end_time=$(date +%s.%N)
|
||||
elapsed=$(echo "$end_time - $start_time" | bc)
|
||||
qps=$(echo "$QUERIES / $elapsed" | bc)
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "压力测试后系统状态:"
|
||||
echo "========================================="
|
||||
echo "CPU使用率:"
|
||||
top -b -n 1 | grep "Cpu(s)" | awk '{print " " $2 "% 用户, " $4 "% 系统, " $8 "% 空闲"}'
|
||||
echo ""
|
||||
echo "内存使用情况:"
|
||||
free -h | grep -E "Mem|Swap" | awk '{print " " $1 ": " $3 "/" $2 " (" $3/$2*100 "% 使用)"}'
|
||||
echo ""
|
||||
echo "DNS服务器进程资源使用:"
|
||||
ps -p $DNS_PID -o %cpu,%mem,cmd --no-headers 2>/dev/null | awk '{print " CPU: " $1 "%, 内存: " $2 "%"}'
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "测试结果:"
|
||||
echo "========================================="
|
||||
echo "总查询数: $QUERIES"
|
||||
echo "执行时间: $elapsed 秒"
|
||||
echo "QPS: $qps 次/秒"
|
||||
echo "========================================="
|
||||
|
||||
echo ""
|
||||
echo "停止DNS服务器..."
|
||||
kill $DNS_PID 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
echo ""
|
||||
echo "测试完成!"
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DNS性能优化验证脚本(使用并发查询)
|
||||
SERVER="127.0.0.1"
|
||||
DOMAIN="example.com"
|
||||
CONCURRENCY=100
|
||||
QUERIES=10000
|
||||
|
||||
echo "========================================="
|
||||
echo "DNS服务器多线程优化验证测试"
|
||||
echo "========================================="
|
||||
echo "服务器: $SERVER"
|
||||
echo "域名: $DOMAIN"
|
||||
echo "并发数: $CONCURRENCY"
|
||||
echo "总查询数: $QUERIES"
|
||||
echo "========================================="
|
||||
|
||||
echo ""
|
||||
echo "启动DNS服务器..."
|
||||
./dns-server > /dev/null 2>&1 &
|
||||
DNS_PID=$!
|
||||
echo "DNS服务器PID: $DNS_PID"
|
||||
|
||||
sleep 5
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "开始压力测试前系统状态:"
|
||||
echo "========================================="
|
||||
echo "CPU使用率:"
|
||||
top -b -n 1 | grep "Cpu(s)" | awk '{print " " $2 "% 用户, " $4 "% 系统, " $8 "% 空闲"}'
|
||||
echo ""
|
||||
echo "内存使用情况:"
|
||||
free -h | grep -E "Mem|Swap" | awk '{print " " $1 ": " $3 "/" $2 " (" $3/$2*100 "% 使用)"}'
|
||||
echo ""
|
||||
echo "DNS服务器进程资源使用:"
|
||||
ps -p $DNS_PID -o %cpu,%mem,cmd --no-headers | awk '{print " CPU: " $1 "%, 内存: " $2 "%"}'
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "开始压力测试..."
|
||||
echo "========================================="
|
||||
|
||||
start_time=$(date +%s.%N)
|
||||
|
||||
for ((i=1; i<=$QUERIES; i++)); do
|
||||
dig @$SERVER $DOMAIN A +short > /dev/null 2>&1 &
|
||||
|
||||
if (( $i % $CONCURRENCY == 0 )); then
|
||||
wait
|
||||
fi
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
end_time=$(date +%s.%N)
|
||||
elapsed=$(echo "$end_time - $start_time" | bc)
|
||||
qps=$(echo "scale=2; $QUERIES / $elapsed" | bc)
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "压力测试后系统状态:"
|
||||
echo "========================================="
|
||||
echo "CPU使用率:"
|
||||
top -b -n 1 | grep "Cpu(s)" | awk '{print " " $2 "% 用户, " $4 "% 系统, " $8 "% 空闲"}'
|
||||
echo ""
|
||||
echo "内存使用情况:"
|
||||
free -h | grep -E "Mem|Swap" | awk '{print " " $1 ": " $3 "/" $2 " (" $3/$2*100 "% 使用)"}'
|
||||
echo ""
|
||||
echo "DNS服务器进程资源使用:"
|
||||
ps -p $DNS_PID -o %cpu,%mem,cmd --no-headers 2>/dev/null | awk '{print " CPU: " $1 "%, 内存: " $2 "%"}'
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "测试结果:"
|
||||
echo "========================================="
|
||||
echo "总查询数: $QUERIES"
|
||||
echo "执行时间: $elapsed 秒"
|
||||
echo "QPS: $qps 次/秒"
|
||||
echo "========================================="
|
||||
|
||||
echo ""
|
||||
echo "停止DNS服务器..."
|
||||
kill $DNS_PID 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
echo ""
|
||||
echo "测试完成!"
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DNS性能优化验证脚本(简化版)
|
||||
SERVER="127.0.0.1"
|
||||
DOMAIN="example.com"
|
||||
CONCURRENCY=50
|
||||
QUERIES=2000
|
||||
|
||||
echo "========================================="
|
||||
echo "DNS服务器多线程优化验证测试"
|
||||
echo "========================================="
|
||||
echo "服务器: $SERVER"
|
||||
echo "域名: $DOMAIN"
|
||||
echo "并发数: $CONCURRENCY"
|
||||
echo "总查询数: $QUERIES"
|
||||
echo "========================================="
|
||||
|
||||
echo ""
|
||||
echo "启动DNS服务器..."
|
||||
./dns-server > /dev/null 2>&1 &
|
||||
DNS_PID=$!
|
||||
echo "DNS服务器PID: $DNS_PID"
|
||||
|
||||
sleep 5
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "开始压力测试前系统状态:"
|
||||
echo "========================================="
|
||||
echo "CPU使用率:"
|
||||
top -b -n 1 | grep "Cpu(s)" | awk '{print " " $2 "% 用户, " $4 "% 系统, " $8 "% 空闲"}'
|
||||
echo ""
|
||||
echo "内存使用情况:"
|
||||
free -h | grep -E "Mem|Swap" | awk '{print " " $1 ": " $3 "/" $2 " (" $3/$2*100 "% 使用)"}'
|
||||
echo ""
|
||||
echo "DNS服务器进程资源使用:"
|
||||
ps -p $DNS_PID -o %cpu,%mem,cmd --no-headers | awk '{print " CPU: " $1 "%, 内存: " $2 "%"}'
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "开始压力测试..."
|
||||
echo "========================================="
|
||||
|
||||
start_time=$(date +%s.%N)
|
||||
|
||||
for ((i=1; i<=$QUERIES; i++)); do
|
||||
dig @$SERVER $DOMAIN A +short > /dev/null 2>&1 &
|
||||
|
||||
if (( $i % $CONCURRENCY == 0 )); then
|
||||
wait
|
||||
fi
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
end_time=$(date +%s.%N)
|
||||
elapsed=$(echo "$end_time - $start_time" | bc)
|
||||
qps=$(echo "scale=2; $QUERIES / $elapsed" | bc)
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "压力测试后系统状态:"
|
||||
echo "========================================="
|
||||
echo "CPU使用率:"
|
||||
top -b -n 1 | grep "Cpu(s)" | awk '{print " " $2 "% 用户, " $4 "% 系统, " $8 "% 空闲"}'
|
||||
echo ""
|
||||
echo "内存使用情况:"
|
||||
free -h | grep -E "Mem|Swap" | awk '{print " " $1 ": " $3 "/" $2 " (" $3/$2*100 "% 使用)"}'
|
||||
echo ""
|
||||
echo "DNS服务器进程资源使用:"
|
||||
ps -p $DNS_PID -o %cpu,%mem,cmd --no-headers 2>/dev/null | awk '{print " CPU: " $1 "%, 内存: " $2 "%"}'
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "测试结果:"
|
||||
echo "========================================="
|
||||
echo "总查询数: $QUERIES"
|
||||
echo "执行时间: $elapsed 秒"
|
||||
echo "QPS: $qps 次/秒"
|
||||
echo "========================================="
|
||||
|
||||
echo ""
|
||||
echo "停止DNS服务器..."
|
||||
kill $DNS_PID 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
echo ""
|
||||
echo "测试完成!"
|
||||
Reference in New Issue
Block a user