1190 lines
39 KiB
HTML
1190 lines
39 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>DNS服务器管理中心</title>
|
||
<!-- 引入Font Awesome图标 -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||
<style>
|
||
:root {
|
||
--primary-color: #3498db;
|
||
--secondary-color: #2c3e50;
|
||
--success-color: #2ecc71;
|
||
--danger-color: #e74c3c;
|
||
--warning-color: #f39c12;
|
||
--info-color: #3498db;
|
||
--light-color: #ecf0f1;
|
||
--dark-color: #34495e;
|
||
--gray-100: #f8f9fa;
|
||
--gray-200: #e9ecef;
|
||
--gray-300: #dee2e6;
|
||
--gray-400: #ced4da;
|
||
--gray-500: #adb5bd;
|
||
--gray-600: #6c757d;
|
||
--gray-700: #495057;
|
||
--gray-800: #343a40;
|
||
--gray-900: #212529;
|
||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||
--transition-fast: 0.2s ease;
|
||
--transition-normal: 0.3s ease;
|
||
--border-radius-sm: 0.25rem;
|
||
--border-radius: 0.375rem;
|
||
--border-radius-md: 0.5rem;
|
||
--border-radius-lg: 0.75rem;
|
||
}
|
||
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||
background-color: var(--gray-100);
|
||
color: var(--gray-800);
|
||
line-height: 1.6;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
header {
|
||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||
color: white;
|
||
padding: 2rem 0;
|
||
border-radius: var(--border-radius-lg);
|
||
margin-bottom: 2rem;
|
||
box-shadow: var(--shadow-md);
|
||
text-align: center;
|
||
}
|
||
|
||
header h1 {
|
||
font-size: 2.5rem;
|
||
margin-bottom: 0.5rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
header p {
|
||
font-size: 1.1rem;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.tabs {
|
||
background-color: white;
|
||
border-radius: var(--border-radius-lg);
|
||
box-shadow: var(--shadow);
|
||
margin-bottom: 2rem;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.tab-nav {
|
||
display: flex;
|
||
background-color: var(--gray-50);
|
||
border-bottom: 1px solid var(--gray-200);
|
||
}
|
||
|
||
.tab-btn {
|
||
flex: 1;
|
||
padding: 1rem 1.5rem;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 1rem;
|
||
font-weight: 500;
|
||
color: var(--gray-600);
|
||
transition: all var(--transition-fast);
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.tab-btn:hover {
|
||
background-color: var(--gray-100);
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.tab-btn.active {
|
||
color: var(--primary-color);
|
||
background-color: white;
|
||
box-shadow: inset 0 3px 0 var(--primary-color);
|
||
}
|
||
|
||
.tab-content {
|
||
display: none;
|
||
padding: 2rem;
|
||
animation: fadeIn 0.3s ease-in-out;
|
||
}
|
||
|
||
.tab-content.active {
|
||
display: block;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(10px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.card {
|
||
background-color: white;
|
||
border-radius: var(--border-radius-lg);
|
||
box-shadow: var(--shadow);
|
||
padding: 1.5rem;
|
||
margin-bottom: 1.5rem;
|
||
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
|
||
}
|
||
|
||
.card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--shadow-md);
|
||
}
|
||
|
||
.card-header {
|
||
border-bottom: 1px solid var(--gray-200);
|
||
padding-bottom: 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 1.25rem;
|
||
font-weight: 600;
|
||
color: var(--gray-800);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.card-body {
|
||
padding: 0.5rem 0;
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||
gap: 1rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.grid-2 {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 1.5rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.stat-card {
|
||
background-color: white;
|
||
border-radius: var(--border-radius-lg);
|
||
box-shadow: var(--shadow);
|
||
padding: 1rem;
|
||
text-align: center;
|
||
transition: transform var(--transition-fast);
|
||
border-top: 4px solid var(--primary-color);
|
||
}
|
||
|
||
.stat-card:hover {
|
||
transform: translateY(-3px);
|
||
}
|
||
|
||
.stat-card i {
|
||
font-size: 1.5rem;
|
||
margin-bottom: 0.5rem;
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
color: var(--gray-800);
|
||
margin-bottom: 0.25rem;
|
||
position: relative;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.stat-value.update {
|
||
animation: glow 1s ease-out;
|
||
}
|
||
|
||
@keyframes glow {
|
||
0% {
|
||
text-shadow: 0 0 5px var(--primary-color), 0 0 10px var(--primary-color);
|
||
transform: scale(1.1);
|
||
}
|
||
100% {
|
||
text-shadow: none;
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
.mini-chart-container {
|
||
height: 60px;
|
||
margin-top: 0.5rem;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 0.9rem;
|
||
color: var(--gray-600);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.chart-container {
|
||
position: relative;
|
||
height: 300px;
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
.input-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
input[type="text"], select {
|
||
flex: 1;
|
||
padding: 0.75rem 1rem;
|
||
border: 1px solid var(--gray-300);
|
||
border-radius: var(--border-radius);
|
||
font-size: 1rem;
|
||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||
background-color: white;
|
||
}
|
||
|
||
input[type="text"]:focus, select:focus {
|
||
outline: none;
|
||
border-color: var(--primary-color);
|
||
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
|
||
}
|
||
|
||
button {
|
||
padding: 0.75rem 1.5rem;
|
||
border: none;
|
||
border-radius: var(--border-radius);
|
||
font-size: 1rem;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all var(--transition-fast);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.btn-primary {
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background-color: #2980b9;
|
||
transform: translateY(-1px);
|
||
box-shadow: var(--shadow-md);
|
||
}
|
||
|
||
.btn-success {
|
||
background-color: var(--success-color);
|
||
color: white;
|
||
}
|
||
|
||
.btn-success:hover {
|
||
background-color: #27ae60;
|
||
}
|
||
|
||
.btn-danger {
|
||
background-color: var(--danger-color);
|
||
color: white;
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background-color: #c0392b;
|
||
}
|
||
|
||
.btn-outline {
|
||
background-color: transparent;
|
||
color: var(--primary-color);
|
||
border: 1px solid var(--primary-color);
|
||
}
|
||
|
||
.btn-outline:hover {
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background-color: var(--secondary-color);
|
||
color: white;
|
||
border: none;
|
||
padding: 8px 16px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background-color: #1a252f;
|
||
transform: translateY(-1px);
|
||
box-shadow: var(--shadow-md);
|
||
}
|
||
|
||
.btn-sm {
|
||
padding: 4px 8px;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.form-label {
|
||
display: block;
|
||
margin-bottom: 0.5rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-text {
|
||
display: block;
|
||
margin-top: 0.25rem;
|
||
font-size: 0.875rem;
|
||
color: var(--gray-600);
|
||
}
|
||
|
||
.mb-3 {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.mt-3 {
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.rule-items {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.rule-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.75rem;
|
||
background-color: var(--gray-50);
|
||
border: 1px solid var(--gray-200);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.rule-text {
|
||
flex: 1;
|
||
word-break: break-all;
|
||
margin-right: 0.5rem;
|
||
}
|
||
|
||
/* 悬浮通知样式 */
|
||
.notification {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 10000;
|
||
padding: 1rem 1.5rem;
|
||
border-radius: var(--border-radius);
|
||
box-shadow: var(--shadow-lg);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
animation: slideIn 0.3s ease-out;
|
||
max-width: 400px;
|
||
}
|
||
|
||
.notification-success {
|
||
background-color: white;
|
||
border-left: 4px solid var(--success-color);
|
||
color: var(--gray-800);
|
||
}
|
||
|
||
.notification-danger {
|
||
background-color: white;
|
||
border-left: 4px solid var(--danger-color);
|
||
color: var(--gray-800);
|
||
}
|
||
|
||
.notification-warning {
|
||
background-color: white;
|
||
border-left: 4px solid var(--warning-color);
|
||
color: var(--gray-800);
|
||
}
|
||
|
||
.notification-info {
|
||
background-color: white;
|
||
border-left: 4px solid var(--info-color);
|
||
color: var(--gray-800);
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(100%);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.notification-icon {
|
||
font-size: 1.25rem;
|
||
}
|
||
|
||
.notification-success .notification-icon {
|
||
color: var(--success-color);
|
||
}
|
||
|
||
.notification-danger .notification-icon {
|
||
color: var(--danger-color);
|
||
}
|
||
|
||
.notification-warning .notification-icon {
|
||
color: var(--warning-color);
|
||
}
|
||
|
||
.notification-info .notification-icon {
|
||
color: var(--info-color);
|
||
}
|
||
|
||
.notification-content {
|
||
flex: 1;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.notification-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: 1.25rem;
|
||
cursor: pointer;
|
||
color: var(--gray-500);
|
||
padding: 0;
|
||
margin-left: 0.5rem;
|
||
transition: color var(--transition-fast);
|
||
}
|
||
|
||
.notification-close:hover {
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
.list-container {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
border-radius: var(--border-radius);
|
||
border: 1px solid var(--gray-200);
|
||
background-color: white;
|
||
}
|
||
|
||
.list-item {
|
||
padding: 1rem 1.5rem;
|
||
border-bottom: 1px solid var(--gray-100);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
transition: background-color var(--transition-fast);
|
||
}
|
||
|
||
.list-item:hover {
|
||
background-color: var(--gray-50);
|
||
}
|
||
|
||
.list-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.list-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
.list-title {
|
||
font-weight: 500;
|
||
color: var(--gray-800);
|
||
}
|
||
|
||
.list-description {
|
||
font-size: 0.875rem;
|
||
color: var(--gray-600);
|
||
}
|
||
|
||
.list-actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.btn-sm {
|
||
padding: 0.375rem 0.75rem;
|
||
font-size: 0.875rem;
|
||
border-radius: var(--border-radius-sm);
|
||
}
|
||
|
||
.btn-edit {
|
||
background-color: var(--info-color);
|
||
color: white;
|
||
border: none;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.btn-edit:hover {
|
||
background-color: #2980b9;
|
||
}
|
||
|
||
.checkbox-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.checkbox-label input[type="checkbox"] {
|
||
cursor: pointer;
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
.list-meta {
|
||
display: flex;
|
||
gap: 1rem;
|
||
font-size: 0.75rem;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
.list-meta span {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 3rem 1rem;
|
||
color: var(--gray-500);
|
||
}
|
||
|
||
.empty-state i {
|
||
font-size: 3rem;
|
||
margin-bottom: 1rem;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.empty-state p {
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.alert {
|
||
padding: 1rem;
|
||
border-radius: var(--border-radius);
|
||
margin-bottom: 1rem;
|
||
border-left: 4px solid;
|
||
}
|
||
|
||
.alert-success {
|
||
background-color: #d4edda;
|
||
color: #155724;
|
||
border-color: var(--success-color);
|
||
}
|
||
|
||
.alert-danger {
|
||
background-color: #f8d7da;
|
||
color: #721c24;
|
||
border-color: var(--danger-color);
|
||
}
|
||
|
||
.alert-info {
|
||
background-color: #d1ecf1;
|
||
color: #0c5460;
|
||
border-color: var(--info-color);
|
||
}
|
||
|
||
.status-info {
|
||
margin-top: 2rem;
|
||
padding: 1.5rem;
|
||
background-color: var(--gray-50);
|
||
border-radius: var(--border-radius-lg);
|
||
border-left: 4px solid var(--info-color);
|
||
box-shadow: var(--shadow);
|
||
}
|
||
|
||
.status-info h3 {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.status-info p {
|
||
margin: 0.5rem 0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.status-info .stat-card {
|
||
border-top: 4px solid var(--primary-color);
|
||
}
|
||
|
||
.status-info p {
|
||
margin-bottom: 0.5rem;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.status-info strong {
|
||
color: var(--gray-700);
|
||
}
|
||
|
||
pre {
|
||
background-color: var(--gray-900);
|
||
color: var(--gray-100);
|
||
padding: 1.5rem;
|
||
border-radius: var(--border-radius);
|
||
overflow-x: auto;
|
||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||
font-size: 0.9rem;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.badge {
|
||
display: inline-block;
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
border-radius: 9999px;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.badge-primary {
|
||
background-color: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.badge-success {
|
||
background-color: var(--success-color);
|
||
color: white;
|
||
}
|
||
|
||
.badge-danger {
|
||
background-color: var(--danger-color);
|
||
color: white;
|
||
}
|
||
|
||
.badge-warning {
|
||
background-color: var(--warning-color);
|
||
color: white;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 768px) {
|
||
.container {
|
||
padding: 1rem;
|
||
}
|
||
|
||
header h1 {
|
||
font-size: 2rem;
|
||
}
|
||
|
||
.stats-grid {
|
||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||
gap: 1rem;
|
||
}
|
||
|
||
.input-group {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.tab-nav {
|
||
overflow-x: auto;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.tab-btn {
|
||
flex: none;
|
||
}
|
||
|
||
.tab-content {
|
||
padding: 1rem;
|
||
}
|
||
}
|
||
|
||
/* 加载动画 */
|
||
.loader {
|
||
display: inline-block;
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 2px solid var(--gray-200);
|
||
border-radius: 50%;
|
||
border-top-color: var(--primary-color);
|
||
animation: spin 0.8s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* 滚动条美化 */
|
||
::-webkit-scrollbar {
|
||
width: 8px;
|
||
height: 8px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: var(--gray-100);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--gray-400);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: var(--gray-500);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header>
|
||
<h1><i class="fas fa-server"></i> DNS服务器管理中心</h1>
|
||
<p>高性能DNS服务器,支持规则屏蔽和Hosts管理</p>
|
||
</header>
|
||
|
||
<div class="tabs">
|
||
<div class="tab-nav">
|
||
<button class="tab-btn active" onclick="openTab(event, 'dashboard')">
|
||
<i class="fas fa-tachometer-alt"></i> 概览
|
||
</button>
|
||
<button class="tab-btn" onclick="openTab(event, 'check-filter')">
|
||
<i class="fas fa-search"></i> 检查过滤
|
||
</button>
|
||
<button class="tab-btn" onclick="openTab(event, 'hosts')">
|
||
<i class="fas fa-list-ul"></i> Hosts管理
|
||
</button>
|
||
<button class="tab-btn" onclick="openTab(event, 'query')">
|
||
<i class="fas fa-search"></i> DNS查询
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 概览面板 -->
|
||
<div id="dashboard" class="tab-content active">
|
||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem;">
|
||
<h2>服务器状态</h2>
|
||
<div style="display: flex; gap: 1rem;">
|
||
<div style="background-color: white; padding: 0.5rem 1rem; border-radius: var(--border-radius-md); box-shadow: var(--shadow); display: flex; align-items: center; gap: 0.5rem;">
|
||
<i class="fas fa-ban" style="color: var(--primary-color);"></i>
|
||
<span style="font-size: 1rem; font-weight: 600;">规则: <span id="rules-count-inline">--</span></span>
|
||
</div>
|
||
<div style="background-color: white; padding: 0.5rem 1rem; border-radius: var(--border-radius-md); box-shadow: var(--shadow); display: flex; align-items: center; gap: 0.5rem;">
|
||
<i class="fas fa-file-alt" style="color: var(--primary-color);"></i>
|
||
<span style="font-size: 1rem; font-weight: 600;">Hosts: <span id="hosts-count-inline">--</span></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<i class="fas fa-question-circle"></i>
|
||
<div class="stat-value" id="query-count">--</div>
|
||
<div class="stat-label">DNS查询次数</div>
|
||
<div class="mini-chart-container">
|
||
<canvas id="query-chart"></canvas>
|
||
</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<i class="fas fa-times-circle"></i>
|
||
<div class="stat-value" id="blocked-count">--</div>
|
||
<div class="stat-label">屏蔽次数</div>
|
||
<div class="mini-chart-container">
|
||
<canvas id="blocked-chart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 style="margin-bottom: 1.5rem; margin-top: 2rem;">TOP域名统计</h2>
|
||
<div class="grid-2">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-ban"></i> TOP 10 屏蔽域名</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="top-blocked-domains" class="list-container">
|
||
<div class="empty-state">
|
||
<i class="fas fa-info-circle"></i>
|
||
<p>加载中...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-globe"></i> TOP 10 解析域名</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="top-resolved-domains" class="list-container">
|
||
<div class="empty-state">
|
||
<i class="fas fa-info-circle"></i>
|
||
<p>加载中...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-chart-line"></i> 24小时屏蔽统计</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="chart-container">
|
||
<canvas id="blockChart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="status-info">
|
||
<h3 style="margin-bottom: 1rem; font-size: 1.25rem; color: var(--gray-700);">服务器信息</h3>
|
||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 1rem; margin-bottom: 1.5rem;">
|
||
<div class="stat-card">
|
||
<i class="fas fa-ban"></i>
|
||
<div class="stat-value" id="rules-count">--</div>
|
||
<div class="stat-label">屏蔽规则数</div>
|
||
<div class="mini-chart-container">
|
||
<canvas id="rules-chart"></canvas>
|
||
</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<i class="fas fa-file-alt"></i>
|
||
<div class="stat-value" id="hosts-count">--</div>
|
||
<div class="stat-label">Hosts条目数</div>
|
||
<div class="mini-chart-container">
|
||
<canvas id="hosts-chart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p><strong>服务器地址:</strong> <span id="server-address">--</span></p>
|
||
<p><strong>当前时间:</strong> <span id="current-time">--</span></p>
|
||
<p><strong>运行状态:</strong> <span class="badge badge-success">正常运行</span></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 检查过滤面板 -->
|
||
<div id="check-filter" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-search"></i> 检查过滤</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<p style="margin-bottom: 1rem;">检查主机名是否被过滤。</p>
|
||
<div class="input-group">
|
||
<input type="text" id="check-domain" placeholder="主机名或域名" value="example.com">
|
||
<select id="check-record-type">
|
||
<option value="A">A</option>
|
||
<option value="AAAA">AAAA</option>
|
||
<option value="CNAME">CNAME</option>
|
||
<option value="MX">MX</option>
|
||
<option value="TXT">TXT</option>
|
||
<option value="NS">NS</option>
|
||
</select>
|
||
<button id="check-btn" class="btn-primary" onclick="checkDomainFilter()">
|
||
<i class="fas fa-search"></i> 检查
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-info-circle"></i> 检查结果</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="check-result" class="list-container" style="max-height: 500px;">
|
||
<div class="empty-state">
|
||
<i class="fas fa-search"></i>
|
||
<p>请输入域名并点击检查按钮</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Hosts管理面板 -->
|
||
<div id="hosts" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-plus-circle"></i> 添加Hosts条目</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="input-group">
|
||
<input type="text" id="hosts-ip" placeholder="IP地址,例如:127.0.0.1">
|
||
<input type="text" id="hosts-domain" placeholder="域名,例如:localhost">
|
||
<button id="add-hosts-btn" class="btn-primary">
|
||
<i class="fas fa-plus"></i> 添加
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-list"></i> 当前Hosts条目</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="hosts-container" class="list-container">
|
||
<div class="empty-state">
|
||
<i class="fas fa-info-circle"></i>
|
||
<p>Hosts列表加载中...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- DNS查询面板 -->
|
||
<div id="query" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-search"></i> DNS查询</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="input-group">
|
||
<input type="text" id="query-domain" placeholder="输入要查询的域名">
|
||
<button id="query-btn" class="btn-primary">
|
||
<i class="fas fa-search"></i> 查询
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title"><i class="fas fa-code"></i> 查询结果</h3>
|
||
</div>
|
||
<div class="card-body">
|
||
<pre id="query-result-text">请输入域名并点击查询按钮</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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)
|
||
};
|
||
|
||
// 检查域名过滤状态
|
||
function checkDomainFilter() {
|
||
const domain = document.getElementById('check-domain').value.trim();
|
||
const recordType = document.getElementById('check-record-type').value;
|
||
const resultContainer = document.getElementById('check-result');
|
||
|
||
if (!domain) {
|
||
showNotification('warning', '请输入域名');
|
||
return;
|
||
}
|
||
|
||
// 显示加载状态
|
||
resultContainer.innerHTML = `
|
||
<div class="empty-state">
|
||
<i class="fas fa-spinner fa-spin"></i>
|
||
<p>正在检查域名过滤状态...</p>
|
||
</div>
|
||
`;
|
||
|
||
// 发送请求到后端API
|
||
fetch(`/api/query?domain=${encodeURIComponent(domain)}&type=${recordType}`)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('网络响应错误');
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
// 渲染结果
|
||
renderCheckResult(data, domain);
|
||
})
|
||
.catch(error => {
|
||
resultContainer.innerHTML = `
|
||
<div class="alert alert-danger">
|
||
<i class="fas fa-exclamation-circle"></i>
|
||
检查失败: ${error.message}
|
||
</div>
|
||
`;
|
||
});
|
||
}
|
||
|
||
// 渲染检查结果
|
||
function renderCheckResult(data, domain) {
|
||
const resultContainer = document.getElementById('check-result');
|
||
|
||
// 清空结果容器
|
||
resultContainer.innerHTML = '';
|
||
|
||
// 创建基本信息卡片
|
||
const basicInfo = document.createElement('div');
|
||
basicInfo.className = 'list-item';
|
||
|
||
let statusIcon, statusText, statusClass;
|
||
if (data.isBlocked) {
|
||
statusIcon = '<i class="fas fa-ban" style="color: var(--danger-color);"></i>';
|
||
statusText = '已屏蔽';
|
||
statusClass = 'badge badge-danger';
|
||
} else {
|
||
statusIcon = '<i class="fas fa-check-circle" style="color: var(--success-color);"></i>';
|
||
statusText = '未屏蔽';
|
||
statusClass = 'badge badge-success';
|
||
}
|
||
|
||
basicInfo.innerHTML = `
|
||
<div class="list-content">
|
||
<div class="list-title">
|
||
${statusIcon} 域名检查结果: ${domain}
|
||
</div>
|
||
<div class="list-meta">
|
||
<span>状态: <span class="${statusClass}">${statusText}</span></span>
|
||
<span>记录类型: ${data.recordType || 'A'}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
resultContainer.appendChild(basicInfo);
|
||
|
||
// 如果被屏蔽,显示详细信息
|
||
if (data.isBlocked && data.blockDetails) {
|
||
// 显示屏蔽规则信息
|
||
const ruleInfo = document.createElement('div');
|
||
ruleInfo.className = 'list-item';
|
||
ruleInfo.innerHTML = `
|
||
<div class="list-content">
|
||
<div class="list-title"><i class="fas fa-info-circle" style="color: var(--info-color);"></i> 屏蔽规则详情</div>
|
||
</div>
|
||
`;
|
||
resultContainer.appendChild(ruleInfo);
|
||
|
||
// 显示具体的屏蔽规则
|
||
const blockDetails = data.blockDetails;
|
||
|
||
// 规则内容
|
||
const ruleItem = document.createElement('div');
|
||
ruleItem.className = 'list-item';
|
||
ruleItem.innerHTML = `
|
||
<div class="list-content">
|
||
<div class="list-title">匹配规则</div>
|
||
<div class="list-description">${blockDetails.ruleContent || '未提供'}</div>
|
||
</div>
|
||
`;
|
||
resultContainer.appendChild(ruleItem);
|
||
|
||
// 规则类型
|
||
const typeItem = document.createElement('div');
|
||
typeItem.className = 'list-item';
|
||
typeItem.innerHTML = `
|
||
<div class="list-content">
|
||
<div class="list-title">规则类型</div>
|
||
<div class="list-description">
|
||
${getRuleTypeText(blockDetails.ruleType)}
|
||
</div>
|
||
</div>
|
||
`;
|
||
resultContainer.appendChild(typeItem);
|
||
|
||
// 屏蔽列表
|
||
if (blockDetails.listName) {
|
||
const listItem = document.createElement('div');
|
||
listItem.className = 'list-item';
|
||
listItem.innerHTML = `
|
||
<div class="list-content">
|
||
<div class="list-title">所属屏蔽列表</div>
|
||
<div class="list-description">${blockDetails.listName}</div>
|
||
</div>
|
||
`;
|
||
resultContainer.appendChild(listItem);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 标签切换功能
|
||
function openTab(evt, tabName) {
|
||
var i, tabcontent, tablinks;
|
||
tabcontent = document.getElementsByClassName("tab-content");
|
||
for (i = 0; i < tabcontent.length; i++) {
|
||
tabcontent[i].classList.remove("active");
|
||
}
|
||
tablinks = document.getElementsByClassName("tab-btn");
|
||
for (i = 0; i < tablinks.length; i++) {
|
||
tablinks[i].classList.remove("active");
|
||
}
|
||
document.getElementById(tabName).classList.add("active");
|
||
evt.currentTarget.classList.add("active");
|
||
}
|
||
|
||
// 显示通知
|
||
function showNotification(type, message) {
|
||
const notification = document.createElement('div');
|
||
notification.className = `notification notification-${type}`;
|
||
|
||
let icon;
|
||
switch(type) {
|
||
case 'success':
|
||
icon = 'check-circle';
|
||
break;
|
||
case 'danger':
|
||
icon = 'exclamation-circle';
|
||
break;
|
||
case 'warning':
|
||
icon = 'exclamation-triangle';
|
||
break;
|
||
case 'info':
|
||
default:
|
||
icon = 'info-circle';
|
||
}
|
||
|
||
notification.innerHTML = `
|
||
<div class="notification-icon">
|
||
<i class="fas fa-${icon}"></i>
|
||
</div>
|
||
<div class="notification-content">${message}</div>
|
||
<button class="notification-close" onclick="this.parentElement.remove()">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
`;
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
// 3秒后自动关闭
|
||
setTimeout(() => {
|
||
notification.style.opacity = '0';
|
||
notification.style.transform = 'translateX(100%)';
|
||
notification.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||
setTimeout(() => notification.remove(), 300);
|
||
}, 3000);
|
||
}
|
||
|
||
// 获取规则类型文本描述
|
||
function getRuleTypeText(type) {
|
||
const types = {
|
||
'domain': '域名匹配',
|
||
'regex': '正则表达式',
|
||
'wildcard': '通配符',
|
||
'suffix': '后缀匹配',
|
||
'prefix': '前缀匹配'
|
||
};
|
||
return types[type] || type;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |