<!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;
        }

        /* 悬浮通知样式 */
        .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);
        }

        .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 {
            background-color: var(--gray-50);
            padding: 1.5rem;
            border-radius: var(--border-radius-lg);
            border-left: 4px solid var(--info-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, 'block-rules')">
                    <i class="fas fa-ban"></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">
                <h2 style="margin-bottom: 1.5rem;">服务器状态</h2>
                <div class="stats-grid">
                    <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 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>
                    <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="block-rules" class="tab-content">
                <div class="card">
                    <div class="card-header">
                        <h3 class="card-title"><i class="fas fa-cog"></i> 屏蔽设置</h3>
                    </div>
                    <div class="card-body">
                        <div class="input-group">
                            <select id="block-method">
                                <option value="NXDOMAIN">返回NXDOMAIN</option>
                                <option value="refused">返回拒绝</option>
                                <option value="emptyIP">返回空IP (0.0.0.0)</option>
                                <option value="customIP">返回自定义IP</option>
                            </select>
                            <input type="text" id="custom-block-ip" placeholder="自定义IP地址" disabled>
                            <button id="save-block-settings" class="btn-primary">
                                <i class="fas fa-save"></i> 保存设置
                            </button>
                        </div>
                        <small style="display:block; margin-top:0.5rem; color:var(--gray-600); font-size:0.875rem;">
                            <strong>NXDOMAIN</strong>: 返回域名不存在错误<br>
                            <strong>refused</strong>: 返回查询拒绝错误<br>
                            <strong>emptyIP</strong>: 返回0.0.0.0<br>
                            <strong>customIP</strong>: 返回自定义IP地址
                        </small>
                    </div>
                </div>
                <div class="card">
                    <div class="card-header">
                        <h3 class="card-title"><i class="fas fa-plus-circle"></i> 添加屏蔽规则</h3>
                    </div>
                    <div class="card-body">
                    <div class="input-group">
                        <select id="rule-type">
                            <option value="domain">域名规则</option>
                            <option value="exception">排除规则</option>
                            <option value="regex">正则表达式</option>
                            <option value="wildcard">通配符规则</option>
                            <option value="start">URL开头</option>
                            <option value="end">URL结尾</option>
                        </select>
                        <input type="text" id="new-rule" placeholder="输入规则内容，例如：example.com">
                        <button id="add-rule-btn" class="btn-primary">
                            <i class="fas fa-plus"></i> 添加
                        </button>
                    </div>
                    <small style="display:block; margin-top:0.5rem; color:var(--gray-600); font-size:0.875rem;">支持AdGuardHome规则格式：域名规则(||example.com^)、排除规则(@@||example.com^)、正则规则(/regex/)、通配符规则(*example.com)等</small>
                </div>
                </div>
                <div class="card">
                    <div class="card-header">
                        <h3 class="card-title"><i class="fas fa-list"></i> 规则列表</h3>
                    </div>
                    <div class="card-body">
                        <div id="rules-container" class="list-container">
                            <div class="empty-state">
                                <i class="fas fa-info-circle"></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 openTab(evt, tabName) {
            var i, tabcontent, tablinks;
            tabcontent = document.getElementsByClassName("tab-content");
            for (i = 0; i < tabcontent.length; i++) {
                tabcontent[i].className = tabcontent[i].className.replace(" active", "");
            }
            tablinks = document.getElementsByClassName("tab-btn");
            for (i = 0; i < tablinks.length; i++) {
                tablinks[i].className = tablinks[i].className.replace(" active", "");
            }
            document.getElementById(tabName).className += " active";
            evt.currentTarget.className += " active";
            
            // 当切换到特定标签时加载数据
            if (tabName === 'dashboard') {
                loadDashboardData();
                // 启动定时更新，但确保只启动一次
                if (!updateTimer) {
                    startRealTimeUpdate();
                }
            } else {
                // 切换到其他标签页时停止定时更新
                stopRealTimeUpdate();
                if (tabName === 'block-rules') {
                    loadRules();
                } else if (tabName === 'hosts') {
                    loadHosts();
                }
            }
        }
        
        // 定时更新的定时器
        let updateTimer = null;
        const UPDATE_INTERVAL = 2000; // 降低间隔以便更快地响应数据变化
        
        // 启动实时更新
        function startRealTimeUpdate() {
            // 确保完全清除之前的定时器
            if (updateTimer) {
                clearInterval(updateTimer);
                updateTimer = null;
            }
            // 启动新的定时器，保持轮询机制，但在loadDashboardData内部判断是否需要更新
            updateTimer = setInterval(loadDashboardData, UPDATE_INTERVAL);
        }
        
        // 停止实时更新
        function stopRealTimeUpdate() {
            if (updateTimer) {
                clearInterval(updateTimer);
                updateTimer = null;
            }
        }
        
        // 检查数据是否变化并添加光晕效果
        function checkAndAnimate(elementId, newValue) {
            const element = document.getElementById(elementId);
            const oldValue = previousStats[elementId] || 0;
            
            // 检查值是否发生变化
            if (newValue !== oldValue && oldValue !== 0) {
                // 添加更新类触发动画
                element.classList.add('update');
                // 动画结束后移除类
                setTimeout(() => {
                    element.classList.remove('update');
                }, 1000);
            }
            
            // 更新存储的上一次值
            previousStats[elementId] = newValue;
        }
        
        // 更新数据历史记录
        function updateDataHistory(key, value) {
            dataHistory[key].shift(); // 移除最旧的数据点
            dataHistory[key].push(value); // 添加新的数据点
        }
        
        // 初始化小型图表
        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).getContext('2d');
                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();
            }
        }
        
        // 页面加载完成时初始化
        window.addEventListener('load', function() {
            // 初始化小型图表
            initMiniCharts();
            
            // 加载仪表盘数据
            if (document.getElementById('dashboard').classList.contains('active')) {
                loadDashboardData();
                // 确保只启动一次定时器
                if (!updateTimer) {
                    startRealTimeUpdate();
                }
            }
        });
        
        // 页面卸载时清理定时器
        window.addEventListener('beforeunload', function() {
            stopRealTimeUpdate();
        });
        
        // 保存上一次完整的API数据，用于检测是否有任何变动
        let previousFullData = null;
        
        // 用于比较数据是否变化的辅助函数
        function isDataChanged(newData, oldData) {
            if (!oldData) return true;
            
            // 检查关键统计数据是否有变化
            const newRulesCount = (newData.shield && (newData.shield.domainRules + newData.shield.regexRules)) || 0;
            const oldRulesCount = (oldData.shield && (oldData.shield.domainRules + oldData.shield.regexRules)) || 0;
            
            const newHostsCount = (newData.shield && newData.shield.hostsRules) || 0;
            const oldHostsCount = (oldData.shield && oldData.shield.hostsRules) || 0;
            
            const newQueryCount = (newData.dns && newData.dns.Queries) || 0;
            const oldQueryCount = (oldData.dns && oldData.dns.Queries) || 0;
            
            const newBlockedCount = (newData.dns && newData.dns.Blocked) || 0;
            const oldBlockedCount = (oldData.dns && oldData.dns.Blocked) || 0;
            
            // 如果任何数据项发生变化，返回true
            return newRulesCount !== oldRulesCount || 
                   newHostsCount !== oldHostsCount || 
                   newQueryCount !== oldQueryCount || 
                   newBlockedCount !== oldBlockedCount;
        }
        
        // 加载概览数据 - 仅在数据变动时更新页面
        function loadDashboardData() {
            // 更新当前时间
            document.getElementById('current-time').textContent = new Date().toLocaleString('zh-CN');
            
            fetch('/api/stats')
                .then(response => response.json())
                .then(data => {
                    // 检查数据是否有变动
                    if (isDataChanged(data, previousFullData)) {
                        console.log('数据有变动，正在更新页面...');
                        
                        // 获取各项统计数据
                        const rulesCount = (data.shield && (data.shield.domainRules + data.shield.regexRules)) || 0;
                        const hostsCount = (data.shield && data.shield.hostsRules) || 0;
                        const queryCount = (data.dns && data.dns.Queries) || 0;
                        const blockedCount = (data.dns && data.dns.Blocked) || 0;
                        
                        // 检查数据变化并添加动画
                        checkAndAnimate('rules-count', rulesCount);
                        checkAndAnimate('hosts-count', hostsCount);
                        checkAndAnimate('query-count', queryCount);
                        checkAndAnimate('blocked-count', blockedCount);
                        
                        // 更新显示
                        document.getElementById('rules-count').textContent = rulesCount;
                        document.getElementById('hosts-count').textContent = hostsCount;
                        document.getElementById('query-count').textContent = queryCount;
                        document.getElementById('blocked-count').textContent = blockedCount;
                        document.getElementById('server-address').textContent = window.location.hostname + ':8080';
                        
                        // 更新数据历史记录
                        updateDataHistory('rules', rulesCount);
                        updateDataHistory('hosts', hostsCount);
                        updateDataHistory('query', queryCount);
                        updateDataHistory('blocked', blockedCount);
                        
                        // 更新小型图表
                        updateMiniChart('rules-chart', dataHistory.rules);
                        updateMiniChart('hosts-chart', dataHistory.hosts);
                        updateMiniChart('query-chart', dataHistory.query);
                        updateMiniChart('blocked-chart', dataHistory.blocked);
                        
                        // 更新完整数据缓存
                        previousFullData = JSON.parse(JSON.stringify(data));
                        
                        // 数据有变动时才加载其他相关数据
                        loadChartData();
                        loadTopDomains();
                    } else {
                        console.log('数据无变动，不更新页面');
                    }
                })
                .catch(error => console.error('加载统计数据失败:', error));
        }

