增加更多匹配的域名信息
This commit is contained in:
3045
static/css/font-awesome.min.css
vendored
Normal file
3045
static/css/font-awesome.min.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
static/css/webfonts/fontawesome-webfont.eot
Normal file
BIN
static/css/webfonts/fontawesome-webfont.eot
Normal file
Binary file not shown.
2671
static/css/webfonts/fontawesome-webfont.svg
Normal file
2671
static/css/webfonts/fontawesome-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 434 KiB |
BIN
static/css/webfonts/fontawesome-webfont.woff
Normal file
BIN
static/css/webfonts/fontawesome-webfont.woff
Normal file
Binary file not shown.
BIN
static/css/webfonts/fontawesome-webfont.woff2
Normal file
BIN
static/css/webfonts/fontawesome-webfont.woff2
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="css/vendor/tailwind.css"></script>
|
||||
<!-- Font Awesome -->
|
||||
<link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link href="css/font-awesome.min.css" rel="stylesheet">
|
||||
<!-- 自定义样式 -->
|
||||
<link href="css/style.css" rel="stylesheet">
|
||||
<!-- Chart.js 本地备用 -->
|
||||
@@ -1086,20 +1086,36 @@
|
||||
<!-- 屏蔽配置 -->
|
||||
<div class="mb-8">
|
||||
<h4 class="text-md font-medium mb-4">屏蔽配置</h4>
|
||||
<div>
|
||||
<label for="shield-update-interval" class="block text-sm font-medium text-gray-700 mb-1">更新间隔 (秒)</label>
|
||||
<input type="number" id="shield-update-interval" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="3600">
|
||||
</div>
|
||||
<div>
|
||||
<label for="shield-block-method" class="block text-sm font-medium text-gray-700 mb-1">屏蔽方法</label>
|
||||
<select id="shield-block-method" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<option value="0.0.0.0">返回0.0.0.0</option>
|
||||
<option value="NXDOMAIN">返回NXDOMAIN</option>
|
||||
<option value="refused">返回refused</option>
|
||||
<option value="emptyIP">返回空IP</option>
|
||||
<option value="customIP">返回自定义IP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="shield-update-interval" class="block text-sm font-medium text-gray-700 mb-1">更新间隔 (秒)</label>
|
||||
<input type="number" id="shield-update-interval" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="3600">
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label for="shield-block-method" class="block text-sm font-medium text-gray-700 mb-1">屏蔽方法</label>
|
||||
<select id="shield-block-method" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<option value="0.0.0.0">返回0.0.0.0</option>
|
||||
<option value="NXDOMAIN">返回NXDOMAIN</option>
|
||||
<option value="refused">返回refused</option>
|
||||
<option value="emptyIP">返回空IP</option>
|
||||
<option value="customIP">返回自定义IP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label for="shield-custom-block-ip" class="block text-sm font-medium text-gray-700 mb-1">自定义屏蔽IP</label>
|
||||
<input type="text" id="shield-custom-block-ip" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="127.0.0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GFWList配置 -->
|
||||
<div class="mb-8">
|
||||
<h4 class="text-md font-medium mb-4">GFWList配置</h4>
|
||||
<div>
|
||||
<label for="shield-gfwlist-ip" class="block text-sm font-medium text-gray-700 mb-1">GFWList解析目标IP</label>
|
||||
<input type="text" id="shield-gfwlist-ip" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="127.0.0.1">
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<label for="shield-gfwlist-content" class="block text-sm font-medium text-gray-700 mb-1">GFWList内容</label>
|
||||
<textarea id="shield-gfwlist-content" rows="10" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="输入GFWList规则内容..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -74,6 +74,10 @@ function populateConfigForm(config) {
|
||||
//setElementValue('shield-hosts-file', getSafeValue(shieldConfig.HostsFile, 'data/hosts.txt'));
|
||||
// 使用服务器端接受的屏蔽方法值,默认使用NXDOMAIN, 可选值: NXDOMAIN, NULL, REFUSED
|
||||
setElementValue('shield-block-method', getSafeValue(shieldConfig.BlockMethod, 'NXDOMAIN'));
|
||||
setElementValue('shield-custom-block-ip', getSafeValue(shieldConfig.CustomBlockIP, ''));
|
||||
// GFWList配置
|
||||
setElementValue('shield-gfwlist-ip', getSafeValue(shieldConfig.GFWListIP, ''));
|
||||
setElementValue('shield-gfwlist-content', getSafeValue(shieldConfig.GFWListContent, ''));
|
||||
}
|
||||
|
||||
// 工具函数:安全设置元素值
|
||||
@@ -197,7 +201,10 @@ function collectFormData() {
|
||||
},
|
||||
shield: {
|
||||
updateInterval: updateInterval,
|
||||
blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN'
|
||||
blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN',
|
||||
customBlockIP: getElementValue('shield-custom-block-ip'),
|
||||
gfwListIP: getElementValue('shield-gfwlist-ip'),
|
||||
gfwListContent: getElementValue('shield-gfwlist-content')
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -205,9 +212,14 @@ function collectFormData() {
|
||||
// 工具函数:安全获取元素值
|
||||
function getElementValue(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element && element.tagName === 'INPUT') {
|
||||
if (element.type === 'checkbox') {
|
||||
return element.checked;
|
||||
if (element) {
|
||||
if (element.tagName === 'INPUT') {
|
||||
if (element.type === 'checkbox') {
|
||||
return element.checked;
|
||||
}
|
||||
return element.value;
|
||||
} else if (element.tagName === 'TEXTAREA') {
|
||||
return element.value;
|
||||
}
|
||||
return element.value;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,19 @@ function processRealTimeData(stats) {
|
||||
// 计算响应时间趋势
|
||||
let responsePercent = '---';
|
||||
let trendClass = 'text-gray-400';
|
||||
let trendIcon = '---';
|
||||
let trendIcon = '•';
|
||||
|
||||
// 查找箭头元素
|
||||
const responseTimePercentElem = document.getElementById('response-time-percent');
|
||||
let parent = null;
|
||||
let arrowIcon = null;
|
||||
|
||||
if (responseTimePercentElem) {
|
||||
parent = responseTimePercentElem.parentElement;
|
||||
if (parent) {
|
||||
arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle');
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.avgResponseTime !== undefined && stats.avgResponseTime !== null) {
|
||||
// 首次加载时初始化历史数据,不计算趋势
|
||||
@@ -171,6 +183,10 @@ function processRealTimeData(stats) {
|
||||
responsePercent = '0.0%';
|
||||
trendIcon = '•';
|
||||
trendClass = 'text-gray-500';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-circle mr-1 text-xs';
|
||||
parent.className = 'text-gray-500 text-sm flex items-center';
|
||||
}
|
||||
} else {
|
||||
const prevResponseTime = window.dashboardHistoryData.prevResponseTime;
|
||||
|
||||
@@ -178,16 +194,36 @@ function processRealTimeData(stats) {
|
||||
const changePercent = ((stats.avgResponseTime - prevResponseTime) / prevResponseTime) * 100;
|
||||
responsePercent = Math.abs(changePercent).toFixed(1) + '%';
|
||||
|
||||
// 设置趋势图标和颜色
|
||||
// 处理-0.0%的情况
|
||||
if (responsePercent === '-0.0%') {
|
||||
responsePercent = '0.0%';
|
||||
}
|
||||
|
||||
// 根据用户要求:数量下降显示红色箭头,上升显示绿色箭头
|
||||
if (changePercent > 0) {
|
||||
trendIcon = '↓';
|
||||
trendClass = 'text-danger';
|
||||
} else if (changePercent < 0) {
|
||||
// 响应时间增加,数值上升
|
||||
trendIcon = '↑';
|
||||
trendClass = 'text-success';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-arrow-up mr-1';
|
||||
parent.className = 'text-success text-sm flex items-center';
|
||||
}
|
||||
} else if (changePercent < 0) {
|
||||
// 响应时间减少,数值下降
|
||||
trendIcon = '↓';
|
||||
trendClass = 'text-danger';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-arrow-down mr-1';
|
||||
parent.className = 'text-danger text-sm flex items-center';
|
||||
}
|
||||
} else {
|
||||
// 趋势为0时,显示圆点图标
|
||||
trendIcon = '•';
|
||||
trendClass = 'text-gray-500';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-circle mr-1 text-xs';
|
||||
parent.className = 'text-gray-500 text-sm flex items-center';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新历史数据
|
||||
@@ -196,7 +232,6 @@ function processRealTimeData(stats) {
|
||||
}
|
||||
|
||||
document.getElementById('avg-response-time').textContent = responseTime;
|
||||
const responseTimePercentElem = document.getElementById('response-time-percent');
|
||||
if (responseTimePercentElem) {
|
||||
responseTimePercentElem.textContent = trendIcon + ' ' + responsePercent;
|
||||
responseTimePercentElem.className = `text-sm flex items-center ${trendClass}`;
|
||||
@@ -227,7 +262,19 @@ function processRealTimeData(stats) {
|
||||
// 计算活跃IP趋势
|
||||
let ipsPercent = '---';
|
||||
let trendClass = 'text-gray-400';
|
||||
let trendIcon = '---';
|
||||
let trendIcon = '•';
|
||||
|
||||
// 查找箭头元素
|
||||
const activeIpsPercentElem = document.getElementById('active-ips-percent');
|
||||
let parent = null;
|
||||
let arrowIcon = null;
|
||||
|
||||
if (activeIpsPercentElem) {
|
||||
parent = activeIpsPercentElem.parentElement;
|
||||
if (parent) {
|
||||
arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle');
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.activeIPs !== undefined) {
|
||||
const prevActiveIPs = window.dashboardHistoryData.prevActiveIPs;
|
||||
@@ -238,20 +285,43 @@ function processRealTimeData(stats) {
|
||||
ipsPercent = '0.0%';
|
||||
trendIcon = '•';
|
||||
trendClass = 'text-gray-500';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-circle mr-1 text-xs';
|
||||
parent.className = 'text-gray-500 text-sm flex items-center';
|
||||
}
|
||||
} else {
|
||||
if (prevActiveIPs > 0) {
|
||||
const changePercent = ((stats.activeIPs - prevActiveIPs) / prevActiveIPs) * 100;
|
||||
ipsPercent = Math.abs(changePercent).toFixed(1) + '%';
|
||||
|
||||
// 处理-0.0%的情况
|
||||
if (ipsPercent === '-0.0%') {
|
||||
ipsPercent = '0.0%';
|
||||
}
|
||||
|
||||
// 根据用户要求:数量下降显示红色箭头,上升显示绿色箭头
|
||||
if (changePercent > 0) {
|
||||
trendIcon = '↑';
|
||||
trendClass = 'text-primary';
|
||||
trendClass = 'text-success';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-arrow-up mr-1';
|
||||
parent.className = 'text-success text-sm flex items-center';
|
||||
}
|
||||
} else if (changePercent < 0) {
|
||||
trendIcon = '↓';
|
||||
trendClass = 'text-secondary';
|
||||
trendClass = 'text-danger';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-arrow-down mr-1';
|
||||
parent.className = 'text-danger text-sm flex items-center';
|
||||
}
|
||||
} else {
|
||||
// 趋势为0时,显示圆点图标
|
||||
trendIcon = '•';
|
||||
trendClass = 'text-gray-500';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-circle mr-1 text-xs';
|
||||
parent.className = 'text-gray-500 text-sm flex items-center';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +331,6 @@ function processRealTimeData(stats) {
|
||||
}
|
||||
|
||||
document.getElementById('active-ips').textContent = activeIPs;
|
||||
const activeIpsPercentElem = document.getElementById('active-ips-percentage');
|
||||
if (activeIpsPercentElem) {
|
||||
activeIpsPercentElem.textContent = trendIcon + ' ' + ipsPercent;
|
||||
activeIpsPercentElem.className = `text-sm flex items-center ${trendClass}`;
|
||||
@@ -627,7 +696,19 @@ async function loadDashboardData() {
|
||||
// 计算响应时间趋势
|
||||
let responsePercent = '---';
|
||||
let trendClass = 'text-gray-400';
|
||||
let trendIcon = '---';
|
||||
let trendIcon = '•';
|
||||
|
||||
// 查找箭头元素
|
||||
const responseTimePercentElem = document.getElementById('response-time-percent');
|
||||
let parent = null;
|
||||
let arrowIcon = null;
|
||||
|
||||
if (responseTimePercentElem) {
|
||||
parent = responseTimePercentElem.parentElement;
|
||||
if (parent) {
|
||||
arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle');
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.avgResponseTime !== undefined && stats.avgResponseTime !== null) {
|
||||
// 首次加载时初始化历史数据,不计算趋势
|
||||
@@ -636,6 +717,10 @@ async function loadDashboardData() {
|
||||
responsePercent = '0.0%';
|
||||
trendIcon = '•';
|
||||
trendClass = 'text-gray-500';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-circle mr-1 text-xs';
|
||||
parent.className = 'text-gray-500 text-sm flex items-center';
|
||||
}
|
||||
} else {
|
||||
const prevResponseTime = window.dashboardHistoryData.prevResponseTime;
|
||||
|
||||
@@ -644,16 +729,38 @@ async function loadDashboardData() {
|
||||
const changePercent = ((stats.avgResponseTime - prevResponseTime) / prevResponseTime) * 100;
|
||||
responsePercent = Math.abs(changePercent).toFixed(1) + '%';
|
||||
|
||||
// 设置趋势图标和颜色(响应时间增加是负面的,减少是正面的)
|
||||
// 处理-0.0%的情况
|
||||
if (responsePercent === '-0.0%') {
|
||||
responsePercent = '0.0%';
|
||||
}
|
||||
|
||||
// 根据用户要求:数量下降显示红色箭头,上升显示绿色箭头
|
||||
// 对于响应时间,数值增加表示性能下降,数值减少表示性能提升
|
||||
// 但根据用户要求,我们只根据数值变化方向来设置颜色
|
||||
if (changePercent > 0) {
|
||||
trendIcon = '↓';
|
||||
trendClass = 'text-danger';
|
||||
} else if (changePercent < 0) {
|
||||
// 响应时间增加(性能下降),数值上升
|
||||
trendIcon = '↑';
|
||||
trendClass = 'text-success';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-arrow-up mr-1';
|
||||
parent.className = 'text-success text-sm flex items-center';
|
||||
}
|
||||
} else if (changePercent < 0) {
|
||||
// 响应时间减少(性能提升),数值下降
|
||||
trendIcon = '↓';
|
||||
trendClass = 'text-danger';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-arrow-down mr-1';
|
||||
parent.className = 'text-danger text-sm flex items-center';
|
||||
}
|
||||
} else {
|
||||
// 趋势为0时,显示圆点图标
|
||||
trendIcon = '•';
|
||||
trendClass = 'text-gray-500';
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-circle mr-1 text-xs';
|
||||
parent.className = 'text-gray-500 text-sm flex items-center';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +770,6 @@ async function loadDashboardData() {
|
||||
}
|
||||
|
||||
document.getElementById('avg-response-time').textContent = responseTime;
|
||||
const responseTimePercentElem = document.getElementById('response-time-percent');
|
||||
if (responseTimePercentElem) {
|
||||
responseTimePercentElem.textContent = trendIcon + ' ' + responsePercent;
|
||||
responseTimePercentElem.className = `text-sm flex items-center ${trendClass}`;
|
||||
@@ -697,7 +803,19 @@ async function loadDashboardData() {
|
||||
// 计算活跃IP趋势
|
||||
let ipsPercent = '---';
|
||||
let trendClass = 'text-gray-400';
|
||||
let trendIcon = '---';
|
||||
let trendIcon = '•';
|
||||
|
||||
// 查找箭头元素
|
||||
const activeIpsPercentElem = document.getElementById('active-ips-percent');
|
||||
let parent = null;
|
||||
let arrowIcon = null;
|
||||
|
||||
if (activeIpsPercentElem) {
|
||||
parent = activeIpsPercentElem.parentElement;
|
||||
if (parent) {
|
||||
arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down');
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.activeIPs !== undefined && stats.activeIPs !== null) {
|
||||
// 存储当前值用于下次计算趋势
|
||||
@@ -713,9 +831,21 @@ async function loadDashboardData() {
|
||||
if (changePercent > 0) {
|
||||
trendIcon = '↑';
|
||||
trendClass = 'text-success';
|
||||
|
||||
// 更新箭头图标和颜色
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-arrow-up mr-1';
|
||||
parent.className = 'text-success text-sm flex items-center';
|
||||
}
|
||||
} else if (changePercent < 0) {
|
||||
trendIcon = '↓';
|
||||
trendClass = 'text-danger';
|
||||
|
||||
// 更新箭头图标和颜色
|
||||
if (arrowIcon) {
|
||||
arrowIcon.className = 'fa fa-arrow-down mr-1';
|
||||
parent.className = 'text-danger text-sm flex items-center';
|
||||
}
|
||||
} else {
|
||||
trendIcon = '•';
|
||||
trendClass = 'text-gray-500';
|
||||
@@ -724,7 +854,6 @@ async function loadDashboardData() {
|
||||
}
|
||||
|
||||
document.getElementById('active-ips').textContent = activeIPs;
|
||||
const activeIpsPercentElem = document.getElementById('active-ips-percent');
|
||||
if (activeIpsPercentElem) {
|
||||
activeIpsPercentElem.textContent = trendIcon + ' ' + ipsPercent;
|
||||
activeIpsPercentElem.className = `text-sm flex items-center ${trendClass}`;
|
||||
@@ -768,29 +897,51 @@ function updateStatsCards(stats) {
|
||||
console.log('更新统计卡片,收到数据:', stats);
|
||||
|
||||
// 适配不同的数据结构
|
||||
let totalQueries = 0, blockedQueries = 0, allowedQueries = 0, errorQueries = 0;
|
||||
// 保存当前显示的值,用于在数据缺失时保留
|
||||
let totalQueries, blockedQueries, allowedQueries, errorQueries;
|
||||
let topQueryType = 'A', queryTypePercentage = 0;
|
||||
let activeIPs = 0, activeIPsPercentage = 0;
|
||||
let activeIPs, activeIPsPercentage = 0;
|
||||
|
||||
// 优先从DOM中获取当前显示的值,作为默认值
|
||||
const totalQueriesElem = document.getElementById('total-queries');
|
||||
const blockedQueriesElem = document.getElementById('blocked-queries');
|
||||
const allowedQueriesElem = document.getElementById('allowed-queries');
|
||||
const errorQueriesElem = document.getElementById('error-queries');
|
||||
const activeIPsElem = document.getElementById('active-ips');
|
||||
|
||||
// 解析当前显示的值,作为默认值
|
||||
const getCurrentValue = (elem) => {
|
||||
if (!elem) return 0;
|
||||
const text = elem.textContent.replace(/,/g, '').replace(/[^0-9]/g, '');
|
||||
return parseInt(text) || 0;
|
||||
};
|
||||
|
||||
// 初始化默认值为当前显示的值
|
||||
totalQueries = getCurrentValue(totalQueriesElem);
|
||||
blockedQueries = getCurrentValue(blockedQueriesElem);
|
||||
allowedQueries = getCurrentValue(allowedQueriesElem);
|
||||
errorQueries = getCurrentValue(errorQueriesElem);
|
||||
activeIPs = getCurrentValue(activeIPsElem);
|
||||
|
||||
// 检查数据结构,兼容可能的不同格式
|
||||
if (stats) {
|
||||
// 优先使用顶层字段
|
||||
totalQueries = stats.totalQueries || 0;
|
||||
blockedQueries = stats.blockedQueries || 0;
|
||||
allowedQueries = stats.allowedQueries || 0;
|
||||
errorQueries = stats.errorQueries || 0;
|
||||
topQueryType = stats.topQueryType || 'A';
|
||||
queryTypePercentage = stats.queryTypePercentage || 0;
|
||||
activeIPs = stats.activeIPs || 0;
|
||||
activeIPsPercentage = stats.activeIPsPercentage || 0;
|
||||
// 优先使用顶层字段,只有当值存在时才更新
|
||||
if (stats.totalQueries !== undefined) totalQueries = stats.totalQueries;
|
||||
if (stats.blockedQueries !== undefined) blockedQueries = stats.blockedQueries;
|
||||
if (stats.allowedQueries !== undefined) allowedQueries = stats.allowedQueries;
|
||||
if (stats.errorQueries !== undefined) errorQueries = stats.errorQueries;
|
||||
if (stats.topQueryType !== undefined) topQueryType = stats.topQueryType;
|
||||
if (stats.queryTypePercentage !== undefined) queryTypePercentage = stats.queryTypePercentage;
|
||||
if (stats.activeIPs !== undefined) activeIPs = stats.activeIPs;
|
||||
if (stats.activeIPsPercentage !== undefined) activeIPsPercentage = stats.activeIPsPercentage;
|
||||
|
||||
|
||||
// 如果dns对象存在,优先使用其中的数据
|
||||
if (stats.dns) {
|
||||
totalQueries = stats.dns.Queries || totalQueries;
|
||||
blockedQueries = stats.dns.Blocked || blockedQueries;
|
||||
allowedQueries = stats.dns.Allowed || allowedQueries;
|
||||
errorQueries = stats.dns.Errors || errorQueries;
|
||||
if (stats.dns.Queries !== undefined) totalQueries = stats.dns.Queries;
|
||||
if (stats.dns.Blocked !== undefined) blockedQueries = stats.dns.Blocked;
|
||||
if (stats.dns.Allowed !== undefined) allowedQueries = stats.dns.Allowed;
|
||||
if (stats.dns.Errors !== undefined) errorQueries = stats.dns.Errors;
|
||||
|
||||
// 计算最常用查询类型的百分比
|
||||
if (stats.dns.QueryTypes && stats.dns.Queries > 0) {
|
||||
@@ -805,14 +956,14 @@ function updateStatsCards(stats) {
|
||||
}
|
||||
} else if (Array.isArray(stats) && stats.length > 0) {
|
||||
// 可能的数据结构3: 数组形式
|
||||
totalQueries = stats[0].total || 0;
|
||||
blockedQueries = stats[0].blocked || 0;
|
||||
allowedQueries = stats[0].allowed || 0;
|
||||
errorQueries = stats[0].error || 0;
|
||||
topQueryType = stats[0].topQueryType || 'A';
|
||||
queryTypePercentage = stats[0].queryTypePercentage || 0;
|
||||
activeIPs = stats[0].activeIPs || 0;
|
||||
activeIPsPercentage = stats[0].activeIPsPercentage || 0;
|
||||
if (stats[0].total !== undefined) totalQueries = stats[0].total;
|
||||
if (stats[0].blocked !== undefined) blockedQueries = stats[0].blocked;
|
||||
if (stats[0].allowed !== undefined) allowedQueries = stats[0].allowed;
|
||||
if (stats[0].error !== undefined) errorQueries = stats[0].error;
|
||||
if (stats[0].topQueryType !== undefined) topQueryType = stats[0].topQueryType;
|
||||
if (stats[0].queryTypePercentage !== undefined) queryTypePercentage = stats[0].queryTypePercentage;
|
||||
if (stats[0].activeIPs !== undefined) activeIPs = stats[0].activeIPs;
|
||||
if (stats[0].activeIPsPercentage !== undefined) activeIPsPercentage = stats[0].activeIPsPercentage;
|
||||
}
|
||||
|
||||
// 存储正在进行的动画状态,避免动画重叠
|
||||
@@ -1040,23 +1191,33 @@ function updateStatsCards(stats) {
|
||||
animateValue('active-ips', activeIPs);
|
||||
|
||||
// DNSSEC相关数据
|
||||
let dnssecEnabled = false, dnssecQueries = 0, dnssecSuccess = 0, dnssecFailed = 0, dnssecUsage = 0;
|
||||
// 优先从DOM中获取当前显示的值,作为默认值
|
||||
const dnssecSuccessElem = document.getElementById('dnssec-success');
|
||||
const dnssecFailedElem = document.getElementById('dnssec-failed');
|
||||
const dnssecQueriesElem = document.getElementById('dnssec-queries');
|
||||
|
||||
// 从当前显示值初始化,确保数据刷新前保留前一次结果
|
||||
let dnssecEnabled = false;
|
||||
let dnssecQueries = getCurrentValue(dnssecQueriesElem);
|
||||
let dnssecSuccess = getCurrentValue(dnssecSuccessElem);
|
||||
let dnssecFailed = getCurrentValue(dnssecFailedElem);
|
||||
let dnssecUsage = 0;
|
||||
|
||||
// 检查DNSSEC数据
|
||||
if (stats) {
|
||||
// 优先使用顶层字段
|
||||
dnssecEnabled = stats.dnssecEnabled || false;
|
||||
dnssecQueries = stats.dnssecQueries || 0;
|
||||
dnssecSuccess = stats.dnssecSuccess || 0;
|
||||
dnssecFailed = stats.dnssecFailed || 0;
|
||||
dnssecUsage = stats.dnssecUsage || 0;
|
||||
// 优先使用顶层字段,只有当值存在时才更新
|
||||
if (stats.dnssecEnabled !== undefined) dnssecEnabled = stats.dnssecEnabled;
|
||||
if (stats.dnssecQueries !== undefined) dnssecQueries = stats.dnssecQueries;
|
||||
if (stats.dnssecSuccess !== undefined) dnssecSuccess = stats.dnssecSuccess;
|
||||
if (stats.dnssecFailed !== undefined) dnssecFailed = stats.dnssecFailed;
|
||||
if (stats.dnssecUsage !== undefined) dnssecUsage = stats.dnssecUsage;
|
||||
|
||||
// 如果dns对象存在,优先使用其中的数据
|
||||
if (stats.dns) {
|
||||
dnssecEnabled = stats.dns.DNSSECEnabled || dnssecEnabled;
|
||||
dnssecQueries = stats.dns.DNSSECQueries || dnssecQueries;
|
||||
dnssecSuccess = stats.dns.DNSSECSuccess || dnssecSuccess;
|
||||
dnssecFailed = stats.dns.DNSSECFailed || dnssecFailed;
|
||||
if (stats.dns.DNSSECEnabled !== undefined) dnssecEnabled = stats.dns.DNSSECEnabled;
|
||||
if (stats.dns.DNSSECQueries !== undefined) dnssecQueries = stats.dns.DNSSECQueries;
|
||||
if (stats.dns.DNSSECSuccess !== undefined) dnssecSuccess = stats.dns.DNSSECSuccess;
|
||||
if (stats.dns.DNSSECFailed !== undefined) dnssecFailed = stats.dns.DNSSECFailed;
|
||||
}
|
||||
|
||||
// 如果没有直接提供使用率,计算使用率
|
||||
@@ -1102,12 +1263,66 @@ function updateStatsCards(stats) {
|
||||
if (queryTypePercentageElement) queryTypePercentageElement.textContent = `${Math.round(queryTypePercentage)}%`;
|
||||
if (activeIpsPercentElement) activeIpsPercentElement.textContent = `${Math.round(activeIPsPercentage)}%`;
|
||||
|
||||
// 计算并平滑更新百分比
|
||||
// 计算并平滑更新百分比,同时更新箭头颜色和方向
|
||||
function updatePercentWithArrow(elementId, percentage, prevValue, currentValue) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
// 更新百分比数值
|
||||
updatePercentage(elementId, percentage);
|
||||
|
||||
// 查找父元素,获取箭头图标
|
||||
const parent = element.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
let arrowIcon = parent.querySelector('.fa-arrow-up, .fa-arrow-down, .fa-circle');
|
||||
if (!arrowIcon) return;
|
||||
|
||||
// 计算变化趋势
|
||||
let isIncrease = currentValue > prevValue;
|
||||
let isDecrease = currentValue < prevValue;
|
||||
let isNoChange = currentValue === prevValue;
|
||||
|
||||
// 处理百分比显示,避免-0.0%的情况
|
||||
let formattedPercentage = percentage;
|
||||
if (percentage === '-0.0%') {
|
||||
formattedPercentage = '0.0%';
|
||||
updatePercentage(elementId, formattedPercentage);
|
||||
}
|
||||
|
||||
// 更新箭头图标和颜色
|
||||
if (isIncrease) {
|
||||
arrowIcon.className = 'fa fa-arrow-up mr-1';
|
||||
parent.className = 'text-success text-sm flex items-center';
|
||||
} else if (isDecrease) {
|
||||
arrowIcon.className = 'fa fa-arrow-down mr-1';
|
||||
parent.className = 'text-danger text-sm flex items-center';
|
||||
} else if (isNoChange) {
|
||||
// 趋势为0时,显示圆点图标
|
||||
arrowIcon.className = 'fa fa-circle mr-1 text-xs';
|
||||
parent.className = 'text-gray-500 text-sm flex items-center';
|
||||
}
|
||||
}
|
||||
|
||||
// 保存历史数据,用于计算趋势
|
||||
window.dashboardHistoryData = window.dashboardHistoryData || {
|
||||
totalQueries: 0,
|
||||
blockedQueries: 0,
|
||||
allowedQueries: 0,
|
||||
errorQueries: 0
|
||||
};
|
||||
|
||||
// 计算百分比并更新箭头
|
||||
if (totalQueries > 0) {
|
||||
updatePercentage('blocked-percent', `${Math.round((blockedQueries / totalQueries) * 100)}%`);
|
||||
updatePercentage('allowed-percent', `${Math.round((allowedQueries / totalQueries) * 100)}%`);
|
||||
updatePercentage('error-percent', `${Math.round((errorQueries / totalQueries) * 100)}%`);
|
||||
updatePercentage('queries-percent', '100%');
|
||||
const queriesPercent = '100%';
|
||||
const blockedPercent = `${Math.round((blockedQueries / totalQueries) * 100)}%`;
|
||||
const allowedPercent = `${Math.round((allowedQueries / totalQueries) * 100)}%`;
|
||||
const errorPercent = `${Math.round((errorQueries / totalQueries) * 100)}%`;
|
||||
|
||||
updatePercentWithArrow('queries-percent', queriesPercent, window.dashboardHistoryData.totalQueries, totalQueries);
|
||||
updatePercentWithArrow('blocked-percent', blockedPercent, window.dashboardHistoryData.blockedQueries, blockedQueries);
|
||||
updatePercentWithArrow('allowed-percent', allowedPercent, window.dashboardHistoryData.allowedQueries, allowedQueries);
|
||||
updatePercentWithArrow('error-percent', errorPercent, window.dashboardHistoryData.errorQueries, errorQueries);
|
||||
} else {
|
||||
updatePercentage('queries-percent', '---');
|
||||
updatePercentage('blocked-percent', '---');
|
||||
@@ -1115,6 +1330,12 @@ function updateStatsCards(stats) {
|
||||
updatePercentage('error-percent', '---');
|
||||
}
|
||||
|
||||
// 更新历史数据
|
||||
window.dashboardHistoryData.totalQueries = totalQueries;
|
||||
window.dashboardHistoryData.blockedQueries = blockedQueries;
|
||||
window.dashboardHistoryData.allowedQueries = allowedQueries;
|
||||
window.dashboardHistoryData.errorQueries = errorQueries;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -280,45 +280,45 @@ async function getDomainInfo(domain) {
|
||||
// 如果有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
|
||||
};
|
||||
}
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
// 处理对象类型的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);
|
||||
console.log(' 检查嵌套网站/类别:', nestedWebsiteKey);
|
||||
const nestedWebsite = website[nestedWebsiteKey];
|
||||
|
||||
if (nestedWebsite.url) {
|
||||
@@ -356,8 +356,54 @@ 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,
|
||||
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属性');
|
||||
console.log(' 嵌套网站没有URL属性且不是对象类型');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1891,7 +1937,7 @@ async function showLogDetailModal(log) {
|
||||
<span class="flex-grow">${domainInfo.categoryName || '未知'}</span>
|
||||
</div>
|
||||
<div class="flex items-center flex-wrap">
|
||||
<span class="text-gray-500 mr-2">所属单位:</span>
|
||||
<span class="text-gray-500 mr-2">所属单位/公司:</span>
|
||||
<span class="flex-grow">${domainInfo.company || '未知'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user