web异常待修复

This commit is contained in:
Alex Yang
2025-11-24 12:54:25 +08:00
parent 23b8847c35
commit 86faca036d
5 changed files with 2201 additions and 120 deletions

View File

@@ -1,5 +1,9 @@
// 初始化仪表盘面板
function initDashboardPanel() {
// 初始化小型图表
if (typeof initMiniCharts === 'function') {
initMiniCharts();
}
// 加载统计数据
loadDashboardData();
// 启动实时更新
@@ -29,19 +33,19 @@ function updateStatCards() {
if (data && data.dns) {
// 屏蔽请求
const blockedCount = data.dns.Blocked || data.dns.blocked || 0;
updateStatCard('blocked-count', blockedCount);
smoothUpdateStatCard('blocked-count', blockedCount);
// 允许请求
const allowedCount = data.dns.Allowed || data.dns.allowed || 0;
updateStatCard('allowed-count', allowedCount);
smoothUpdateStatCard('allowed-count', allowedCount);
// 错误请求
const errorCount = data.dns.Errors || data.dns.errors || 0;
updateStatCard('error-count', errorCount);
smoothUpdateStatCard('error-count', errorCount);
// 总请求数
const totalCount = blockedCount + allowedCount + errorCount;
updateStatCard('total-queries', totalCount);
smoothUpdateStatCard('total-queries', totalCount);
// 更新数据历史记录和小型图表
if (typeof updateDataHistory === 'function') {
@@ -56,15 +60,14 @@ function updateStatCards() {
}
} else {
// 处理其他可能的数据格式
// 修复语法错误,使用传统的对象属性访问方式
const blockedValue = data && (data.Blocked !== undefined ? data.Blocked : (data.blocked !== undefined ? data.blocked : 0));
const allowedValue = data && (data.Allowed !== undefined ? data.Allowed : (data.allowed !== undefined ? data.allowed : 0));
const errorValue = data && (data.Errors !== undefined ? data.Errors : (data.errors !== undefined ? data.errors : 0));
updateStatCard('blocked-count', blockedValue);
updateStatCard('allowed-count', allowedValue);
updateStatCard('error-count', errorValue);
smoothUpdateStatCard('blocked-count', blockedValue);
smoothUpdateStatCard('allowed-count', allowedValue);
smoothUpdateStatCard('error-count', errorValue);
const totalCount = blockedValue + allowedValue + errorValue;
updateStatCard('total-queries', totalCount);
smoothUpdateStatCard('total-queries', totalCount);
}
})
.catch(error => {
@@ -75,13 +78,32 @@ function updateStatCards() {
apiRequest('/shield')
.then(data => {
let rulesCount = 0;
// 增强的数据格式处理,确保能正确处理各种返回格式
if (Array.isArray(data)) {
rulesCount = data.length;
} else if (data && data.rules && Array.isArray(data.rules)) {
rulesCount = data.rules.length;
} else if (data && data.domainRules) {
// 处理可能的规则分类格式
let domainRulesCount = 0;
let regexRulesCount = 0;
if (Array.isArray(data.domainRules)) {
domainRulesCount = data.domainRules.length;
} else if (typeof data.domainRules === 'object') {
domainRulesCount = Object.keys(data.domainRules).length;
}
if (data.regexRules && Array.isArray(data.regexRules)) {
regexRulesCount = data.regexRules.length;
}
rulesCount = domainRulesCount + regexRulesCount;
}
updateStatCard('rules-count', rulesCount);
// 确保至少显示0而不是--
smoothUpdateStatCard('rules-count', rulesCount);
// 更新数据历史记录和小型图表
if (typeof updateDataHistory === 'function') {
@@ -94,6 +116,41 @@ function updateStatCards() {
})
.catch(error => {
console.error('获取规则数失败:', error);
// 即使出错也要设置为0避免显示--
smoothUpdateStatCard('rules-count', 0);
});
// 获取Hosts条目数量
apiRequest('/shield/hosts')
.then(data => {
let hostsCount = 0;
// 处理各种可能的数据格式
if (Array.isArray(data)) {
hostsCount = data.length;
} else if (data && data.hosts && Array.isArray(data.hosts)) {
hostsCount = data.hosts.length;
} else if (data && typeof data === 'object' && data !== null) {
// 如果是对象格式,计算键的数量
hostsCount = Object.keys(data).length;
}
// 确保至少显示0而不是--
smoothUpdateStatCard('hosts-count', hostsCount);
// 更新数据历史记录和小型图表
if (typeof updateDataHistory === 'function') {
updateDataHistory('hosts', hostsCount);
}
if (typeof updateMiniChart === 'function' && typeof dataHistory !== 'undefined') {
updateMiniChart('hosts-chart', dataHistory.hosts);
}
})
.catch(error => {
console.error('获取Hosts数量失败:', error);
// 即使出错也要设置为0避免显示--
smoothUpdateStatCard('hosts-count', 0);
});
// 获取Hosts条目数
@@ -106,7 +163,7 @@ function updateStatCards() {
hostsCount = data.hosts.length;
}
updateStatCard('hosts-count', hostsCount);
smoothUpdateStatCard('hosts-count', hostsCount);
// 更新数据历史记录和小型图表
if (typeof updateDataHistory === 'function') {
@@ -122,6 +179,7 @@ function updateStatCards() {
});
}
// 更新单个统计卡片
function updateStatCard(elementId, value) {
const element = document.getElementById(elementId);
@@ -139,6 +197,61 @@ function updateStatCard(elementId, value) {
}
}
// 平滑更新统计卡片(数字递增动画)
function smoothUpdateStatCard(elementId, newValue) {
const element = document.getElementById(elementId);
if (!element) return;
// 获取旧值
const oldValue = previousStats[elementId] || 0;
// 如果值相同,不更新
if (newValue === oldValue) return;
// 如果是初始值,直接更新
if (oldValue === 0 || oldValue === '--') {
updateStatCard(elementId, newValue);
return;
}
// 设置动画持续时间
const duration = 500; // 500ms
const startTime = performance.now();
// 动画函数
function animate(currentTime) {
const elapsedTime = currentTime - startTime;
const progress = Math.min(elapsedTime / duration, 1);
// 使用缓动函数
const easeOutQuad = 1 - (1 - progress) * (1 - progress);
// 计算当前值
const currentValue = Math.floor(oldValue + (newValue - oldValue) * easeOutQuad);
// 更新显示
element.textContent = formatNumber(currentValue);
// 继续动画
if (progress < 1) {
requestAnimationFrame(animate);
} else {
// 动画完成,设置最终值
element.textContent = formatNumber(newValue);
// 添加光晕效果
element.classList.add('update');
setTimeout(() => {
element.classList.remove('update');
}, 1000);
// 更新记录
previousStats[elementId] = newValue;
}
}
// 开始动画
requestAnimationFrame(animate);
}
// 加载24小时统计数据
function loadHourlyStats() {
apiRequest('/hourly-stats')
@@ -524,7 +637,6 @@ function renderRequestsPieChart(labels, data) {
// 加载最常屏蔽的域名
function loadTopBlockedDomains() {
// 首先获取表格元素并显示加载状态
// 修复语法错误使用传统的DOM访问方式
const topBlockedTable = document.getElementById('top-blocked-table');
const tbody = topBlockedTable ? topBlockedTable.querySelector('tbody') : null;
if (tbody) {
@@ -537,12 +649,9 @@ function loadTopBlockedDomains() {
// 处理多种可能的数据格式,特别优化对用户提供格式的支持
let processedData = [];
console.log('最常屏蔽域名API返回数据:', data);
if (Array.isArray(data)) {
// 数组格式:直接使用,并过滤出有效的域名数据
processedData = data.filter(item => item && (item.domain || item.name || item.Domain || item.Name) && (item.count !== undefined || item.Count !== undefined || item.hits !== undefined || item.Hits !== undefined));
console.log('处理后的域名数据:', processedData);
} else if (data && data.domains && Array.isArray(data.domains)) {
// 嵌套在domains属性中
processedData = data.domains;
@@ -554,7 +663,7 @@ function loadTopBlockedDomains() {
}));
}
renderTopBlockedDomains(processedData);
smoothRenderTable('#top-blocked-table', processedData, renderDomainRow);
})
.catch(error => {
console.error('获取最常屏蔽域名失败:', error);
@@ -570,48 +679,6 @@ function loadTopBlockedDomains() {
});
}
// 渲染最常屏蔽的域名表格
function renderTopBlockedDomains(domains) {
// 修复语法错误使用传统的DOM访问方式
const topBlockedTable = document.getElementById('top-blocked-table');
const tbody = topBlockedTable ? topBlockedTable.querySelector('tbody') : null;
if (!tbody) return;
console.log('准备渲染的域名数据:', domains);
if (!domains || domains.length === 0) {
showEmpty(tbody, '暂无屏蔽记录');
return;
}
tbody.innerHTML = '';
domains.forEach((domain, index) => {
if (!domain) return;
// 支持不同的字段名和格式,特别针对用户提供的数据格式优化
const domainName = domain.domain || domain.name || domain.Domain || domain.Name || '未知域名';
const count = domain.count !== undefined ? domain.count :
(domain.Count !== undefined ? domain.Count :
(domain.hits !== undefined ? domain.hits :
(domain.Hits !== undefined ? domain.Hits : 0)));
console.log(`渲染域名 ${index + 1}:`, {domainName, count});
const row = document.createElement('tr');
row.innerHTML = `
<td>${domainName}</td>
<td>${formatNumber(count)}</td>
`;
tbody.appendChild(row);
});
// 初始化表格排序
if (typeof initTableSort === 'function') {
initTableSort('top-blocked-table');
}
}
// 加载最常解析的域名
function loadTopResolvedDomains() {
apiRequest('/top-resolved')
@@ -633,7 +700,7 @@ function loadTopResolvedDomains() {
}));
}
renderTopResolvedDomains(processedData);
smoothRenderTable('#top-resolved-table', processedData, renderDomainRow);
})
.catch(error => {
console.error('获取最常解析域名失败:', error);
@@ -650,31 +717,177 @@ function loadTopResolvedDomains() {
});
}
// 渲染最常解析的域名表格
function renderTopResolvedDomains(domains) {
const tbody = document.getElementById('top-resolved-table').querySelector('tbody');
// 渲染域名行
function renderDomainRow(item, index) {
if (!item) return null;
// 支持不同的字段名和格式
const domainName = item.domain || item.name || item.Domain || item.Name || '未知域名';
const count = item.count !== undefined ? item.count :
(item.Count !== undefined ? item.Count :
(item.hits !== undefined ? item.hits :
(item.Hits !== undefined ? item.Hits : 0)));
const row = document.createElement('tr');
row.className = 'fade-in'; // 添加淡入动画类
row.dataset.domain = domainName;
row.dataset.count = count;
row.innerHTML = `
<td>${domainName}</td>
<td class="count-cell">${formatNumber(count)}</td>
`;
// 设置动画延迟,创建级联效果
row.style.animationDelay = `${index * 50}ms`;
return row;
}
// 平滑渲染表格数据
function smoothRenderTable(tableId, newData, rowRenderer) {
const table = document.getElementById(tableId);
const tbody = table ? table.querySelector('tbody') : null;
if (!tbody) return;
if (!domains || domains.length === 0) {
showEmpty(tbody, '暂无解析记录');
if (!newData || newData.length === 0) {
showEmpty(tbody, '暂无数据记录');
return;
}
tbody.innerHTML = '';
// 创建映射以提高查找效率
const oldRows = Array.from(tbody.querySelectorAll('tr'));
const rowMap = new Map();
domains.forEach((domain, index) => {
// 支持不同的字段名和格式
const domainName = domain.domain || domain.name || domain.Domain || domain.Name || '未知域名';
const count = domain.count || domain.Count || domain.hits || domain.Hits || 0;
const row = document.createElement('tr');
row.innerHTML = `
<td>${domainName}</td>
<td>${formatNumber(count)}</td>
`;
tbody.appendChild(row);
oldRows.forEach(row => {
if (!row.querySelector('td:first-child')) return;
const key = row.dataset.domain || row.querySelector('td:first-child').textContent;
rowMap.set(key, row);
});
// 初始化表格排序
initTableSort('top-resolved-table');
// 准备新的数据行
const newRows = [];
const updatedRows = new Set();
// 处理每一条新数据
newData.forEach((item, index) => {
const key = item.domain || item.name || item.Domain || item.Name || '未知域名';
if (rowMap.has(key)) {
// 数据项已存在,更新它
const existingRow = rowMap.get(key);
const oldCount = parseInt(existingRow.dataset.count) || 0;
const count = item.count !== undefined ? item.count :
(item.Count !== undefined ? item.Count :
(item.hits !== undefined ? item.hits :
(item.Hits !== undefined ? item.Hits : 0)));
// 更新数据属性
existingRow.dataset.count = count;
// 如果计数变化,应用平滑更新
if (oldCount !== count) {
const countCell = existingRow.querySelector('.count-cell');
if (countCell) {
smoothUpdateNumber(countCell, oldCount, count);
}
}
// 更新位置
existingRow.style.animationDelay = `${index * 50}ms`;
newRows.push(existingRow);
updatedRows.add(key);
} else {
// 新数据项,创建新行
const newRow = rowRenderer(item, index);
if (newRow) {
// 先设置透明度为0避免在错误位置闪烁
newRow.style.opacity = '0';
newRows.push(newRow);
}
}
});
// 移除不再存在的数据行
oldRows.forEach(row => {
if (!row.querySelector('td:first-child')) return;
const key = row.dataset.domain || row.querySelector('td:first-child').textContent;
if (!updatedRows.has(key)) {
// 添加淡出动画
row.classList.add('fade-out');
setTimeout(() => {
if (row.parentNode === tbody) {
tbody.removeChild(row);
}
}, 300);
}
});
// 批量更新表格内容,减少重排
requestAnimationFrame(() => {
// 保留未移除的行并按新顺序插入
const fragment = document.createDocumentFragment();
newRows.forEach(row => {
// 如果是新行,添加到文档片段
if (!row.parentNode || row.parentNode !== tbody) {
fragment.appendChild(row);
}
// 如果是已有行,移除它以便按新顺序重新插入
else if (tbody.contains(row)) {
tbody.removeChild(row);
fragment.appendChild(row);
}
});
// 将文档片段添加到表格
tbody.appendChild(fragment);
// 触发动画
setTimeout(() => {
newRows.forEach(row => {
row.style.opacity = '1';
});
}, 10);
// 初始化表格排序
if (typeof initTableSort === 'function') {
initTableSort(tableId);
}
});
}
// 平滑更新数字
function smoothUpdateNumber(element, oldValue, newValue) {
// 如果值相同,不更新
if (oldValue === newValue) return;
// 设置动画持续时间
const duration = 500;
const startTime = performance.now();
function animate(currentTime) {
const elapsedTime = currentTime - startTime;
const progress = Math.min(elapsedTime / duration, 1);
// 使用缓动函数
const easeOutQuad = 1 - (1 - progress) * (1 - progress);
// 计算当前值
const currentValue = Math.floor(oldValue + (newValue - oldValue) * easeOutQuad);
// 更新显示
element.textContent = formatNumber(currentValue);
// 继续动画
if (progress < 1) {
requestAnimationFrame(animate);
} else {
// 动画完成
element.textContent = formatNumber(newValue);
}
}
// 开始动画
requestAnimationFrame(animate);
}