revert
This commit is contained in:
@@ -59,6 +59,12 @@
|
||||
<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>
|
||||
@@ -143,9 +149,22 @@
|
||||
<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>
|
||||
<div class="flex items-center">
|
||||
<img src="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-8 h-8 rounded-full">
|
||||
<span class="ml-2 hidden md:block">管理员</span>
|
||||
<!-- 账户下拉菜单 -->
|
||||
<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="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-8 h-8 rounded-full">
|
||||
<span class="ml-2 hidden md:block">管理员</span>
|
||||
<i class="fa fa-caret-down ml-1 text-xs hidden md:block"></i>
|
||||
</button>
|
||||
<!-- 下拉菜单 -->
|
||||
<div class="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg py-2 z-50 hidden group-hover:block" id="account-menu">
|
||||
<button id="change-password-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
|
||||
<i class="fa fa-key mr-2"></i>修改密码
|
||||
</button>
|
||||
<button id="logout-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
|
||||
<i class="fa fa-sign-out mr-2"></i>注销
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -762,6 +781,195 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查询日志页面 -->
|
||||
<div id="logs-content" class="hidden 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="bg-blue-50 rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="absolute -bottom-8 -right-8 w-24 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>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="logs-total-queries">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 平均响应时间 -->
|
||||
<div class="bg-white rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="absolute -bottom-8 -right-8 w-24 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>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="logs-avg-response-time">0ms</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 活跃来源IP -->
|
||||
<div class="bg-white rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="absolute -bottom-8 -right-8 w-24 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>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="logs-active-ips">0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 屏蔽率 -->
|
||||
<div class="bg-red-50 rounded-lg p-4 card-shadow relative overflow-hidden">
|
||||
<div class="absolute -bottom-8 -right-8 w-24 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>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="flex items-end justify-between">
|
||||
<p class="text-3xl font-bold" id="logs-block-rate">0%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
<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">
|
||||
<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">
|
||||
<option value="10">10条/页</option>
|
||||
<option value="20">20条/页</option>
|
||||
<option value="30" selected>30条/页</option>
|
||||
<option value="50">50条/页</option>
|
||||
<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>
|
||||
</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="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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-64">
|
||||
<canvas id="logs-trend-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日志详情表格 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
<div class="flex items-center justify-between 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="刷新日志">
|
||||
<i class="fa fa-refresh"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="logs-loading" class="flex items-center text-sm text-gray-500 hidden">
|
||||
<i class="fa fa-spinner fa-spin mr-2"></i>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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-left py-3 px-4 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>
|
||||
<div>暂无查询日志</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="flex items-center justify-between mt-6">
|
||||
<div class="text-sm text-gray-500">
|
||||
显示 <span id="logs-current-page">1</span> / <span id="logs-total-pages">1</span> 页
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button id="logs-prev-page" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent" disabled>
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
</button>
|
||||
<button id="logs-next-page" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:border-transparent" disabled>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="config-content" class="hidden">
|
||||
<!-- 系统设置页面内容 -->
|
||||
<div class="bg-white rounded-lg p-6 card-shadow">
|
||||
@@ -852,6 +1060,41 @@
|
||||
</main>
|
||||
</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">
|
||||
<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>
|
||||
<input type="password" id="current-password" name="currentPassword" 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" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="new-password" class="block text-sm font-medium text-gray-700 mb-1">新密码</label>
|
||||
<input type="password" id="new-password" name="newPassword" 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" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label for="confirm-password" class="block text-sm font-medium text-gray-700 mb-1">确认密码</label>
|
||||
<input type="password" id="confirm-password" name="confirmPassword" 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" required>
|
||||
<div id="password-mismatch" class="text-danger text-sm mt-1 hidden">新密码和确认密码不匹配</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-3">
|
||||
<button type="button" id="cancel-change-password" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors">取消</button>
|
||||
<button type="submit" id="save-password-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 脚本 -->
|
||||
<script src="js/main.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
@@ -860,6 +1103,7 @@
|
||||
<script src="js/shield.js"></script>
|
||||
<script src="js/hosts.js"></script>
|
||||
<script src="js/query.js"></script>
|
||||
<script src="js/logs.js"></script>
|
||||
<script src="js/config.js"></script>
|
||||
|
||||
<!-- 直接渲染滚动列表的静态HTML内容 -->
|
||||
|
||||
@@ -38,6 +38,13 @@ async function apiRequest(endpoint, method = 'GET', data = null) {
|
||||
// 优化错误响应处理
|
||||
console.warn(`API请求失败: ${response.status}`);
|
||||
|
||||
// 处理401未授权错误,重定向到登录页面
|
||||
if (response.status === 401) {
|
||||
console.warn('未授权访问,重定向到登录页面');
|
||||
window.location.href = '/login';
|
||||
return { error: '未授权访问' };
|
||||
}
|
||||
|
||||
// 尝试解析JSON,但如果失败,直接使用原始文本作为错误信息
|
||||
try {
|
||||
const errorData = JSON.parse(responseText);
|
||||
|
||||
@@ -6,12 +6,18 @@ let dnsRequestsChart = null;
|
||||
let detailedDnsRequestsChart = null; // 详细DNS请求趋势图表(浮窗)
|
||||
let queryTypeChart = null; // 解析类型统计饼图
|
||||
let intervalId = null;
|
||||
let wsConnection = null;
|
||||
let wsReconnectTimer = null;
|
||||
let dashboardWsConnection = null;
|
||||
let dashboardWsReconnectTimer = null;
|
||||
// 存储统计卡片图表实例
|
||||
let statCardCharts = {};
|
||||
// 存储统计卡片历史数据
|
||||
let statCardHistoryData = {};
|
||||
// 存储仪表盘历史数据,用于计算趋势
|
||||
window.dashboardHistoryData = window.dashboardHistoryData || {
|
||||
prevResponseTime: null,
|
||||
prevActiveIPs: null,
|
||||
prevTopQueryTypeCount: null
|
||||
};
|
||||
|
||||
// 引入颜色配置文件
|
||||
const COLOR_CONFIG = window.COLOR_CONFIG || {};
|
||||
@@ -53,22 +59,22 @@ function connectWebSocket() {
|
||||
console.log('正在连接WebSocket:', wsUrl);
|
||||
|
||||
// 创建WebSocket连接
|
||||
wsConnection = new WebSocket(wsUrl);
|
||||
dashboardWsConnection = new WebSocket(wsUrl);
|
||||
|
||||
// 连接打开事件
|
||||
wsConnection.onopen = function() {
|
||||
dashboardWsConnection.onopen = function() {
|
||||
console.log('WebSocket连接已建立');
|
||||
showNotification('数据更新成功', 'success');
|
||||
|
||||
// 清除重连计时器
|
||||
if (wsReconnectTimer) {
|
||||
clearTimeout(wsReconnectTimer);
|
||||
wsReconnectTimer = null;
|
||||
if (dashboardWsReconnectTimer) {
|
||||
clearTimeout(dashboardWsReconnectTimer);
|
||||
dashboardWsReconnectTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 接收消息事件
|
||||
wsConnection.onmessage = function(event) {
|
||||
dashboardWsConnection.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
@@ -82,16 +88,16 @@ function connectWebSocket() {
|
||||
};
|
||||
|
||||
// 连接关闭事件
|
||||
wsConnection.onclose = function(event) {
|
||||
dashboardWsConnection.onclose = function(event) {
|
||||
console.warn('WebSocket连接已关闭,代码:', event.code);
|
||||
wsConnection = null;
|
||||
dashboardWsConnection = null;
|
||||
|
||||
// 设置重连
|
||||
setupReconnect();
|
||||
};
|
||||
|
||||
// 连接错误事件
|
||||
wsConnection.onerror = function(error) {
|
||||
dashboardWsConnection.onerror = function(error) {
|
||||
console.error('WebSocket连接错误:', error);
|
||||
};
|
||||
|
||||
@@ -104,14 +110,14 @@ function connectWebSocket() {
|
||||
|
||||
// 设置重连逻辑
|
||||
function setupReconnect() {
|
||||
if (wsReconnectTimer) {
|
||||
if (dashboardWsReconnectTimer) {
|
||||
return; // 已经有重连计时器在运行
|
||||
}
|
||||
|
||||
const reconnectDelay = 5000; // 5秒后重连
|
||||
console.log(`将在${reconnectDelay}ms后尝试重新连接WebSocket`);
|
||||
|
||||
wsReconnectTimer = setTimeout(() => {
|
||||
dashboardWsReconnectTimer = setTimeout(() => {
|
||||
connectWebSocket();
|
||||
}, reconnectDelay);
|
||||
}
|
||||
@@ -192,14 +198,42 @@ function processRealTimeData(stats) {
|
||||
|
||||
if (document.getElementById('top-query-type')) {
|
||||
const queryType = stats.topQueryType || '---';
|
||||
document.getElementById('top-query-type').textContent = queryType;
|
||||
|
||||
const queryPercentElem = document.getElementById('query-type-percentage');
|
||||
if (queryPercentElem) {
|
||||
queryPercentElem.textContent = '• ---';
|
||||
queryPercentElem.className = 'text-sm flex items-center text-gray-500';
|
||||
// 计算查询类型趋势
|
||||
let queryPercent = '---';
|
||||
let trendClass = 'text-gray-400';
|
||||
let trendIcon = '---';
|
||||
|
||||
if (stats.topQueryTypeCount !== undefined && stats.topQueryTypeCount !== null) {
|
||||
// 存储当前值用于下次计算趋势
|
||||
const prevTopQueryTypeCount = window.dashboardHistoryData.prevTopQueryTypeCount || stats.topQueryTypeCount;
|
||||
window.dashboardHistoryData.prevTopQueryTypeCount = stats.topQueryTypeCount;
|
||||
|
||||
// 计算变化百分比
|
||||
if (prevTopQueryTypeCount > 0) {
|
||||
const changePercent = ((stats.topQueryTypeCount - prevTopQueryTypeCount) / prevTopQueryTypeCount) * 100;
|
||||
queryPercent = Math.abs(changePercent).toFixed(1) + '%';
|
||||
|
||||
// 设置趋势图标和颜色
|
||||
if (changePercent > 0) {
|
||||
trendIcon = '↑';
|
||||
trendClass = 'text-primary';
|
||||
} else if (changePercent < 0) {
|
||||
trendIcon = '↓';
|
||||
trendClass = 'text-secondary';
|
||||
} else {
|
||||
trendIcon = '•';
|
||||
trendClass = 'text-gray-500';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queryPercentElem.textContent = trendIcon + ' ' + queryPercent;
|
||||
queryPercentElem.className = `text-sm flex items-center ${trendClass}`;
|
||||
}
|
||||
|
||||
document.getElementById('top-query-type').textContent = queryType;
|
||||
}
|
||||
|
||||
if (document.getElementById('active-ips')) {
|
||||
@@ -362,15 +396,15 @@ function fallbackToIntervalRefresh() {
|
||||
// 清理资源
|
||||
function cleanupResources() {
|
||||
// 清除WebSocket连接
|
||||
if (wsConnection) {
|
||||
wsConnection.close();
|
||||
wsConnection = null;
|
||||
if (dashboardWsConnection) {
|
||||
dashboardWsConnection.close();
|
||||
dashboardWsConnection = null;
|
||||
}
|
||||
|
||||
// 清除重连计时器
|
||||
if (wsReconnectTimer) {
|
||||
clearTimeout(wsReconnectTimer);
|
||||
wsReconnectTimer = null;
|
||||
if (dashboardWsReconnectTimer) {
|
||||
clearTimeout(dashboardWsReconnectTimer);
|
||||
dashboardWsReconnectTimer = null;
|
||||
}
|
||||
|
||||
// 清除定时刷新
|
||||
@@ -797,17 +831,20 @@ function updateStatsCards(stats) {
|
||||
const originalStyle = element.getAttribute('style') || '';
|
||||
|
||||
try {
|
||||
// 复制原始元素的样式到新元素,确保大小完全一致
|
||||
const computedStyle = getComputedStyle(element);
|
||||
|
||||
// 配置翻页容器样式,确保与原始元素大小完全一致
|
||||
const containerStyle =
|
||||
'position: relative; '
|
||||
+ 'display: ' + computedStyle.display + '; '
|
||||
+ 'overflow: hidden; '
|
||||
+ 'height: ' + element.offsetHeight + 'px; '
|
||||
+ 'width: ' + element.offsetWidth + 'px; '
|
||||
+ 'margin: ' + computedStyle.margin + '; '
|
||||
+ 'padding: ' + computedStyle.padding + '; '
|
||||
+ 'box-sizing: ' + computedStyle.boxSizing + '; '
|
||||
+ 'line-height: ' + computedStyle.lineHeight + ';';
|
||||
'position: relative; ' +
|
||||
'display: ' + computedStyle.display + '; ' +
|
||||
'overflow: hidden; ' +
|
||||
'height: ' + element.offsetHeight + 'px; ' +
|
||||
'width: ' + element.offsetWidth + 'px; ' +
|
||||
'margin: ' + computedStyle.margin + '; ' +
|
||||
'padding: ' + computedStyle.padding + '; ' +
|
||||
'box-sizing: ' + computedStyle.boxSizing + '; ' +
|
||||
'line-height: ' + computedStyle.lineHeight + ';';
|
||||
|
||||
// 创建翻页容器
|
||||
const flipContainer = document.createElement('div');
|
||||
@@ -844,9 +881,6 @@ function updateStatsCards(stats) {
|
||||
'transition: transform 400ms ease-in-out; ' +
|
||||
'transform-origin: center; ' +
|
||||
'transform: translateY(100%);';
|
||||
|
||||
// 复制原始元素的样式到新元素,确保大小完全一致
|
||||
const computedStyle = getComputedStyle(element);
|
||||
[oldValueElement, newValueElement].forEach(el => {
|
||||
el.style.fontSize = computedStyle.fontSize;
|
||||
el.style.fontWeight = computedStyle.fontWeight;
|
||||
|
||||
@@ -9,6 +9,7 @@ function setupNavigation() {
|
||||
document.getElementById('shield-content'),
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('logs-content'),
|
||||
document.getElementById('config-content')
|
||||
];
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
@@ -21,14 +22,6 @@ function setupNavigation() {
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
|
||||
// 页面特定初始化 - 保留这部分逻辑,因为它不会与hashchange事件处理逻辑冲突
|
||||
const target = item.getAttribute('href').substring(1);
|
||||
if (target === 'shield' && typeof initShieldPage === 'function') {
|
||||
initShieldPage();
|
||||
} else if (target === 'hosts' && typeof initHostsPage === 'function') {
|
||||
initHostsPage();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,15 +109,84 @@ function setupNavigation() {
|
||||
});
|
||||
}
|
||||
|
||||
// 页面初始化函数 - 根据当前hash值初始化对应页面
|
||||
function initPageByHash() {
|
||||
const hash = window.location.hash.substring(1);
|
||||
|
||||
// 隐藏所有内容区域
|
||||
const contentSections = [
|
||||
document.getElementById('dashboard-content'),
|
||||
document.getElementById('shield-content'),
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('logs-content'),
|
||||
document.getElementById('config-content')
|
||||
];
|
||||
|
||||
contentSections.forEach(section => {
|
||||
if (section) {
|
||||
section.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 显示当前页面内容
|
||||
const currentSection = document.getElementById(`${hash}-content`);
|
||||
if (currentSection) {
|
||||
currentSection.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 更新页面标题
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
if (pageTitle) {
|
||||
const titles = {
|
||||
'dashboard': '仪表盘',
|
||||
'shield': '屏蔽管理',
|
||||
'hosts': 'Hosts管理',
|
||||
'query': 'DNS屏蔽查询',
|
||||
'logs': '查询日志',
|
||||
'config': '系统设置'
|
||||
};
|
||||
pageTitle.textContent = titles[hash] || '仪表盘';
|
||||
}
|
||||
|
||||
// 页面特定初始化 - 使用setTimeout延迟调用,确保所有脚本文件都已加载完成
|
||||
if (hash === 'shield') {
|
||||
setTimeout(() => {
|
||||
if (typeof initShieldPage === 'function') {
|
||||
initShieldPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'hosts') {
|
||||
setTimeout(() => {
|
||||
if (typeof initHostsPage === 'function') {
|
||||
initHostsPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'logs') {
|
||||
setTimeout(() => {
|
||||
if (typeof initLogsPage === 'function') {
|
||||
initLogsPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'dashboard') {
|
||||
setTimeout(() => {
|
||||
if (typeof loadDashboardData === 'function') {
|
||||
loadDashboardData();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置导航
|
||||
setupNavigation();
|
||||
|
||||
// 加载仪表盘数据
|
||||
if (typeof loadDashboardData === 'function') {
|
||||
loadDashboardData();
|
||||
}
|
||||
// 初始化页面
|
||||
initPageByHash();
|
||||
|
||||
// 添加hashchange事件监听,处理浏览器前进/后退按钮
|
||||
window.addEventListener('hashchange', initPageByHash);
|
||||
|
||||
// 定期更新系统状态
|
||||
setInterval(updateSystemStatus, 5000);
|
||||
@@ -169,5 +231,175 @@ function formatUptime(milliseconds) {
|
||||
}
|
||||
}
|
||||
|
||||
// 账户功能 - 下拉菜单、注销和修改密码
|
||||
function setupAccountFeatures() {
|
||||
// 下拉菜单功能
|
||||
const accountDropdown = document.getElementById('account-dropdown');
|
||||
const accountMenu = document.getElementById('account-menu');
|
||||
const changePasswordBtn = document.getElementById('change-password-btn');
|
||||
const logoutBtn = document.getElementById('logout-btn');
|
||||
const changePasswordModal = document.getElementById('change-password-modal');
|
||||
const closeModalBtn = document.getElementById('close-modal-btn');
|
||||
const cancelChangePasswordBtn = document.getElementById('cancel-change-password');
|
||||
const changePasswordForm = document.getElementById('change-password-form');
|
||||
const passwordMismatch = document.getElementById('password-mismatch');
|
||||
const newPassword = document.getElementById('new-password');
|
||||
const confirmPassword = document.getElementById('confirm-password');
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
document.addEventListener('click', (e) => {
|
||||
if (accountDropdown && !accountDropdown.contains(e.target)) {
|
||||
accountMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 点击账户区域切换下拉菜单
|
||||
if (accountDropdown) {
|
||||
accountDropdown.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
accountMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// 打开修改密码模态框
|
||||
if (changePasswordBtn) {
|
||||
changePasswordBtn.addEventListener('click', () => {
|
||||
accountMenu.classList.add('hidden');
|
||||
changePasswordModal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭修改密码模态框
|
||||
function closeModal() {
|
||||
changePasswordModal.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
changePasswordForm.reset();
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 绑定关闭模态框事件
|
||||
if (closeModalBtn) {
|
||||
closeModalBtn.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
if (cancelChangePasswordBtn) {
|
||||
cancelChangePasswordBtn.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
// 点击模态框外部关闭模态框
|
||||
if (changePasswordModal) {
|
||||
changePasswordModal.addEventListener('click', (e) => {
|
||||
if (e.target === changePasswordModal) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 按ESC键关闭模态框
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && !changePasswordModal.classList.contains('hidden')) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 密码匹配验证
|
||||
if (newPassword && confirmPassword) {
|
||||
confirmPassword.addEventListener('input', () => {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
} else {
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
newPassword.addEventListener('input', () => {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
} else {
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 修改密码表单提交
|
||||
if (changePasswordForm) {
|
||||
changePasswordForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 验证密码匹配
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(changePasswordForm);
|
||||
const data = {
|
||||
currentPassword: formData.get('currentPassword'),
|
||||
newPassword: formData.get('newPassword')
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.status === 'success') {
|
||||
// 密码修改成功
|
||||
alert('密码修改成功');
|
||||
closeModal();
|
||||
} else {
|
||||
// 密码修改失败
|
||||
alert(result.error || '密码修改失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('修改密码失败:', error);
|
||||
alert('修改密码失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 注销功能
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
await fetch('/api/logout', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
// 重定向到登录页面
|
||||
window.location.href = '/login';
|
||||
} catch (error) {
|
||||
console.error('注销失败:', error);
|
||||
alert('注销失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置导航
|
||||
setupNavigation();
|
||||
|
||||
// 设置账户功能
|
||||
setupAccountFeatures();
|
||||
|
||||
// 初始化页面
|
||||
initPageByHash();
|
||||
|
||||
// 添加hashchange事件监听,处理浏览器前进/后退按钮
|
||||
window.addEventListener('hashchange', initPageByHash);
|
||||
|
||||
// 定期更新系统状态
|
||||
setInterval(updateSystemStatus, 5000);
|
||||
}
|
||||
|
||||
// 页面加载完成后执行初始化
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
Reference in New Issue
Block a user