Files
dns-server/static/index.html
2025-11-24 23:28:49 +08:00

764 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="description" content="DNS服务器管理控制台 - 高性能DNS服务器支持规则屏蔽和Hosts管理">
<title>DNS服务器管理控制台</title>
<link rel="stylesheet" href="css/vendor/all.min.css">
<!-- Chart.js 4.x不需要单独的CSS文件 -->
<link rel="stylesheet" href="css/style.css">
<style>
/* 光晕效果样式 */
.stat-value.update {
animation: glow 1s ease-out;
}
@keyframes glow {
0% { box-shadow: 0 0 5px rgba(75, 192, 192, 0.5); }
50% { box-shadow: 0 0 20px rgba(75, 192, 192, 0.8); }
100% { box-shadow: 0 0 5px rgba(75, 192, 192, 0); }
}
/* 小型图表容器 */
.mini-chart-container {
position: absolute;
bottom: 5px;
right: 5px;
width: 60px;
height: 20px;
opacity: 0.7;
}
/* 小型图表 */
.mini-chart {
width: 100% !important;
height: 100% !important;
}
/* 统计数据更新动画 */
.stat-value.update {
position: relative;
animation: stat-pulse 1s ease;
}
@keyframes stat-pulse {
0% {
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(76, 175, 80, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0);
}
}
/* 表格行淡入淡出动画 */
table tr.fade-in {
animation: fadeIn 0.3s ease-out;
}
table tr.fade-out {
animation: fadeOut 0.3s ease-in;
opacity: 0;
}
/* 优化的表格过渡动画 */
.table-transition {
transition: all 0.3s ease;
}
/* 表格行淡入动画 */
.table-row-fade-in {
animation: fadeIn 0.3s ease-out;
}
/* 表格行淡出动画 */
.table-row-fade-out {
animation: fadeOut 0.3s ease-in;
opacity: 0;
}
/* 表格行高亮效果 */
.table-row-highlight {
background-color: rgba(75, 192, 192, 0.2) !important;
transition: background-color 0.3s ease;
animation: highlightPulse 1s ease;
}
@keyframes highlightPulse {
0% {
background-color: rgba(75, 192, 192, 0.2);
}
50% {
background-color: rgba(75, 192, 192, 0.4);
}
100% {
background-color: rgba(75, 192, 192, 0.0);
}
}
/* 数字更新完成动画 */
.number-update-complete {
animation: numberComplete 0.3s ease;
}
@keyframes numberComplete {
0% {
color: #2196F3;
font-weight: bold;
}
100% {
color: inherit;
font-weight: inherit;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-10px);
}
}
/* 通知样式 */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 5px;
color: white;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10000;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
}
</style>
</head>
<body>
<div class="container">
<header class="header-container">
<div class="logo">
<i class="fas fa-server fa-2x"></i>
<h1>DNS服务器管理控制台</h1>
</div>
<p>高性能DNS服务器支持规则屏蔽和Hosts管理</p>
</header>
<div class="main-layout">
<nav class="sidebar">
<ul class="nav-menu">
<li class="nav-item active" data-target="dashboard">
<i class="fas fa-tachometer-alt"></i>
<span>概览</span>
</li>
<li class="nav-item" data-target="rules">
<i class="fas fa-ban"></i>
<span>屏蔽规则</span>
</li>
<li class="nav-item" data-target="hosts">
<i class="fas fa-list-ul"></i>
<span>Hosts管理</span>
</li>
<li class="nav-item" data-target="blacklists">
<i class="fas fa-shield-alt"></i>
<span>远程黑名单</span>
</li>
<li class="nav-item" data-target="query">
<i class="fas fa-search"></i>
<span>DNS查询</span>
</li>
<li class="nav-item" data-target="config">
<i class="fas fa-cog"></i>
<span>配置管理</span>
</li>
</ul>
</nav>
</nav>
<main class="content">
<!-- 概览面板 -->
<div id="dashboard" class="panel active">
<div class="panel-header">
<h2>服务器状态</h2>
<div class="status-indicator">
<span class="status-dot"></span>
<span id="server-status">加载中...</span>
</div>
</div>
<div class="stats-grid">
<div class="stat-card" style="position: relative;">
<i class="fas fa-ban"></i>
<div class="stat-value" id="blocked-count">--</div>
<div class="stat-label">屏蔽请求</div>
<div class="mini-chart-container">
<canvas id="blocked-chart" class="mini-chart"></canvas>
</div>
</div>
<div class="stat-card" style="position: relative;">
<i class="fas fa-check-circle"></i>
<div class="stat-value" id="allowed-count">--</div>
<div class="stat-label">允许请求</div>
</div>
<div class="stat-card" style="position: relative;">
<i class="fas fa-question-circle"></i>
<div class="stat-value" id="error-count">--</div>
<div class="stat-label">错误请求</div>
</div>
<div class="stat-card" style="position: relative;">
<i class="fas fa-history"></i>
<div class="stat-value" id="total-queries">--</div>
<div class="stat-label">总请求数</div>
<div class="mini-chart-container">
<canvas id="query-chart" class="mini-chart"></canvas>
</div>
</div>
<div class="stat-card" style="position: relative;">
<i class="fas fa-list"></i>
<div class="stat-value" id="rules-count">--</div>
<div class="stat-label">屏蔽规则数</div>
<div class="mini-chart-container">
<canvas id="rules-chart" class="mini-chart"></canvas>
</div>
</div>
<div class="stat-card" style="position: relative;">
<i class="fas fa-globe"></i>
<div class="stat-value" id="hosts-count">--</div>
<div class="stat-label">Hosts条目</div>
<div class="mini-chart-container">
<canvas id="hosts-chart" class="mini-chart"></canvas>
</div>
</div>
</div>
<!-- 图表区域已移除 -->
<div class="tables-container">
<div class="table-card">
<h3>最常屏蔽的域名(每5秒刷新)</h3>
<div class="table-wrapper">
<table id="top-blocked-table">
<thead>
<tr>
<th>域名</th>
<th>屏蔽次数</th>
</tr>
</thead>
<tbody>
<tr><td colspan="2" class="loading">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
<div class="table-card">
<h3>最常解析的域名(每5秒刷新)</h3>
<div class="table-wrapper">
<table id="top-resolved-table">
<thead>
<tr>
<th>域名</th>
<th>解析次数</th>
</tr>
</thead>
<tbody>
<tr><td colspan="2" class="loading">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 屏蔽规则面板 -->
<div id="rules" class="panel">
<div class="panel-header">
<h2>屏蔽规则管理</h2>
<button id="reload-rules-btn" class="btn btn-secondary">
<i class="fas fa-sync-alt"></i> 重新加载
</button>
</div>
<div class="rules-management">
<div class="rules-input">
<input type="text" id="rule-input" placeholder="输入要屏蔽的域名或正则表达式">
<button id="add-rule-btn" class="btn btn-primary">
<i class="fas fa-plus"></i> 添加规则
</button>
</div>
<div class="rules-filter">
<input type="text" id="rule-search" placeholder="搜索规则...">
</div>
<div class="rules-list">
<div id="rules-panel">
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>序号</th>
<th>规则内容</th>
<th>操作</th>
</tr>
</thead>
<tbody id="rules-list">
<tr><td colspan="4" class="loading">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- 分页控件 -->
<div class="pagination-controls">
<div class="pagination-info" id="pagination-info">
共0条规则
</div>
<div class="pagination-buttons">
<div class="items-per-page">
<label for="items-per-page">每页显示:</label>
<select id="items-per-page">
<option value="20">20条</option>
<option value="50" selected>50条</option>
<option value="100">100条</option>
<option value="200">200条</option>
</select>
</div>
<div class="nav-buttons">
<button id="first-page-btn" class="btn btn-sm btn-secondary" disabled>
<i class="fas fa-angle-double-left"></i> 首页
</button>
<button id="prev-page-btn" class="btn btn-sm btn-secondary" disabled>
<i class="fas fa-angle-left"></i> 上一页
</button>
<button id="next-page-btn" class="btn btn-sm btn-secondary" disabled>
下一页 <i class="fas fa-angle-right"></i>
</button>
<button id="last-page-btn" class="btn btn-sm btn-secondary" disabled>
末页 <i class="fas fa-angle-double-right"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Hosts管理面板 -->
<div id="hosts" class="panel">
<div class="panel-header">
<h2>Hosts管理</h2>
</div>
<div class="hosts-management">
<div class="hosts-form">
<div class="form-row">
<div class="form-group">
<label for="hosts-ip">IP地址</label>
<input type="text" id="hosts-ip" placeholder="127.0.0.1">
</div>
<div class="form-group">
<label for="hosts-domain">域名</label>
<input type="text" id="hosts-domain" placeholder="example.com">
</div>
<div class="form-group">
<label>&nbsp;</label>
<button id="add-hosts" class="btn btn-primary w-full">
<i class="fas fa-plus"></i> 添加
</button>
</div>
</div>
</div>
<div class="hosts-filter">
<input type="text" id="hosts-filter" placeholder="搜索域名...">
</div>
<div class="hosts-list">
<div class="table-wrapper">
<table id="hosts-table">
<thead>
<tr>
<th>IP地址</th>
<th>域名</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr><td colspan="3" class="loading">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 远程黑名单面板 -->
<div id="blacklists" class="panel">
<div class="panel-header">
<h2>远程黑名单管理</h2>
<button id="update-all-blacklists" class="btn btn-secondary">
<i class="fas fa-sync-alt"></i> 更新所有
</button>
</div>
<div class="blacklists-management">
<div class="blacklists-form">
<div class="form-row">
<div class="form-group">
<label for="blacklist-name">名称</label>
<input type="text" id="blacklist-name" placeholder="黑名单名称">
</div>
<div class="form-group">
<label for="blacklist-url">URL</label>
<input type="text" id="blacklist-url" placeholder="https://example.com/rules.txt">
</div>
<div class="form-group">
<label>&nbsp;</label>
<button id="add-blacklist" class="btn btn-primary w-full">
<i class="fas fa-plus"></i> 添加
</button>
</div>
</div>
</div>
<div class="blacklists-list">
<div class="table-wrapper">
<table id="blacklists-table">
<thead>
<tr>
<th>名称</th>
<th>URL</th>
<th>状态</th>
<th>规则数</th>
<th>最后更新</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr><td colspan="6" class="loading">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- DNS查询面板 -->
<div id="query" class="panel">
<div class="panel-header">
<h2>DNS查询测试</h2>
</div>
<div class="query-form">
<div class="form-group">
<label for="query-domain">域名</label>
<input type="text" id="query-domain" placeholder="example.com">
<button id="run-query" class="btn btn-primary">
<i class="fas fa-search"></i> 查询
</button>
</div>
</div>
<div class="query-result">
<div id="query-result-container" class="hidden">
<div class="result-header">
<h3>查询结果</h3>
</div>
<div class="result-content">
<div class="result-item">
<strong>域名:</strong> <span id="result-domain"></span>
</div>
<div class="result-item">
<strong>状态:</strong> <span id="result-status"></span>
</div>
<div class="result-item">
<strong>规则类型:</strong> <span id="result-rule-type"></span>
</div>
<div class="result-item">
<strong>匹配规则:</strong> <span id="result-rule"></span>
</div>
<div class="result-item">
<strong>Hosts记录:</strong> <span id="result-hosts"></span>
</div>
<div class="result-item">
<strong>查询时间:</strong> <span id="result-time"></span>
</div>
</div>
</div>
</div>
</div>
<!-- 配置管理面板 -->
<div id="config" class="panel">
<div class="panel-header">
<h2>系统配置</h2>
</div>
<div class="config-form">
<div class="config-section">
<h3>屏蔽设置</h3>
<div class="form-group">
<label for="block-method">屏蔽方法</label>
<select id="block-method">
<option value="NXDOMAIN">NXDOMAIN (域名不存在)</option>
<option value="refused">REFUSED (拒绝请求)</option>
<option value="emptyIP">Empty IP (0.0.0.0)</option>
<option value="customIP">自定义IP</option>
</select>
</div>
<div class="form-group">
<label for="custom-block-ip">自定义屏蔽IP</label>
<input type="text" id="custom-block-ip" placeholder="127.0.0.1">
</div>
<div class="form-group">
<label for="update-interval">远程规则更新间隔 (秒)</label>
<input type="number" id="update-interval" min="60" value="3600">
</div>
</div>
<div class="config-actions">
<button id="save-config" class="btn btn-primary">
<i class="fas fa-save"></i> 保存配置
</button>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- 通知组件 -->
<div id="notification" class="notification hidden">
<div class="notification-content">
<i class="fas fa-info-circle"></i>
<span id="notification-message"></span>
</div>
</div>
<script src="js/vendor/chart.umd.min.js"></script>
<script>
// 全局数据变化检测和图表管理
let previousStats = {};
let miniCharts = {};
let dataHistory = {
rules: Array(10).fill(0),
hosts: Array(10).fill(0),
query: Array(10).fill(0),
blocked: Array(10).fill(0)
};
let updateTimer = null;
const UPDATE_INTERVAL = 2000;
let previousFullData = null;
let previousChartData = null;
// 初始化小型图表
function initMiniCharts() {
const chartConfigs = {
'rules-chart': { label: '规则数', color: 'rgb(75, 192, 192)' },
'hosts-chart': { label: 'Hosts数', color: 'rgb(153, 102, 255)' },
'query-chart': { label: '查询数', color: 'rgb(255, 159, 64)' },
'blocked-chart': { label: '屏蔽数', color: 'rgb(255, 99, 132)' }
};
Object.entries(chartConfigs).forEach(([id, config]) => {
const ctx = document.getElementById(id);
if (!ctx) return;
miniCharts[id] = new Chart(ctx, {
type: 'line',
data: {
labels: Array(10).fill(''),
datasets: [{
label: config.label,
data: Array(10).fill(0),
borderColor: config.color,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.4,
pointRadius: 0,
borderWidth: 2,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: { enabled: false }
},
scales: {
x: { display: false },
y: { display: false, beginAtZero: true }
},
animation: false
}
});
});
}
// 更新小型图表
function updateMiniChart(chartId, data) {
if (miniCharts[chartId]) {
miniCharts[chartId].data.datasets[0].data = data;
miniCharts[chartId].update();
}
}
// 更新数据历史记录
function updateDataHistory(key, value) {
dataHistory[key].shift();
dataHistory[key].push(value);
}
// 检查数据是否变化并添加光晕效果
function checkAndAnimate(elementId, newValue) {
const element = document.getElementById(elementId);
if (!element) return;
const oldValue = previousStats[elementId] || 0;
if (newValue !== oldValue && oldValue !== 0 && oldValue !== '--') {
element.classList.add('update');
setTimeout(() => {
element.classList.remove('update');
}, 1000);
}
previousStats[elementId] = newValue;
}
// 启动实时更新
function startRealTimeUpdate() {
if (updateTimer) {
clearInterval(updateTimer);
}
updateTimer = setInterval(() => {
// 仅当当前面板是仪表盘时更新数据
if (document.getElementById('dashboard').classList.contains('active')) {
loadDashboardData();
}
}, UPDATE_INTERVAL);
}
// 停止实时更新
function stopRealTimeUpdate() {
if (updateTimer) {
clearInterval(updateTimer);
updateTimer = null;
}
}
// 显示悬浮通知
function showNotification(type, message) {
// 创建通知元素
const notification = document.createElement('div');
notification.className = `notification notification-${type} show`;
// 设置图标
let iconClass = 'info-circle';
if (type === 'success') iconClass = 'check-circle';
else if (type === 'danger') iconClass = 'exclamation-circle';
else if (type === 'warning') iconClass = 'exclamation-triangle';
// 设置通知内容
notification.innerHTML = `
<div class="notification-icon">
<i class="fas fa-${iconClass}"></i>
</div>
<div class="notification-content">${message}</div>
<button class="notification-close">
<i class="fas fa-times"></i>
</button>
`;
// 添加关闭事件
notification.querySelector('.notification-close').addEventListener('click', () => {
notification.style.animation = 'slideIn 0.3s ease-out reverse';
setTimeout(() => notification.remove(), 300);
});
// 添加到页面
document.body.appendChild(notification);
// 3秒后自动关闭
setTimeout(() => {
notification.style.animation = 'slideIn 0.3s ease-out reverse';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
</script>
<script src="js/app.js"></script>
<script src="js/modules/dashboard.js"></script>
<script src="js/modules/rules.js"></script>
<script src="js/modules/hosts.js"></script>
<script src="js/modules/blacklists.js"></script>
<script src="js/modules/query.js"></script>
<script src="js/modules/config.js"></script>
<script>
// 页面加载完成后初始化
window.addEventListener('load', function() {
initMiniCharts();
// 为侧边栏导航添加切换事件
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', function() {
const target = this.getAttribute('data-target');
const panels = document.querySelectorAll('.panel');
const navItems = document.querySelectorAll('.nav-item');
// 更新面板显示
panels.forEach(panel => {
panel.classList.remove('active');
if (panel.id === target) {
panel.classList.add('active');
}
});
// 更新导航高亮
navItems.forEach(navItem => {
navItem.classList.remove('active');
});
this.classList.add('active');
// 根据面板类型控制更新
if (target === 'dashboard') {
startRealTimeUpdate();
} else {
stopRealTimeUpdate();
}
});
});
});
// 页面卸载时清理定时器
window.addEventListener('beforeunload', function() {
stopRealTimeUpdate();
});
</script>
</body>
</html>