This commit is contained in:
Alex Yang
2026-03-30 01:04:46 +08:00
parent 050aa421b1
commit f627244b8f
5978 changed files with 1502187 additions and 2947 deletions
+293
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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();
}
}
+96
View File
@@ -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) {
+1109
View File
File diff suppressed because it is too large Load Diff