多项更新优化
This commit is contained in:
@@ -1122,7 +1122,23 @@ tr:hover {
|
||||
font-weight: 600;
|
||||
color: #2d3748;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 搜索框样式优化 */
|
||||
#logs-search {
|
||||
/* 确保搜索框在所有设备上都有合适的宽度 */
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 在移动设备上进一步优化搜索框 */
|
||||
@media (max-width: 768px) {
|
||||
/* 确保搜索框在移动设备上占满宽度 */
|
||||
#logs-search {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 浮窗内容项 */
|
||||
|
||||
@@ -18,9 +18,67 @@
|
||||
<body class="bg-gray-50 text-dark font-sans">
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<!-- 侧边栏 -->
|
||||
<aside id="sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 md:relative md:translate-x-0 -translate-x-full shadow-lg">
|
||||
<aside id="sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 hidden md:flex">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center justify-center h-16 border-b border-gray-200">
|
||||
<i class="fa fa-server text-3xl text-primary mr-3"></i>
|
||||
<h1 class="text-xl font-bold text-primary">DNS 控制台</h1>
|
||||
</div>
|
||||
|
||||
<!-- 菜单 -->
|
||||
<nav class="flex-1 overflow-y-auto p-4">
|
||||
<ul class="space-y-1">
|
||||
<li>
|
||||
<a href="#dashboard" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all sidebar-item-active">
|
||||
<i class="fa fa-tachometer mr-3 text-lg"></i>
|
||||
<span>仪表盘</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#shield" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-shield mr-3 text-lg"></i>
|
||||
<span>屏蔽管理</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#hosts" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-file-text mr-3 text-lg"></i>
|
||||
<span>Hosts管理</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="#query" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-search mr-3 text-lg"></i>
|
||||
<span>DNS屏蔽查询</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#logs" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-file-text-o mr-3 text-lg"></i>
|
||||
<span>查询日志</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#config" class="flex items-center px-4 py-3 text-gray-700 hover:bg-gray-100 rounded-md transition-all">
|
||||
<i class="fa fa-cog mr-3 text-lg"></i>
|
||||
<span>系统设置</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<div class="p-4 border-t border-gray-200 text-center text-gray-500 text-sm">
|
||||
<p>DNS服务器 v1.2.0</p>
|
||||
<p class="mt-1" id="uptime">正常运行中</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 移动端侧边栏 -->
|
||||
<aside id="mobile-sidebar" class="fixed inset-y-0 left-0 w-64 bg-white border-r border-gray-200 flex flex-col transition-transform duration-300 z-50 -translate-x-full md:hidden">
|
||||
<!-- 移动端关闭按钮 -->
|
||||
<div class="absolute top-4 right-4 md:hidden">
|
||||
<div class="absolute top-4 right-4">
|
||||
<button id="close-sidebar" class="p-2 text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<i class="fa fa-times text-xl"></i>
|
||||
</button>
|
||||
@@ -85,9 +143,9 @@
|
||||
<div id="sidebar-overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<main class="flex-1 flex flex-col md:ml-64">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6">
|
||||
<header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-6 sticky top-0 z-30">
|
||||
<div class="flex items-center">
|
||||
<button id="toggle-sidebar" class="block md:hidden text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<i class="fa fa-bars text-xl"></i>
|
||||
@@ -96,55 +154,6 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 服务器状态组件 -->
|
||||
<div class="relative bg-white rounded-lg shadow-md px-3 py-2 flex items-center space-x-2 server-status-widget md:min-w-[300px] sm:min-w-[250px] min-w-[180px]" id="server-status-widget">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">CPU</span>
|
||||
<span id="server-cpu-value" class="ml-2 text-sm font-semibold">0%</span>
|
||||
</div>
|
||||
<div class="w-16 h-1 bg-gray-100 rounded-full mt-1">
|
||||
<div id="server-cpu-bar" class="h-full bg-warning rounded-full" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">查询</span>
|
||||
<span id="server-queries-value" class="ml-2 text-sm font-semibold">0</span>
|
||||
</div>
|
||||
<div class="w-16 h-1 bg-gray-100 rounded-full mt-1">
|
||||
<div id="server-queries-bar" class="h-full bg-primary rounded-full" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 额外指标区域 - 初始隐藏,只在非首页显示 -->
|
||||
<div id="server-additional-stats" class="hidden md:flex items-center">
|
||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">总量</span>
|
||||
<span id="server-total-queries" class="ml-2 text-sm font-semibold">0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">屏蔽</span>
|
||||
<span id="server-blocked-queries" class="ml-2 text-sm font-semibold">0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1 h-8 bg-gray-200 rounded-full mx-1"></div>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs font-medium text-gray-500">正常</span>
|
||||
<span id="server-allowed-queries" class="ml-2 text-sm font-semibold">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-1 right-1">
|
||||
<span id="server-status-indicator" class="inline-block w-2 h-2 bg-success rounded-full"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
|
||||
<i class="fa fa-bell text-lg"></i>
|
||||
@@ -170,7 +179,7 @@
|
||||
</header>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<div class="p-6">
|
||||
<div class="p-6 overflow-y-auto flex-1">
|
||||
<!-- 仪表盘部分 -->
|
||||
<div id="dashboard-content" class="space-y-6">
|
||||
<!-- 统计卡片 -->
|
||||
@@ -975,13 +984,12 @@
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">响应时间</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">屏蔽规则</th>
|
||||
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="logs-table-body">
|
||||
<tr>
|
||||
<td colspan="6" class="py-8 text-center text-gray-500 border-b border-gray-100">
|
||||
<td colspan="5" class="py-8 text-center text-gray-500 border-b border-gray-100">
|
||||
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
|
||||
<div>暂无查询日志</div>
|
||||
</td>
|
||||
@@ -1050,13 +1058,19 @@
|
||||
<input type="text" id="dns-upstream-servers" 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="8.8.8.8, 1.1.1.1">
|
||||
</div>
|
||||
<div>
|
||||
<label for="dns-stats-file" class="block text-sm font-medium text-gray-700 mb-1">统计文件路径</label>
|
||||
<input type="text" id="dns-stats-file" 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="./stats.json">
|
||||
<label for="dns-dnssec-upstream-servers" class="block text-sm font-medium text-gray-700 mb-1">DNSSEC上游DNS服务器 (逗号分隔)</label>
|
||||
<input type="text" id="dns-dnssec-upstream-servers" 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="8.8.8.8, 1.1.1.1">
|
||||
</div>
|
||||
<div>
|
||||
<label for="dns-save-interval" class="block text-sm font-medium text-gray-700 mb-1">保存间隔 (秒)</label>
|
||||
<input type="number" id="dns-save-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="300">
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox" id="dns-enable-ipv6" class="rounded text-primary focus:ring-primary">
|
||||
<span class="text-sm font-medium text-gray-700">启用IPv6解析(AAAA记录)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1074,15 +1088,6 @@
|
||||
<!-- 屏蔽配置 -->
|
||||
<div class="mb-8">
|
||||
<h4 class="text-md font-medium mb-4">屏蔽配置</h4>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="shield-local-rules-file" class="block text-sm font-medium text-gray-700 mb-1">自定义规则文件</label>
|
||||
<input type="text" id="shield-local-rules-file" 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="./rules.txt">
|
||||
</div>
|
||||
<div>
|
||||
<label for="shield-hosts-file" class="block text-sm font-medium text-gray-700 mb-1">Hosts文件</label>
|
||||
<input type="text" id="shield-hosts-file" 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="/etc/hosts">
|
||||
</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">
|
||||
@@ -1117,15 +1122,29 @@
|
||||
</div>
|
||||
|
||||
<!-- 修改密码模态框 -->
|
||||
<!-- 日志详情模态框 -->
|
||||
<div id="log-detail-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
|
||||
<div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-semibold">日志详情</h3>
|
||||
<button id="close-log-modal-btn" class="text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<i class="fa fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="log-detail-content">
|
||||
<!-- 日志详情内容将通过JS动态填充 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="change-password-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
|
||||
<div class="bg-white rounded-lg shadow-xl w-full max-w-md p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-semibold">修改密码</h3>
|
||||
<button id="close-modal-btn" class="text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<i class="fa fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="change-password-form">
|
||||
<div class="mb-4">
|
||||
<label for="current-password" class="block text-sm font-medium text-gray-700 mb-1">当前密码</label>
|
||||
@@ -1155,7 +1174,6 @@
|
||||
<script src="js/main.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/dashboard.js"></script>
|
||||
<script src="js/server-status.js"></script>
|
||||
<script src="js/shield.js"></script>
|
||||
<script src="js/hosts.js"></script>
|
||||
<script src="js/query.js"></script>
|
||||
|
||||
@@ -61,18 +61,20 @@ function populateConfigForm(config) {
|
||||
// DNS配置 - 使用函数安全设置值,避免 || 操作符可能的错误处理
|
||||
setElementValue('dns-port', getSafeValue(dnsServerConfig.Port, 53));
|
||||
setElementValue('dns-upstream-servers', getSafeArray(dnsServerConfig.UpstreamServers).join(', '));
|
||||
setElementValue('dns-dnssec-upstream-servers', getSafeArray(dnsServerConfig.DNSSECUpstreamServers).join(', '));
|
||||
setElementValue('dns-timeout', getSafeValue(dnsServerConfig.Timeout, 5));
|
||||
setElementValue('dns-stats-file', getSafeValue(dnsServerConfig.StatsFile, 'data/stats.json'));
|
||||
setElementValue('dns-save-interval', getSafeValue(dnsServerConfig.SaveInterval, 300));
|
||||
|
||||
//setElementValue('dns-stats-file', getSafeValue(dnsServerConfig.StatsFile, 'data/stats.json'));
|
||||
setElementValue('dns-save-interval', getSafeValue(dnsServerConfig.SaveInterval, 30));
|
||||
//setElementValue('dns-cache-ttl', getSafeValue(dnsServerConfig.CacheTTL, 10));
|
||||
setElementValue('dns-enable-ipv6', getSafeValue(dnsServerConfig.EnableIPv6, false));
|
||||
// HTTP配置
|
||||
setElementValue('http-port', getSafeValue(httpServerConfig.Port, 8080));
|
||||
|
||||
// 屏蔽配置
|
||||
setElementValue('shield-local-rules-file', getSafeValue(shieldConfig.LocalRulesFile, 'data/rules.txt'));
|
||||
//setElementValue('shield-local-rules-file', getSafeValue(shieldConfig.LocalRulesFile, 'data/rules.txt'));
|
||||
setElementValue('shield-update-interval', getSafeValue(shieldConfig.UpdateInterval, 3600));
|
||||
setElementValue('shield-hosts-file', getSafeValue(shieldConfig.HostsFile, 'data/hosts.txt'));
|
||||
// 使用服务器端接受的屏蔽方法值,默认使用NXDOMAIN
|
||||
//setElementValue('shield-hosts-file', getSafeValue(shieldConfig.HostsFile, 'data/hosts.txt'));
|
||||
// 使用服务器端接受的屏蔽方法值,默认使用NXDOMAIN, 可选值: NXDOMAIN, NULL, REFUSED
|
||||
setElementValue('shield-block-method', getSafeValue(shieldConfig.BlockMethod, 'NXDOMAIN'));
|
||||
}
|
||||
|
||||
@@ -80,7 +82,11 @@ function populateConfigForm(config) {
|
||||
function setElementValue(elementId, value) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element && element.tagName === 'INPUT') {
|
||||
element.value = value;
|
||||
if (element.type === 'checkbox') {
|
||||
element.checked = value;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
} else if (!element) {
|
||||
console.warn(`Element with id "${elementId}" not found for setting value: ${value}`);
|
||||
}
|
||||
@@ -163,6 +169,12 @@ function collectFormData() {
|
||||
upstreamServersText.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
|
||||
[];
|
||||
|
||||
// 安全获取DNSSEC上游服务器列表
|
||||
const dnssecUpstreamServersText = getElementValue('dns-dnssec-upstream-servers');
|
||||
const dnssecUpstreamServers = dnssecUpstreamServersText ?
|
||||
dnssecUpstreamServersText.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
|
||||
[];
|
||||
|
||||
// 安全获取并转换整数值
|
||||
const timeoutValue = getElementValue('dns-timeout');
|
||||
const timeout = timeoutValue ? parseInt(timeoutValue, 10) : 5;
|
||||
@@ -174,21 +186,20 @@ function collectFormData() {
|
||||
const updateInterval = updateIntervalValue ? parseInt(updateIntervalValue, 10) : 3600;
|
||||
|
||||
return {
|
||||
DNSServer: {
|
||||
Port: dnsPort,
|
||||
UpstreamServers: upstreamServers,
|
||||
Timeout: timeout,
|
||||
StatsFile: getElementValue('dns-stats-file') || './data/stats.json',
|
||||
SaveInterval: saveInterval
|
||||
dnsserver: {
|
||||
port: dnsPort,
|
||||
upstreamServers: upstreamServers,
|
||||
dnssecUpstreamServers: dnssecUpstreamServers,
|
||||
timeout: timeout,
|
||||
saveInterval: saveInterval,
|
||||
enableIPv6: getElementValue('dns-enable-ipv6')
|
||||
},
|
||||
HTTPServer: {
|
||||
Port: httpPort
|
||||
httpserver: {
|
||||
port: httpPort
|
||||
},
|
||||
Shield: {
|
||||
LocalRulesFile: getElementValue('shield-local-rules-file') || './data/rules.txt',
|
||||
UpdateInterval: updateInterval,
|
||||
HostsFile: getElementValue('shield-hosts-file') || './data/hosts.txt',
|
||||
BlockMethod: getElementValue('shield-block-method') || 'NXDOMAIN'
|
||||
shield: {
|
||||
updateInterval: updateInterval,
|
||||
blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN'
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -197,6 +208,9 @@ function collectFormData() {
|
||||
function getElementValue(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element && element.tagName === 'INPUT') {
|
||||
if (element.type === 'checkbox') {
|
||||
return element.checked;
|
||||
}
|
||||
return element.value;
|
||||
}
|
||||
return ''; // 默认返回空字符串
|
||||
|
||||
@@ -2964,33 +2964,10 @@ window.addEventListener('hashchange', handleHashChange);
|
||||
// 初始化hash路由 - 确保在页面加载时就能被调用
|
||||
initHashRoute();
|
||||
|
||||
// 侧边栏切换
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
sidebar.classList.toggle('-translate-x-full');
|
||||
}
|
||||
|
||||
// 响应式处理
|
||||
function handleResponsive() {
|
||||
const toggleBtn = document.getElementById('toggle-sidebar');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
|
||||
toggleBtn.addEventListener('click', toggleSidebar);
|
||||
|
||||
// 初始状态处理
|
||||
function updateSidebarState() {
|
||||
if (window.innerWidth < 1024) {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
} else {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
}
|
||||
}
|
||||
|
||||
updateSidebarState();
|
||||
|
||||
// 窗口大小改变时处理
|
||||
window.addEventListener('resize', () => {
|
||||
updateSidebarState();
|
||||
|
||||
// 更新所有图表大小
|
||||
if (dnsRequestsChart) {
|
||||
|
||||
0
static/js/guide.js
Normal file
0
static/js/guide.js
Normal file
@@ -107,13 +107,25 @@ function initLogsPage() {
|
||||
// 绑定事件
|
||||
bindLogsEvents();
|
||||
|
||||
// 初始化日志详情弹窗
|
||||
initLogDetailModal();
|
||||
|
||||
// 建立WebSocket连接,用于实时更新统计数据和图表
|
||||
connectLogsWebSocket();
|
||||
|
||||
// 窗口大小改变时重新加载日志表格
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
|
||||
// 在页面卸载时清理资源
|
||||
window.addEventListener('beforeunload', cleanupLogsResources);
|
||||
}
|
||||
|
||||
// 处理窗口大小改变
|
||||
function handleWindowResize() {
|
||||
// 重新加载日志表格,以适应新的屏幕尺寸
|
||||
loadLogs();
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
function cleanupLogsResources() {
|
||||
// 清除WebSocket连接
|
||||
@@ -127,6 +139,9 @@ function cleanupLogsResources() {
|
||||
clearTimeout(logsWsReconnectTimer);
|
||||
logsWsReconnectTimer = null;
|
||||
}
|
||||
|
||||
// 清除窗口大小改变事件监听器
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
@@ -425,13 +440,16 @@ async function updateLogsTable(logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测是否为移动设备
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
// 填充表格
|
||||
for (const log of logs) {
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'border-b border-gray-100 hover:bg-gray-50 transition-colors';
|
||||
|
||||
// 格式化时间 - 两行显示,第一行显示时间,第二行显示日期
|
||||
const time = new Date(log.Timestamp);
|
||||
const time = new Date(log.timestamp);
|
||||
const formattedDate = time.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
@@ -445,13 +463,13 @@ async function updateLogsTable(logs) {
|
||||
|
||||
// 根据结果添加不同的背景色
|
||||
let rowClass = '';
|
||||
switch (log.Result) {
|
||||
switch (log.result) {
|
||||
case 'blocked':
|
||||
rowClass = 'bg-red-50'; // 淡红色填充
|
||||
break;
|
||||
case 'allowed':
|
||||
// 检查是否是规则允许项目
|
||||
if (log.BlockRule && log.BlockRule.includes('allow')) {
|
||||
if (log.blockRule && log.blockRule.includes('allow')) {
|
||||
rowClass = 'bg-green-50'; // 规则允许项目用淡绿色填充
|
||||
} else {
|
||||
rowClass = ''; // 允许的不填充
|
||||
@@ -469,7 +487,7 @@ async function updateLogsTable(logs) {
|
||||
// 添加被屏蔽或允许显示,并增加颜色
|
||||
let statusText = '';
|
||||
let statusClass = '';
|
||||
switch (log.Result) {
|
||||
switch (log.result) {
|
||||
case 'blocked':
|
||||
statusText = '被屏蔽';
|
||||
statusClass = 'text-danger';
|
||||
@@ -488,16 +506,16 @@ async function updateLogsTable(logs) {
|
||||
}
|
||||
|
||||
// 检查域名是否在跟踪器数据库中
|
||||
const trackerInfo = await isDomainInTrackerDatabase(log.Domain);
|
||||
const trackerInfo = await isDomainInTrackerDatabase(log.domain);
|
||||
const isTracker = trackerInfo !== null;
|
||||
|
||||
// 构建行内容 - 两行显示,时间列显示时间和日期,请求列显示域名和类型状态
|
||||
// 构建行内容 - 根据设备类型决定显示内容
|
||||
// 添加缓存状态显示
|
||||
const cacheStatusClass = log.FromCache ? 'text-primary' : 'text-gray-500';
|
||||
const cacheStatusText = log.FromCache ? '缓存' : '非缓存';
|
||||
const cacheStatusClass = log.fromCache ? 'text-primary' : 'text-gray-500';
|
||||
const cacheStatusText = log.fromCache ? '缓存' : '非缓存';
|
||||
|
||||
// 检查域名是否被拦截
|
||||
const isBlocked = log.Result === 'blocked';
|
||||
const isBlocked = log.result === 'blocked';
|
||||
|
||||
// 构建跟踪器浮窗内容
|
||||
const trackerTooltip = isTracker ? `
|
||||
@@ -510,36 +528,58 @@ async function updateLogsTable(logs) {
|
||||
</div>
|
||||
` : '';
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="py-3 px-4">
|
||||
<div class="text-sm font-medium">${formattedTime}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="font-medium">${log.ClientIP}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${log.Location || '未知 未知'}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="font-medium flex items-center relative">
|
||||
${log.DNSSEC ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
||||
<div class="tracker-icon-container relative">
|
||||
${isTracker ? '<i class="fa fa-eye text-red-500 mr-1"></i>' : '<i class="fa fa-eye-slash text-gray-300 mr-1"></i>'}
|
||||
${trackerTooltip}
|
||||
if (isMobile) {
|
||||
// 移动设备只显示时间和请求信息
|
||||
row.innerHTML = `
|
||||
<td class="py-3 px-4">
|
||||
<div class="text-sm font-medium">${formattedTime}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm" colspan="4">
|
||||
<div class="font-medium flex items-center relative">
|
||||
${log.dnssec ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
||||
<div class="tracker-icon-container relative">
|
||||
${isTracker ? '<i class="fa fa-eye text-red-500 mr-1"></i>' : '<i class="fa fa-eye-slash text-gray-300 mr-1"></i>'}
|
||||
${trackerTooltip}
|
||||
</div>
|
||||
${log.domain}
|
||||
</div>
|
||||
${log.Domain}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">类型: ${log.QueryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.FromCache ? '缓存' : '实时'}</span>${log.DNSSEC ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.EDNS ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">DNS 服务器: ${log.DNSServer || '无'}, DNSSEC专用: ${log.DNSSECServer || '无'}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">${log.ResponseTime}ms</td>
|
||||
<td class="py-3 px-4 text-sm text-gray-500">${log.BlockRule || '-'}</td>
|
||||
<td class="py-3 px-4 text-sm text-center">
|
||||
${isBlocked ?
|
||||
`<button class="unblock-btn px-3 py-1 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors text-xs" data-domain="${log.Domain}">放行</button>` :
|
||||
`<button class="block-btn px-3 py-1 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors text-xs" data-domain="${log.Domain}">拦截</button>`
|
||||
}
|
||||
</td>
|
||||
`;
|
||||
<div class="text-xs text-gray-500 mt-1">类型: ${log.queryType}, <span class="${statusClass}">${statusText}</span></div>
|
||||
<div class="text-xs text-gray-500 mt-1">客户端: ${log.clientIP}</div>
|
||||
</td>
|
||||
`;
|
||||
} else {
|
||||
// 桌面设备显示完整信息
|
||||
row.innerHTML = `
|
||||
<td class="py-3 px-4">
|
||||
<div class="text-sm font-medium">${formattedTime}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${formattedDate}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="font-medium">${log.clientIP}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${log.location || '未知 未知'}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="font-medium flex items-center relative">
|
||||
${log.dnssec ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>' : ''}
|
||||
<div class="tracker-icon-container relative">
|
||||
${isTracker ? '<i class="fa fa-eye text-red-500 mr-1"></i>' : '<i class="fa fa-eye-slash text-gray-300 mr-1"></i>'}
|
||||
${trackerTooltip}
|
||||
</div>
|
||||
${log.domain}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">类型: ${log.queryType}, <span class="${statusClass}">${statusText}</span>, <span class="${cacheStatusClass}">${log.fromCache ? '缓存' : '非缓存'}</span>${log.dnssec ? ', <span class="text-green-500"><i class="fa fa-lock"></i> DNSSEC</span>' : ''}${log.edns ? ', <span class="text-blue-500"><i class="fa fa-exchange"></i> EDNS</span>' : ''}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">DNS 服务器: ${log.dnsServer || '无'}, DNSSEC专用: ${log.dnssecServer || '无'}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">${log.responseTime}ms</td>
|
||||
<td class="py-3 px-4 text-sm text-center">
|
||||
${isBlocked ?
|
||||
`<button class="unblock-btn px-3 py-1 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors text-xs" data-domain="${log.domain}">放行</button>` :
|
||||
`<button class="block-btn px-3 py-1 bg-red-500 text-white rounded-md hover:bg-red-600 transition-colors text-xs" data-domain="${log.domain}">拦截</button>`
|
||||
}
|
||||
</td>
|
||||
`;
|
||||
}
|
||||
|
||||
// 添加跟踪器图标悬停事件
|
||||
if (isTracker) {
|
||||
@@ -576,6 +616,16 @@ async function updateLogsTable(logs) {
|
||||
unblockDomain(domain);
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定日志详情点击事件
|
||||
row.addEventListener('click', (e) => {
|
||||
// 如果点击的是按钮,不触发详情弹窗
|
||||
if (e.target.closest('button')) {
|
||||
return;
|
||||
}
|
||||
console.log('Row clicked, log object:', log);
|
||||
showLogDetailModal(log);
|
||||
});
|
||||
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
@@ -932,6 +982,467 @@ async function unblockDomain(domain) {
|
||||
}
|
||||
}
|
||||
|
||||
// 独立的DNS记录格式化函数
|
||||
function formatDNSRecords(log, result) {
|
||||
if (result === 'blocked') return '无';
|
||||
|
||||
let records = '';
|
||||
const sources = [
|
||||
log.answers,
|
||||
log.answer,
|
||||
log.Records,
|
||||
log.records,
|
||||
log.response
|
||||
];
|
||||
|
||||
for (const source of sources) {
|
||||
if (records) break;
|
||||
if (!source || source === '无') continue;
|
||||
|
||||
// 处理数组类型
|
||||
if (Array.isArray(source)) {
|
||||
records = source.map(answer => {
|
||||
const type = answer.type || answer.Type || '未知';
|
||||
let value = answer.value || answer.Value || answer.data || answer.Data || '未知';
|
||||
const ttl = answer.TTL || answer.ttl || answer.expires || '未知';
|
||||
|
||||
// 增强的记录值提取逻辑
|
||||
if (typeof value === 'string') {
|
||||
value = value.trim();
|
||||
// 处理制表符分隔的格式
|
||||
if (value.includes('\t') || value.includes('\\t')) {
|
||||
const parts = value.replace(/\\t/g, '\t').split('\t');
|
||||
if (parts.length >= 4) {
|
||||
value = parts[parts.length - 1].trim();
|
||||
}
|
||||
}
|
||||
// 处理JSON格式
|
||||
else if (value.startsWith('{') && value.endsWith('}')) {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
value = parsed.data || parsed.value || value;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
}
|
||||
// 处理字符串类型
|
||||
else if (typeof source === 'string') {
|
||||
// 尝试解析为JSON数组
|
||||
if (source.startsWith('[') && source.endsWith(']')) {
|
||||
try {
|
||||
const parsed = JSON.parse(source);
|
||||
if (Array.isArray(parsed)) {
|
||||
records = parsed.map(answer => {
|
||||
const type = answer.type || answer.Type || '未知';
|
||||
let value = answer.value || answer.Value || answer.data || answer.Data || '未知';
|
||||
const ttl = answer.TTL || answer.ttl || answer.expires || '未知';
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = value.trim();
|
||||
}
|
||||
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}).join('\n').trim();
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败,尝试直接格式化
|
||||
records = formatDNSString(source);
|
||||
}
|
||||
} else {
|
||||
// 直接格式化字符串
|
||||
records = formatDNSString(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return records || '无解析记录';
|
||||
}
|
||||
|
||||
// 格式化DNS字符串记录
|
||||
function formatDNSString(str) {
|
||||
// 处理可能的转义字符并分割行
|
||||
const recordLines = str.split(/\r?\n/).map(line => line.replace(/^\s+/, '')).filter(line => line.trim() !== '');
|
||||
|
||||
return recordLines.map(line => {
|
||||
// 检查是否已经是标准格式
|
||||
if (line.includes(':') && line.includes('(')) {
|
||||
return line;
|
||||
}
|
||||
// 尝试解析为标准DNS格式
|
||||
const parts = line.split(/\s+/);
|
||||
if (parts.length >= 5) {
|
||||
const type = parts[3];
|
||||
const value = parts.slice(4).join(' ');
|
||||
const ttl = parts[1];
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
}
|
||||
// 无法解析,返回原始行但移除前导空格
|
||||
return line.replace(/^\s+/, '');
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
// 显示日志详情弹窗
|
||||
async function showLogDetailModal(log) {
|
||||
console.log('showLogDetailModal called with log:', JSON.stringify(log, null, 2)); // 输出完整的log对象
|
||||
|
||||
if (!log) {
|
||||
console.error('No log data provided!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 安全获取log属性,提供默认值
|
||||
const timestamp = log.timestamp ? new Date(log.timestamp) : null;
|
||||
const dateStr = timestamp ? timestamp.toLocaleDateString() : '未知';
|
||||
const timeStr = timestamp ? timestamp.toLocaleTimeString() : '未知';
|
||||
const domain = log.domain || '未知';
|
||||
const queryType = log.queryType || '未知';
|
||||
const result = log.result || '未知';
|
||||
const responseTime = log.responseTime || '未知';
|
||||
const clientIP = log.clientIP || '未知';
|
||||
const location = log.location || '未知';
|
||||
const fromCache = log.fromCache || false;
|
||||
const dnssec = log.dnssec || false;
|
||||
const edns = log.edns || false;
|
||||
const dnsServer = log.dnsServer || '无';
|
||||
const dnssecServer = log.dnssecServer || '无';
|
||||
const blockRule = log.blockRule || '无';
|
||||
|
||||
// 检查域名是否在跟踪器数据库中
|
||||
const trackerInfo = await isDomainInTrackerDatabase(log.domain);
|
||||
const isTracker = trackerInfo !== null;
|
||||
|
||||
// 格式化DNS解析记录
|
||||
const dnsRecords = formatDNSRecords(log, result);
|
||||
|
||||
// 创建模态框容器
|
||||
const modalContainer = document.createElement('div');
|
||||
modalContainer.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4 animate-fade-in';
|
||||
modalContainer.style.zIndex = '9999';
|
||||
|
||||
// 创建模态框内容
|
||||
const modalContent = document.createElement('div');
|
||||
modalContent.className = 'bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto animate-slide-in';
|
||||
|
||||
// 创建标题栏
|
||||
const header = document.createElement('div');
|
||||
header.className = 'sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center';
|
||||
|
||||
const title = document.createElement('h3');
|
||||
title.className = 'text-xl font-semibold text-gray-900';
|
||||
title.textContent = '日志详情';
|
||||
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.innerHTML = '<i class="fa fa-times text-xl"></i>';
|
||||
closeButton.className = 'text-gray-500 hover:text-gray-700 focus:outline-none transition-colors';
|
||||
closeButton.onclick = () => closeModal();
|
||||
|
||||
header.appendChild(title);
|
||||
header.appendChild(closeButton);
|
||||
|
||||
// 创建内容区域
|
||||
const content = document.createElement('div');
|
||||
content.className = 'p-6 space-y-6';
|
||||
|
||||
// 基本信息部分
|
||||
const basicInfo = document.createElement('div');
|
||||
basicInfo.className = 'space-y-4';
|
||||
|
||||
const basicInfoTitle = document.createElement('h4');
|
||||
basicInfoTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider';
|
||||
basicInfoTitle.textContent = '基本信息';
|
||||
|
||||
const basicInfoGrid = document.createElement('div');
|
||||
basicInfoGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-4';
|
||||
|
||||
// 添加基本信息项
|
||||
basicInfoGrid.innerHTML = `
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">日期</div>
|
||||
<div class="text-sm font-medium text-gray-900">${dateStr}</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">时间</div>
|
||||
<div class="text-sm font-medium text-gray-900">${timeStr}</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">状态</div>
|
||||
<div class="text-sm font-medium ${result === 'blocked' ? 'text-red-600' : result === 'allowed' ? 'text-green-600' : 'text-gray-500'}">
|
||||
${result === 'blocked' ? '已拦截' : result === 'allowed' ? '允许' : result}
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">域名</div>
|
||||
<div class="text-sm font-medium text-gray-900 break-all">${domain}</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">类型</div>
|
||||
<div class="text-sm font-medium text-gray-900">${queryType}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// DNS特性
|
||||
const dnsFeatures = document.createElement('div');
|
||||
dnsFeatures.className = 'col-span-1 md:col-span-2 space-y-1';
|
||||
dnsFeatures.innerHTML = `
|
||||
<div class="text-xs text-gray-500">DNS特性</div>
|
||||
<div class="text-sm font-medium text-gray-900">
|
||||
${dnssec ? '<i class="fa fa-lock text-green-500 mr-1" title="DNSSEC已启用"></i>DNSSEC ' : ''}
|
||||
${edns ? '<i class="fa fa-exchange text-blue-500 mr-1" title="EDNS已启用"></i>EDNS' : ''}
|
||||
${!dnssec && !edns ? '无' : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 跟踪器信息
|
||||
const trackerDiv = document.createElement('div');
|
||||
trackerDiv.className = 'col-span-1 md:col-span-2 space-y-1';
|
||||
trackerDiv.innerHTML = `
|
||||
<div class="text-xs text-gray-500">跟踪器信息</div>
|
||||
<div class="text-sm font-medium text-gray-900">
|
||||
${isTracker ? `
|
||||
<div class="flex items-center">
|
||||
<i class="fa fa-eye text-red-500 mr-1"></i>
|
||||
<span>${trackerInfo.name} (${trackersDatabase.categories[trackerInfo.categoryId] || '未知'})</span>
|
||||
</div>
|
||||
` : '无'}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 解析记录
|
||||
const recordsDiv = document.createElement('div');
|
||||
recordsDiv.className = 'col-span-1 md:col-span-2 space-y-1';
|
||||
recordsDiv.innerHTML = `
|
||||
<div class="text-xs text-gray-500">解析记录</div>
|
||||
<div class="text-sm font-medium text-gray-900 whitespace-pre-wrap break-all bg-gray-50 p-3 rounded-md border border-gray-200">${dnsRecords}</div>
|
||||
`;
|
||||
|
||||
// DNS服务器
|
||||
const dnsServerDiv = document.createElement('div');
|
||||
dnsServerDiv.className = 'col-span-1 md:col-span-2 space-y-1';
|
||||
dnsServerDiv.innerHTML = `
|
||||
<div class="text-xs text-gray-500">DNS服务器</div>
|
||||
<div class="text-sm font-medium text-gray-900">${dnsServer}</div>
|
||||
`;
|
||||
|
||||
// DNSSEC专用服务器
|
||||
const dnssecServerDiv = document.createElement('div');
|
||||
dnssecServerDiv.className = 'col-span-1 md:col-span-2 space-y-1';
|
||||
dnssecServerDiv.innerHTML = `
|
||||
<div class="text-xs text-gray-500">DNSSEC专用服务器</div>
|
||||
<div class="text-sm font-medium text-gray-900">${dnssecServer}</div>
|
||||
`;
|
||||
|
||||
basicInfoGrid.appendChild(dnsFeatures);
|
||||
basicInfoGrid.appendChild(trackerDiv);
|
||||
basicInfoGrid.appendChild(recordsDiv);
|
||||
basicInfoGrid.appendChild(dnsServerDiv);
|
||||
basicInfoGrid.appendChild(dnssecServerDiv);
|
||||
|
||||
basicInfo.appendChild(basicInfoTitle);
|
||||
basicInfo.appendChild(basicInfoGrid);
|
||||
|
||||
// 响应细节部分
|
||||
const responseDetails = document.createElement('div');
|
||||
responseDetails.className = 'space-y-4 pt-4 border-t border-gray-200';
|
||||
|
||||
const responseDetailsTitle = document.createElement('h4');
|
||||
responseDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider';
|
||||
responseDetailsTitle.textContent = '响应细节';
|
||||
|
||||
// 准备响应细节内容,根据条件添加规则信息
|
||||
let responseDetailsHTML = `
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">响应时间</div>
|
||||
<div class="text-sm font-medium text-gray-900">${responseTime}毫秒</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">响应代码</div>
|
||||
<div class="text-sm font-medium text-gray-900">${getResponseCodeText(log.responseCode)}</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">缓存状态</div>
|
||||
<div class="text-sm font-medium ${fromCache ? 'text-primary' : 'text-gray-500'}">
|
||||
${fromCache ? '缓存' : '非缓存'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 只有被屏蔽或者有自定义规则时才显示规则信息
|
||||
if (result === 'blocked' || (blockRule && blockRule !== '无' && blockRule !== '-')) {
|
||||
responseDetailsHTML += `
|
||||
<div class="space-y-1">
|
||||
<div class="text-xs text-gray-500">规则</div>
|
||||
<div class="text-sm font-medium text-gray-900">${blockRule || '-'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const responseGrid = document.createElement('div');
|
||||
responseGrid.className = 'grid grid-cols-1 md:grid-cols-2 gap-4';
|
||||
responseGrid.innerHTML = responseDetailsHTML;
|
||||
|
||||
responseDetails.appendChild(responseDetailsTitle);
|
||||
responseDetails.appendChild(responseGrid);
|
||||
|
||||
// 客户端详情部分
|
||||
const clientDetails = document.createElement('div');
|
||||
clientDetails.className = 'space-y-4 pt-4 border-t border-gray-200';
|
||||
|
||||
const clientDetailsTitle = document.createElement('h4');
|
||||
clientDetailsTitle.className = 'text-sm font-medium text-gray-700 uppercase tracking-wider';
|
||||
clientDetailsTitle.textContent = '客户端详情';
|
||||
|
||||
const clientIPDiv = document.createElement('div');
|
||||
clientIPDiv.className = 'space-y-1';
|
||||
clientIPDiv.innerHTML = `
|
||||
<div class="text-xs text-gray-500">IP地址</div>
|
||||
<div class="text-sm font-medium text-gray-900">${clientIP} (${location})</div>
|
||||
`;
|
||||
|
||||
clientDetails.appendChild(clientDetailsTitle);
|
||||
clientDetails.appendChild(clientIPDiv);
|
||||
|
||||
// 组装内容
|
||||
content.appendChild(basicInfo);
|
||||
content.appendChild(responseDetails);
|
||||
content.appendChild(clientDetails);
|
||||
|
||||
// 组装模态框
|
||||
modalContent.appendChild(header);
|
||||
modalContent.appendChild(content);
|
||||
modalContainer.appendChild(modalContent);
|
||||
|
||||
// 添加到页面
|
||||
document.body.appendChild(modalContainer);
|
||||
|
||||
// 关闭模态框函数
|
||||
function closeModal() {
|
||||
modalContainer.classList.add('animate-fade-out');
|
||||
modalContent.classList.add('animate-slide-out');
|
||||
|
||||
// 等待动画结束后移除元素
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(modalContainer);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 点击外部关闭
|
||||
modalContainer.addEventListener('click', (e) => {
|
||||
if (e.target === modalContainer) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// ESC键关闭
|
||||
const handleEsc = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeModal();
|
||||
document.removeEventListener('keydown', handleEsc);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEsc);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in showLogDetailModal:', error);
|
||||
|
||||
// 显示错误提示
|
||||
const errorModal = document.createElement('div');
|
||||
errorModal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4 animate-fade-in';
|
||||
errorModal.style.zIndex = '9999';
|
||||
|
||||
const errorContent = document.createElement('div');
|
||||
errorContent.className = 'bg-white rounded-xl shadow-2xl p-6 w-full max-w-md animate-slide-in';
|
||||
|
||||
errorContent.innerHTML = `
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-semibold text-gray-900">错误</h3>
|
||||
<button onclick="closeErrorModal()" class="text-gray-500 hover:text-gray-700 focus:outline-none transition-colors">
|
||||
<i class="fa fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-red-600 text-sm">
|
||||
加载日志详情失败: ${error.message}
|
||||
</div>
|
||||
`;
|
||||
|
||||
errorModal.appendChild(errorContent);
|
||||
document.body.appendChild(errorModal);
|
||||
|
||||
// 关闭错误模态框函数
|
||||
function closeErrorModal() {
|
||||
errorModal.classList.add('animate-fade-out');
|
||||
errorContent.classList.add('animate-slide-out');
|
||||
|
||||
// 等待动画结束后移除元素
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(errorModal);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// ESC键关闭错误模态框
|
||||
const handleErrorEsc = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeErrorModal();
|
||||
document.removeEventListener('keydown', handleErrorEsc);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleErrorEsc);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭日志详情弹窗
|
||||
// 获取响应代码文本
|
||||
function getResponseCodeText(rcode) {
|
||||
const rcodeMap = {
|
||||
0: 'NOERROR',
|
||||
1: 'FORMERR',
|
||||
2: 'SERVFAIL',
|
||||
3: 'NXDOMAIN',
|
||||
4: 'NOTIMP',
|
||||
5: 'REFUSED',
|
||||
6: 'YXDOMAIN',
|
||||
7: 'YXRRSET',
|
||||
8: 'NXRRSET',
|
||||
9: 'NOTAUTH',
|
||||
10: 'NOTZONE'
|
||||
};
|
||||
return rcodeMap[rcode] || `UNKNOWN(${rcode})`;
|
||||
}
|
||||
|
||||
function closeLogDetailModal() {
|
||||
const modal = document.getElementById('log-detail-modal');
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 初始化日志详情弹窗事件
|
||||
function initLogDetailModal() {
|
||||
// 关闭按钮事件
|
||||
const closeBtn = document.getElementById('close-log-modal-btn');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', closeLogDetailModal);
|
||||
}
|
||||
|
||||
// 点击模态框外部关闭
|
||||
const modal = document.getElementById('log-detail-modal');
|
||||
if (modal) {
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
closeLogDetailModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ESC键关闭
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeLogDetailModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 定期更新日志统计数据(备用方案)
|
||||
setInterval(() => {
|
||||
// 只有在查询日志页面时才更新
|
||||
|
||||
@@ -28,7 +28,7 @@ function setupNavigation() {
|
||||
// 移动端侧边栏切换
|
||||
const toggleSidebar = document.getElementById('toggle-sidebar');
|
||||
const closeSidebarBtn = document.getElementById('close-sidebar');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebar = document.getElementById('mobile-sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
|
||||
// 打开侧边栏函数
|
||||
|
||||
Reference in New Issue
Block a user