Files
dns-server/static/index.html
T
2025-11-24 13:26:01 +08:00

764 lines
31 KiB
HTML

<!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="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/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>最常屏蔽的域名</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>最常解析的域名</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}`;
// 设置图标
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>