// 保存上一次的图表数据，用于检测图表数据是否变化
let previousChartData = null;

// 加载图表数据并渲染 - 仅在数据变动时更新
function loadChartData() {
    fetch('/api/hourly-stats')
        .then(response => response.json())
        .then(data => {
            // 检查图表数据是否有变动
            const dataChanged = !previousChartData || 
                              JSON.stringify(data.labels) !== JSON.stringify(previousChartData.labels) ||
                              JSON.stringify(data.data) !== JSON.stringify(previousChartData.data);
            
            if (dataChanged) {
                console.log('图表数据有变动，正在更新图表...');
                renderBlockChart(data.labels, data.data);
                // 更新缓存
                previousChartData = { labels: [...data.labels], data: [...data.data] };
            }
        })
        .catch(error => console.error('加载图表数据失败:', error));
}

// 渲染屏蔽统计图表
let blockChart = null;
function renderBlockChart(labels, data) {
    const ctx = document.getElementById('blockChart').getContext('2d');
    
    // 如果图表已存在，先销毁
    if (blockChart) {
        blockChart.destroy();
    }
    
    blockChart = new Chart(ctx, {
        type: 'line',
        data: {
            labels: labels,
            datasets: [{
                label: '屏蔽次数',
                data: data,
                backgroundColor: 'rgba(255, 99, 132, 0.2)',
                borderColor: 'rgb(255, 99, 132)',
                borderWidth: 2,
                pointBackgroundColor: 'rgb(255, 99, 132)',
                pointRadius: 4,
                tension: 0.4
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: {
                    position: 'top',
                },
                tooltip: {
                    mode: 'index',
                    intersect: false,
                }
            },
            scales: {
                y: {
                    beginAtZero: true,
                    ticks: {
                        precision: 0
                    }
                },
                x: {
                    grid: {
                        display: false
                    }
                }
            },
            animation: {
                duration: 1000
            }
        }
    });
}
        
        // 加载屏蔽规则
function loadRules() {
    const rulesContainer = document.getElementById('rules-container');
    rulesContainer.innerHTML = '<div class="empty-state"><div class="loader"></div><p>规则列表加载中...</p></div>';
    
    // 从服务器获取真实规则列表
    fetch('/api/shield')
        .then(response => {
            if (!response.ok) {
                throw new Error('获取规则失败');
            }
            return response.json();
        })
        .then(data => {
            rulesContainer.innerHTML = '';
            
            // 合并所有规则类型
            let allRules = [];
            
            // 添加域名规则
            if (Array.isArray(data.domainRules)) {
                data.domainRules.forEach(rule => {
                    allRules.push({ rule: rule, type: 'domain', block: true });
                });
            }
            
            // 添加排除规则
            if (Array.isArray(data.domainExceptions)) {
                data.domainExceptions.forEach(rule => {
                    allRules.push({ rule: rule, type: 'exception', block: false });
                });
            }
            
            // 添加正则规则
            if (Array.isArray(data.regexRules)) {
                data.regexRules.forEach(rule => {
                    allRules.push({ rule: rule, type: 'regex', block: true });
                });
            }
            
            // 添加正则排除规则
            if (Array.isArray(data.regexExceptions)) {
                data.regexExceptions.forEach(rule => {
                    allRules.push({ rule: rule, type: 'regex_exception', block: false });
                });
            }
            
            // 如果没有规则，显示空状态
            if (allRules.length === 0) {
                rulesContainer.innerHTML = '<div class="empty-state"><i class="fas fa-info-circle"></i><p>暂无规则</p></div>';
                return;
            }
            
            // 渲染所有规则
            allRules.forEach(item => {
                const ruleItem = document.createElement('div');
                ruleItem.className = 'list-item';
                
                // 根据规则类型添加不同的样式和图标
                let icon = 'ban';
                let title = '屏蔽规则';
                let badgeClass = 'badge-danger';
                let typeText = '域名';
                
                if (item.type === 'exception') {
                    icon = 'check-circle';
                    title = '排除规则';
                    badgeClass = 'badge-success';
                    typeText = '排除';
                } else if (item.type === 'regex' || item.type === 'regex_exception') {
                    icon = 'code';
                    title = item.type === 'regex' ? '正则屏蔽规则' : '正则排除规则';
                    badgeClass = item.type === 'regex' ? 'badge-primary' : 'badge-success';
                    typeText = '正则';
                }
                
                // 转义规则中的特殊字符，确保在HTML和JavaScript中正确处理
                const escapedRule = item.rule.replace(/'/g, "\\'");
                
                ruleItem.innerHTML = `
                    <div class="list-content">
                        <div class="list-title"><i class="fas fa-${icon}"></i> ${title}</div>
                        <div class="list-description">${item.rule}</div>
                    </div>
                    <div class="list-actions">
                        <span class="badge ${badgeClass}">${typeText}</span>
                        <button class="btn-danger btn-sm" onclick="deleteRule('${escapedRule}')">
                            <i class="fas fa-trash-alt"></i>
                        </button>
                    </div>
                `;
                rulesContainer.appendChild(ruleItem);
            });
        })
        .catch(error => {
            console.error('加载规则失败:', error);
            rulesContainer.innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载规则失败</p></div>';
        });
}
        
        // 添加屏蔽规则
        document.getElementById('add-rule-btn').addEventListener('click', function() {
            const rule = document.getElementById('new-rule').value.trim();
            const ruleType = document.getElementById('rule-type').value;
            const btn = this;
            const originalText = btn.innerHTML;
            
            if (!rule) {
                showNotification('warning', '请输入规则内容');
                return;
            }
            
            // 显示加载状态
            btn.innerHTML = '<div class="loader"></div> 添加中';
            btn.disabled = true;
            
            // 根据规则类型生成AdGuardHome格式的规则
            let fullRule = '';
            switch(ruleType) {
                case 'domain':
                    // 标准域名规则: ||example.com^
                    fullRule = `||${rule}^`;
                    break;
                case 'exception':
                    // 排除规则: @@||example.com^
                    fullRule = `@@||${rule}^`;
                    break;
                case 'regex':
                    // 正则表达式规则: /pattern/
                    fullRule = `/${rule}/`;
                    break;
                case 'wildcard':
                    // 通配符规则: *example.com
                    fullRule = `*${rule}`;
                    break;
                case 'start':
                    // URL开头匹配: |http://example.com
                    fullRule = `|${rule}`;
                    break;
                case 'end':
                    // URL结尾匹配: example.com|
                    fullRule = `${rule}|`;
                    break;
                default:
                    fullRule = rule;
            }
            
            fetch('/api/shield', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ rule: fullRule })
            })
            .then(response => response.json())
            .then(data => {
                // 重置按钮状态
                btn.innerHTML = originalText;
                btn.disabled = false;
                
                // 显示成功消息
                showNotification('success', '规则添加成功');
                
                // 清空输入框
                document.getElementById('new-rule').value = '';
                
                // 重新加载规则
                loadRules();
            })
            .catch(error => {
                // 重置按钮状态
                btn.innerHTML = originalText;
                btn.disabled = false;
                
                console.error('添加规则失败:', error);
                showNotification('danger', '添加规则失败，请稍后重试');
            });
        });
        
        // 加载Hosts条目
        function loadHosts() {
            const hostsContainer = document.getElementById('hosts-container');
            hostsContainer.innerHTML = '<div class="empty-state"><div class="loader"></div><p>Hosts列表加载中...</p></div>';
            
            fetch('/api/shield/hosts')
                .then(response => response.json())
                .then(data => {
                    // 注意：这需要在shieldManager中添加一个获取所有hosts条目的方法
                    // 暂时返回统计信息
                    const hostsCount = data.hostsCount || 0;
                    
                    if (hostsCount > 0) {
                        hostsContainer.innerHTML = `<div class="list-item">
                            <div class="list-content">
                                <div class="list-title">Hosts概览</div>
                                <div class="list-description">共 ${hostsCount} 个Hosts条目</div>
                            </div>
                            <div class="list-actions">
                                <button class="btn-outline btn-sm" onclick="location.reload()">刷新</button>
                            </div>
                        </div>`;
                    } else {
                        hostsContainer.innerHTML = '<div class="empty-state"><i class="fas fa-info-circle"></i><p>暂无Hosts条目</p></div>';
                    }
                })
                .catch(error => {
                    console.error('加载Hosts失败:', error);
                    hostsContainer.innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载失败，请稍后重试</p></div>';
                });
        }
        
        // 添加Hosts条目
        document.getElementById('add-hosts-btn').addEventListener('click', function() {
            const ip = document.getElementById('hosts-ip').value.trim();
            const domain = document.getElementById('hosts-domain').value.trim();
            const btn = this;
            const originalText = btn.innerHTML;
            
            if (!ip || !domain) {
                showNotification('warning', '请输入IP地址和域名');
                return;
            }
            
            // 显示加载状态
            btn.innerHTML = '<div class="loader"></div> 添加中';
            btn.disabled = true;
            
            fetch('/api/shield/hosts', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ ip: ip, domain: domain })
            })
            .then(response => response.json())
            .then(data => {
                // 重置按钮状态
                btn.innerHTML = originalText;
                btn.disabled = false;
                
                // 显示成功消息
                showNotification('success', 'Hosts条目添加成功');
                
                // 清空输入框
                document.getElementById('hosts-ip').value = '';
                document.getElementById('hosts-domain').value = '';
                
                // 重新加载Hosts
                loadHosts();
            })
            .catch(error => {
                // 重置按钮状态
                btn.innerHTML = originalText;
                btn.disabled = false;
                
                console.error('添加Hosts条目失败:', error);
                showNotification('danger', '添加Hosts条目失败，请稍后重试');
            });
        });
        
        // DNS查询
        document.getElementById('query-btn').addEventListener('click', function() {
            const domain = document.getElementById('query-domain').value.trim();
            const btn = this;
            const originalText = btn.innerHTML;
            const resultElement = document.getElementById('query-result-text');
            
            if (!domain) {
                showNotification('warning', '请输入要查询的域名');
                return;
            }
            
            // 显示加载状态
            btn.innerHTML = '<div class="loader"></div> 查询中';
            btn.disabled = true;
            resultElement.textContent = '正在查询...';
            
            fetch(`/api/query?domain=${encodeURIComponent(domain)}`)
                .then(response => response.json())
                .then(data => {
                    // 重置按钮状态
                    btn.innerHTML = originalText;
                    btn.disabled = false;
                    
                    // 格式化显示结果
                    resultElement.textContent = JSON.stringify(data, null, 2);
                })
                .catch(error => {
                // 重置按钮状态
                btn.innerHTML = originalText;
                btn.disabled = false;
                
                console.error('查询失败:', error);
                resultElement.textContent = '查询失败: ' + error.message;
                showNotification('danger', '查询失败，请稍后重试');
            });
        });
        
        // 删除规则函数
        function deleteRule(rule) {
            if (!confirm('确定要删除这条规则吗？')) {
                return;
            }
            
            fetch('/api/shield', {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ rule: rule })
            })
            .then(response => {
                if (!response.ok) {
                    throw new Error('删除失败');
                }
                return response.json();
            })
            .then(data => {
                // 显示成功消息
                showNotification('success', '规则删除成功');
                
                // 重新加载规则列表
                loadRules();
            })
            .catch(error => {
                console.error('删除规则失败:', error);
                showNotification('danger', '删除规则失败，请稍后重试');
            });
        }
        
        // 加载TOP域名数据
        function loadTopDomains() {
            // 加载TOP屏蔽域名
            fetch('/api/top-blocked')
                .then(response => response.json())
                .then(data => {
                    const container = document.getElementById('top-blocked-domains');
                    container.innerHTML = '';
                    
                    if (!data || data.length === 0) {
                        container.innerHTML = '<div class="empty-state"><i class="fas fa-info-circle"></i><p>暂无屏蔽域名统计</p></div>';
                        return;
                    }
                    
                    data.forEach((item, index) => {
                        const listItem = document.createElement('div');
                        listItem.className = 'list-item';
                        listItem.innerHTML = `
                            <div class="list-content">
                                <div class="list-title">${index + 1}. ${item.domain}</div>
                                <div class="list-description">屏蔽次数: ${item.count}</div>
                            </div>
                            <div class="list-actions">
                                <span class="badge badge-danger">屏蔽</span>
                            </div>
                        `;
                        container.appendChild(listItem);
                    });
                })
                .catch(error => {
                    console.error('加载TOP屏蔽域名失败:', error);
                    document.getElementById('top-blocked-domains').innerHTML = '<div class="empty-state"><i class="fas fa-exclamation-circle"></i><p>加载失败</p></div>';
                });
            
            // 加载TOP解析域名（由于没有直接的API，这里使用模拟数据）
            // 实际使用时，应该调用后端提供的相关API
            setTimeout(() => {
                const mockResolvedDomains = [
                    { domain: 'baidu.com', count: 1532 },
                    { domain: 'sina.com.cn', count: 987 },
                    { domain: 'qq.com', count: 845 },
                    { domain: 'aliyun.com', count: 732 },
                    { domain: 'github.com', count: 654 },
                    { domain: 'gitee.com', count: 521 },
                    { domain: 'bing.com', count: 489 },
                    { domain: 'sohu.com', count: 398 },
                    { domain: 'jd.com', count: 345 },
                    { domain: 'taobao.com', count: 298 }
                ];
                
                const container = document.getElementById('top-resolved-domains');
                container.innerHTML = '';
                
                mockResolvedDomains.forEach((item, index) => {
                    const listItem = document.createElement('div');
                    listItem.className = 'list-item';
                    listItem.innerHTML = `
                        <div class="list-content">
                            <div class="list-title">${index + 1}. ${item.domain}</div>
                            <div class="list-description">解析次数: ${item.count}</div>
                        </div>
                        <div class="list-actions">
                            <span class="badge badge-success">解析</span>
                        </div>
                    `;
                    container.appendChild(listItem);
                });
            }, 500);
        }
        
        // 初始化页面
        window.onload = function() {
            loadDashboardData();
            // 每秒更新时间
            setInterval(() => {
                document.getElementById('current-time').textContent = new Date().toLocaleString('zh-CN');
            }, 1000);
            
            // 屏蔽方法选择事件
            document.getElementById('block-method').addEventListener('change', function() {
                document.getElementById('custom-block-ip').disabled = this.value !== 'customIP';
            });
            
            // 保存屏蔽设置按钮点击事件
            document.getElementById('save-block-settings').addEventListener('click', saveBlockSettings);
            
            // 加载当前屏蔽设置
            loadBlockSettings();
        };
        
        // 加载当前屏蔽设置
        function loadBlockSettings() {
            fetch('/api/config', {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            .then(response => response.json())
            .then(data => {
                if (data.shield) {
                    document.getElementById('block-method').value = data.shield.blockMethod || 'NXDOMAIN';
                    document.getElementById('custom-block-ip').value = data.shield.customBlockIP || '';
                    document.getElementById('custom-block-ip').disabled = data.shield.blockMethod !== 'customIP';
                }
            })
            .catch(error => {
                console.error('加载屏蔽设置失败:', error);
            });
        }
        
        // 保存屏蔽设置
        function saveBlockSettings() {
            const blockMethod = document.getElementById('block-method').value;
            const customBlockIP = document.getElementById('custom-block-ip').value;
            
            // 验证自定义IP
            if (blockMethod === 'customIP' && customBlockIP) {
                const ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
                if (!ipRegex.test(customBlockIP)) {
                    showNotification('warning', '请输入有效的IP地址');
                    return;
                }
            }
            
            fetch('/api/config', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    shield: {
                        blockMethod: blockMethod,
                        customBlockIP: customBlockIP
                    }
                })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    showNotification('success', '屏蔽设置已保存');
                } else {
                    showNotification('danger', '保存失败: ' + (data.error || '未知错误'));
                }
            })
            .catch(error => {
                console.error('保存屏蔽设置失败:', error);
                showNotification('danger', '保存失败: ' + error.message);
            });
        }
        
        // 显示悬浮通知
        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>
</body>
</html>
