更新
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
// about.js - 关于页面功能实现
|
||||
|
||||
// 初始化关于页面
|
||||
async function initAboutPage() {
|
||||
try {
|
||||
console.log('初始化关于页面');
|
||||
|
||||
// 立即显示内容,确保页面不会空白
|
||||
const contentElement = document.getElementById('about-content');
|
||||
if (contentElement) {
|
||||
contentElement.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 检查是否有有效的缓存数据
|
||||
const cachedData = window.pageDataCache && window.pageDataCache.getCache('about');
|
||||
if (cachedData) {
|
||||
console.log('使用缓存的关于页面数据');
|
||||
updateAboutPageUI(cachedData);
|
||||
return;
|
||||
}
|
||||
|
||||
// 并行获取服务器信息和版本信息
|
||||
const [serverInfo, versionInfo] = await Promise.all([
|
||||
fetchServerInfo(),
|
||||
fetchVersionInfo()
|
||||
]);
|
||||
|
||||
// 组合数据
|
||||
const aboutData = {
|
||||
serverInfo,
|
||||
versionInfo
|
||||
};
|
||||
|
||||
// 更新UI
|
||||
updateAboutPageUI(aboutData);
|
||||
|
||||
// 存储数据到缓存
|
||||
if (window.pageDataCache) {
|
||||
window.pageDataCache.setCache('about', aboutData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化关于页面失败:', error);
|
||||
showAboutError('加载数据失败,请稍后重试');
|
||||
|
||||
// 即使出错也显示默认内容
|
||||
const defaultData = {
|
||||
serverInfo: {
|
||||
version: '1.0.0',
|
||||
uptime: 0,
|
||||
system: 'Unknown',
|
||||
platform: 'Unknown',
|
||||
arch: 'Unknown',
|
||||
goVersion: 'Unknown',
|
||||
cpu: 'Unknown',
|
||||
memory: 'Unknown',
|
||||
disk: 'Unknown'
|
||||
},
|
||||
versionInfo: {
|
||||
mainFile: {
|
||||
version: '1.0.0',
|
||||
lastUpdate: new Date().toISOString()
|
||||
},
|
||||
configFile: {
|
||||
version: '1.0.0',
|
||||
lastUpdate: new Date().toISOString()
|
||||
},
|
||||
ruleFiles: {
|
||||
version: '1.0.0',
|
||||
lastUpdate: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
};
|
||||
updateAboutPageUI(defaultData);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取服务器信息
|
||||
async function fetchServerInfo() {
|
||||
try {
|
||||
const response = await fetch('/api/server-info');
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取服务器信息失败: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('获取服务器信息失败:', error);
|
||||
// 返回默认值
|
||||
return {
|
||||
version: '1.0.0',
|
||||
uptime: 0,
|
||||
system: 'Unknown',
|
||||
platform: 'Unknown',
|
||||
arch: 'Unknown',
|
||||
goVersion: 'Unknown',
|
||||
cpu: 'Unknown',
|
||||
memory: 'Unknown',
|
||||
disk: 'Unknown'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取版本信息
|
||||
async function fetchVersionInfo() {
|
||||
try {
|
||||
const response = await fetch('/api/version-info');
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取版本信息失败: ${response.status}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('获取版本信息失败:', error);
|
||||
// 返回默认值
|
||||
return {
|
||||
mainFile: {
|
||||
version: '1.0.0',
|
||||
lastUpdate: new Date().toISOString()
|
||||
},
|
||||
configFile: {
|
||||
version: '1.0.0',
|
||||
lastUpdate: new Date().toISOString()
|
||||
},
|
||||
ruleFiles: {
|
||||
version: '1.0.0',
|
||||
lastUpdate: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 更新关于页面UI
|
||||
function updateAboutPageUI(data) {
|
||||
if (!data) return;
|
||||
|
||||
const { serverInfo, versionInfo } = data;
|
||||
|
||||
// 更新服务器基本信息
|
||||
updateServerInfoUI(serverInfo);
|
||||
|
||||
// 更新版本信息
|
||||
updateVersionInfoUI(versionInfo);
|
||||
|
||||
// 隐藏加载状态(已移除加载元素)
|
||||
|
||||
// 显示内容
|
||||
const contentElement = document.getElementById('about-content');
|
||||
if (contentElement) {
|
||||
contentElement.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新服务器基本信息UI
|
||||
function updateServerInfoUI(info) {
|
||||
if (!info) return;
|
||||
|
||||
const elements = {
|
||||
'server-version': info.version || '1.0.0',
|
||||
'server-uptime': formatUptime(info.uptime || 0),
|
||||
'server-system': info.system || 'Unknown',
|
||||
'server-platform': info.platform || 'Unknown',
|
||||
'server-arch': info.arch || 'Unknown',
|
||||
'server-go-version': info.goVersion || 'Unknown',
|
||||
'server-cpu': info.cpu || 'Unknown',
|
||||
'server-memory': info.memory || 'Unknown',
|
||||
'server-disk': info.disk || 'Unknown'
|
||||
};
|
||||
|
||||
Object.entries(elements).forEach(([id, value]) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 更新版本信息UI
|
||||
function updateVersionInfoUI(info) {
|
||||
if (!info) return;
|
||||
|
||||
const elements = {
|
||||
'main-file-version': info.mainFile?.version || '1.0.0',
|
||||
'main-file-update': formatDate(info.mainFile?.lastUpdate || new Date()),
|
||||
'config-file-version': info.configFile?.version || '1.0.0',
|
||||
'config-file-update': formatDate(info.configFile?.lastUpdate || new Date()),
|
||||
'rule-files-version': info.ruleFiles?.version || '1.0.0',
|
||||
'rule-files-update': formatDate(info.ruleFiles?.lastUpdate || new Date())
|
||||
};
|
||||
|
||||
Object.entries(elements).forEach(([id, value]) => {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function showAboutError(message) {
|
||||
const errorElement = document.getElementById('about-error');
|
||||
if (errorElement) {
|
||||
errorElement.textContent = message;
|
||||
errorElement.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 隐藏加载状态(已移除加载元素)
|
||||
}
|
||||
|
||||
// 格式化运行时间
|
||||
function formatUptime(seconds) {
|
||||
if (!seconds || seconds < 0) return '0秒';
|
||||
|
||||
const days = Math.floor(seconds / (24 * 60 * 60));
|
||||
const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60));
|
||||
const minutes = Math.floor((seconds % (60 * 60)) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
|
||||
let result = '';
|
||||
if (days > 0) result += `${days}天 `;
|
||||
if (hours > 0) result += `${hours}小时 `;
|
||||
if (minutes > 0) result += `${minutes}分钟 `;
|
||||
if (secs > 0 || result === '') result += `${secs}秒`;
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(date) {
|
||||
if (!date) return 'Unknown';
|
||||
|
||||
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
||||
if (isNaN(dateObj.getTime())) return 'Unknown';
|
||||
|
||||
return dateObj.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
// 刷新关于页面数据
|
||||
function refreshAboutPage() {
|
||||
// 清除缓存
|
||||
if (window.pageDataCache) {
|
||||
window.pageDataCache.clearCache('about');
|
||||
window.pageDataCache.initializedPages['about'] = false;
|
||||
}
|
||||
|
||||
// 显示加载状态(已移除加载元素)
|
||||
|
||||
// 隐藏错误信息
|
||||
const errorElement = document.getElementById('about-error');
|
||||
if (errorElement) {
|
||||
errorElement.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 重新初始化页面
|
||||
initAboutPage();
|
||||
}
|
||||
|
||||
// 设置关于页面事件监听器
|
||||
function setupAboutEventListeners() {
|
||||
// 刷新按钮事件
|
||||
const refreshBtn = document.getElementById('refresh-about-btn');
|
||||
if (refreshBtn) {
|
||||
refreshBtn.addEventListener('click', refreshAboutPage);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 检查当前是否是about页面
|
||||
if (window.location.hash === '#about') {
|
||||
initAboutPage();
|
||||
}
|
||||
setupAboutEventListeners();
|
||||
});
|
||||
} else {
|
||||
// 检查当前是否是about页面
|
||||
if (window.location.hash === '#about') {
|
||||
initAboutPage();
|
||||
}
|
||||
setupAboutEventListeners();
|
||||
}
|
||||
|
||||
// 当切换到about页面时重新加载数据
|
||||
window.addEventListener('hashchange', function() {
|
||||
if (window.location.hash === '#about') {
|
||||
initAboutPage();
|
||||
}
|
||||
});
|
||||
|
||||
+4
-1
@@ -184,7 +184,10 @@ const api = {
|
||||
saveConfig: (config) => apiRequest('/config', 'POST', config),
|
||||
|
||||
// 重启服务
|
||||
restartService: () => apiRequest('/config/restart', 'POST')
|
||||
restartService: () => apiRequest('/config/restart', 'POST'),
|
||||
|
||||
// 域名信息查询
|
||||
domainInfo: (domain) => apiRequest(`/domain-info?domains=${encodeURIComponent(domain)}`, 'GET')
|
||||
};
|
||||
|
||||
// 导出API工具
|
||||
|
||||
+59
-37
@@ -22,6 +22,10 @@ window.dashboardHistoryData = window.dashboardHistoryData || {
|
||||
let lastProcessedTime = 0;
|
||||
const PROCESS_THROTTLE_INTERVAL = 1000; // 1秒节流间隔
|
||||
|
||||
// 跟踪器信息缓存
|
||||
const trackerInfoCache = {};
|
||||
const TRACKER_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24小时缓存过期时间
|
||||
|
||||
// 引入颜色配置文件
|
||||
const COLOR_CONFIG = window.COLOR_CONFIG || {};
|
||||
|
||||
@@ -944,7 +948,7 @@ async function updateTopBlockedTable(domains) {
|
||||
<div class="tracker-tooltip absolute z-50 bg-white shadow-lg rounded-md border p-3 min-w-64 text-sm">
|
||||
<div class="font-semibold mb-2">已知跟踪器</div>
|
||||
<div class="mb-1"><strong>名称:</strong> ${trackerInfo.name || '未知'}</div>
|
||||
<div class="mb-1"><strong>类别:</strong> ${trackerInfo.categoryId && trackersDatabase && trackersDatabase.categories ? trackersDatabase.categories[trackerInfo.categoryId] : '未知'}</div>
|
||||
<div class="mb-1"><strong>类别:</strong> ${trackerInfo.category || '未知'}</div>
|
||||
${trackerInfo.url ? `<div class="mb-1"><strong>URL:</strong> <a href="${trackerInfo.url}" target="_blank" class="text-blue-500 hover:underline">${trackerInfo.url}</a></div>` : ''}
|
||||
${trackerInfo.source ? `<div class="mb-1"><strong>源:</strong> ${trackerInfo.source}</div>` : ''}
|
||||
</div>
|
||||
@@ -1082,7 +1086,7 @@ async function loadTrackersDatabase() {
|
||||
trackersLoading = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('domain-info/tracker/trackers.json');
|
||||
const response = await fetch('/api/domain-info?trackers');
|
||||
if (!response.ok) {
|
||||
console.error('加载跟踪器数据库失败:', response.statusText);
|
||||
trackersDatabase = { trackers: {} };
|
||||
@@ -1103,37 +1107,61 @@ async function loadTrackersDatabase() {
|
||||
|
||||
// 检查域名是否在跟踪器数据库中
|
||||
async function isDomainInTrackerDatabase(domain) {
|
||||
if (!trackersDatabase || !trackersLoaded) {
|
||||
await loadTrackersDatabase();
|
||||
// 检查缓存
|
||||
const now = Date.now();
|
||||
if (trackerInfoCache[domain] && (now - trackerInfoCache[domain].timestamp) < TRACKER_CACHE_EXPIRY) {
|
||||
return trackerInfoCache[domain].data;
|
||||
}
|
||||
|
||||
if (!trackersDatabase || !trackersDatabase.trackers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查域名是否直接作为跟踪器键存在
|
||||
if (trackersDatabase.trackers.hasOwnProperty(domain)) {
|
||||
return trackersDatabase.trackers[domain];
|
||||
}
|
||||
|
||||
// 检查域名是否在跟踪器URL中
|
||||
for (const trackerKey in trackersDatabase.trackers) {
|
||||
if (trackersDatabase.trackers.hasOwnProperty(trackerKey)) {
|
||||
const tracker = trackersDatabase.trackers[trackerKey];
|
||||
if (tracker && tracker.url) {
|
||||
try {
|
||||
const trackerUrl = new URL(tracker.url);
|
||||
if (trackerUrl.hostname === domain) {
|
||||
return tracker;
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略无效URL
|
||||
}
|
||||
try {
|
||||
// 使用新的API端点获取跟踪器信息
|
||||
const response = await fetch(`/api/domain-info?trackers=${encodeURIComponent(domain)}`);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('获取跟踪器信息失败:', response.statusText);
|
||||
// 将失败结果也存入缓存,避免频繁请求失败的域名
|
||||
trackerInfoCache[domain] = {
|
||||
data: null,
|
||||
timestamp: now
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
let result = null;
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// 如果有多个跟踪器,返回一个包含所有跟踪器的对象
|
||||
if (data.length === 1) {
|
||||
result = data[0];
|
||||
} else {
|
||||
// 构建一个包含所有跟踪器信息的对象
|
||||
result = {
|
||||
name: data.map(t => t.name).join(' | '),
|
||||
category: data.map(t => t.category).filter((v, i, a) => a.indexOf(v) === i).join(' | '),
|
||||
url: data.map(t => t.url).join(' | '),
|
||||
company: data.map(t => t.company).filter((v, i, a) => a.indexOf(v) === i).join(' | '),
|
||||
multiple: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 将结果存入缓存
|
||||
trackerInfoCache[domain] = {
|
||||
data: result,
|
||||
timestamp: now
|
||||
};
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('获取跟踪器信息失败:', error);
|
||||
// 将失败结果也存入缓存,避免频繁请求失败的域名
|
||||
trackerInfoCache[domain] = {
|
||||
data: null,
|
||||
timestamp: now
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取IP地理位置信息
|
||||
@@ -1360,7 +1388,7 @@ async function updateTopDomainsTable(domains) {
|
||||
<div class="tracker-tooltip absolute z-50 bg-white shadow-lg rounded-md border p-3 min-w-64 text-sm">
|
||||
<div class="font-semibold mb-2">已知跟踪器</div>
|
||||
<div class="mb-1"><strong>名称:</strong> ${trackerInfo.name || '未知'}</div>
|
||||
<div class="mb-1"><strong>类别:</strong> ${trackerInfo.categoryId && trackersDatabase && trackersDatabase.categories ? trackersDatabase.categories[trackerInfo.categoryId] : '未知'}</div>
|
||||
<div class="mb-1"><strong>类别:</strong> ${trackerInfo.category || '未知'}</div>
|
||||
${trackerInfo.url ? `<div class="mb-1"><strong>URL:</strong> <a href="${trackerInfo.url}" target="_blank" class="text-blue-500 hover:underline">${trackerInfo.url}</a></div>` : ''}
|
||||
${trackerInfo.source ? `<div class="mb-1"><strong>源:</strong> ${trackerInfo.source}</div>` : ''}
|
||||
</div>
|
||||
@@ -3180,10 +3208,7 @@ async function loadDashboardData() {
|
||||
// 更新图表
|
||||
updateCharts(stats, queryTypeStats);
|
||||
|
||||
// 初始化或更新查询类型统计饼图
|
||||
if (queryTypeStats) {
|
||||
drawQueryTypeChart(queryTypeStats);
|
||||
}
|
||||
|
||||
|
||||
// 更新查询类型统计信息
|
||||
if (document.getElementById('top-query-type')) {
|
||||
@@ -3307,10 +3332,7 @@ function processDashboardData(cachedData) {
|
||||
// 更新图表
|
||||
updateCharts(stats, queryTypeStats);
|
||||
|
||||
// 初始化或更新查询类型统计饼图
|
||||
if (queryTypeStats) {
|
||||
drawQueryTypeChart(queryTypeStats);
|
||||
}
|
||||
|
||||
|
||||
// 更新查询类型统计信息
|
||||
if (document.getElementById('top-query-type')) {
|
||||
|
||||
+38
-173
@@ -121,10 +121,9 @@ if (typeof trackersDatabase === 'undefined') {
|
||||
var trackersLoading = false;
|
||||
}
|
||||
|
||||
// 域名信息数据库缓存
|
||||
let domainInfoDatabase = null;
|
||||
let domainInfoLoaded = false;
|
||||
let domainInfoLoading = false;
|
||||
// 域名信息缓存(用于 API 调用)
|
||||
let domainInfoCache = {};
|
||||
let domainInfoCacheLoading = {};
|
||||
|
||||
// WebSocket连接和重连计时器
|
||||
let logsWsConnection = null;
|
||||
@@ -163,43 +162,51 @@ async function loadTrackersDatabase() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载域名信息数据库
|
||||
async function loadDomainInfoDatabase() {
|
||||
|
||||
if (domainInfoLoaded) {
|
||||
return domainInfoDatabase;
|
||||
// 从 API 获取域名信息
|
||||
async function getDomainInfoFromAPI(domain) {
|
||||
// 检查缓存
|
||||
if (domainInfoCache[domain]) {
|
||||
return domainInfoCache[domain];
|
||||
}
|
||||
|
||||
if (domainInfoLoading) {
|
||||
// 等待正在进行的加载完成
|
||||
while (domainInfoLoading) {
|
||||
// 检查是否正在加载中
|
||||
if (domainInfoCacheLoading[domain]) {
|
||||
while (domainInfoCacheLoading[domain]) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
return domainInfoDatabase;
|
||||
return domainInfoCache[domain] || null;
|
||||
}
|
||||
|
||||
domainInfoLoading = true;
|
||||
domainInfoCacheLoading[domain] = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('domain-info/domains/domain-info.json');
|
||||
|
||||
const response = await fetch(`/api/domain-info?domains=${encodeURIComponent(domain)}`);
|
||||
if (!response.ok) {
|
||||
console.error('加载域名信息数据库失败,HTTP状态:', response.status, response.statusText);
|
||||
console.error('请求URL:', response.url);
|
||||
domainInfoDatabase = { domains: {}, categories: {} };
|
||||
return domainInfoDatabase;
|
||||
console.error('获取域名信息失败:', response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
domainInfoDatabase = await response.json();
|
||||
domainInfoLoaded = true;
|
||||
return domainInfoDatabase;
|
||||
const data = await response.json();
|
||||
|
||||
// API 返回的是数组,取第一个匹配的结果
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const info = data[0];
|
||||
// 缓存结果
|
||||
domainInfoCache[domain] = {
|
||||
name: info.name || '未知',
|
||||
icon: info.icon || null,
|
||||
category: info.category || '未知',
|
||||
company: info.company || '未知'
|
||||
};
|
||||
return domainInfoCache[domain];
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('加载域名信息数据库失败,错误信息:', error.message);
|
||||
console.error('错误堆栈:', error.stack);
|
||||
domainInfoDatabase = { domains: {}, categories: {} };
|
||||
return domainInfoDatabase;
|
||||
console.error('获取域名信息失败:', error);
|
||||
return null;
|
||||
} finally {
|
||||
domainInfoLoading = false;
|
||||
domainInfoCacheLoading[domain] = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,152 +245,10 @@ async function isDomainInTrackerDatabase(domain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据域名查找对应的网站信息
|
||||
// 根据域名查找对应的网站信息(使用 API)
|
||||
async function getDomainInfo(domain) {
|
||||
|
||||
if (!domainInfoDatabase || !domainInfoLoaded) {
|
||||
await loadDomainInfoDatabase();
|
||||
}
|
||||
|
||||
if (!domainInfoDatabase || !domainInfoDatabase.domains) {
|
||||
console.error('域名信息数据库无效或为空');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 规范化域名,移除可能的端口号
|
||||
const normalizedDomain = domain.replace(/:\d+$/, '').toLowerCase();
|
||||
|
||||
// 遍历所有公司
|
||||
for (const companyKey in domainInfoDatabase.domains) {
|
||||
if (domainInfoDatabase.domains.hasOwnProperty(companyKey)) {
|
||||
const companyData = domainInfoDatabase.domains[companyKey];
|
||||
const companyName = companyData.company || companyKey;
|
||||
|
||||
// 遍历公司下的所有网站和类别
|
||||
for (const websiteKey in companyData) {
|
||||
if (companyData.hasOwnProperty(websiteKey) && websiteKey !== 'company') {
|
||||
const website = companyData[websiteKey];
|
||||
|
||||
// 如果有URL属性,直接检查域名
|
||||
if (website.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof website.url === 'string') {
|
||||
if (isDomainMatch(website.url, normalizedDomain, website.categoryId)) {
|
||||
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') {
|
||||
for (const urlKey in website.url) {
|
||||
if (website.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = website.url[urlKey];
|
||||
if (isDomainMatch(urlValue, normalizedDomain, website.categoryId)) {
|
||||
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属性,可能是嵌套的类别
|
||||
for (const nestedWebsiteKey in website) {
|
||||
if (website.hasOwnProperty(nestedWebsiteKey) && nestedWebsiteKey !== 'company') {
|
||||
const nestedWebsite = website[nestedWebsiteKey];
|
||||
|
||||
if (nestedWebsite.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof nestedWebsite.url === 'string') {
|
||||
if (isDomainMatch(nestedWebsite.url, normalizedDomain, nestedWebsite.categoryId)) {
|
||||
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') {
|
||||
for (const urlKey in nestedWebsite.url) {
|
||||
if (nestedWebsite.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = nestedWebsite.url[urlKey];
|
||||
if (isDomainMatch(urlValue, normalizedDomain, nestedWebsite.categoryId)) {
|
||||
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) {
|
||||
// 嵌套类别中的嵌套类别,递归检查
|
||||
for (const secondNestedWebsiteKey in nestedWebsite) {
|
||||
if (nestedWebsite.hasOwnProperty(secondNestedWebsiteKey) && secondNestedWebsiteKey !== 'company') {
|
||||
const secondNestedWebsite = nestedWebsite[secondNestedWebsiteKey];
|
||||
|
||||
if (secondNestedWebsite.url) {
|
||||
// 处理字符串类型的URL
|
||||
if (typeof secondNestedWebsite.url === 'string') {
|
||||
if (isDomainMatch(secondNestedWebsite.url, normalizedDomain, secondNestedWebsite.categoryId)) {
|
||||
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') {
|
||||
for (const urlKey in secondNestedWebsite.url) {
|
||||
if (secondNestedWebsite.url.hasOwnProperty(urlKey)) {
|
||||
const urlValue = secondNestedWebsite.url[urlKey];
|
||||
if (isDomainMatch(urlValue, normalizedDomain, secondNestedWebsite.categoryId)) {
|
||||
return {
|
||||
name: secondNestedWebsite.name,
|
||||
icon: secondNestedWebsite.icon,
|
||||
categoryId: secondNestedWebsite.categoryId,
|
||||
categoryName: domainInfoDatabase.categories[secondNestedWebsite.categoryId] || '未知',
|
||||
company: secondNestedWebsite.company || companyName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return await getDomainInfoFromAPI(domain);
|
||||
}
|
||||
|
||||
// 检查域名是否匹配
|
||||
function isDomainMatch(urlValue, targetDomain, categoryId) {
|
||||
|
||||
@@ -1931,7 +1796,7 @@ async function showLogDetailModal(log) {
|
||||
<div class="mt-1">
|
||||
<div class="flex items-center mb-1 flex-wrap">
|
||||
<span class="text-gray-500 dark:text-gray-400 mr-2">类别:</span>
|
||||
<span class="flex-grow">${domainInfo.categoryName || '未知'}</span>
|
||||
<span class="flex-grow">${domainInfo.category || '未知'}</span>
|
||||
</div>
|
||||
<div class="flex items-center flex-wrap">
|
||||
<span class="text-gray-500 dark:text-gray-400 mr-2">所属单位/公司:</span>
|
||||
|
||||
+60
-20
@@ -30,6 +30,16 @@ const pageDataCache = {
|
||||
timestamp: 0,
|
||||
data: null,
|
||||
expiry: 10 * 60 * 1000 // 10分钟过期
|
||||
},
|
||||
about: {
|
||||
timestamp: 0,
|
||||
data: null,
|
||||
expiry: 10 * 60 * 1000 // 10分钟过期
|
||||
},
|
||||
threats: {
|
||||
timestamp: 0,
|
||||
data: null,
|
||||
expiry: 5 * 60 * 1000 // 5分钟过期
|
||||
}
|
||||
},
|
||||
|
||||
@@ -90,8 +100,11 @@ function initPageByHash() {
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('gfwlist-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('domain-content'),
|
||||
document.getElementById('logs-content'),
|
||||
document.getElementById('config-content')
|
||||
document.getElementById('config-content'),
|
||||
document.getElementById('about-content'),
|
||||
document.getElementById('threats-content')
|
||||
];
|
||||
|
||||
contentSections.forEach(section => {
|
||||
@@ -109,17 +122,20 @@ function initPageByHash() {
|
||||
// 更新页面标题
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
if (pageTitle) {
|
||||
const titles = {
|
||||
'dashboard': '仪表盘',
|
||||
'shield': '屏蔽管理',
|
||||
'hosts': 'Hosts管理',
|
||||
'gfwlist': 'GFWList管理',
|
||||
'query': 'DNS屏蔽查询',
|
||||
'logs': '查询日志',
|
||||
'config': '系统设置'
|
||||
};
|
||||
pageTitle.textContent = titles[hash] || '仪表盘';
|
||||
}
|
||||
const titles = {
|
||||
'dashboard': '仪表盘',
|
||||
'shield': '屏蔽管理',
|
||||
'hosts': 'Hosts管理',
|
||||
'gfwlist': 'GFWList管理',
|
||||
'query': 'DNS屏蔽查询',
|
||||
'domain': '域名查询',
|
||||
'logs': '查询日志',
|
||||
'config': '系统设置',
|
||||
'about': '关于',
|
||||
'threats': '威胁告警'
|
||||
};
|
||||
pageTitle.textContent = titles[hash] || '仪表盘';
|
||||
}
|
||||
|
||||
// 页面特定初始化 - 使用setTimeout延迟调用,确保所有脚本文件都已加载完成
|
||||
if (hash === 'shield') {
|
||||
@@ -172,23 +188,43 @@ function initPageByHash() {
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'about') {
|
||||
setTimeout(() => {
|
||||
if (typeof initAboutPage === 'function') {
|
||||
// 检查页面是否已经初始化
|
||||
if (!pageDataCache.isPageInitialized('about') || !pageDataCache.isCacheValid('about')) {
|
||||
initAboutPage();
|
||||
pageDataCache.markPageInitialized('about');
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'threats') {
|
||||
setTimeout(() => {
|
||||
if (typeof initThreatsPage === 'function') {
|
||||
// 检查页面是否已经初始化
|
||||
if (!pageDataCache.isPageInitialized('threats') || !pageDataCache.isCacheValid('threats')) {
|
||||
initThreatsPage();
|
||||
pageDataCache.markPageInitialized('threats');
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化运行时间
|
||||
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);
|
||||
const totalSeconds = Math.floor(milliseconds / 1000);
|
||||
const days = Math.floor(totalSeconds / (24 * 60 * 60));
|
||||
const hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
|
||||
const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
if (days > 0) {
|
||||
return `${days}天${hours % 24}小时`;
|
||||
return `${days}天${hours}小时${minutes}分钟`;
|
||||
} else if (hours > 0) {
|
||||
return `${hours}小时${minutes % 60}分钟`;
|
||||
return `${hours}小时${minutes}分钟${seconds}秒`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}分钟${seconds % 60}秒`;
|
||||
return `${minutes}分钟${seconds}秒`;
|
||||
} else {
|
||||
return `${seconds}秒`;
|
||||
}
|
||||
@@ -528,6 +564,10 @@ function handleVisibilityChange() {
|
||||
initShieldPage();
|
||||
} else if (hash === 'hosts' && typeof initHostsPage === 'function') {
|
||||
initHostsPage();
|
||||
} else if (hash === 'about' && typeof initAboutPage === 'function') {
|
||||
initAboutPage();
|
||||
} else if (hash === 'threats' && typeof initThreatsPage === 'function') {
|
||||
initThreatsPage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1280,6 +1280,96 @@ async function handleAddBlacklist(event) {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理一键更新所有黑名单
|
||||
async function handleUpdateAllBlacklists() {
|
||||
try {
|
||||
// 显示通知
|
||||
showNotification('开始更新所有黑名单...', 'info');
|
||||
|
||||
// 获取当前所有黑名单
|
||||
const response = await fetch('/api/shield/blacklists');
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取黑名单失败: ${response.status}`);
|
||||
}
|
||||
|
||||
const blacklists = await response.json();
|
||||
|
||||
// 确保blacklists是数组
|
||||
const blacklistArray = Array.isArray(blacklists) ? blacklists : [];
|
||||
|
||||
if (blacklistArray.length === 0) {
|
||||
showNotification('暂无黑名单可更新', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新所有黑名单的最后更新时间
|
||||
const updatedBlacklists = blacklistArray.map(blacklist => ({
|
||||
Name: blacklist.name,
|
||||
URL: blacklist.url,
|
||||
Enabled: blacklist.enabled,
|
||||
LastUpdateTime: new Date().toISOString()
|
||||
}));
|
||||
|
||||
// 显示所有黑名单的加载状态
|
||||
blacklistArray.forEach(blacklist => {
|
||||
updateStatus(blacklist.url, 'loading');
|
||||
});
|
||||
|
||||
// 发送更新请求
|
||||
const updateResponse = await fetch('/api/shield/blacklists', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(updatedBlacklists)
|
||||
});
|
||||
|
||||
// 解析服务器响应
|
||||
let responseData;
|
||||
try {
|
||||
responseData = await updateResponse.json();
|
||||
} catch (jsonError) {
|
||||
responseData = {};
|
||||
}
|
||||
|
||||
// 根据服务器响应判断是否成功
|
||||
if (updateResponse.ok && (responseData.status === 'success' || !responseData.status)) {
|
||||
// 显示所有黑名单的成功状态
|
||||
blacklistArray.forEach(blacklist => {
|
||||
updateStatus(blacklist.url, 'success');
|
||||
});
|
||||
|
||||
// 显示通知
|
||||
showNotification('所有黑名单更新成功', 'success');
|
||||
|
||||
// 延迟重新加载黑名单和统计信息,让用户能看到成功状态
|
||||
setTimeout(() => {
|
||||
// 重新加载黑名单
|
||||
loadRemoteBlacklists();
|
||||
// 重新加载统计信息
|
||||
loadShieldStats();
|
||||
}, 3000);
|
||||
} else {
|
||||
// 显示所有黑名单的错误状态
|
||||
blacklistArray.forEach(blacklist => {
|
||||
updateStatus(blacklist.url, 'error', responseData.error || responseData.message || `更新失败: ${updateResponse.status}`);
|
||||
});
|
||||
showNotification(`更新失败: ${responseData.error || responseData.message || updateResponse.status}`, 'error');
|
||||
|
||||
// 延迟重新加载黑名单和统计信息
|
||||
setTimeout(() => {
|
||||
// 重新加载黑名单
|
||||
loadRemoteBlacklists();
|
||||
// 重新加载统计信息
|
||||
loadShieldStats();
|
||||
}, 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新所有黑名单失败:', error);
|
||||
showNotification('更新所有黑名单失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 当前显示的规则类型:'local' 或 'remote'
|
||||
@@ -1299,6 +1389,12 @@ function setupShieldEventListeners() {
|
||||
saveBlacklistBtn.addEventListener('click', handleAddBlacklist);
|
||||
}
|
||||
|
||||
// 一键更新所有黑名单事件
|
||||
const updateAllBlacklistsBtn = document.getElementById('update-all-blacklists-btn');
|
||||
if (updateAllBlacklistsBtn) {
|
||||
updateAllBlacklistsBtn.addEventListener('click', handleUpdateAllBlacklists);
|
||||
}
|
||||
|
||||
// 添加切换查看自定义规则和远程规则的事件监听
|
||||
const viewLocalRulesBtn = document.getElementById('view-local-rules-btn');
|
||||
if (viewLocalRulesBtn) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user