更新web
@@ -946,7 +946,7 @@ tr:hover {
|
||||
/* 通知组件 */
|
||||
.notification {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
@@ -954,12 +954,7 @@ tr:hover {
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.notification.show {
|
||||
transform: translateX(0);
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
@@ -1141,8 +1136,9 @@ tr:hover {
|
||||
/* 跟踪器浮窗样式 */
|
||||
.tracker-tooltip {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
transform: translateY(-50%);
|
||||
margin-left: 10px;
|
||||
background-color: white;
|
||||
border: 1px solid #e2e8f0;
|
||||
@@ -1150,9 +1146,15 @@ tr:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: 12px;
|
||||
min-width: 250px;
|
||||
z-index: 50;
|
||||
max-width: 350px;
|
||||
z-index: 9999;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
|
||||
visibility: hidden;
|
||||
word-wrap: break-word;
|
||||
/* 添加箭头 */
|
||||
}
|
||||
|
||||
|
||||
BIN
static/images/gfwlist/amazon.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
static/images/gfwlist/bbc.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
static/images/gfwlist/discord.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
static/images/gfwlist/dropbox.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
static/images/gfwlist/google.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
static/images/gfwlist/mediawiki.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
static/images/gfwlist/microsoft.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
static/images/gfwlist/steam.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
static/images/gfwlist/telegram.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
static/images/gfwlist/tiktok.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
static/images/gfwlist/v2ex.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
static/images/gfwlist/wikimedia.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
static/images/gfwlist/yahoo.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
static/images/gfwlist/youtube.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
@@ -159,24 +159,24 @@
|
||||
<!-- 主内容区 -->
|
||||
<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 sticky top-0 z-30">
|
||||
<header class="bg-white border-b border-gray-200 h-16 flex items-center justify-between px-4 sm:px-6 sticky top-0 z-30 shadow-sm">
|
||||
<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>
|
||||
<button id="toggle-sidebar" class="block md:hidden text-gray-500 hover:text-gray-700 focus:outline-none p-1 sm:p-2 transition-all">
|
||||
<i class="fa fa-bars text-lg sm:text-xl"></i>
|
||||
</button>
|
||||
<h2 class="ml-4 text-xl font-semibold" id="page-title">仪表盘</h2>
|
||||
<h2 class="ml-2 sm:ml-4 text-base sm:text-lg md:text-xl font-semibold" id="page-title">仪表盘</h2>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex items-center space-x-2 sm:space-x-4">
|
||||
|
||||
<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>
|
||||
<button class="p-1.5 sm:p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100 transition-all">
|
||||
<i class="fa fa-bell text-sm sm:text-lg"></i>
|
||||
</button>
|
||||
<!-- 账户下拉菜单 -->
|
||||
<div class="relative group" id="account-dropdown">
|
||||
<button class="flex items-center p-2 rounded-full hover:bg-gray-100 transition-colors focus:outline-none">
|
||||
<img src="images/user.jpg" alt="用户头像" class="w-8 h-8 rounded-full">
|
||||
<span class="ml-2 hidden md:block">管理员</span>
|
||||
<button class="flex items-center p-1.5 sm:p-2 rounded-full hover:bg-gray-100 transition-colors focus:outline-none">
|
||||
<img src="images/user.jpg" alt="用户头像" class="w-7 h-7 sm:w-8 sm:h-8 rounded-full">
|
||||
<span class="ml-1 sm:ml-2 hidden md:block text-sm">管理员</span>
|
||||
<i class="fa fa-caret-down ml-1 text-xs hidden md:block"></i>
|
||||
</button>
|
||||
<!-- 下拉菜单 -->
|
||||
@@ -193,27 +193,27 @@
|
||||
</header>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<div class="p-6 overflow-y-auto flex-1">
|
||||
<div class="p-4 sm:p-6 overflow-y-auto flex-1">
|
||||
<!-- 仪表盘部分 -->
|
||||
<div id="dashboard-content" class="space-y-6">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4 gap-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 sm:gap-6">
|
||||
<!-- 查询总量卡片 -->
|
||||
<div class="bg-blue-50 rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="bg-blue-50 rounded-lg p-3 sm:p-4 card-shadow relative overflow-hidden transition-all hover:shadow-md">
|
||||
<!-- 颜色蒙版 -->
|
||||
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-primary opacity-10"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-20 h-20 sm:w-24 sm:h-24 rounded-full bg-primary opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-gray-500 font-medium">查询总量</h3>
|
||||
<div class="p-2 rounded-full bg-primary/10 text-primary">
|
||||
<i class="fa fa-refresh"></i>
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
||||
<h3 class="text-xs sm:text-sm text-gray-500 font-medium">查询总量</h3>
|
||||
<div class="p-1.5 sm:p-2 rounded-full bg-primary/10 text-primary">
|
||||
<i class="fa fa-refresh text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="total-queries">0</p>
|
||||
<span class="text-success text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1"></i>
|
||||
<p class="text-2xl sm:text-3xl font-bold" id="total-queries">0</p>
|
||||
<span class="text-success text-xs sm:text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1 text-xs"></i>
|
||||
<span id="queries-percent">0%</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -222,21 +222,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 屏蔽数量卡片 -->
|
||||
<div class="bg-red-50 rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="bg-red-50 rounded-lg p-3 sm:p-4 card-shadow relative overflow-hidden transition-all hover:shadow-md">
|
||||
<!-- 颜色蒙版 -->
|
||||
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-danger opacity-10"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-20 h-20 sm:w-24 sm:h-24 rounded-full bg-danger opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-gray-500 font-medium">屏蔽数量</h3>
|
||||
<div class="p-2 rounded-full bg-danger/10 text-danger">
|
||||
<i class="fa fa-ban"></i>
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
||||
<h3 class="text-xs sm:text-sm text-gray-500 font-medium">屏蔽数量</h3>
|
||||
<div class="p-1.5 sm:p-2 rounded-full bg-danger/10 text-danger">
|
||||
<i class="fa fa-ban text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="blocked-queries">0</p>
|
||||
<span class="text-danger text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1"></i>
|
||||
<p class="text-2xl sm:text-3xl font-bold" id="blocked-queries">0</p>
|
||||
<span class="text-danger text-xs sm:text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1 text-xs"></i>
|
||||
<span id="blocked-percent">0%</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -245,21 +245,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 正常解析卡片 -->
|
||||
<div class="bg-green-50 rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="bg-green-50 rounded-lg p-3 sm:p-4 card-shadow relative overflow-hidden transition-all hover:shadow-md">
|
||||
<!-- 颜色蒙版 -->
|
||||
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-success opacity-10"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-20 h-20 sm:w-24 sm:h-24 rounded-full bg-success opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-gray-500 font-medium">正常解析</h3>
|
||||
<div class="p-2 rounded-full bg-success/10 text-success">
|
||||
<i class="fa fa-check"></i>
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
||||
<h3 class="text-xs sm:text-sm text-gray-500 font-medium">正常解析</h3>
|
||||
<div class="p-1.5 sm:p-2 rounded-full bg-success/10 text-success">
|
||||
<i class="fa fa-check text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="allowed-queries">0</p>
|
||||
<span class="text-success text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1"></i>
|
||||
<p class="text-2xl sm:text-3xl font-bold" id="allowed-queries">0</p>
|
||||
<span class="text-success text-xs sm:text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1 text-xs"></i>
|
||||
<span id="allowed-percent">0%</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -268,21 +268,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 错误数量卡片 -->
|
||||
<div class="bg-yellow-50 rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="bg-yellow-50 rounded-lg p-3 sm:p-4 card-shadow relative overflow-hidden transition-all hover:shadow-md">
|
||||
<!-- 颜色蒙版 -->
|
||||
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-warning opacity-10"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-20 h-20 sm:w-24 sm:h-24 rounded-full bg-warning opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-gray-500 font-medium">错误数量</h3>
|
||||
<div class="p-2 rounded-full bg-warning/10 text-warning">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
||||
<h3 class="text-xs sm:text-sm text-gray-500 font-medium">错误数量</h3>
|
||||
<div class="p-1.5 sm:p-2 rounded-full bg-warning/10 text-warning">
|
||||
<i class="fa fa-exclamation-triangle text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="error-queries">0</p>
|
||||
<span class="text-warning text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1"></i>
|
||||
<p class="text-2xl sm:text-3xl font-bold" id="error-queries">0</p>
|
||||
<span class="text-warning text-xs sm:text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1 text-xs"></i>
|
||||
<span id="error-percent">0%</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -291,21 +291,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 平均响应时间卡片 -->
|
||||
<div class="bg-white rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 card-shadow relative overflow-hidden transition-all hover:shadow-md">
|
||||
<!-- 颜色蒙版 -->
|
||||
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-info opacity-10"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-20 h-20 sm:w-24 sm:h-24 rounded-full bg-info opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-gray-500 font-medium">平均响应时间</h3>
|
||||
<div class="p-2 rounded-full bg-info/10 text-info">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
||||
<h3 class="text-xs sm:text-sm text-gray-500 font-medium">平均响应时间</h3>
|
||||
<div class="p-1.5 sm:p-2 rounded-full bg-info/10 text-info">
|
||||
<i class="fa fa-clock-o text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="avg-response-time">0ms</p>
|
||||
<span class="text-success text-sm flex items-center">
|
||||
<i class="fa fa-arrow-down mr-1"></i>
|
||||
<p class="text-2xl sm:text-3xl font-bold" id="avg-response-time">0ms</p>
|
||||
<span class="text-success text-xs sm:text-sm flex items-center">
|
||||
<i class="fa fa-arrow-down mr-1 text-xs"></i>
|
||||
<span id="response-time-percent">0%</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -314,19 +314,19 @@
|
||||
</div>
|
||||
|
||||
<!-- 最常用查询类型卡片 -->
|
||||
<div class="bg-white rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 card-shadow relative overflow-hidden transition-all hover:shadow-md">
|
||||
<!-- 颜色蒙版 -->
|
||||
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-secondary opacity-10"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-20 h-20 sm:w-24 sm:h-24 rounded-full bg-secondary opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-gray-500 font-medium">最常用查询类型</h3>
|
||||
<div class="p-2 rounded-full bg-secondary/10 text-secondary">
|
||||
<i class="fa fa-database"></i>
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
||||
<h3 class="text-xs sm:text-sm text-gray-500 font-medium">最常用查询类型</h3>
|
||||
<div class="p-1.5 sm:p-2 rounded-full bg-secondary/10 text-secondary">
|
||||
<i class="fa fa-database text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="top-query-type">A</p>
|
||||
<span class="text-primary text-sm flex items-center">
|
||||
<p class="text-2xl sm:text-3xl font-bold" id="top-query-type">A</p>
|
||||
<span class="text-primary text-xs sm:text-sm flex items-center">
|
||||
<i class="fa fa-circle text-xs mr-1"></i>
|
||||
<span id="query-type-percentage">0%</span>
|
||||
</span>
|
||||
@@ -335,21 +335,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 活跃来源IP数卡片 -->
|
||||
<div class="bg-white rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 card-shadow relative overflow-hidden transition-all hover:shadow-md">
|
||||
<!-- 颜色蒙版 -->
|
||||
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-success opacity-10"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-20 h-20 sm:w-24 sm:h-24 rounded-full bg-success opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-gray-500 font-medium">活跃来源IP</h3>
|
||||
<div class="p-2 rounded-full bg-success/10 text-success">
|
||||
<i class="fa fa-globe"></i>
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
||||
<h3 class="text-xs sm:text-sm text-gray-500 font-medium">活跃来源IP</h3>
|
||||
<div class="p-1.5 sm:p-2 rounded-full bg-success/10 text-success">
|
||||
<i class="fa fa-globe text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="active-ips">0</p>
|
||||
<span class="text-success text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1"></i>
|
||||
<p class="text-2xl sm:text-3xl font-bold" id="active-ips">0</p>
|
||||
<span class="text-success text-xs sm:text-sm flex items-center">
|
||||
<i class="fa fa-arrow-up mr-1 text-xs"></i>
|
||||
<span id="active-ips-percent">0%</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -358,26 +358,26 @@
|
||||
</div>
|
||||
|
||||
<!-- DNSSEC使用率卡片 -->
|
||||
<div class="bg-white rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="bg-white rounded-lg p-3 sm:p-4 card-shadow relative overflow-hidden transition-all hover:shadow-md">
|
||||
<!-- 颜色蒙版 -->
|
||||
<div class="absolute -bottom-8 -right-8 w-24 h-24 rounded-full bg-primary opacity-10"></div>
|
||||
<div class="absolute -bottom-6 -right-6 w-20 h-20 sm:w-24 sm:h-24 rounded-full bg-primary opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-gray-500 font-medium">DNSSEC使用率</h3>
|
||||
<div class="p-2 rounded-full bg-primary/10 text-primary">
|
||||
<i class="fa fa-lock"></i>
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-4">
|
||||
<h3 class="text-xs sm:text-sm text-gray-500 font-medium">DNSSEC使用率</h3>
|
||||
<div class="p-1.5 sm:p-2 rounded-full bg-primary/10 text-primary">
|
||||
<i class="fa fa-lock text-xs sm:text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="dnssec-usage">0%</p>
|
||||
<span class="text-primary text-sm flex items-center">
|
||||
<i class="fa fa-check mr-1"></i>
|
||||
<p class="text-2xl sm:text-3xl font-bold" id="dnssec-usage">0%</p>
|
||||
<span class="text-primary text-xs sm:text-sm flex items-center">
|
||||
<i class="fa fa-check mr-1 text-xs"></i>
|
||||
<span id="dnssec-status">已禁用</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4 text-xs text-gray-500">
|
||||
<div class="flex items-center space-x-2 sm:space-x-4 text-xs sm:text-xs text-gray-500">
|
||||
<div class="flex items-center">
|
||||
<span class="w-2 h-2 bg-green-500 rounded-full mr-1"></span>
|
||||
<span>成功: <span id="dnssec-success">0</span></span>
|
||||
@@ -397,31 +397,31 @@
|
||||
</div>
|
||||
|
||||
<!-- 图表和数据表格 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
|
||||
<!-- 三个图表在同一行显示 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-1 md:col-span-1">
|
||||
<h3 class="text-lg font-semibold mb-6">解析与屏蔽比例</h3>
|
||||
<div class="h-64 flex items-center justify-center">
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-3 sm:mb-6">解析与屏蔽比例</h3>
|
||||
<div class="h-48 sm:h-64 flex items-center justify-center">
|
||||
<canvas id="ratio-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-1 md:col-span-1">
|
||||
<h3 class="text-lg font-semibold mb-6">解析类型统计</h3>
|
||||
<div class="h-64 flex items-center justify-center">
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-3 sm:mb-6">解析类型统计</h3>
|
||||
<div class="h-48 sm:h-64 flex items-center justify-center">
|
||||
<canvas id="query-type-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg p-6 card-shadow lg:col-span-1 md:col-span-1">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="text-lg font-semibold">DNS请求趋势</h3>
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<div class="flex items-center justify-between mb-3 sm:mb-6">
|
||||
<h3 class="text-base sm:text-lg font-semibold">DNS请求趋势</h3>
|
||||
<!-- 展开按钮 -->
|
||||
<button id="expand-chart-btn" class="p-2 rounded-full bg-primary/10 text-primary hover:bg-primary/20 transition-colors" title="展开详细图表">
|
||||
<i class="fa fa-expand"></i>
|
||||
<button id="expand-chart-btn" class="p-1.5 sm:p-2 rounded-full bg-primary/10 text-primary hover:bg-primary/20 transition-colors" title="展开详细图表">
|
||||
<i class="fa fa-expand text-xs sm:text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="h-64">
|
||||
<div class="h-48 sm:h-64">
|
||||
<canvas id="dns-requests-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -431,11 +431,10 @@
|
||||
<div id="chart-modal" class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 hidden">
|
||||
<div class="bg-white rounded-lg w-full max-w-5xl max-h-[90vh] overflow-hidden">
|
||||
<div class="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<h3 class="text-xl font-semibold">DNS请求趋势详细图表</h3>
|
||||
<h3 class="text-lg sm:text-xl font-semibold">DNS请求趋势详细图表</h3>
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 时间范围切换按钮 -->
|
||||
<div class="flex space-x-2">
|
||||
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="mixed">混合视图</button>
|
||||
<button class="time-range-btn px-4 py-2 rounded-md bg-primary text-white" data-range="24h">24小时</button>
|
||||
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="7d">7天</button>
|
||||
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="30d">30天</button>
|
||||
@@ -458,7 +457,7 @@
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- 被拦截域名排行 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-4">被拦截域名排行</h3>
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-3 sm:mb-4">被拦截域名排行</h3>
|
||||
<div class="h-64 overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent">
|
||||
<div class="space-y-3" id="top-blocked-table">
|
||||
<div class="flex items-center justify-between p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-danger">
|
||||
@@ -512,7 +511,7 @@
|
||||
|
||||
<!-- 请求域名排行 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-4">请求域名排行</h3>
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-3 sm:mb-4">请求域名排行</h3>
|
||||
<div class="h-64 overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-transparent">
|
||||
<div class="space-y-3" id="top-domains-table">
|
||||
<div class="flex items-center justify-between p-3 rounded-md hover:bg-gray-50 transition-colors border-l-4 border-success">
|
||||
@@ -534,7 +533,7 @@
|
||||
<!-- 客户端排行 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold">客户端排行</h3>
|
||||
<h3 class="text-base sm:text-lg font-semibold">客户端排行</h3>
|
||||
<div id="top-clients-loading" class="flex items-center text-sm text-gray-500">
|
||||
<i class="fa fa-spinner fa-spin mr-2"></i>
|
||||
<span>加载中...</span>
|
||||
@@ -602,7 +601,7 @@
|
||||
<div id="shield-content" class="hidden space-y-6">
|
||||
<!-- 屏蔽规则统计信息 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-6">屏蔽规则统计</h3>
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-4 sm:mb-6">屏蔽规则统计</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="bg-blue-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
@@ -654,17 +653,17 @@
|
||||
</div>
|
||||
|
||||
<!-- 自定义规则管理 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-6">自定义规则管理</h3>
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-4 sm:mb-6">自定义规则管理</h3>
|
||||
|
||||
<!-- 添加规则表单 -->
|
||||
<div id="add-rule-form" class="mb-6 bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center space-x-4">
|
||||
<input type="text" id="new-rule" placeholder="输入规则(例如:example.com 或 regex:/example\.com/)" class="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<button id="save-rule-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
|
||||
<div id="add-rule-form" class="mb-6 bg-gray-50 p-3 sm:p-4 rounded-lg">
|
||||
<div class="flex flex-col sm:flex-row items-stretch sm:items-center space-y-3 sm:space-y-0 sm:space-x-3">
|
||||
<input type="text" id="new-rule" placeholder="输入规则(例如:example.com 或 regex:/example\.com/)" class="flex-1 px-3 sm:px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
<button id="save-rule-btn" class="px-3 sm:px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors text-sm">
|
||||
保存
|
||||
</button>
|
||||
<div id="save-rule-status" class="flex items-center text-sm"></div>
|
||||
<div id="save-rule-status" class="flex items-center justify-center text-xs sm:text-sm mt-2 sm:mt-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -673,14 +672,14 @@
|
||||
<table class="min-w-full resizable-table">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200">
|
||||
<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>
|
||||
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">规则</th>
|
||||
<th class="text-center py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">状态</th>
|
||||
<th class="text-right py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rules-table-body">
|
||||
<tr>
|
||||
<td colspan="3" class="py-4 text-center text-gray-500">暂无规则</td>
|
||||
<td colspan="3" class="py-3 sm:py-4 text-center text-gray-500">暂无规则</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -688,27 +687,27 @@
|
||||
</div>
|
||||
|
||||
<!-- 远程黑名单管理 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-6">远程黑名单管理</h3>
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-4 sm:mb-6">远程黑名单管理</h3>
|
||||
|
||||
<!-- 添加黑名单表单 -->
|
||||
<div id="add-blacklist-form" class="mb-6 bg-gray-50 p-4 rounded-lg">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div id="add-blacklist-form" class="mb-6 bg-gray-50 p-3 sm:p-4 rounded-lg">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div>
|
||||
<label for="blacklist-name" class="block text-sm font-medium text-gray-700 mb-1">名称</label>
|
||||
<input type="text" id="blacklist-name" placeholder="输入黑名单名称" 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">
|
||||
<label for="blacklist-name" class="block text-xs sm:text-sm font-medium text-gray-700 mb-1">名称</label>
|
||||
<input type="text" id="blacklist-name" placeholder="输入黑名单名称" class="w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label for="blacklist-url" class="block text-sm font-medium text-gray-700 mb-1">URL</label>
|
||||
<input type="text" id="blacklist-url" placeholder="输入黑名单URL" 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">
|
||||
<label for="blacklist-url" class="block text-xs sm:text-sm font-medium text-gray-700 mb-1">URL</label>
|
||||
<input type="text" id="blacklist-url" placeholder="输入黑名单URL" class="w-full px-3 sm:px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button id="save-blacklist-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
|
||||
<div class="flex flex-col items-start justify-end space-y-2">
|
||||
<div class="flex items-center space-x-2 w-full">
|
||||
<button id="save-blacklist-btn" class="flex-1 sm:flex-none px-3 sm:px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors text-sm">
|
||||
保存
|
||||
</button>
|
||||
<div id="save-blacklist-status" class="flex items-center text-sm"></div>
|
||||
</div>
|
||||
<div id="save-blacklist-status" class="flex items-center justify-center text-xs sm:text-sm w-full mt-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -718,16 +717,16 @@
|
||||
<table class="min-w-full resizable-table">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200">
|
||||
<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">URL</th>
|
||||
<th class="text-center 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>
|
||||
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">名称</th>
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">URL</th>
|
||||
<th class="text-center py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">状态</th>
|
||||
<th class="text-center py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500"></th>
|
||||
<th class="text-right py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="blacklists-table-body">
|
||||
<tr>
|
||||
<td colspan="5" class="py-4 text-center text-gray-500">暂无黑名单</td>
|
||||
<td colspan="5" class="py-3 sm:py-4 text-center text-gray-500">暂无黑名单</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -739,15 +738,15 @@
|
||||
|
||||
<div id="hosts-content" class="hidden space-y-6">
|
||||
<!-- Hosts管理页面内容 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-6">Hosts条目管理</h3>
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-4 sm:mb-6">Hosts条目管理</h3>
|
||||
|
||||
<!-- 添加hosts条目表单 -->
|
||||
<div id="add-hosts-form" class="mb-6 bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center space-x-4">
|
||||
<input type="text" id="hosts-ip" placeholder="IP地址" class="w-32 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<input type="text" id="hosts-domain" placeholder="域名" class="flex-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<button id="save-hosts-btn" class="px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors">
|
||||
<div id="add-hosts-form" class="mb-6 bg-gray-50 p-3 sm:p-4 rounded-lg">
|
||||
<div class="flex flex-col sm:flex-row items-stretch sm:items-center space-y-3 sm:space-y-0 sm:space-x-3">
|
||||
<input type="text" id="hosts-ip" placeholder="IP地址" class="w-full sm:w-32 px-3 sm:px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
<input type="text" id="hosts-domain" placeholder="域名" class="flex-1 px-3 sm:px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
<button id="save-hosts-btn" class="px-3 sm:px-4 py-2 bg-success text-white rounded-md hover:bg-success/90 transition-colors text-sm">
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
@@ -758,14 +757,14 @@
|
||||
<table class="min-w-full resizable-table">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200">
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">IP地址</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">域名</th>
|
||||
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">IP地址</th>
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">域名</th>
|
||||
<th class="text-right py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="hosts-table-body">
|
||||
<tr>
|
||||
<td colspan="3" class="py-4 text-center text-gray-500">暂无Hosts条目</td>
|
||||
<td colspan="3" class="py-3 sm:py-4 text-center text-gray-500">暂无Hosts条目</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -777,23 +776,23 @@
|
||||
|
||||
<div id="query-content" class="hidden space-y-6">
|
||||
<!-- DNS查询表单 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<h3 class="text-lg font-semibold mb-6">DNS查询</h3>
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-4 sm:mb-6">DNS查询</h3>
|
||||
|
||||
<!-- 查询表单 -->
|
||||
<div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4">
|
||||
<div class="flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-3">
|
||||
<div class="flex-1">
|
||||
<input type="text" id="dns-query-domain" placeholder="输入域名(例如:example.com)" class="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<input type="text" id="dns-query-domain" placeholder="输入域名(例如:example.com)" class="w-full px-3 sm:px-4 py-2 sm:py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
</div>
|
||||
<button id="dns-query-btn" class="px-6 py-3 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
|
||||
<i class="fa fa-search mr-2"></i>查询
|
||||
<button id="dns-query-btn" class="px-4 sm:px-6 py-2 sm:py-3 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors text-sm sm:text-base">
|
||||
<i class="fa fa-search mr-1 sm:mr-2 text-xs sm:text-sm"></i>查询
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查询结果展示 -->
|
||||
<div id="query-result" class="bg-white rounded-lg p-6 card-shadow hidden">
|
||||
<h3 class="text-lg font-semibold mb-4">查询结果</h3>
|
||||
<div id="query-result" class="bg-white rounded-lg p-4 sm:p-6 card-shadow hidden">
|
||||
<h3 class="text-base sm:text-lg font-semibold mb-3 sm:mb-4">查询结果</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
@@ -832,7 +831,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 历史记录列表 -->
|
||||
<div id="query-history" class="space-y-3">
|
||||
<div id="query-history" class="space-y-2 sm:space-y-3">
|
||||
<div class="text-center text-gray-500 py-4">
|
||||
暂无查询历史
|
||||
</div>
|
||||
@@ -918,21 +917,21 @@
|
||||
</div>
|
||||
|
||||
<!-- 日志搜索和过滤 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4">
|
||||
<div class="flex-1">
|
||||
<input type="text" id="logs-search" placeholder="搜索域名或客户端IP" class="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<div class="sm:col-span-2">
|
||||
<input type="text" id="logs-search" placeholder="搜索域名或客户端IP" class="w-full px-3 sm:px-4 py-2 sm:py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
</div>
|
||||
<div class="w-32">
|
||||
<select id="logs-result-filter" class="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<div class="w-full">
|
||||
<select id="logs-result-filter" class="w-full px-3 sm:px-4 py-2 sm:py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
<option value="">全部结果</option>
|
||||
<option value="allowed">允许</option>
|
||||
<option value="blocked">屏蔽</option>
|
||||
<option value="error">错误</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-32">
|
||||
<select id="logs-per-page" class="w-full px-4 py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent">
|
||||
<div class="w-full">
|
||||
<select id="logs-per-page" class="w-full px-3 sm:px-4 py-2 sm:py-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent text-sm">
|
||||
<option value="10">10条/页</option>
|
||||
<option value="20">20条/页</option>
|
||||
<option value="30" selected>30条/页</option>
|
||||
@@ -940,16 +939,18 @@
|
||||
<option value="100">100条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="logs-search-btn" class="px-6 py-3 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">
|
||||
<i class="fa fa-search mr-2"></i>搜索
|
||||
</button>
|
||||
<div class="sm:col-span-4 mt-3">
|
||||
<button id="logs-search-btn" class="w-full sm:w-auto px-4 sm:px-6 py-2 sm:py-3 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors text-sm sm:text-base">
|
||||
<i class="fa fa-search mr-1 sm:mr-2 text-xs sm:text-sm"></i>搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日志趋势图表 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="text-lg font-semibold">查询趋势</h3>
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<div class="flex items-center justify-between mb-4 sm:mb-6">
|
||||
<h3 class="text-base sm:text-lg font-semibold">查询趋势</h3>
|
||||
<div class="flex space-x-2">
|
||||
<button class="time-range-btn px-4 py-2 rounded-md bg-primary text-white" data-range="24h">24小时</button>
|
||||
<button class="time-range-btn px-4 py-2 rounded-md bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors" data-range="7d">7天</button>
|
||||
@@ -962,8 +963,8 @@
|
||||
</div>
|
||||
|
||||
<!-- 日志详情表格 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="bg-white rounded-lg p-4 sm:p-6 card-shadow">
|
||||
<div class="flex items-center justify-between mb-4 sm:mb-6">
|
||||
<div class="flex items-center">
|
||||
<h3 class="text-lg font-semibold">查询日志详情</h3>
|
||||
<button id="logs-refresh-btn" class="ml-3 p-2 text-gray-500 hover:text-primary hover:bg-gray-100 rounded-full transition-colors" title="刷新日志">
|
||||
@@ -979,32 +980,32 @@
|
||||
<table class="min-w-full resizable-table">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200">
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="time">
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="time">
|
||||
<div class="flex items-center">
|
||||
时间
|
||||
<i class="fa fa-sort ml-1 text-xs"></i>
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="clientIp">
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="clientIp">
|
||||
<div class="flex items-center">
|
||||
客户端IP
|
||||
<i class="fa fa-sort ml-1 text-xs"></i>
|
||||
</div>
|
||||
</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="domain">
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500 cursor-pointer hover:text-primary transition-colors" data-sort="domain">
|
||||
<div class="flex items-center">
|
||||
请求
|
||||
<i class="fa fa-sort ml-1 text-xs"></i>
|
||||
</div>
|
||||
</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>
|
||||
<th class="text-left py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">响应时间</th>
|
||||
<th class="text-center py-2 sm:py-3 px-2 sm:px-4 text-xs sm:text-sm font-medium text-gray-500">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="logs-table-body">
|
||||
<tr>
|
||||
<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>
|
||||
<td colspan="5" class="py-6 sm:py-8 text-center text-gray-500 border-b border-gray-100">
|
||||
<i class="fa fa-file-text-o text-3xl sm:text-4xl mb-2 text-gray-300"></i>
|
||||
<div>暂无查询日志</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1081,13 +1082,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-8">
|
||||
<div class="mb-8" id="allowed-sites-section">
|
||||
<h4 class="text-md font-medium mb-4">通行网站</h4>
|
||||
<p class="text-sm text-gray-500 mb-4">开启以下开关后,对应域名将使用GFWList目标IP进行解析</p>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/google.png" alt="Google" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">谷歌 (Google)</span>
|
||||
</div>
|
||||
<button id="gfwlist-google" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
@@ -1097,7 +1099,8 @@
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/youtube.png" alt="YouTube" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">YouTube</span>
|
||||
</div>
|
||||
<button id="gfwlist-youtube" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
@@ -1125,6 +1128,127 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/amazon.png" alt="Amazon" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">Amazon</span>
|
||||
</div>
|
||||
<button id="gfwlist-amazon" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/bbc.png" alt="BBC" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">BBC</span>
|
||||
</div>
|
||||
<button id="gfwlist-bbc" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/discord.png" alt="Discord" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">Discord</span>
|
||||
</div>
|
||||
<button id="gfwlist-discord" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/dropbox.png" alt="Dropbox" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">Dropbox</span>
|
||||
</div>
|
||||
<button id="gfwlist-dropbox" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/microsoft.png" alt="Microsoft" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">Microsoft</span>
|
||||
</div>
|
||||
<button id="gfwlist-microsoft" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/steam.png" alt="Steam" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">Steam</span>
|
||||
</div>
|
||||
<button id="gfwlist-steam" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/telegram.png" alt="Telegram" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">Telegram</span>
|
||||
</div>
|
||||
<button id="gfwlist-telegram" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/tiktok.png" alt="TikTok" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">TikTok</span>
|
||||
</div>
|
||||
<button id="gfwlist-tiktok" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/v2ex.png" alt="V2EX" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">V2EX</span>
|
||||
</div>
|
||||
<button id="gfwlist-v2ex" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/wikimedia.png" alt="Wikimedia" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">Wikimedia</span>
|
||||
</div>
|
||||
<button id="gfwlist-wikimedia" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img src="/images/gfwlist/yahoo.png" alt="Yahoo" class="w-5 h-5 mr-2">
|
||||
<span class="text-sm font-medium text-gray-700">Yahoo</span>
|
||||
</div>
|
||||
<button id="gfwlist-yahoo" type="button" class="toggle-btn bg-gray-300 hover:bg-gray-400 text-white font-medium py-1 px-3 rounded-md transition-colors duration-200">
|
||||
<i class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1152,6 +1276,13 @@
|
||||
<label for="dns-port" class="block text-sm font-medium text-gray-700 mb-1">端口</label>
|
||||
<input type="number" id="dns-port" 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="53">
|
||||
</div>
|
||||
<div>
|
||||
<label for="dns-run-mode" class="block text-sm font-medium text-gray-700 mb-1">运行模式</label>
|
||||
<select id="dns-run-mode" 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="parallel">并行模式</option>
|
||||
<option value="fast-ip">最快的上游DNS服务器</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label for="dns-upstream-servers" class="block text-sm font-medium text-gray-700 mb-1">上游DNS服务器 (每行一个)</label>
|
||||
<textarea id="dns-upstream-servers" rows="4" 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"></textarea>
|
||||
|
||||
168
static/js/api.js
@@ -31,11 +31,10 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
|
||||
// 竞争:请求或超时
|
||||
const response = await Promise.race([fetch(url, options), timeoutPromise]);
|
||||
|
||||
// 获取响应文本,用于调试和错误处理
|
||||
// 获取响应文本
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
// 优化错误响应处理
|
||||
console.warn(`API请求失败: ${response.status}`);
|
||||
|
||||
// 处理401未授权错误,重定向到登录页面
|
||||
@@ -45,73 +44,28 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
|
||||
return { error: '未授权访问' };
|
||||
}
|
||||
|
||||
// 尝试解析JSON,但如果失败,直接使用原始文本作为错误信息
|
||||
// 尝试解析JSON错误响应
|
||||
try {
|
||||
const errorData = JSON.parse(responseText);
|
||||
return { error: errorData.error || responseText || `请求失败: ${response.status}` };
|
||||
} catch (parseError) {
|
||||
// 当响应不是有效的JSON时(如中文错误信息),直接使用原始文本
|
||||
console.warn('非JSON格式错误响应:', responseText);
|
||||
// 当响应不是有效的JSON时,直接使用原始文本作为错误信息
|
||||
return { error: responseText || `请求失败: ${response.status}` };
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试解析成功响应
|
||||
// 如果响应文本为空,返回null
|
||||
if (!responseText || responseText.trim() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 尝试解析JSON响应
|
||||
try {
|
||||
// 首先检查响应文本是否为空
|
||||
if (!responseText || responseText.trim() === '') {
|
||||
console.warn('空响应文本');
|
||||
return null; // 返回null表示空响应
|
||||
}
|
||||
|
||||
// 尝试解析JSON
|
||||
const parsedData = JSON.parse(responseText);
|
||||
|
||||
// 检查解析后的数据是否有效
|
||||
if (parsedData === null) {
|
||||
console.warn('解析后的数据为null');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 允许返回空数组,但不允许返回空对象
|
||||
if (typeof parsedData === 'object' && !Array.isArray(parsedData) && Object.keys(parsedData).length === 0) {
|
||||
console.warn('解析后的数据为空对象');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 限制所有数字为两位小数
|
||||
const formatNumbers = (obj) => {
|
||||
if (typeof obj === 'number') {
|
||||
return parseFloat(obj.toFixed(2));
|
||||
} else if (Array.isArray(obj)) {
|
||||
return obj.map(formatNumbers);
|
||||
} else if (obj && typeof obj === 'object') {
|
||||
const formattedObj = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
formattedObj[key] = formatNumbers(obj[key]);
|
||||
}
|
||||
}
|
||||
return formattedObj;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
const formattedData = formatNumbers(parsedData);
|
||||
return formattedData;
|
||||
return parsedData;
|
||||
} catch (parseError) {
|
||||
// 详细记录错误信息和响应内容
|
||||
console.error('JSON解析错误:', parseError);
|
||||
console.error('原始响应文本:', responseText);
|
||||
console.error('响应长度:', responseText.length);
|
||||
console.error('响应前100字符:', responseText.substring(0, 100));
|
||||
|
||||
// 如果是位置66附近的错误,特别标记
|
||||
if (parseError.message.includes('position 66')) {
|
||||
console.error('位置66附近的字符:', responseText.substring(60, 75));
|
||||
}
|
||||
|
||||
// 返回错误对象,让上层处理
|
||||
return { error: 'JSON解析错误' };
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -156,66 +110,6 @@ const api = {
|
||||
// 获取查询类型统计
|
||||
getQueryTypeStats: () => apiRequest('/query/type?t=' + Date.now()),
|
||||
|
||||
// 获取屏蔽规则 - 已禁用
|
||||
getShieldRules: () => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({}); // 返回空对象而非API调用
|
||||
},
|
||||
|
||||
// 添加屏蔽规则 - 已禁用
|
||||
addShieldRule: (rule) => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 删除屏蔽规则 - 已禁用
|
||||
deleteShieldRule: (rule) => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 更新远程规则 - 已禁用
|
||||
updateRemoteRules: () => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 获取黑名单列表 - 已禁用
|
||||
getBlacklists: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve([]); // 返回空数组而非API调用
|
||||
},
|
||||
|
||||
// 添加黑名单 - 已禁用
|
||||
addBlacklist: (url) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 删除黑名单 - 已禁用
|
||||
deleteBlacklist: (url) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 获取Hosts内容 - 已禁用
|
||||
getHosts: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ content: '' }); // 返回空内容而非API调用
|
||||
},
|
||||
|
||||
// 保存Hosts内容 - 已禁用
|
||||
saveHosts: (content) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 刷新Hosts - 已禁用
|
||||
refreshHosts: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 查询DNS记录 - 兼容多种参数格式
|
||||
queryDNS: async function(domain, recordType) {
|
||||
try {
|
||||
@@ -231,36 +125,22 @@ const api = {
|
||||
params = { domain, recordType };
|
||||
}
|
||||
|
||||
// 尝试不同的API端点
|
||||
const endpoints = ['/api/dns/query', '/dns/query', '/api/query', '/query'];
|
||||
let lastError;
|
||||
// 使用正确的API端点
|
||||
const response = await fetch('/api/query', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
console.log(`尝试API端点: ${endpoint}`);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('DNS查询成功:', data);
|
||||
return data;
|
||||
} else {
|
||||
lastError = new Error(`HTTP error! status: ${response.status} for endpoint: ${endpoint}`);
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.log(`端点 ${endpoint} 调用失败,尝试下一个`);
|
||||
}
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('DNS查询成功:', data);
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// 如果所有端点都失败,抛出最后一个错误
|
||||
throw lastError || new Error('所有API端点调用失败');
|
||||
} catch (error) {
|
||||
console.error('DNS查询API调用失败:', error);
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ function populateConfigForm(config) {
|
||||
|
||||
// DNS配置 - 使用函数安全设置值,避免 || 操作符可能的错误处理
|
||||
setElementValue('dns-port', getSafeValue(dnsServerConfig.Port, 53));
|
||||
setElementValue('dns-run-mode', getSafeValue(dnsServerConfig.QueryMode, 'parallel'));
|
||||
setElementValue('dns-upstream-servers', getSafeArray(dnsServerConfig.UpstreamServers).join('\n'));
|
||||
setElementValue('dns-dnssec-upstream-servers', getSafeArray(dnsServerConfig.DNSSECUpstreamServers).join('\n'));
|
||||
//setElementValue('dns-stats-file', getSafeValue(dnsServerConfig.StatsFile, 'data/stats.json'));
|
||||
@@ -69,7 +70,7 @@ function populateConfigForm(config) {
|
||||
setElementValue('dns-cache-size', getSafeValue(dnsServerConfig.CacheSize, 100));
|
||||
setElementValue('dns-max-cache-ttl', getSafeValue(dnsServerConfig.MaxCacheTTL, 120));
|
||||
setElementValue('dns-min-cache-ttl', getSafeValue(dnsServerConfig.MinCacheTTL, 5));
|
||||
setElementValue('dns-enable-ipv6', getSafeValue(dnsServerConfig.EnableIPv6, false));
|
||||
setElementValue('dns-enable-ipv6', getSafeValue(dnsServerConfig.enableIPv6, false));
|
||||
// HTTP配置
|
||||
setElementValue('http-port', getSafeValue(httpServerConfig.Port, 8080));
|
||||
// 屏蔽配置
|
||||
@@ -96,6 +97,8 @@ function setElementValue(elementId, value) {
|
||||
}
|
||||
} else if (element.tagName === 'TEXTAREA') {
|
||||
element.value = value;
|
||||
} else if (element.tagName === 'SELECT') {
|
||||
element.value = value;
|
||||
} else if (element.tagName === 'BUTTON' && element.classList.contains('toggle-btn')) {
|
||||
const icon = element.querySelector('i');
|
||||
if (icon) {
|
||||
@@ -220,6 +223,7 @@ function collectFormData() {
|
||||
return {
|
||||
dnsserver: {
|
||||
port: dnsPort,
|
||||
queryMode: getElementValue('dns-run-mode') || 'parallel',
|
||||
upstreamServers: upstreamServers,
|
||||
dnssecUpstreamServers: dnssecUpstreamServers,
|
||||
timeout: timeout,
|
||||
@@ -313,12 +317,12 @@ function showNotification(message, type = 'info') {
|
||||
|
||||
// 创建新通知
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
notification.className = `notification fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
|
||||
// 设置通知样式(兼容Tailwind和原生CSS)
|
||||
notification.style.cssText += `
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px;
|
||||
@@ -331,15 +335,17 @@ function showNotification(message, type = 'info') {
|
||||
if (type === 'success') {
|
||||
notification.style.backgroundColor = '#10b981';
|
||||
notification.style.color = 'white';
|
||||
notification.innerHTML = `<i class="fa fa-check-circle mr-2"></i>${message}`;
|
||||
} else if (type === 'error') {
|
||||
notification.style.backgroundColor = '#ef4444';
|
||||
notification.style.color = 'white';
|
||||
notification.innerHTML = `<i class="fa fa-exclamation-circle mr-2"></i>${message}`;
|
||||
} else {
|
||||
notification.style.backgroundColor = '#3b82f6';
|
||||
notification.style.color = 'white';
|
||||
notification.innerHTML = `<i class="fa fa-info-circle mr-2"></i>${message}`;
|
||||
}
|
||||
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 显示通知
|
||||
@@ -383,13 +389,28 @@ async function loadGFWListConfig() {
|
||||
// 填充GFWList配置表单
|
||||
function populateGFWListForm(config) {
|
||||
const gfwListConfig = config.gfwList || {};
|
||||
const enabled = getSafeValue(gfwListConfig.enabled, false);
|
||||
|
||||
setElementValue('gfwlist-enabled', getSafeValue(gfwListConfig.enabled, false));
|
||||
setElementValue('gfwlist-enabled', enabled);
|
||||
setElementValue('gfwlist-target-ip', getSafeValue(gfwListConfig.ip, ''));
|
||||
setElementValue('gfwlist-google', getSafeValue(config.allowGoogle, false));
|
||||
setElementValue('gfwlist-youtube', getSafeValue(config.allowYouTube, false));
|
||||
setElementValue('gfwlist-facebook', getSafeValue(config.allowFacebook, false));
|
||||
setElementValue('gfwlist-twitter', getSafeValue(config.allowTwitter, false));
|
||||
setElementValue('gfwlist-amazon', getSafeValue(config.allowAmazon, false));
|
||||
setElementValue('gfwlist-bbc', getSafeValue(config.allowBBC, false));
|
||||
setElementValue('gfwlist-discord', getSafeValue(config.allowDiscord, false));
|
||||
setElementValue('gfwlist-dropbox', getSafeValue(config.allowDropbox, false));
|
||||
setElementValue('gfwlist-microsoft', getSafeValue(config.allowMicrosoft, false));
|
||||
setElementValue('gfwlist-steam', getSafeValue(config.allowSteam, false));
|
||||
setElementValue('gfwlist-telegram', getSafeValue(config.allowTelegram, false));
|
||||
setElementValue('gfwlist-tiktok', getSafeValue(config.allowTikTok, false));
|
||||
setElementValue('gfwlist-v2ex', getSafeValue(config.allowV2EX, false));
|
||||
setElementValue('gfwlist-wikimedia', getSafeValue(config.allowWikimedia, false));
|
||||
setElementValue('gfwlist-yahoo', getSafeValue(config.allowYahoo, false));
|
||||
|
||||
// 更新通行网站部分的显示效果
|
||||
updateAllowedSitesSection(enabled);
|
||||
}
|
||||
|
||||
// 保存GFWList配置
|
||||
@@ -424,10 +445,53 @@ function collectGFWListFormData() {
|
||||
allowGoogle: getElementValue('gfwlist-google'),
|
||||
allowYouTube: getElementValue('gfwlist-youtube'),
|
||||
allowFacebook: getElementValue('gfwlist-facebook'),
|
||||
allowTwitter: getElementValue('gfwlist-twitter')
|
||||
allowTwitter: getElementValue('gfwlist-twitter'),
|
||||
allowAmazon: getElementValue('gfwlist-amazon'),
|
||||
allowBBC: getElementValue('gfwlist-bbc'),
|
||||
allowDiscord: getElementValue('gfwlist-discord'),
|
||||
allowDropbox: getElementValue('gfwlist-dropbox'),
|
||||
allowMicrosoft: getElementValue('gfwlist-microsoft'),
|
||||
allowSteam: getElementValue('gfwlist-steam'),
|
||||
allowTelegram: getElementValue('gfwlist-telegram'),
|
||||
allowTikTok: getElementValue('gfwlist-tiktok'),
|
||||
allowV2EX: getElementValue('gfwlist-v2ex'),
|
||||
allowWikimedia: getElementValue('gfwlist-wikimedia'),
|
||||
allowYahoo: getElementValue('gfwlist-yahoo')
|
||||
};
|
||||
}
|
||||
|
||||
// 更新通行网站部分的显示效果
|
||||
function updateAllowedSitesSection(enabled) {
|
||||
const section = document.getElementById('allowed-sites-section');
|
||||
if (!section) return;
|
||||
|
||||
const siteCards = section.querySelectorAll('.bg-gray-50');
|
||||
const siteLabels = section.querySelectorAll('.text-gray-700');
|
||||
const siteToggles = section.querySelectorAll('.toggle-btn');
|
||||
|
||||
if (enabled) {
|
||||
// GFWList已启用,显示彩色且可点击
|
||||
section.classList.remove('opacity-50');
|
||||
siteCards.forEach(card => {
|
||||
card.style.filter = 'grayscale(0%)';
|
||||
});
|
||||
siteToggles.forEach(toggle => {
|
||||
toggle.disabled = false;
|
||||
toggle.classList.remove('cursor-not-allowed');
|
||||
});
|
||||
} else {
|
||||
// GFWList已禁用,显示灰色且不可点击
|
||||
section.classList.add('opacity-50');
|
||||
siteCards.forEach(card => {
|
||||
card.style.filter = 'grayscale(100%)';
|
||||
});
|
||||
siteToggles.forEach(toggle => {
|
||||
toggle.disabled = true;
|
||||
toggle.classList.add('cursor-not-allowed');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 重启GFWList服务
|
||||
async function handleRestartGFWListService() {
|
||||
if (!confirm('确定要重启DNS服务吗?重启期间服务可能会短暂不可用。')) return;
|
||||
@@ -460,6 +524,11 @@ function setupGFWListEventListeners() {
|
||||
// 切换按钮状态
|
||||
const currentState = this.classList.contains('bg-success');
|
||||
setElementValue(this.id, !currentState);
|
||||
|
||||
// 如果是GFWList启用开关,更新通行网站部分的显示效果
|
||||
if (this.id === 'gfwlist-enabled') {
|
||||
updateAllowedSitesSection(!currentState);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1232,11 +1232,11 @@ async function updateLogsTable(logs) {
|
||||
// 构建跟踪器浮窗内容
|
||||
const trackerTooltip = isTracker ? `
|
||||
<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-1">已知跟踪器</div>
|
||||
<div class="mb-1">名称: ${trackerInfo.name}</div>
|
||||
<div class="mb-1">类别: ${trackersDatabase.categories[trackerInfo.categoryId] || '未知'}</div>
|
||||
${trackerInfo.url ? `<div class="mb-1">URL: <a href="${trackerInfo.url}" target="_blank" class="text-blue-500 hover:underline">${trackerInfo.url}</a></div>` : ''}
|
||||
${trackerInfo.source ? `<div class="mb-1">源: ${trackerInfo.source}</div>` : ''}
|
||||
<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>
|
||||
${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>
|
||||
` : '';
|
||||
|
||||
@@ -1307,14 +1307,15 @@ async function updateLogsTable(logs) {
|
||||
const iconContainer = row.querySelector('.tracker-icon-container');
|
||||
const tooltip = iconContainer.querySelector('.tracker-tooltip');
|
||||
if (iconContainer && tooltip) {
|
||||
tooltip.style.display = 'none';
|
||||
// 移除内联样式,使用CSS类控制显示
|
||||
tooltip.removeAttribute('style');
|
||||
|
||||
iconContainer.addEventListener('mouseenter', () => {
|
||||
tooltip.style.display = 'block';
|
||||
tooltip.classList.add('visible');
|
||||
});
|
||||
|
||||
iconContainer.addEventListener('mouseleave', () => {
|
||||
tooltip.style.display = 'none';
|
||||
tooltip.classList.remove('visible');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1945,6 +1946,17 @@ async function showLogDetailModal(log) {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 构建跟踪器浮窗内容
|
||||
const trackerTooltip = isTracker ? `
|
||||
<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>
|
||||
${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>
|
||||
` : '';
|
||||
|
||||
// 跟踪器信息
|
||||
const trackerDiv = document.createElement('div');
|
||||
trackerDiv.className = 'col-span-1 md:col-span-2 space-y-1';
|
||||
@@ -1953,13 +1965,34 @@ async function showLogDetailModal(log) {
|
||||
<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 class="tracker-icon-container relative">
|
||||
<i class="fa fa-eye text-red-500 mr-1" title="已知跟踪器"></i>
|
||||
${trackerTooltip}
|
||||
</div>
|
||||
<span>${trackerInfo.name} (${trackerInfo.categoryId && trackersDatabase && trackersDatabase.categories ? trackersDatabase.categories[trackerInfo.categoryId] : '未知'})</span>
|
||||
</div>
|
||||
` : '无'}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 添加跟踪器图标悬停事件
|
||||
if (isTracker) {
|
||||
const iconContainer = trackerDiv.querySelector('.tracker-icon-container');
|
||||
const tooltip = iconContainer.querySelector('.tracker-tooltip');
|
||||
if (iconContainer && tooltip) {
|
||||
// 移除内联样式,使用CSS类控制显示
|
||||
tooltip.removeAttribute('style');
|
||||
|
||||
iconContainer.addEventListener('mouseenter', () => {
|
||||
tooltip.classList.add('visible');
|
||||
});
|
||||
|
||||
iconContainer.addEventListener('mouseleave', () => {
|
||||
tooltip.classList.remove('visible');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 解析记录
|
||||
const recordsDiv = document.createElement('div');
|
||||
recordsDiv.className = 'col-span-1 md:col-span-2 space-y-1';
|
||||
|
||||
@@ -1,115 +1,5 @@
|
||||
// main.js - 主脚本文件
|
||||
|
||||
// 页面导航功能
|
||||
function setupNavigation() {
|
||||
// 侧边栏菜单项
|
||||
const menuItems = document.querySelectorAll('nav a');
|
||||
const contentSections = [
|
||||
document.getElementById('dashboard-content'),
|
||||
document.getElementById('shield-content'),
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('gfwlist-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('logs-content'),
|
||||
document.getElementById('config-content')
|
||||
];
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
|
||||
menuItems.forEach((item, index) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
// 允许浏览器自动更新地址栏中的hash,不阻止默认行为
|
||||
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 移动端侧边栏切换
|
||||
const toggleSidebar = document.getElementById('toggle-sidebar');
|
||||
const closeSidebarBtn = document.getElementById('close-sidebar');
|
||||
const sidebar = document.getElementById('mobile-sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
|
||||
// 打开侧边栏函数
|
||||
function openSidebar() {
|
||||
console.log('Opening sidebar...');
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
sidebar.classList.add('translate-x-0');
|
||||
}
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.classList.remove('hidden');
|
||||
sidebarOverlay.classList.add('block');
|
||||
}
|
||||
// 防止页面滚动
|
||||
document.body.style.overflow = 'hidden';
|
||||
console.log('Sidebar opened successfully');
|
||||
}
|
||||
|
||||
// 关闭侧边栏函数
|
||||
function closeSidebar() {
|
||||
console.log('Closing sidebar...');
|
||||
if (sidebar) {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebar.classList.remove('translate-x-0');
|
||||
}
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
sidebarOverlay.classList.remove('block');
|
||||
}
|
||||
// 恢复页面滚动
|
||||
document.body.style.overflow = '';
|
||||
console.log('Sidebar closed successfully');
|
||||
}
|
||||
|
||||
// 切换侧边栏函数
|
||||
function toggleSidebarVisibility() {
|
||||
console.log('Toggling sidebar visibility...');
|
||||
console.log('Current sidebar classes:', sidebar ? sidebar.className : 'sidebar not found');
|
||||
if (sidebar && sidebar.classList.contains('-translate-x-full')) {
|
||||
console.log('Sidebar is hidden, opening...');
|
||||
openSidebar();
|
||||
} else {
|
||||
console.log('Sidebar is visible, closing...');
|
||||
closeSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定切换按钮事件
|
||||
if (toggleSidebar) {
|
||||
toggleSidebar.addEventListener('click', toggleSidebarVisibility);
|
||||
}
|
||||
|
||||
// 绑定关闭按钮事件
|
||||
if (closeSidebarBtn) {
|
||||
closeSidebarBtn.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// 绑定遮罩层点击事件
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
menuItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
// 检查是否是移动设备视图
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 添加键盘事件监听,按ESC键关闭侧边栏
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面初始化函数 - 根据当前hash值初始化对应页面
|
||||
function initPageByHash() {
|
||||
const hash = window.location.hash.substring(1);
|
||||
@@ -186,41 +76,6 @@ function initPageByHash() {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置导航
|
||||
setupNavigation();
|
||||
|
||||
// 初始化页面
|
||||
initPageByHash();
|
||||
|
||||
// 添加hashchange事件监听,处理浏览器前进/后退按钮
|
||||
window.addEventListener('hashchange', initPageByHash);
|
||||
|
||||
// 定期更新系统状态
|
||||
setInterval(updateSystemStatus, 5000);
|
||||
}
|
||||
|
||||
// 更新系统状态
|
||||
function updateSystemStatus() {
|
||||
fetch('/api/status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
if (uptimeElement) {
|
||||
uptimeElement.textContent = `正常运行中 | ${formatUptime(data.uptime)}`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新系统状态失败:', error);
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
if (uptimeElement) {
|
||||
uptimeElement.textContent = '连接异常';
|
||||
uptimeElement.classList.add('text-danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化运行时间
|
||||
function formatUptime(milliseconds) {
|
||||
// 简化版的格式化,实际使用时需要根据API返回的数据格式调整
|
||||
@@ -240,6 +95,29 @@ function formatUptime(milliseconds) {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新系统状态
|
||||
function updateSystemStatus() {
|
||||
api.getStatus()
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
if (uptimeElement) {
|
||||
uptimeElement.textContent = `正常运行中 | ${formatUptime(data.uptime)}`;
|
||||
uptimeElement.classList.remove('text-danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新系统状态失败:', error);
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
if (uptimeElement) {
|
||||
uptimeElement.textContent = '连接异常';
|
||||
uptimeElement.classList.add('text-danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 账户功能 - 下拉菜单、注销和修改密码
|
||||
function setupAccountFeatures() {
|
||||
// 下拉菜单功能
|
||||
@@ -392,6 +270,107 @@ function setupAccountFeatures() {
|
||||
}
|
||||
}
|
||||
|
||||
// 页面导航功能
|
||||
function setupNavigation() {
|
||||
// 侧边栏菜单项
|
||||
const menuItems = document.querySelectorAll('nav a');
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
|
||||
menuItems.forEach((item, index) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
// 允许浏览器自动更新地址栏中的hash,不阻止默认行为
|
||||
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 移动端侧边栏切换
|
||||
const toggleSidebar = document.getElementById('toggle-sidebar');
|
||||
const closeSidebarBtn = document.getElementById('close-sidebar');
|
||||
const sidebar = document.getElementById('mobile-sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
|
||||
// 打开侧边栏函数
|
||||
function openSidebar() {
|
||||
console.log('Opening sidebar...');
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
sidebar.classList.add('translate-x-0');
|
||||
}
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.classList.remove('hidden');
|
||||
sidebarOverlay.classList.add('block');
|
||||
// 防止页面滚动
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
console.log('Sidebar opened successfully');
|
||||
}
|
||||
|
||||
// 关闭侧边栏函数
|
||||
function closeSidebar() {
|
||||
console.log('Closing sidebar...');
|
||||
if (sidebar) {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebar.classList.remove('translate-x-0');
|
||||
}
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
sidebarOverlay.classList.remove('block');
|
||||
// 恢复页面滚动
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
console.log('Sidebar closed successfully');
|
||||
}
|
||||
|
||||
// 切换侧边栏函数
|
||||
function toggleSidebarVisibility() {
|
||||
console.log('Toggling sidebar visibility...');
|
||||
console.log('Current sidebar classes:', sidebar ? sidebar.className : 'sidebar not found');
|
||||
if (sidebar && sidebar.classList.contains('-translate-x-full')) {
|
||||
console.log('Sidebar is hidden, opening...');
|
||||
openSidebar();
|
||||
} else {
|
||||
console.log('Sidebar is visible, closing...');
|
||||
closeSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定切换按钮事件
|
||||
if (toggleSidebar) {
|
||||
toggleSidebar.addEventListener('click', toggleSidebarVisibility);
|
||||
}
|
||||
|
||||
// 绑定关闭按钮事件
|
||||
if (closeSidebarBtn) {
|
||||
closeSidebarBtn.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// 绑定遮罩层点击事件
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
menuItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
// 检查是否是移动设备视图
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 添加键盘事件监听,按ESC键关闭侧边栏
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置导航
|
||||
|
||||
@@ -1248,7 +1248,7 @@ function showNotification(message, type = 'info') {
|
||||
|
||||
// 创建新通知
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
notification.className = `notification fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
|
||||
// 设置通知样式
|
||||
if (type === 'success') {
|
||||
|
||||