656 lines
28 KiB
HTML
656 lines
28 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;
|
||
}
|
||
</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="charts-container">
|
||
<div class="chart-card">
|
||
<h3>24小时屏蔽统计</h3>
|
||
<canvas id="hourly-chart" style="height: 300px;"></canvas>
|
||
</div>
|
||
<div class="chart-card">
|
||
<h3>请求类型分布</h3>
|
||
<canvas id="requests-pie-chart"></canvas>
|
||
</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> </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> </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> |