<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>项目服务管理面板 v3.3.1</title>
    <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📊</text></svg>">
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
    <style>
        body { background: #0f172a; }
        .card { background: #1e293b; border: 1px solid #334155; }
        .status-dot { width: 12px; height: 12px; border-radius: 50%; }
        .status-running { background: #22c55e; box-shadow: 0 0 10px #22c55e; }
        .status-stopped { background: #ef4444; }
        .status-partial { background: #f59e0b; }
        .status-ready { background: #3b82f6; }
        .status-configured { background: #22c55e; }
        .status-not-configured { background: #6b7280; }
        .status-completed { background: #8b5cf6; }
        .btn { transition: all 0.2s; }
        .btn:hover { transform: translateY(-1px); }
        .btn:active { transform: translateY(0); }
        .type-badge { font-size: 0.7rem; padding: 2px 6px; border-radius: 4px; }
        .nav-buttons { position: fixed; right: 20px; bottom: 20px; z-index: 100; display: flex; flex-direction: column; gap: 8px; }
        .nav-btn { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s; background: #334155; border: 1px solid #475569; }
        .nav-btn:hover { background: #475569; transform: scale(1.1); }
        .nav-btn i { font-size: 20px; color: #94a3b8; }
        .nav-btn:hover i { color: #f1f5f9; }
        .filter-bar { position: sticky; top: 0; z-index: 50; display: flex; gap: 8px; padding: 12px 0; background: #0f172a; margin-bottom: 16px; flex-wrap: wrap; }
        .filter-bar-btn { display: flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 8px; background: #1e293b; border: 1px solid #334155; color: #94a3b8; font-size: 14px; cursor: pointer; transition: all 0.2s; }
        .filter-bar-btn:hover { background: #334155; color: #f1f5f9; }
        .filter-bar-btn.active { background: #3b82f6; border-color: #3b82f6; color: #fff; }
        .filter-bar-btn.add-btn { background: #22c55e; border-color: #22c55e; color: #fff; }
        .filter-bar-btn.add-btn:hover { background: #16a34a; }
        .realtime-toggle { display: flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 8px; background: #1e293b; border: 1px solid #334155; }
        .realtime-toggle.active { background: #22c55e/20; border-color: #22c55e; }
        .speed-badge { display: inline-flex; align-items: center; gap: 4px; padding: 4px 12px; border-radius: 6px; background: #334155; font-size: 13px; }
        .speed-badge.upload { color: #f97316; }
        .speed-badge.download { color: #3b82f6; }
        .threshold-section { background: #1e293b; border: 1px solid #334155; border-radius: 12px; padding: 16px; margin-bottom: 16px; }
        .threshold-item { display: flex; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid #334155; }
        .threshold-item:last-child { border-bottom: none; }
        .notification-toggle { display: flex; align-items: center; gap: 8px; padding: 12px; background: #334155; border-radius: 8px; margin-top: 12px; }
        .notification-toggle label { cursor: pointer; }
        .threshold-label { flex: 1; display: flex; align-items: center; gap: 8px; }
        .threshold-input { width: 60px; background: #334155; border: 1px solid #475569; border-radius: 6px; padding: 4px 8px; color: #f1f5f9; text-align: center; }
        .threshold-input:focus { outline: none; border-color: #3b82f6; }
        .threshold-icon { font-size: 18px; }
        .threshold-warning { color: #f97316; }
        .alert-popup { position: fixed; top: 20px; right: 20px; z-index: 1000; background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%); border-radius: 12px; padding: 16px 20px; color: white; box-shadow: 0 10px 40px rgba(0,0,0,0.3); animation: slideIn 0.3s ease-out; max-width: 320px; }
        .alert-popup .alert-title { font-weight: bold; font-size: 16px; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
        .alert-popup .alert-content { font-size: 14px; }
        .alert-popup .alert-close { position: absolute; top: 8px; right: 12px; cursor: pointer; opacity: 0.7; }
        .alert-popup .alert-close:hover { opacity: 1; }
        @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
        @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
        .tab-btn { border-bottom: 2px solid transparent; }
        .tab-btn.active { border-bottom-color: #3b82f6; }
        .cron-card { transition: all 0.2s; }
        .cron-card:hover { border-color: #475569; }
        .history-item { border-left: 3px solid #334155; padding-left: 12px; }
        .history-item.rollback { border-left-color: #f59e0b; }
        .history-item.update { border-left-color: #3b82f6; }
        .history-item.create { border-left-color: #22c55e; }
        .history-item.delete { border-left-color: #ef4444; }
        .history-item.toggle { border-left-color: #8b5cf6; }
        .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 50; display: flex; align-items: center; justify-content: center; }
        .modal-content { max-width: 600px; width: 90%; max-height: 80vh; overflow: auto; }
    </style>
</head>
<body class="min-h-screen text-gray-100">
    <div class="container mx-auto px-4 py-8">
        <!-- 头部 -->
        <div class="flex items-center justify-between mb-6">
            <div>
                <h1 class="text-3xl font-bold flex items-center gap-3">
                    <i class="ri-dashboard-3-line text-blue-400"></i>
                    项目服务管理面板
                    <span class="text-sm text-gray-500">v2.1</span>
                </h1>
            </div>
            <div class="flex items-center gap-3">
                <div class="flex items-center gap-2">
                    <span class="text-gray-400 text-sm">对外IP:</span>
                    <input type="text" id="externalIp" value="121.40.164.32" class="bg-gray-700 text-gray-200 px-2 py-1 rounded text-sm w-28" onchange="saveExternalIp()">
                </div>
                <button onclick="refreshAll()" class="btn bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg flex items-center gap-2">
                    <i class="ri-refresh-line"></i> 刷新
                </button>
                <button onclick="exportConfig()" class="btn bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg flex items-center gap-2">
                    <i class="ri-download-line"></i> 导出配置
                </button>
                <label class="btn bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded-lg flex items-center gap-2 cursor-pointer">
                    <i class="ri-upload-line"></i> 导入配置
                    <input type="file" accept=".json" onchange="importConfig(event)" class="hidden">
                </label>
                <div class="flex items-center gap-1">
                    <span class="text-gray-400 text-xs">间隔:</span>
                    <input type="number" id="refreshInterval" value="30" min="5" max="300" class="bg-gray-700 text-gray-200 px-2 py-1 rounded text-xs w-12" onchange="updateRefreshInterval()">
                    <span class="text-gray-500 text-xs">s</span>
                </div>
                <span id="updateTime" class="text-gray-400 text-sm"></span>
                <div id="connectionStatus" class="flex items-center gap-1 px-2 py-1 rounded bg-green-500/20">
                    <div class="w-2 h-2 rounded-full bg-green-400 animate-pulse"></div>
                    <span class="text-green-400 text-xs">已连接</span>
                </div>
            </div>
        </div>

        <!-- Tab 切换 -->
        <div class="flex gap-4 mb-6 border-b border-gray-700">
            <button onclick="switchTab('projects')" class="tab-btn active px-4 py-2 text-gray-300 hover:text-white" data-tab="projects">
                <i class="ri-apps-line"></i> 项目服务
            </button>
            <button onclick="switchTab('system')" class="tab-btn px-4 py-2 text-gray-300 hover:text-white" data-tab="system">
                <i class="ri-dashboard-2-line"></i> 系统资源
            </button>
            <button onclick="switchTab('cron')" class="tab-btn px-4 py-2 text-gray-300 hover:text-white" data-tab="cron">
                <i class="ri-timer-line"></i> Cron 管理
            </button>
        </div>

        <!-- 项目服务 Tab -->
        <div id="projectsTab">
            <!-- 统计卡片 -->
            <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">Web服务</p>
                            <p id="webCount" class="text-2xl font-bold text-blue-400">-</p>
                        </div>
                        <i class="ri-server-line text-3xl text-blue-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">运行中</p>
                            <p id="runningCount" class="text-2xl font-bold text-green-400">-</p>
                        </div>
                        <i class="ri-play-circle-line text-3xl text-green-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">Cron任务</p>
                            <p id="cronCount" class="text-2xl font-bold text-yellow-400">-</p>
                        </div>
                        <i class="ri-time-line text-3xl text-yellow-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">CLI工具</p>
                            <p id="cliCount" class="text-2xl font-bold text-purple-400">-</p>
                        </div>
                        <i class="ri-terminal-box-line text-3xl text-purple-400 opacity-50"></i>
                    </div>
                </div>
            </div>

            <!-- 筛选器（顶部固定） -->
            <div class="filter-bar">
                <button onclick="filterType('all')" class="filter-bar-btn active" data-type="all"><i class="ri-apps-line"></i> 全部</button>
                <button onclick="filterType('web')" class="filter-bar-btn" data-type="web"><i class="ri-server-line"></i> Web服务</button>
                <button onclick="filterType('custom')" class="filter-bar-btn" data-type="custom"><i class="ri-global-line"></i> 自定义</button>
                <button onclick="filterType('cli')" class="filter-bar-btn" data-type="cli"><i class="ri-terminal-box-line"></i> CLI工具</button>
                <button onclick="filterType('extension')" class="filter-bar-btn" data-type="extension"><i class="ri-chrome-line"></i> 插件</button>
                <button onclick="showAddServiceModal()" class="filter-bar-btn add-btn"><i class="ri-add-circle-line"></i> 新增服务</button>
            </div>

            <!-- 项目列表 -->
            <div id="projectsList" class="space-y-4">
                <div class="text-center py-12 text-gray-400"><i class="ri-loader-4-line text-4xl animate-spin"></i><p class="mt-2">加载中...</p></div>
            </div>
        </div>

        <!-- 系统资源 Tab -->
        <div id="systemTab" class="hidden">
            <!-- 系统概览 -->
            <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">系统运行时间</p>
                            <p id="sysUptime" class="text-lg font-bold text-green-400">-</p>
                        </div>
                        <i class="ri-time-line text-3xl text-green-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">启动时间</p>
                            <p id="sysBootTime" class="text-lg font-bold text-blue-400">-</p>
                        </div>
                        <i class="ri-calendar-line text-3xl text-blue-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">进程数</p>
                            <p id="sysProcessCount" class="text-lg font-bold text-yellow-400">-</p>
                        </div>
                        <i class="ri-terminal-line text-3xl text-yellow-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">CPU核心</p>
                            <p id="sysCpuCount" class="text-lg font-bold text-purple-400">-</p>
                        </div>
                        <i class="ri-cpu-line text-3xl text-purple-400 opacity-50"></i>
                    </div>
                </div>
            </div>

            <!-- CPU & 内存 -->
            <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
                <div class="card rounded-xl p-6">
                    <div class="flex items-center justify-between mb-4">
                        <h3 class="font-bold text-lg flex items-center gap-2"><i class="ri-cpu-line text-blue-400"></i> CPU 使用率</h3>
                        <span id="cpuPercent" class="text-2xl font-bold text-blue-400">-</span>
                    </div>
                    <div class="w-full bg-gray-700 rounded-full h-4 mb-2">
                        <div id="cpuBar" class="bg-blue-500 h-4 rounded-full transition-all duration-300" style="width: 0%"></div>
                    </div>
                    <p class="text-gray-400 text-sm">物理核心: <span id="cpuPhysical">-</span> | 逻辑核心: <span id="cpuLogical">-</span></p>
                    <div class="mt-3 pt-3 border-t border-gray-700">
                        <p class="text-gray-400 text-sm flex items-center gap-4">
                            <span><i class="ri-temp-hot-line text-red-400"></i> CPU0: <span id="cpuTemp0" class="text-white font-medium">-</span>°C</span>
                            <span><i class="ri-temp-hot-line text-red-400"></i> CPU1: <span id="cpuTemp1" class="text-white font-medium">-</span>°C</span>
                        </p>
                    </div>
                </div>
                <div class="card rounded-xl p-6">
                    <div class="flex items-center justify-between mb-4">
                        <h3 class="font-bold text-lg flex items-center gap-2"><i class="ri-database-2-line text-green-400"></i> 内存使用</h3>
                        <span id="memPercent" class="text-2xl font-bold text-green-400">-</span>
                    </div>
                    <div class="w-full bg-gray-700 rounded-full h-4 mb-2">
                        <div id="memBar" class="bg-green-500 h-4 rounded-full transition-all duration-300" style="width: 0%"></div>
                    </div>
                    <p class="text-gray-400 text-sm">已用: <span id="memUsed">-</span> GB / 总量: <span id="memTotal">-</span> GB | 可用: <span id="memAvailable">-</span> GB</p>
                </div>
            </div>

            <!-- 磁盘 & 网络 -->
            <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
                <div class="card rounded-xl p-6">
                    <div class="flex items-center justify-between mb-4">
                        <h3 class="font-bold text-lg flex items-center gap-2"><i class="ri-hard-drive-2-line text-orange-400"></i> 磁盘使用</h3>
                        <span id="diskPercent" class="text-2xl font-bold text-orange-400">-</span>
                    </div>
                    <div class="w-full bg-gray-700 rounded-full h-4 mb-2">
                        <div id="diskBar" class="bg-orange-500 h-4 rounded-full transition-all duration-300" style="width: 0%"></div>
                    </div>
                    <p class="text-gray-400 text-sm">已用: <span id="diskUsed">-</span> GB / 总量: <span id="diskTotal">-</span> GB | 剩余: <span id="diskFree">-</span> GB</p>
                </div>
                <div class="card rounded-xl p-6">
                    <div class="flex items-center justify-between mb-4">
                        <h3 class="font-bold text-lg flex items-center gap-2"><i class="ri-wifi-line text-cyan-400"></i> 网络流量</h3>
                    </div>
                    <!-- 实时网速 -->
                    <div class="flex gap-3 mb-4">
                        <span class="speed-badge upload"><i class="ri-upload-line"></i> ↑ <span id="netSentSpeed">-</span></span>
                        <span class="speed-badge download"><i class="ri-download-line"></i> ↓ <span id="netRecvSpeed">-</span></span>
                    </div>
                    <div class="grid grid-cols-2 gap-4">
                        <div>
                            <p class="text-gray-400 text-sm mb-1">累计发送</p>
                            <p id="netSent" class="text-xl font-bold text-cyan-400">-</p>
                            <p class="text-gray-500 text-xs">MB</p>
                        </div>
                        <div>
                            <p class="text-gray-400 text-sm mb-1">累计接收</p>
                            <p id="netRecv" class="text-xl font-bold text-purple-400">-</p>
                            <p class="text-gray-500 text-xs">MB</p>
                        </div>
                    </div>
                </div>
            </div>

            <!-- 阈值监控设置 -->
            <div class="threshold-section">
                <div class="flex items-center justify-between mb-3">
                    <h3 class="font-bold text-gray-200 flex items-center gap-2">
                        <i class="ri-alarm-warning-line text-orange-400"></i> 监控阈值设置
                    </h3>
                    <span class="text-xs text-gray-500">超过阈值将弹窗警告</span>
                </div>
                <div class="threshold-item">
                    <div class="threshold-label">
                        <i class="ri-cpu-line threshold-icon text-blue-400"></i>
                        <span class="text-gray-300 text-sm">CPU 使用率</span>
                    </div>
                    <div class="flex items-center gap-2">
                        <input type="number" id="thresholdCpu" class="threshold-input" min="0" max="100" value="80" onchange="saveThresholds()">
                        <span class="text-gray-500 text-xs">%</span>
                    </div>
                </div>
                <div class="threshold-item">
                    <div class="threshold-label">
                        <i class="ri-database-2-line threshold-icon text-green-400"></i>
                        <span class="text-gray-300 text-sm">内存使用率</span>
                    </div>
                    <div class="flex items-center gap-2">
                        <input type="number" id="thresholdMemory" class="threshold-input" min="0" max="100" value="85" onchange="saveThresholds()">
                        <span class="text-gray-500 text-xs">%</span>
                    </div>
                </div>
                <div class="threshold-item">
                    <div class="threshold-label">
                        <i class="ri-hard-drive-2-line threshold-icon text-orange-400"></i>
                        <span class="text-gray-300 text-sm">磁盘使用率</span>
                    </div>
                    <div class="flex items-center gap-2">
                        <input type="number" id="thresholdDisk" class="threshold-input" min="0" max="100" value="90" onchange="saveThresholds()">
                        <span class="text-gray-500 text-xs">%</span>
                    </div>
                </div>
                <div class="threshold-item">
                    <div class="threshold-label">
                        <i class="ri-timer-line threshold-icon text-purple-400"></i>
                        <span class="text-gray-300 text-sm">警告间隔</span>
                    </div>
                    <div class="flex items-center gap-2">
                        <input type="number" id="thresholdInterval" class="threshold-input" min="10" max="300" value="60" onchange="saveThresholds()">
                        <span class="text-gray-500 text-xs">秒</span>
                    </div>
                </div>
                <!-- 桌面通知开关 -->
                <div class="notification-toggle">
                    <input type="checkbox" id="enableDesktopNotify" onchange="toggleDesktopNotify()" class="w-4 h-4 cursor-pointer">
                    <label for="enableDesktopNotify" class="text-gray-300 text-sm cursor-pointer">
                        <i class="ri-notification-line text-yellow-400"></i> 系统桌面通知
                    </label>
                    <span id="notifyStatus" class="text-xs text-gray-500 ml-1"></span>
                </div>

                <!-- 邮件通知规则 -->
                <div class="mt-4 pt-4 border-t border-gray-600">
                    <div class="flex items-center justify-between mb-3">
                        <h4 class="text-gray-200 text-sm font-medium flex items-center gap-2">
                            <i class="ri-mail-line text-green-400"></i> 邮件通知规则
                        </h4>
                        <button onclick="showAddEmailRuleModal()" class="btn bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-xs flex items-center gap-1">
                            <i class="ri-add-line"></i> 添加规则
                        </button>
                    </div>
                    <div id="emailRulesList" class="space-y-2 text-sm">
                        <div class="text-gray-500 text-center py-4">暂无邮件通知规则</div>
                    </div>
                </div>
            </div>

            <!-- 控制按钮 -->
            <div class="flex gap-3 mb-4 items-center">
                <button onclick="loadSystemStats()" class="btn bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg flex items-center gap-2">
                    <i class="ri-refresh-line"></i> 刷新数据
                </button>
                <div class="realtime-toggle" id="realtimeToggle">
                    <input type="checkbox" id="realtimeCheck" onchange="toggleRealtime()" class="w-4 h-4 cursor-pointer">
                    <label for="realtimeCheck" class="text-gray-300 text-sm cursor-pointer">实时监控</label>
                    <select id="realtimeIntervalSelect" class="bg-gray-700 text-gray-200 px-2 py-1 rounded text-xs ml-2 cursor-pointer" onchange="updateRealtimeInterval()">
                        <option value="1">1秒</option>
                        <option value="2" selected>2秒</option>
                        <option value="3">3秒</option>
                        <option value="5">5秒</option>
                        <option value="10">10秒</option>
                    </select>
                    <span id="realtimeIndicator" class="text-xs text-green-400 ml-1 hidden">●</span>
                </div>
                <div class="flex items-center gap-2 ml-4">
                    <input type="checkbox" id="showProcessListCheck" class="w-4 h-4 cursor-pointer" onchange="toggleProcessList()">
                    <label for="showProcessListCheck" class="text-gray-300 text-xs cursor-pointer">显示进程列表</label>
                </div>
            </div>

            <!-- 进程列表（实时监控时显示） -->
            <div id="processListSection" class="hidden mt-6">
                <div class="flex items-center justify-between mb-3">
                    <h3 class="font-bold text-gray-200 flex items-center gap-2">
                        <i class="ri-bar-chart-horizontal text-purple-400"></i> CPU占用最高的进程
                    </h3>
                    <div class="flex items-center gap-2">
                        <span class="text-gray-400 text-xs">显示数量:</span>
                        <select id="processLimitSelect" class="bg-gray-700 text-gray-200 px-2 py-1 rounded text-xs" onchange="loadTopProcesses()">
                            <option value="5">5</option>
                            <option value="10" selected>10</option>
                            <option value="15">15</option>
                            <option value="20">20</option>
                            <option value="30">30</option>
                        </select>
                    </div>
                </div>
                <div id="processList" class="space-y-2">
                    <div class="text-gray-500 text-center py-4"><i class="ri-loader-4-line animate-spin"></i> 加载中...</div>
                </div>
            </div>
        </div>

        <!-- Cron 管理 Tab -->
        <div id="cronTab" class="hidden">
            <!-- Cron 统计 -->
            <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">总任务数</p>
                            <p id="cronTotal" class="text-2xl font-bold text-orange-400">-</p>
                        </div>
                        <i class="ri-timer-line text-3xl text-orange-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">启用</p>
                            <p id="cronEnabled" class="text-2xl font-bold text-green-400">-</p>
                        </div>
                        <i class="ri-play-circle-line text-3xl text-green-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">禁用</p>
                            <p id="cronDisabled" class="text-2xl font-bold text-red-400">-</p>
                        </div>
                        <i class="ri-pause-circle-line text-3xl text-red-400 opacity-50"></i>
                    </div>
                </div>
                <div class="card rounded-xl p-4">
                    <div class="flex items-center justify-between">
                        <div>
                            <p class="text-gray-400 text-sm">历史记录</p>
                            <p id="cronHistoryCount" class="text-2xl font-bold text-purple-400">-</p>
                        </div>
                        <i class="ri-history-line text-3xl text-purple-400 opacity-50"></i>
                    </div>
                </div>
            </div>

            <!-- Cron 操作按钮 -->
            <div class="flex gap-2 mb-4">
                <button onclick="showAddCronModal()" class="btn bg-green-600 hover:bg-green-700 px-3 py-2 rounded-lg flex items-center gap-1">
                    <i class="ri-add-line"></i> 添加任务
                </button>
                <button onclick="syncFromSystem()" class="btn bg-blue-600 hover:bg-blue-700 px-3 py-2 rounded-lg flex items-center gap-1">
                    <i class="ri-refresh-line"></i> 从系统同步
                </button>
                <button onclick="loadCronTasks()" class="btn bg-gray-600 hover:bg-gray-700 px-3 py-2 rounded-lg flex items-center gap-1">
                    <i class="ri-reload-line"></i> 刷新列表
                </button>
                <div class="flex items-center gap-2 ml-4">
                    <span class="text-gray-400 text-sm">执行日志:</span>
                    <input type="checkbox" id="enableLogs" onchange="toggleLogs()" class="w-4 h-4">
                </div>
            </div>

            <!-- Cron 任务列表 -->
            <div id="cronTasksList" class="space-y-3">
                <div class="text-center py-12 text-gray-400"><i class="ri-loader-4-line text-4xl animate-spin"></i><p class="mt-2">加载中...</p></div>
            </div>
        </div>
    </div>

    <!-- 日志模态框 -->
    <div id="logModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
        <div class="card rounded-xl w-11/12 max-w-4xl max-h-[80vh] flex flex-col">
            <div class="flex items-center justify-between p-4 border-b border-gray-700">
                <h3 id="logTitle" class="font-bold text-lg">日志</h3>
                <button onclick="closeLogModal()" class="text-gray-400 hover:text-white"><i class="ri-close-line text-xl"></i></button>
            </div>
            <div class="flex-1 overflow-auto p-4">
                <pre id="logContent" class="text-sm text-gray-300 font-mono whitespace-pre-wrap"></pre>
            </div>
        </div>
    </div>

    <!-- 添加/编辑 Cron 模态框 -->
    <div id="cronModal" class="modal-overlay hidden">
        <div class="card rounded-xl modal-content p-6">
            <div class="flex items-center justify-between mb-4">
                <h3 id="cronModalTitle" class="font-bold text-lg">添加 Cron 任务</h3>
                <button onclick="closeCronModal()" class="text-gray-400 hover:text-white"><i class="ri-close-line text-xl"></i></button>
            </div>
            
            <form id="cronForm" onsubmit="saveCronTask(event)">
                <input type="hidden" id="cronTaskId">
                
                <div class="mb-4">
                    <label class="block text-gray-400 text-sm mb-1">任务名称 *</label>
                    <input type="text" id="cronName" required class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：服务监控">
                </div>
                
                <div class="mb-4">
                    <label class="block text-gray-400 text-sm mb-1">时间表达式 *</label>
                    <input type="text" id="cronExpression" required class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：0 4 * * * (每天凌晨4点)">
                    <p id="cronHumanTime" class="text-sm text-blue-400 mt-1"></p>
                </div>
                
                <div class="mb-4">
                    <label class="block text-gray-400 text-sm mb-1">执行命令 *</label>
                    <textarea id="cronCommand" required class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" rows="3" placeholder="例如：python3 /path/to/script.py"></textarea>
                </div>
                
                <div class="mb-4">
                    <label class="block text-gray-400 text-sm mb-1">任务分类</label>
                    <select id="cronCategory" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg">
                        <option value="monitor">系统监控</option>
                        <option value="backup">数据备份</option>
                        <option value="cleanup">清理任务</option>
                        <option value="report">报表生成</option>
                        <option value="sync">数据同步</option>
                        <option value="notification">通知提醒</option>
                        <option value="other">其他任务</option>
                    </select>
                </div>
                
                <div class="mb-4">
                    <label class="block text-gray-400 text-sm mb-1">说明备注</label>
                    <textarea id="cronDescription" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" rows="2" placeholder="任务的详细说明..."></textarea>
                </div>
                
                <div class="flex items-center gap-2 mb-4">
                    <input type="checkbox" id="cronEnabled" checked class="w-4 h-4">
                    <label class="text-gray-300">启用任务</label>
                </div>
                
                <div class="flex justify-end gap-2">
                    <button type="button" onclick="closeCronModal()" class="btn bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg">取消</button>
                    <button type="submit" class="btn bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg">保存</button>
                </div>
            </form>
        </div>
    </div>

    <!-- 历史记录模态框 -->
    <div id="historyModal" class="modal-overlay hidden">
        <div class="card rounded-xl modal-content p-6">
            <div class="flex items-center justify-between mb-4">
                <h3 id="historyModalTitle" class="font-bold text-lg">历史版本</h3>
                <button onclick="closeHistoryModal()" class="text-gray-400 hover:text-white"><i class="ri-close-line text-xl"></i></button>
            </div>
            <div id="historyList" class="space-y-3"></div>
        </div>
    </div>

    <!-- 自定义链接模态框 -->
    <div id="linkModal" class="modal-overlay hidden">
        <div class="card rounded-xl modal-content p-6">
            <div class="flex items-center justify-between mb-4">
                <h3 id="linkModalTitle" class="font-bold text-lg">添加自定义链接</h3>
                <button onclick="closeLinkModal()" class="text-gray-400 hover:text-white"><i class="ri-close-line text-xl"></i></button>
            </div>
            
            <form id="linkForm" onsubmit="saveCustomLink(event)">
                <input type="hidden" id="linkProjectId">
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">链接名称 *</label>
                    <input type="text" id="linkName" required class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：API文档、管理后台">
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">端口（使用统一IP）</label>
                    <input type="text" id="linkPort" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：19001" onchange="updateLinkPreview()">
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">路径（可选）</label>
                    <input type="text" id="linkPath" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：/admin /api/docs" onchange="updateLinkPreview()">
                    <p id="linkPreview" class="text-sm text-cyan-400 mt-1"></p>
                </div>
                
                <div class="mb-4">
                    <label class="block text-gray-400 text-sm mb-1">完整URL（不填则用端口生成）</label>
                    <input type="text" id="linkFullUrl" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：http://192.168.2.17:19000/docs">
                </div>
                
                <div id="existingLinks" class="mb-4"></div>
                
                <div class="flex justify-end gap-2">
                    <button type="button" onclick="closeLinkModal()" class="btn bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg">取消</button>
                    <button type="submit" class="btn bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg">添加</button>
                </div>
            </form>
        </div>
    </div>

    <!-- 新增自定义服务模态框 -->
    <div id="addServiceModal" class="modal-overlay hidden">
        <div class="card rounded-xl modal-content p-6">
            <div class="flex items-center justify-between mb-4">
                <h3 class="font-bold text-lg">新增自定义服务</h3>
                <button onclick="closeAddServiceModal()" class="text-gray-400 hover:text-white"><i class="ri-close-line text-xl"></i></button>
            </div>
            
            <form id="addServiceForm" onsubmit="saveCustomService(event)">
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">服务名称 *</label>
                    <input type="text" id="serviceName" required class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：外部API、监控面板">
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">端口（使用统一IP）</label>
                    <input type="text" id="servicePort" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：8080" onchange="updateServicePreview()">
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">路径（可选）</label>
                    <input type="text" id="servicePath" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：/admin" onchange="updateServicePreview()">
                    <p id="servicePreview" class="text-sm text-cyan-400 mt-1"></p>
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">完整URL（不填则用端口生成）</label>
                    <input type="text" id="serviceFullUrl" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：http://external.com:8080">
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">后台URL（可选）</label>
                    <input type="text" id="serviceAdminUrl" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：http://external.com:8080/admin">
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">描述</label>
                    <input type="text" id="serviceDesc" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="简要描述">
                </div>
                
                <div class="flex justify-end gap-2">
                    <button type="button" onclick="closeAddServiceModal()" class="btn bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg">取消</button>
                    <button type="submit" class="btn bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg">添加</button>
                </div>
            </form>
        </div>
    </div>

    <!-- 邮件通知规则模态框 -->
    <div id="emailRuleModal" class="modal-overlay hidden">
        <div class="card rounded-xl modal-content p-6">
            <div class="flex items-center justify-between mb-4">
                <h3 id="emailRuleModalTitle" class="font-bold text-lg">添加邮件通知规则</h3>
                <button onclick="closeEmailRuleModal()" class="text-gray-400 hover:text-white"><i class="ri-close-line text-xl"></i></button>
            </div>
            
            <form id="emailRuleForm" onsubmit="saveEmailRule(event)">
                <input type="hidden" id="emailRuleId">
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">规则名称 *</label>
                    <input type="text" id="emailRuleName" required class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如：CPU告警通知">
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">监控资源 *</label>
                    <select id="emailRuleResource" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg">
                        <option value="cpu">CPU 使用率</option>
                        <option value="memory">内存使用率</option>
                        <option value="disk">磁盘使用率</option>
                    </select>
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">触发阈值 *</label>
                    <div class="flex items-center gap-2">
                        <input type="number" id="emailRuleThreshold" required min="1" max="100" value="80" class="w-20 bg-gray-700 text-gray-200 px-3 py-2 rounded-lg">
                        <span class="text-gray-400">%</span>
                    </div>
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">触发机制 *</label>
                    <select id="emailRuleTriggerType" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" onchange="updateTriggerParams()">
                        <option value="single">一次性触发 - 超过阈值立即通知</option>
                        <option value="continuous">连续触发 - 连续超过阈值N次</option>
                        <option value="duration">持续时间触发 - 累计超过阈值N秒</option>
                        <option value="average">平均值触发 - 最近N次平均值超过阈值</option>
                    </select>
                </div>
                
                <div class="mb-3" id="triggerParamsDiv" style="display:none;">
                    <label class="block text-gray-400 text-sm mb-1" id="triggerParamsLabel">触发参数</label>
                    <div class="flex items-center gap-2">
                        <input type="number" id="emailRuleTriggerParam" min="1" max="100" value="3" class="w-20 bg-gray-700 text-gray-200 px-3 py-2 rounded-lg">
                        <span class="text-gray-400" id="triggerParamUnit">次</span>
                    </div>
                    <p class="text-gray-500 text-xs mt-1" id="triggerParamHint">连续超过阈值多少次才触发</p>
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">收件邮箱 *</label>
                    <input type="email" id="emailRuleAddress" required class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="alert@example.com">
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">通知间隔</label>
                    <div class="flex items-center gap-2">
                        <input type="number" id="emailRuleInterval" min="60" max="3600" value="300" class="w-20 bg-gray-700 text-gray-200 px-3 py-2 rounded-lg">
                        <span class="text-gray-400">秒（最少60秒）</span>
                    </div>
                </div>
                
                <div class="mb-3">
                    <label class="block text-gray-400 text-sm mb-1">静默时间区间</label>
                    <p class="text-gray-500 text-xs mb-2">在此时间段内不发送邮件通知（如夜间休息时段）</p>
                    <div class="flex items-center gap-2">
                        <input type="time" id="emailRuleSilentStart" class="bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" value="23:00">
                        <span class="text-gray-400">至</span>
                        <input type="time" id="emailRuleSilentEnd" class="bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" value="08:00">
                        <input type="checkbox" id="emailRuleSilentEnabled" class="w-4 h-4 cursor-pointer ml-2">
                        <label for="emailRuleSilentEnabled" class="text-gray-300 text-xs cursor-pointer">启用静默</label>
                    </div>
                </div>
                
                <div class="mb-3 flex items-center gap-2">
                    <input type="checkbox" id="emailRuleEnabled" checked class="w-4 h-4 cursor-pointer">
                    <label for="emailRuleEnabled" class="text-gray-300 text-sm cursor-pointer">启用此规则</label>
                </div>
                
                <div class="flex justify-end gap-2">
                    <button type="button" onclick="closeEmailRuleModal()" class="btn bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg">取消</button>
                    <button type="submit" class="btn bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg">保存</button>
                </div>
            </form>
        </div>
    </div>

    <!-- 导航按钮 -->
    <div class="nav-buttons">
        <button onclick="scrollToTop()" class="nav-btn" title="回到顶部"><i class="ri-arrow-up-line"></i></button>
        <button onclick="scrollToBottom()" class="nav-btn" title="滚动到底部"><i class="ri-arrow-down-line"></i></button>
    </div>

    <script>
        let projects = [];
        let cronTasks = [];
        let currentTab = 'projects';
        let currentFilter = 'all';
        let externalIp = localStorage.getItem('externalIp') || '192.168.2.17';
        let enableLogs = localStorage.getItem('enableLogs') === 'true';

        document.getElementById('externalIp').value = externalIp;
        document.getElementById('enableLogs').checked = enableLogs;

        // Tab 切换
        // 系统资源监控变量（必须先声明）
        let realtimeTimer = null;
        let realtimeInterval = 2; // 秒
        let lastAlertTime = 0; // 上次警告时间
        let thresholds = { cpu: 80, memory: 85, disk: 90, interval: 60 };
        let desktopNotifyEnabled = false;

        function switchTab(tab) {
            currentTab = tab;
            document.querySelectorAll('.tab-btn').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.tab === tab);
            });
            document.getElementById('projectsTab').classList.toggle('hidden', tab !== 'projects');
            document.getElementById('systemTab').classList.toggle('hidden', tab !== 'system');
            document.getElementById('cronTab').classList.toggle('hidden', tab !== 'cron');
            
            // 切换Tab时关闭实时监控
            if (tab !== 'system' && realtimeTimer) {
                clearInterval(realtimeTimer);
                realtimeTimer = null;
                document.getElementById('realtimeCheck').checked = false;
                document.getElementById('showProcessListCheck').checked = false;
                document.getElementById('realtimeToggle').classList.remove('active');
                document.getElementById('realtimeIndicator').classList.add('hidden');
                document.getElementById('processListSection').classList.add('hidden');
            }
            
            if (tab === 'cron') {
                loadCronTasks();
            }
            if (tab === 'system') {
                loadThresholds();
                loadDesktopNotifySetting();
                renderEmailRules();
                loadSystemStats();
            }
        }

        // 加载阈值设置
        async function loadThresholds() {
            try {
                const response = await fetch('/api/alerts');
                const config = await response.json();
                
                if (config.thresholds) {
                    thresholds = config.thresholds;
                    document.getElementById('thresholdCpu').value = thresholds.cpu || 80;
                    document.getElementById('thresholdMemory').value = thresholds.memory || 85;
                    document.getElementById('thresholdDisk').value = thresholds.disk || 90;
                    document.getElementById('thresholdInterval').value = thresholds.interval || 60;
                }
            } catch (e) {
                console.error('加载阈值配置失败:', e);
            }
        }
        
        // 保存阈值设置
        async function saveThresholds() {
            thresholds.cpu = parseInt(document.getElementById('thresholdCpu').value) || 80;
            thresholds.memory = parseInt(document.getElementById('thresholdMemory').value) || 85;
            thresholds.disk = parseInt(document.getElementById('thresholdDisk').value) || 90;
            thresholds.interval = parseInt(document.getElementById('thresholdInterval').value) || 60;
            
            try {
                // 先获取现有配置
                let config = {};
                try {
                    const response = await fetch('/api/alerts');
                    config = await response.json();
                } catch (e) {}
                
                // 更新阈值
                config.thresholds = thresholds;
                
                // 保存到服务器
                await fetch('/api/alerts', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(config)
                });
            } catch (e) {
                console.error('保存阈值配置失败:', e);
            }
        }
        
        // 桌面通知开关（已在外部声明 desktopNotifyEnabled）
        
        function toggleDesktopNotify() {
            const checked = document.getElementById('enableDesktopNotify').checked;
            const status = document.getElementById('notifyStatus');
            
            if (checked) {
                // 检查浏览器支持
                if (!('Notification' in window)) {
                    status.textContent = '不支持';
                    status.className = 'text-xs text-red-500 ml-1';
                    document.getElementById('enableDesktopNotify').checked = false;
                    return;
                }
                
                // 检查权限状态
                if (Notification.permission === 'granted') {
                    desktopNotifyEnabled = true;
                    status.textContent = '已启用';
                    status.className = 'text-xs text-green-400 ml-1';
                    localStorage.setItem('desktopNotify', 'true');
                } else if (Notification.permission === 'denied') {
                    status.textContent = '已拒绝';
                    status.className = 'text-xs text-red-500 ml-1';
                    document.getElementById('enableDesktopNotify').checked = false;
                } else {
                    // 请求权限
                    Notification.requestPermission().then(permission => {
                        if (permission === 'granted') {
                            desktopNotifyEnabled = true;
                            status.textContent = '已启用';
                            status.className = 'text-xs text-green-400 ml-1';
                            localStorage.setItem('desktopNotify', 'true');
                        } else {
                            status.textContent = '已拒绝';
                            status.className = 'text-xs text-red-500 ml-1';
                            document.getElementById('enableDesktopNotify').checked = false;
                        }
                    });
                }
            } else {
                desktopNotifyEnabled = false;
                status.textContent = '';
                localStorage.setItem('desktopNotify', 'false');
            }
        }
        
        // 加载桌面通知设置
        function loadDesktopNotifySetting() {
            const saved = localStorage.getItem('desktopNotify');
            if (saved === 'true' && Notification.permission === 'granted') {
                desktopNotifyEnabled = true;
                document.getElementById('enableDesktopNotify').checked = true;
                document.getElementById('notifyStatus').textContent = '已启用';
                document.getElementById('notifyStatus').className = 'text-xs text-green-400 ml-1';
            }
        }
        
        // 发送桌面通知
        function sendDesktopNotification(warnings) {
            if (!desktopNotifyEnabled || Notification.permission !== 'granted') return;
            
            const notification = new Notification('⚠️ 系统资源告警', {
                body: warnings.join('\\n'),
                icon: 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">📊</text></svg>',
                tag: 'system-alert',
                requireInteraction: false
            });
            
            // 点击后聚焦窗口
            notification.onclick = () => {
                window.focus();
                notification.close();
            };
            
            // 5秒后自动关闭
            setTimeout(() => notification.close(), 5000);
        }
        
        // 检查阈值并弹窗警告
        function checkThresholds(data) {
            const now = Date.now();
            const intervalMs = thresholds.interval * 1000;
            
            // 检查是否超过间隔时间
            if (now - lastAlertTime < intervalMs) return;
            
            const warnings = [];
            
            if (data.cpu.percent >= thresholds.cpu) {
                warnings.push(`CPU使用率 ${data.cpu.percent.toFixed(1)}% 超过阈值 ${thresholds.cpu}%`);
            }
            if (data.memory.percent >= thresholds.memory) {
                warnings.push(`内存使用率 ${data.memory.percent.toFixed(1)}% 超过阈值 ${thresholds.memory}%`);
            }
            if (data.disk.percent >= thresholds.disk) {
                warnings.push(`磁盘使用率 ${data.disk.percent.toFixed(1)}% 超过阈值 ${thresholds.disk}%`);
            }
            
            if (warnings.length > 0) {
                lastAlertTime = now;
                showAlertPopup(warnings);
            }
            
            // 检查邮件通知规则
            checkEmailRules(data);
        }
        
        // 显示警告弹窗（同时发送桌面通知）
        function showAlertPopup(warnings) {
            // 发送桌面通知
            sendDesktopNotification(warnings);
            
            // 移除已有弹窗
            const existing = document.querySelector('.alert-popup');
            if (existing) existing.remove();
            
            const popup = document.createElement('div');
            popup.className = 'alert-popup';
            popup.innerHTML = `
                <span class="alert-close" onclick="this.parentElement.remove()"><i class="ri-close-line"></i></span>
                <div class="alert-title"><i class="ri-alarm-warning-line"></i> 资源告警</div>
                <div class="alert-content">${warnings.map(w => `<p>⚠️ ${w}</p>`).join('')}</div>
            `;
            document.body.appendChild(popup);
            
            // 自动关闭（10秒后）
            setTimeout(() => {
                popup.style.animation = 'slideOut 0.3s ease-out forwards';
                setTimeout(() => popup.remove(), 300);
            }, 10000);
        }
        
        function updateRealtimeInterval() {
            realtimeInterval = parseInt(document.getElementById('realtimeIntervalSelect').value) || 2;
            // 如果正在监控，重新启动定时器
            if (realtimeTimer) {
                clearInterval(realtimeTimer);
                realtimeTimer = setInterval(loadSystemStats, realtimeInterval * 1000);
            }
        }
        
        async function loadSystemStats() {
            try {
                const res = await fetch('/api/system/stats');
                const data = await res.json();
                
                if (!data.available) {
                    document.getElementById('sysUptime').textContent = 'psutil未安装';
                    document.getElementById('sysBootTime').textContent = '-';
                    return;
                }
                
                // 系统概览
                document.getElementById('sysUptime').textContent = data.system.uptime;
                document.getElementById('sysBootTime').textContent = data.system.boot_time;
                document.getElementById('sysProcessCount').textContent = data.system.process_count;
                document.getElementById('sysCpuCount').textContent = data.cpu.logical + '核';
                
                // CPU
                document.getElementById('cpuPercent').textContent = data.cpu.percent.toFixed(1) + '%';
                document.getElementById('cpuBar').style.width = data.cpu.percent + '%';
                document.getElementById('cpuPhysical').textContent = data.cpu.count;
                document.getElementById('cpuLogical').textContent = data.cpu.logical;
                // CPU 温度
                if (data.cpu.temp_package_0 !== null) {
                    document.getElementById('cpuTemp0').textContent = data.cpu.temp_package_0.toFixed(1);
                }
                if (data.cpu.temp_package_1 !== null) {
                    document.getElementById('cpuTemp1').textContent = data.cpu.temp_package_1.toFixed(1);
                }
                
                // 内存
                document.getElementById('memPercent').textContent = data.memory.percent.toFixed(1) + '%';
                document.getElementById('memBar').style.width = data.memory.percent + '%';
                document.getElementById('memUsed').textContent = data.memory.used_gb;
                document.getElementById('memTotal').textContent = data.memory.total_gb;
                document.getElementById('memAvailable').textContent = data.memory.available_gb;
                
                // 磁盘
                document.getElementById('diskPercent').textContent = data.disk.percent.toFixed(1) + '%';
                document.getElementById('diskBar').style.width = data.disk.percent + '%';
                document.getElementById('diskUsed').textContent = data.disk.used_gb;
                document.getElementById('diskTotal').textContent = data.disk.total_gb;
                document.getElementById('diskFree').textContent = data.disk.free_gb;
                
                // 网络 - 累计流量
                document.getElementById('netSent').textContent = data.network.sent_mb;
                document.getElementById('netRecv').textContent = data.network.recv_mb;
                // 实时网速
                document.getElementById('netSentSpeed').textContent = data.network.sent_speed || '-';
                document.getElementById('netRecvSpeed').textContent = data.network.recv_speed || '-';
                
                // 检查阈值
                checkThresholds(data);
                
                // 如果实时监控和进程列表都开启，加载进程列表
                if (document.getElementById('realtimeCheck').checked && document.getElementById('showProcessListCheck').checked) {
                    loadTopProcesses();
                }
                
            } catch (e) {
                console.error('系统资源加载失败:', e);
            }
        }
        
        // 加载CPU占用最高的进程
        async function loadTopProcesses() {
            try {
                const limit = document.getElementById('processLimitSelect').value;
                const res = await fetch(`/api/system/processes?limit=${limit}`);
                const data = await res.json();
                
                if (!data.available) {
                    document.getElementById('processList').innerHTML = '<div class="text-red-400 text-center py-4">无法获取进程信息</div>';
                    return;
                }
                
                const list = document.getElementById('processList');
                if (data.processes.length === 0) {
                    list.innerHTML = '<div class="text-gray-500 text-center py-4">暂无进程数据</div>';
                    return;
                }
                
                list.innerHTML = data.processes.map(proc => `
                    <div class="card rounded-lg p-3 hover:border-gray-500 transition-colors">
                        <div class="flex items-center justify-between">
                            <div class="flex items-center gap-3 min-w-0">
                                <span class="text-purple-400 font-mono text-sm">${proc.pid}</span>
                                <div class="min-w-0 flex-1">
                                    <div class="text-gray-200 truncate">${proc.name}</div>
                                    <div class="text-gray-500 text-xs truncate">${proc.cmdline || proc.exe}</div>
                                </div>
                            </div>
                            <div class="flex items-center gap-4 text-xs shrink-0">
                                <div class="text-right">
                                    <span class="text-blue-400 font-bold">${proc.cpu.toFixed(1)}%</span>
                                    <span class="text-gray-500 ml-1">CPU</span>
                                </div>
                                <div class="text-right">
                                    <span class="text-green-400 font-bold">${proc.memory.toFixed(1)}%</span>
                                    <span class="text-gray-500 ml-1">内存</span>
                                </div>
                                <div class="text-gray-400">${proc.create_time}</div>
                            </div>
                        </div>
                    </div>
                `).join('');
            } catch (e) {
                console.error('进程列表加载失败:', e);
            }
        }
        
        function toggleRealtime() {
            const checked = document.getElementById('realtimeCheck').checked;
            const toggle = document.getElementById('realtimeToggle');
            const indicator = document.getElementById('realtimeIndicator');
            
            if (checked) {
                toggle.classList.add('active');
                indicator.classList.remove('hidden');
                // 立即加载一次
                loadSystemStats();
                // 启动定时器
                realtimeTimer = setInterval(loadSystemStats, realtimeInterval * 1000);
                // 如果进程列表开关也开启，则显示并加载
                if (document.getElementById('showProcessListCheck').checked) {
                    document.getElementById('processListSection').classList.remove('hidden');
                    loadTopProcesses();
                }
            } else {
                toggle.classList.remove('active');
                indicator.classList.add('hidden');
                document.getElementById('processListSection').classList.add('hidden');
                // 停止定时器
                if (realtimeTimer) {
                    clearInterval(realtimeTimer);
                    realtimeTimer = null;
                }
            }
        }
        
        function toggleProcessList() {
            const checked = document.getElementById('showProcessListCheck').checked;
            const realtimeChecked = document.getElementById('realtimeCheck').checked;
            const processSection = document.getElementById('processListSection');
            
            if (checked && realtimeChecked) {
                processSection.classList.remove('hidden');
                loadTopProcesses();
            } else {
                processSection.classList.add('hidden');
            }
        }

        // IP 保存
        async function saveExternalIp() {
            externalIp = document.getElementById('externalIp').value.trim();
            
            try {
                await fetch('/api/web-settings', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ external_ip: externalIp, refresh_interval: parseInt(document.getElementById('refreshInterval').value) || 30 })
                });
            } catch (e) {
                console.error('保存失败:', e);
            }
            
            renderProjects();
        }

        // 项目加载和渲染（保持原有逻辑）
        async function loadProjects() {
            try {
                const res = await fetch('/api/projects');
                const data = await res.json();
                projects = data.projects;
                renderProjects();
                updateStats();
                updateConnectionStatus(true);
                
                // 同时检查系统资源阈值（主页也有效）
                loadThresholds();
                fetch('/api/system/stats').then(r => r.json()).then(sysData => {
                    if (sysData.available) checkThresholds(sysData);
                }).catch(() => {});
                
            } catch (e) {
                console.error('加载失败:', e);
                updateConnectionStatus(false);
            }
        }

        function updateStats() {
            const webProjects = projects.filter(p => p.type === 'web');
            const runningProjects = webProjects.filter(p => p.status?.status === 'running');
            const cronProjects = projects.filter(p => p.type === 'cron');
            const cliProjects = projects.filter(p => p.type === 'cli' || p.type === 'extension');
            
            document.getElementById('webCount').textContent = webProjects.length;
            document.getElementById('runningCount').textContent = `${runningProjects.length}/${webProjects.length}`;
            document.getElementById('cronCount').textContent = cronProjects.length;
            document.getElementById('cliCount').textContent = cliProjects.length;
            document.getElementById('updateTime').textContent = new Date().toLocaleTimeString();
        }

        function filterType(type) {
            currentFilter = type;
            document.querySelectorAll('.filter-bar-btn').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.type === type);
            });
            renderProjects();
        }

        function renderProjects() {
            const list = document.getElementById('projectsList');
            
            // 合合自定义服务到项目列表
            const customServices = getCustomServices();
            const allProjects = [...projects, ...customServices];
            
            const filtered = currentFilter === 'all' ? allProjects : allProjects.filter(p => p.type === currentFilter || (currentFilter === 'web' && p.isCustom));
            
            if (filtered.length === 0) {
                list.innerHTML = '<div class="text-center py-12 text-gray-400"><i class="ri-folder-open-line text-4xl"></i><p class="mt-2">暂无项目</p></div>';
                return;
            }

            const activeStatus = getActiveStatus();
            const grouped = {};
            filtered.forEach(p => {
                const groupType = p.isCustom ? 'custom' : p.type;
                if (!grouped[groupType]) grouped[groupType] = [];
                grouped[groupType].push(p);
            });

            let html = '';
            const typeNames = {
                'web': { name: 'Web服务', icon: 'ri-server-line', color: 'blue' },
                'custom': { name: '自定义服务', icon: 'ri-global-line', color: 'cyan' },
                'cron': { name: 'Cron任务', icon: 'ri-time-line', color: 'yellow' },
                'cli': { name: 'CLI工具', icon: 'ri-terminal-box-line', color: 'purple' },
                'extension': { name: '浏览器插件', icon: 'ri-chrome-line', color: 'pink' }
            };

            for (const [type, projs] of Object.entries(grouped)) {
                const typeInfo = typeNames[type] || { name: type, icon: 'ri-folder-line', color: 'gray' };
                
                if (type === 'web') {
                    const activeProjs = projs.filter(p => activeStatus[p.id] !== false);
                    const archivedProjs = projs.filter(p => activeStatus[p.id] === false);
                    
                    if (activeProjs.length > 0) {
                        html += `<div class="mb-6"><h2 class="text-lg font-semibold text-gray-300 mb-3 flex items-center gap-2"><i class="${typeInfo.icon} text-${typeInfo.color}-400"></i>${typeInfo.name}<span class="text-sm text-gray-500">(${activeProjs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">`;
                        activeProjs.forEach(p => { html += renderProjectCard(p, true); });
                        html += '</div></div>';
                    }
                    
                    if (archivedProjs.length > 0) {
                        html += `<div class="mb-6 opacity-60"><h2 class="text-lg font-semibold text-gray-400 mb-3 flex items-center gap-2"><i class="ri-archive-line text-gray-400"></i>归档服务<span class="text-sm text-gray-500">(${archivedProjs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">`;
                        archivedProjs.forEach(p => { html += renderProjectCard(p, false); });
                        html += '</div></div>';
                    }
                } else {
                    html += `<div class="mb-6"><h2 class="text-lg font-semibold text-gray-300 mb-3 flex items-center gap-2"><i class="${typeInfo.icon} text-${typeInfo.color}-400"></i>${typeInfo.name}<span class="text-sm text-gray-500">(${projs.length})</span></h2><div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">`;
                    projs.forEach(p => { html += renderProjectCard(p, true); });
                    html += '</div></div>';
                }
            }
            list.innerHTML = html;
        }

        function getActiveStatus() {
            const stored = localStorage.getItem('serviceActiveStatus');
            return stored ? JSON.parse(stored) : {};
        }

        function toggleServiceActive(id) {
            const status = getActiveStatus();
            status[id] = status[id] === false ? true : false;
            localStorage.setItem('serviceActiveStatus', JSON.stringify(status));
            renderProjects();
        }

        function renderProjectCard(p, isActive = true) {
            // 自定义服务特殊处理
            if (p.isCustom) {
                return `
                    <div class="card rounded-lg p-3 hover:border-cyan-500 transition-colors border-cyan-500/30">
                        <div class="flex items-center justify-between mb-2">
                            <div class="flex items-center gap-2">
                                <div class="status-dot bg-cyan-400" style="box-shadow: 0 0 8px #22d3ee;" title="外部服务"></div>
                                <h3 class="font-semibold text-sm truncate">${p.name}</h3>
                            </div>
                            <span class="text-xs text-cyan-400">外部</span>
                        </div>
                        <p class="text-gray-400 text-xs mb-2 truncate">${p.description || ''}</p>
                        <div class="flex items-center gap-1 text-xs flex-wrap">
                            <a href="${p.url}" target="_blank" class="px-2 py-0.5 rounded bg-cyan-500/20 text-cyan-400 hover:bg-cyan-500/30">访问</a>
                            ${p.admin_url ? `<a href="${p.admin_url}" target="_blank" class="text-yellow-400 hover:text-yellow-300 ml-1">后台</a>` : ''}
                            <button onclick="deleteCustomService('${p.id}')" class="text-red-400 hover:text-red-300 ml-2" title="删除服务"><i class="ri-delete-bin-line"></i></button>
                        </div>
                    </div>
                `;
            }
            
            const statusInfo = getStatusInfo(p.status?.status);
            
            // 处理后台链接：将 localhost 替换为 externalIp
            let adminUrl = p.admin_url || '';
            if (adminUrl && adminUrl.includes('localhost')) {
                adminUrl = adminUrl.replace(/localhost/g, externalIp);
            }
            
            // 获取自定义链接
            const customLinks = getCustomLinks(p.id);
            
            return `
                <div class="card rounded-lg p-3 hover:border-gray-500 transition-colors">
                    <div class="flex items-center justify-between mb-2">
                        <div class="flex items-center gap-2">
                            <div class="status-dot ${statusInfo.class}" title="${statusInfo.text}"></div>
                            <h3 class="font-semibold text-sm truncate">${p.name}</h3>
                        </div>
                        <span class="text-xs ${statusInfo.textColor}">${statusInfo.text}</span>
                    </div>
                    ${p.ports && p.ports.length > 0 ? `
                    <div class="flex items-center gap-1 text-xs mb-2 flex-wrap">
                        ${p.ports.map(port => {
                            const portStatus = p.status?.ports?.[port];
                            const isRunning = portStatus?.running;
                            return `<a href="http://${externalIp}:${port}" target="_blank" class="px-2 py-0.5 rounded ${isRunning ? 'bg-green-500/20 text-green-400 hover:bg-green-500/30' : 'bg-red-500/20 text-red-400'}">${port}</a>`;
                        }).join('')}
                        ${adminUrl ? `<a href="${adminUrl}" target="_blank" class="text-yellow-400 hover:text-yellow-300 ml-1">后台</a>` : ''}
                        ${customLinks.map(link => `<a href="${link.url}" target="_blank" class="text-cyan-400 hover:text-cyan-300 ml-1">${link.name}</a>`).join('')}
                        <button onclick="showAddLinkModal('${p.id}', '${p.name}')" class="text-green-400 hover:text-green-300 ml-1" title="添加自定义链接"><i class="ri-add-line"></i></button>
                    </div>
                    ` : ''}
                    ${p.type === 'web' ? `
                    <div class="flex items-center justify-between mt-2">
                        <div class="flex items-center gap-1">
                            ${(p.status?.status === 'running' || p.status?.status === 'partial') ? `
                                <button onclick="stopProject('${p.id}')" class="btn bg-red-600 hover:bg-red-700 px-2 py-0.5 rounded text-xs">停止</button>
                                <button onclick="restartProject('${p.id}')" class="btn bg-yellow-600 hover:bg-yellow-700 px-2 py-0.5 rounded text-xs">重启</button>
                            ` : `<button onclick="startProject('${p.id}')" class="btn bg-green-600 hover:bg-green-700 px-2 py-0.5 rounded text-xs">启动</button>`}
                            <button onclick="viewLog('${p.id}')" class="btn bg-gray-600 hover:bg-gray-700 px-2 py-0.5 rounded text-xs">日志</button>
                        </div>
                        <button onclick="toggleServiceActive('${p.id}')" class="text-xs ${isActive ? 'text-green-400 hover:text-green-300' : 'text-gray-400 hover:text-gray-300'}" title="${isActive ? '点击归档' : '点击激活'}">
                            <i class="ri-${isActive ? 'checkbox-circle' : 'archive'}-line"></i>
                        </button>
                    </div>
                    ` : ''}
                </div>
            `;
        }
        
        // ==================== 自定义链接管理 ====================
        
        function getCustomLinks(projectId) {
            const stored = localStorage.getItem('customLinks_' + projectId);
            return stored ? JSON.parse(stored) : [];
        }
        
        function saveCustomLinks(projectId, links) {
            localStorage.setItem('customLinks_' + projectId, JSON.stringify(links));
        }
        
        function showAddLinkModal(projectId, projectName) {
            document.getElementById('linkModalTitle').textContent = `${projectName} - 添加链接`;
            document.getElementById('linkProjectId').value = projectId;
            document.getElementById('linkName').value = '';
            document.getElementById('linkPort').value = '';
            document.getElementById('linkPath').value = '';
            document.getElementById('linkFullUrl').value = '';
            document.getElementById('linkModal').classList.remove('hidden');
            
            // 显示已有的自定义链接（供删除）
            const existingLinks = getCustomLinks(projectId);
            const existingList = document.getElementById('existingLinks');
            if (existingLinks.length > 0) {
                existingList.innerHTML = '<div class="text-gray-400 text-sm mb-2">已有链接：</div>' +
                    existingLinks.map(link => `
                        <div class="flex items-center justify-between bg-gray-700 px-2 py-1 rounded mb-1">
                            <span class="text-cyan-400 text-sm">${link.name}: ${link.url}</span>
                            <button onclick="deleteCustomLink('${projectId}', '${link.id}')" class="text-red-400 hover:text-red-300"><i class="ri-delete-bin-line"></i></button>
                        </div>
                    `).join('');
            } else {
                existingList.innerHTML = '';
            }
        }
        
        function closeLinkModal() {
            document.getElementById('linkModal').classList.add('hidden');
        }
        
        function saveCustomLink(event) {
            event.preventDefault();
            
            const projectId = document.getElementById('linkProjectId').value;
            const name = document.getElementById('linkName').value.trim();
            const port = document.getElementById('linkPort').value.trim();
            const path = document.getElementById('linkPath').value.trim();
            const fullUrl = document.getElementById('linkFullUrl').value.trim();
            
            if (!name) {
                alert('请输入链接名称');
                return;
            }
            
            let url;
            if (fullUrl) {
                url = fullUrl;
            } else if (port) {
                url = `http://${externalIp}:${port}${path || ''}`;
            } else {
                alert('请输入端口或完整URL');
                return;
            }
            
            const links = getCustomLinks(projectId);
            const newLink = {
                id: Date.now().toString(),
                name: name,
                url: url
            };
            links.push(newLink);
            saveCustomLinks(projectId, links);
            
            closeLinkModal();
            renderProjects();
        }
        
        function deleteCustomLink(projectId, linkId) {
            if (!confirm('确定删除此链接？')) return;
            
            const links = getCustomLinks(projectId);
            const filtered = links.filter(l => l.id !== linkId);
            saveCustomLinks(projectId, filtered);
            
            // 更新模态框中的显示
            const existingLinks = getCustomLinks(projectId);
            const existingList = document.getElementById('existingLinks');
            if (existingLinks.length > 0) {
                existingList.innerHTML = '<div class="text-gray-400 text-sm mb-2">已有链接：</div>' +
                    existingLinks.map(link => `
                        <div class="flex items-center justify-between bg-gray-700 px-2 py-1 rounded mb-1">
                            <span class="text-cyan-400 text-sm">${link.name}: ${link.url}</span>
                            <button onclick="deleteCustomLink('${projectId}', '${link.id}')" class="text-red-400 hover:text-red-300"><i class="ri-delete-bin-line"></i></button>
                        </div>
                    `).join('');
            } else {
                existingList.innerHTML = '<div class="text-gray-500 text-sm">暂无自定义链接</div>';
            }
            
            renderProjects();
        }
        
        // 自动生成URL预览
        function updateLinkPreview() {
            const port = document.getElementById('linkPort').value.trim();
            const path = document.getElementById('linkPath').value.trim();
            const preview = document.getElementById('linkPreview');
            
            if (port) {
                preview.textContent = `预览: http://${externalIp}:${port}${path || ''}`;
            } else {
                preview.textContent = '';
            }
        }

        // ==================== 自定义服务管理 ====================
        
        function getCustomServices() {
            const stored = localStorage.getItem('customServices');
            return stored ? JSON.parse(stored) : [];
        }
        
        function saveCustomServices(services) {
            localStorage.setItem('customServices', JSON.stringify(services));
        }
        
        // ==================== 邮件通知规则管理 ====================
        
        function getEmailRules() {
            const stored = localStorage.getItem('emailNotifyRules');
            return stored ? JSON.parse(stored) : [];
        }
        
        function saveEmailRules(rules) {
            localStorage.setItem('emailNotifyRules', JSON.stringify(rules));
        }
        
        function renderEmailRules() {
            const rules = getEmailRules();
            const list = document.getElementById('emailRulesList');
            
            if (rules.length === 0) {
                list.innerHTML = '<div class="text-gray-500 text-center py-4">暂无邮件通知规则</div>';
                return;
            }
            
            const resourceNames = { 'cpu': 'CPU', 'memory': '内存', 'disk': '磁盘' };
            
            list.innerHTML = rules.map(rule => {
                const silentInfo = rule.silentEnabled ? `静默:${rule.silentStart}-${rule.silentEnd}` : '';
                const triggerType = rule.triggerType || 'single';
                const triggerParam = rule.triggerParam || 3;
                let triggerDisplay = '一次性';
                if (triggerType === 'continuous') triggerDisplay = `连续${triggerParam}次`;
                else if (triggerType === 'duration') triggerDisplay = `持续${triggerParam}秒`;
                else if (triggerType === 'average') triggerDisplay = `平均${triggerParam}次`;
                
                return `
                <div class="flex items-center justify-between bg-gray-700/50 px-3 py-2 rounded-lg">
                    <div class="flex items-center gap-3 flex-wrap">
                        <span class="px-2 py-0.5 rounded text-xs ${rule.enabled ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'}">${rule.enabled ? '启用' : '禁用'}</span>
                        <span class="text-gray-200">${rule.name}</span>
                        <span class="text-gray-400 text-xs">${resourceNames[rule.resource]} ≥ ${rule.threshold}%</span>
                        <span class="text-blue-400/70 text-xs"><i class="ri-flashlight-line"></i> ${triggerDisplay}</span>
                        <span class="text-gray-400 text-xs">→ ${rule.email}</span>
                        ${silentInfo ? `<span class="text-yellow-400/70 text-xs"><i class="ri-moon-line"></i> ${silentInfo}</span>` : ''}
                    </div>
                    <div class="flex items-center gap-1">
                        <button onclick="editEmailRule('${rule.id}')" class="text-blue-400 hover:text-blue-300 px-1" title="编辑"><i class="ri-edit-line"></i></button>
                        <button onclick="deleteEmailRule('${rule.id}')" class="text-red-400 hover:text-red-300 px-1" title="删除"><i class="ri-delete-bin-line"></i></button>
                    </div>
                </div>
            `;
            }).join('');
        }
        
        function showAddEmailRuleModal() {
            document.getElementById('emailRuleModalTitle').textContent = '添加邮件通知规则';
            document.getElementById('emailRuleId').value = '';
            document.getElementById('emailRuleName').value = '';
            document.getElementById('emailRuleResource').value = 'cpu';
            document.getElementById('emailRuleThreshold').value = 80;
            document.getElementById('emailRuleTriggerType').value = 'single';
            document.getElementById('emailRuleTriggerParam').value = 3;
            document.getElementById('triggerParamsDiv').style.display = 'none';
            document.getElementById('emailRuleAddress').value = '';
            document.getElementById('emailRuleInterval').value = 300;
            document.getElementById('emailRuleSilentStart').value = '23:00';
            document.getElementById('emailRuleSilentEnd').value = '08:00';
            document.getElementById('emailRuleSilentEnabled').checked = false;
            document.getElementById('emailRuleEnabled').checked = true;
            document.getElementById('emailRuleModal').classList.remove('hidden');
        }
        
        function closeEmailRuleModal() {
            document.getElementById('emailRuleModal').classList.add('hidden');
        }
        
        function updateTriggerParams() {
            const triggerType = document.getElementById('emailRuleTriggerType').value;
            const paramsDiv = document.getElementById('triggerParamsDiv');
            const labelEl = document.getElementById('triggerParamsLabel');
            const unitEl = document.getElementById('triggerParamUnit');
            const hintEl = document.getElementById('triggerParamHint');
            const paramInput = document.getElementById('emailRuleTriggerParam');
            
            // 根据触发类型设置不同的参数
            switch (triggerType) {
                case 'single':
                    paramsDiv.style.display = 'none';
                    break;
                case 'continuous':
                    paramsDiv.style.display = 'flex';
                    labelEl.textContent = '连续次数';
                    unitEl.textContent = '次';
                    hintEl.textContent = '连续超过阈值多少次才触发';
                    paramInput.max = 100;
                    paramInput.value = 3;
                    break;
                case 'duration':
                    paramsDiv.style.display = 'flex';
                    labelEl.textContent = '持续时间';
                    unitEl.textContent = '秒';
                    hintEl.textContent = '累计超过阈值多少秒才触发';
                    paramInput.max = 3600;
                    paramInput.value = 60;
                    break;
                case 'average':
                    paramsDiv.style.display = 'flex';
                    labelEl.textContent = '采样次数';
                    unitEl.textContent = '次';
                    hintEl.textContent = '最近N次采样的平均值超过阈值才触发';
                    paramInput.max = 100;
                    paramInput.value = 5;
                    break;
            }
        }
        
        function editEmailRule(ruleId) {
            const rules = getEmailRules();
            const rule = rules.find(r => r.id === ruleId);
            if (!rule) return;
            
            document.getElementById('emailRuleModalTitle').textContent = '编辑邮件通知规则';
            document.getElementById('emailRuleId').value = rule.id;
            document.getElementById('emailRuleName').value = rule.name;
            document.getElementById('emailRuleResource').value = rule.resource;
            document.getElementById('emailRuleThreshold').value = rule.threshold;
            document.getElementById('emailRuleTriggerType').value = rule.triggerType || 'single';
            document.getElementById('emailRuleTriggerParam').value = rule.triggerParam || 3;
            updateTriggerParams();
            document.getElementById('emailRuleAddress').value = rule.email;
            document.getElementById('emailRuleInterval').value = rule.interval;
            document.getElementById('emailRuleSilentStart').value = rule.silentStart || '23:00';
            document.getElementById('emailRuleSilentEnd').value = rule.silentEnd || '08:00';
            document.getElementById('emailRuleSilentEnabled').checked = rule.silentEnabled || false;
            document.getElementById('emailRuleEnabled').checked = rule.enabled;
            document.getElementById('emailRuleModal').classList.remove('hidden');
        }
        
        function saveEmailRule(event) {
            event.preventDefault();
            
            const ruleId = document.getElementById('emailRuleId').value;
            const name = document.getElementById('emailRuleName').value.trim();
            const resource = document.getElementById('emailRuleResource').value;
            const threshold = parseInt(document.getElementById('emailRuleThreshold').value);
            const triggerType = document.getElementById('emailRuleTriggerType').value;
            const triggerParam = parseInt(document.getElementById('emailRuleTriggerParam').value) || 3;
            const email = document.getElementById('emailRuleAddress').value.trim();
            const interval = parseInt(document.getElementById('emailRuleInterval').value) || 300;
            const silentStart = document.getElementById('emailRuleSilentStart').value;
            const silentEnd = document.getElementById('emailRuleSilentEnd').value;
            const silentEnabled = document.getElementById('emailRuleSilentEnabled').checked;
            const enabled = document.getElementById('emailRuleEnabled').checked;
            
            if (!name || !email) {
                alert('请填写完整信息');
                return;
            }
            
            const rules = getEmailRules();
            
            if (ruleId) {
                // 编辑
                const idx = rules.findIndex(r => r.id === ruleId);
                if (idx >= 0) {
                    rules[idx] = { ...rules[idx], name, resource, threshold, triggerType, triggerParam, email, interval, silentStart, silentEnd, silentEnabled, enabled };
                }
            } else {
                // 新增
                rules.push({
                    id: 'rule_' + Date.now(),
                    name, resource, threshold, triggerType, triggerParam, email, interval, silentStart, silentEnd, silentEnabled, enabled,
                    lastSent: 0,
                    exceedCount: 0,  // 连续超过次数
                    exceedDuration: 0,  // 累计超过时间
                    recentValues: []  // 最近采样值
                });
            }
            
            saveEmailRules(rules);
            closeEmailRuleModal();
            renderEmailRules();
        }
        
        function deleteEmailRule(ruleId) {
            if (!confirm('确定删除此邮件通知规则？')) return;
            const rules = getEmailRules().filter(r => r.id !== ruleId);
            saveEmailRules(rules);
            renderEmailRules();
        }
        
        // 检查邮件规则并发送通知
        async function checkEmailRules(data) {
            const rules = getEmailRules().filter(r => r.enabled);
            const now = Date.now();
            const nowTime = new Date();
            const currentHour = nowTime.getHours();
            const currentMin = nowTime.getMinutes();
            const currentTimeStr = `${currentHour.toString().padStart(2, '0')}:${currentMin.toString().padStart(2, '0')}`;
            
            for (const rule of rules) {
                // 检查间隔
                if (now - rule.lastSent < rule.interval * 1000) continue;
                
                // 检查静默时间
                if (rule.silentEnabled && rule.silentStart && rule.silentEnd) {
                    const start = rule.silentStart;
                    const end = rule.silentEnd;
                    
                    // 处理跨越午夜的情况（如23:00到08:00）
                    let inSilentPeriod = false;
                    if (start > end) {
                        // 跨越午夜：如23:00-08:00，则currentTime >= start 或 currentTime < end 都在静默区间
                        inSilentPeriod = (currentTimeStr >= start || currentTimeStr < end);
                    } else {
                        // 同一天内：如01:00-06:00
                        inSilentPeriod = (currentTimeStr >= start && currentTimeStr < end);
                    }
                    
                    if (inSilentPeriod) {
                        console.log(`规则 ${rule.name} 当前在静默时间段 ${start}-${end}，跳过发送`);
                        continue;
                    }
                }
                
                // 检查阈值和触发机制
                const value = data[rule.resource]?.percent;
                if (value === undefined) continue;
                
                // 初始化触发相关数据
                if (!rule.exceedCount) rule.exceedCount = 0;
                if (!rule.exceedDuration) rule.exceedDuration = 0;
                if (!rule.recentValues) rule.recentValues = [];
                if (!rule.triggerType) rule.triggerType = 'single';
                if (!rule.triggerParam) rule.triggerParam = 3;
                
                const triggerType = rule.triggerType;
                const triggerParam = rule.triggerParam;
                const isExceedThreshold = value >= rule.threshold;
                
                let shouldTrigger = false;
                
                // 根据触发类型判断
                switch (triggerType) {
                    case 'single':
                        // 一次性触发：超过阈值立即触发
                        if (isExceedThreshold) shouldTrigger = true;
                        break;
                    
                    case 'continuous':
                        // 连续触发：连续超过阈值N次
                        if (isExceedThreshold) {
                            rule.exceedCount++;
                            if (rule.exceedCount >= triggerParam) {
                                shouldTrigger = true;
                                rule.exceedCount = 0; // 触发后重置计数
                            }
                        } else {
                            rule.exceedCount = 0; // 未超过阈值时重置计数
                        }
                        break;
                    
                    case 'duration':
                        // 持续时间触发：累计超过阈值N秒
                        if (isExceedThreshold) {
                            // 增加累计时间（假设实时监控间隔为2秒）
                            const intervalSeconds = realtimeInterval || 2;
                            rule.exceedDuration += intervalSeconds;
                            if (rule.exceedDuration >= triggerParam) {
                                shouldTrigger = true;
                                rule.exceedDuration = 0; // 触发后重置
                            }
                        } else {
                            rule.exceedDuration = 0; // 未超过阈值时重置累计时间
                        }
                        break;
                    
                    case 'average':
                        // 平均值触发：最近N次平均值超过阈值
                        rule.recentValues.push(value);
                        if (rule.recentValues.length > triggerParam) {
                            rule.recentValues.shift(); // 保持最近N个值
                        }
                        if (rule.recentValues.length >= triggerParam) {
                            const avg = rule.recentValues.reduce((a, b) => a + b, 0) / rule.recentValues.length;
                            if (avg >= rule.threshold) {
                                shouldTrigger = true;
                            }
                        }
                        break;
                }
                
                // 保存更新的规则数据
                saveEmailRules(getEmailRules().map(r => r.id === rule.id ? rule : r));
                
                if (!shouldTrigger) continue;
                
                // 发送邮件
                try {
                    const res = await fetch('/api/email/send', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                            to: rule.email,
                            subject: `[系统告警] ${rule.name}`,
                            body: `${rule.name} 触发告警\n\n${rule.resource.toUpperCase()} 使用率: ${value.toFixed(1)}%\n阈值: ${rule.threshold}%\n时间: ${new Date().toLocaleString()}\n\n请及时处理。`
                        })
                    });
                    
                    if (res.ok) {
                        // 更新发送时间
                        rule.lastSent = now;
                        saveEmailRules(getEmailRules().map(r => r.id === rule.id ? rule : r));
                    }
                } catch (e) {
                    console.error('邮件发送失败:', e);
                }
            }
        }
        
        function showAddServiceModal() {
            document.getElementById('serviceName').value = '';
            document.getElementById('servicePort').value = '';
            document.getElementById('servicePath').value = '';
            document.getElementById('serviceFullUrl').value = '';
            document.getElementById('serviceAdminUrl').value = '';
            document.getElementById('serviceDesc').value = '';
            document.getElementById('servicePreview').textContent = '';
            document.getElementById('addServiceModal').classList.remove('hidden');
        }
        
        function closeAddServiceModal() {
            document.getElementById('addServiceModal').classList.add('hidden');
        }
        
        function updateServicePreview() {
            const port = document.getElementById('servicePort').value.trim();
            const path = document.getElementById('servicePath').value.trim();
            const preview = document.getElementById('servicePreview');
            
            if (port) {
                preview.textContent = `预览: http://${externalIp}:${port}${path || ''}`;
            } else {
                preview.textContent = '';
            }
        }
        
        function saveCustomService(event) {
            event.preventDefault();
            
            const name = document.getElementById('serviceName').value.trim();
            const port = document.getElementById('servicePort').value.trim();
            const path = document.getElementById('servicePath').value.trim();
            const fullUrl = document.getElementById('serviceFullUrl').value.trim();
            const adminUrl = document.getElementById('serviceAdminUrl').value.trim();
            const desc = document.getElementById('serviceDesc').value.trim();
            
            if (!name) {
                alert('请输入服务名称');
                return;
            }
            
            let mainUrl;
            if (fullUrl) {
                mainUrl = fullUrl;
            } else if (port) {
                mainUrl = `http://${externalIp}:${port}${path || ''}`;
            } else {
                alert('请输入端口或完整URL');
                return;
            }
            
            const services = getCustomServices();
            const newService = {
                id: 'custom_' + Date.now(),
                name: name,
                url: mainUrl,
                admin_url: adminUrl || null,
                description: desc || '自定义外部服务',
                type: 'custom',
                isCustom: true
            };
            services.push(newService);
            saveCustomServices(services);
            
            closeAddServiceModal();
            loadProjects();
        }
        
        function deleteCustomService(serviceId) {
            if (!confirm('确定删除此自定义服务？')) return;
            
            const services = getCustomServices();
            const filtered = services.filter(s => s.id !== serviceId);
            saveCustomServices(filtered);
            
            loadProjects();
        }

        function getStatusInfo(status) {
            const map = {
                'running': { text: '运行中', class: 'status-running', textColor: 'text-green-400' },
                'stopped': { text: '已停止', class: 'status-stopped', textColor: 'text-red-400' },
                'partial': { text: '部分运行', class: 'status-partial', textColor: 'text-yellow-400' },
                'ready': { text: '就绪', class: 'status-ready', textColor: 'text-blue-400' },
                'configured': { text: '已配置', class: 'status-configured', textColor: 'text-green-400' },
                'not_configured': { text: '未配置', class: 'status-not-configured', textColor: 'text-gray-400' },
                'completed': { text: '已完成', class: 'status-completed', textColor: 'text-purple-400' },
                'unknown': { text: '未知', class: 'bg-gray-500', textColor: 'text-gray-400' }
            };
            return map[status] || map['unknown'];
        }

        async function startProject(id) {
            if (!confirm('确定要启动此服务吗？')) return;
            try {
                const res = await fetch(`/api/projects/${id}/start`, { method: 'POST' });
                const data = await res.json();
                alert(data.message || data.error);
                setTimeout(loadProjects, 2000);
            } catch (e) {
                alert('启动失败: ' + e.message);
            }
        }

        async function stopProject(id) {
            if (!confirm('确定要停止此服务吗？')) return;
            try {
                const res = await fetch(`/api/projects/${id}/stop`, { method: 'POST' });
                const data = await res.json();
                alert(data.message || data.error);
                setTimeout(loadProjects, 1000);
            } catch (e) {
                alert('停止失败: ' + e.message);
            }
        }

        async function restartProject(id) {
            if (!confirm('确定要重启此服务吗？')) return;
            try {
                await fetch(`/api/projects/${id}/stop`, { method: 'POST' });
                setTimeout(async () => {
                    const res = await fetch(`/api/projects/${id}/start`, { method: 'POST' });
                    const data = await res.json();
                    alert(data.message || data.error);
                    loadProjects();
                }, 2000);
            } catch (e) {
                alert('重启失败: ' + e.message);
            }
        }

        async function viewLog(id) {
            const project = projects.find(p => p.id === id);
            document.getElementById('logTitle').textContent = `${project?.name || id} - 日志`;
            document.getElementById('logModal').classList.remove('hidden');
            document.getElementById('logModal').classList.add('flex');
            
            try {
                const res = await fetch(`/api/projects/${id}/log`);
                const data = await res.json();
                document.getElementById('logContent').textContent = data.log || '暂无日志';
            } catch (e) {
                document.getElementById('logContent').textContent = '获取日志失败: ' + e.message;
            }
        }

        function closeLogModal() {
            document.getElementById('logModal').classList.add('hidden');
            document.getElementById('logModal').classList.remove('flex');
        }

        // ==================== Cron 管理 ====================

        async function loadCronTasks() {
            try {
                const res = await fetch('/api/cron/tasks');
                const data = await res.json();
                cronTasks = data.tasks;
                renderCronTasks();
                updateCronStats();
            } catch (e) {
                console.error('加载 Cron 任务失败:', e);
                document.getElementById('cronTasksList').innerHTML = '<div class="text-center py-8 text-red-400"><i class="ri-error-warning-line text-2xl"></i><p class="mt-2 text-sm">加载失败: ' + e.message + '</p></div>';
            }
        }

        function updateCronStats() {
            const total = cronTasks.length;
            const enabled = cronTasks.filter(t => t.enabled === 1).length;
            const disabled = total - enabled;
            
            document.getElementById('cronTotal').textContent = total;
            document.getElementById('cronEnabled').textContent = enabled;
            document.getElementById('cronDisabled').textContent = disabled;
            
            // 获取历史总数
            fetch('/api/cron/tasks').then(() => {
                let historyCount = 0;
                cronTasks.forEach(t => {
                    fetch(`/api/cron/tasks/${t.id}/history`).then(res => res.json()).then(data => {
                        historyCount += data.history.length;
                        document.getElementById('cronHistoryCount').textContent = historyCount;
                    });
                });
            });
        }

        function renderCronTasks() {
            const list = document.getElementById('cronTasksList');
            
            if (cronTasks.length === 0) {
                list.innerHTML = '<div class="text-center py-12 text-gray-400"><i class="ri-folder-open-line text-4xl"></i><p class="mt-2">暂无 Cron 任务</p><p class="text-sm mt-1">点击"从系统同步"导入现有任务</p></div>';
                return;
            }

            const categoryIcons = {
                'monitor': { icon: 'ri-heart-pulse-line', color: 'blue' },
                'backup': { icon: 'ri-archive-line', color: 'green' },
                'cleanup': { icon: 'ri-delete-bin-line', color: 'yellow' },
                'report': { icon: 'ri-file-chart-line', color: 'purple' },
                'sync': { icon: 'ri-refresh-line', color: 'cyan' },
                'notification': { icon: 'ri-notification-line', color: 'orange' },
                'other': { icon: 'ri-more-line', color: 'gray' }
            };

            let html = '';
            cronTasks.forEach(task => {
                const catInfo = categoryIcons[task.category] || categoryIcons['other'];
                
                html += `
                    <div class="cron-card card rounded-lg p-4">
                        <div class="flex items-start justify-between gap-4">
                            <div class="flex-1">
                                <div class="flex items-center gap-2 mb-2">
                                    <i class="${catInfo.icon} text-${catInfo.color}-400"></i>
                                    <h3 class="font-semibold">${task.name}</h3>
                                    <span class="px-2 py-0.5 rounded text-xs ${task.enabled ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'}">${task.enabled ? '启用' : '禁用'}</span>
                                </div>
                                <div class="flex items-center gap-3 mb-2">
                                    <code class="text-yellow-400 text-sm font-mono">${task.expression}</code>
                                    <span class="text-gray-500">→</span>
                                    <span class="text-blue-400 text-sm">${task.human_time || ''}</span>
                                    ${task.next_run ? `<span class="text-gray-500 text-xs">下次: ${task.next_run}</span>` : ''}
                                </div>
                                <div class="text-gray-300 text-sm font-mono break-all bg-gray-800/50 rounded p-2">${escapeHtml(task.command)}</div>
                                ${task.description ? `<div class="text-gray-400 text-sm mt-2"><i class="ri-info-line"></i> ${task.description}</div>` : ''}
                            </div>
                            <div class="flex flex-col gap-1">
                                <button onclick="editCronTask(${task.id})" class="btn bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-xs flex items-center gap-1"><i class="ri-edit-line"></i> 编辑</button>
                                <button onclick="toggleCronTask(${task.id})" class="btn ${task.enabled ? 'bg-yellow-600 hover:bg-yellow-700' : 'bg-green-600 hover:bg-green-700'} px-2 py-1 rounded text-xs flex items-center gap-1">
                                    <i class="ri-${task.enabled ? 'pause' : 'play'}-line"></i> ${task.enabled ? '禁用' : '启用'}
                                </button>
                                <button onclick="showHistory(${task.id}, '${task.name}')" class="btn bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-xs flex items-center gap-1"><i class="ri-history-line"></i> 历史</button>
                                <button onclick="deleteCronTask(${task.id})" class="btn bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-xs flex items-center gap-1"><i class="ri-delete-bin-line"></i> 删除</button>
                            </div>
                        </div>
                    </div>
                `;
            });
            list.innerHTML = html;
        }

        function showAddCronModal() {
            document.getElementById('cronModalTitle').textContent = '添加 Cron 任务';
            document.getElementById('cronTaskId').value = '';
            document.getElementById('cronName').value = '';
            document.getElementById('cronExpression').value = '';
            document.getElementById('cronCommand').value = '';
            document.getElementById('cronCategory').value = 'other';
            document.getElementById('cronDescription').value = '';
            document.getElementById('cronEnabled').checked = true;
            document.getElementById('cronHumanTime').textContent = '';
            document.getElementById('cronModal').classList.remove('hidden');
        }

        function editCronTask(id) {
            const task = cronTasks.find(t => t.id === id);
            if (!task) return;
            
            document.getElementById('cronModalTitle').textContent = '编辑 Cron 任务';
            document.getElementById('cronTaskId').value = id;
            document.getElementById('cronName').value = task.name;
            document.getElementById('cronExpression').value = task.expression;
            document.getElementById('cronCommand').value = task.command;
            document.getElementById('cronCategory').value = task.category;
            document.getElementById('cronDescription').value = task.description || '';
            document.getElementById('cronEnabled').checked = task.enabled === 1;
            document.getElementById('cronHumanTime').textContent = task.human_time || '';
            document.getElementById('cronModal').classList.remove('hidden');
        }

        function closeCronModal() {
            document.getElementById('cronModal').classList.add('hidden');
        }

        async function saveCronTask(event) {
            event.preventDefault();
            
            const id = document.getElementById('cronTaskId').value;
            const data = {
                name: document.getElementById('cronName').value,
                expression: document.getElementById('cronExpression').value,
                command: document.getElementById('cronCommand').value,
                category: document.getElementById('cronCategory').value,
                description: document.getElementById('cronDescription').value,
                enabled: document.getElementById('cronEnabled').checked ? 1 : 0
            };
            
            try {
                let res;
                if (id) {
                    res = await fetch(`/api/cron/tasks/${id}`, { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) });
                } else {
                    res = await fetch('/api/cron/tasks', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) });
                }
                
                const result = await res.json();
                if (result.error) {
                    alert('保存失败: ' + result.error);
                } else {
                    alert(result.message);
                    closeCronModal();
                    loadCronTasks();
                }
            } catch (e) {
                alert('保存失败: ' + e.message);
            }
        }

        async function toggleCronTask(id) {
            try {
                const res = await fetch(`/api/cron/tasks/${id}/toggle`, { method: 'POST' });
                const data = await res.json();
                alert(data.message);
                loadCronTasks();
            } catch (e) {
                alert('操作失败: ' + e.message);
            }
        }

        async function deleteCronTask(id) {
            if (!confirm('确定要删除此任务吗？删除后会从系统 crontab 中移除。')) return;
            
            try {
                const res = await fetch(`/api/cron/tasks/${id}`, { method: 'DELETE' });
                const data = await res.json();
                alert(data.message);
                loadCronTasks();
            } catch (e) {
                alert('删除失败: ' + e.message);
            }
        }

        async function showHistory(id, name) {
            document.getElementById('historyModalTitle').textContent = `${name} - 历史版本`;
            document.getElementById('historyModal').classList.remove('hidden');
            
            try {
                const res = await fetch(`/api/cron/tasks/${id}/history`);
                const data = await res.json();
                renderHistory(data.history, id);
            } catch (e) {
                document.getElementById('historyList').innerHTML = '<div class="text-center py-8 text-red-400">加载失败</div>';
            }
        }

        function renderHistory(history, taskId) {
            if (history.length === 0) {
                document.getElementById('historyList').innerHTML = '<div class="text-center py-8 text-gray-400">暂无历史记录</div>';
                return;
            }

            const operationLabels = {
                'create': { text: '创建', class: 'create' },
                'update': { text: '更新', class: 'update' },
                'delete': { text: '删除', class: 'delete' },
                'toggle': { text: '切换状态', class: 'toggle' },
            };

            let html = '';
            history.forEach(h => {
                const opInfo = operationLabels[h.operation] || { text: h.operation, class: 'update' };
                const isRollback = h.operation.includes('rollback');
                
                html += `
                    <div class="history-item ${isRollback ? 'rollback' : opInfo.class} py-2">
                        <div class="flex items-center justify-between">
                            <div>
                                <div class="flex items-center gap-2">
                                    <span class="text-gray-400 text-sm">v${h.version}</span>
                                    <span class="text-xs px-2 py-0.5 rounded ${isRollback ? 'bg-yellow-500/20 text-yellow-400' : 'bg-blue-500/20 text-blue-400'}">${isRollback ? '回退' : opInfo.text}</span>
                                </div>
                                <div class="text-gray-300 text-sm mt-1">${h.created_at}</div>
                                ${h.expression ? `<code class="text-yellow-400 text-xs font-mono">${h.expression}</code>` : ''}
                                ${h.enabled !== null ? `<span class="text-xs ml-2 ${h.enabled ? 'text-green-400' : 'text-red-400'}">${h.enabled ? '启用' : '禁用'}</span>` : ''}
                            </div>
                            ${!isRollback && h.version > 1 ? `<button onclick="rollbackTo(${taskId}, ${h.version})" class="btn bg-yellow-600 hover:bg-yellow-700 px-2 py-1 rounded text-xs"><i class="ri-history-line"></i> 回退</button>` : ''}
                        </div>
                    </div>
                `;
            });
            document.getElementById('historyList').innerHTML = html;
        }

        async function rollbackTo(taskId, version) {
            if (!confirm(`确定要回退到版本 ${version} 吗？`)) return;
            
            try {
                const res = await fetch(`/api/cron/tasks/${taskId}/rollback/${version}`, { method: 'POST' });
                const data = await res.json();
                alert(data.message);
                closeHistoryModal();
                loadCronTasks();
            } catch (e) {
                alert('回退失败: ' + e.message);
            }
        }

        function closeHistoryModal() {
            document.getElementById('historyModal').classList.add('hidden');
        }

        async function syncFromSystem() {
            if (!confirm('从系统 crontab 同步任务到数据库？')) return;
            
            try {
                const res = await fetch('/api/cron/sync', { method: 'POST' });
                const data = await res.json();
                alert(data.message);
                loadCronTasks();
            } catch (e) {
                alert('同步失败: ' + e.message);
            }
        }

        function toggleLogs() {
            enableLogs = document.getElementById('enableLogs').checked;
            localStorage.setItem('enableLogs', enableLogs);
        }

        // 解析 cron 表达式显示友好时间
        document.getElementById('cronExpression').addEventListener('input', function() {
            const expr = this.value;
            // 简单解析
            const patterns = {
                '0 4 * * *': '每天凌晨4点',
                '0 0 * * *': '每天午夜0点',
                '*/5 * * * *': '每5分钟',
                '*/10 * * * *': '每10分钟',
                '*/20 * * * *': '每20分钟',
                '*/30 * * * *': '每30分钟',
                '0 */1 * * *': '每小时',
                '0 */2 * * *': '每2小时',
            };
            
            document.getElementById('cronHumanTime').textContent = patterns[expr] || '';
        });

        // ==================== 通用功能 ====================

        function refreshAll() {
            if (currentTab === 'projects') {
                loadProjects();
            } else {
                loadCronTasks();
            }
        }

        // 导出配置
        async function exportConfig() {
            try {
                const response = await fetch('/api/config/export');
                const config = await response.json();
                
                // 创建下载文件
                const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `project-panel-config-${new Date().toISOString().slice(0,10)}.json`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
                
                alert('配置导出成功！');
            } catch (e) {
                alert('导出失败: ' + e.message);
            }
        }

        // 导入配置
        async function importConfig(event) {
            const file = event.target.files[0];
            if (!file) return;
            
            if (!confirm('导入配置将覆盖当前所有设置，是否继续？')) {
                event.target.value = '';
                return;
            }
            
            try {
                const text = await file.text();
                const config = JSON.parse(text);
                
                const response = await fetch('/api/config/import', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(config)
                });
                
                const result = await response.json();
                
                if (result.error) {
                    alert('导入失败: ' + result.error);
                } else {
                    alert(`配置导入成功！\n项目数: ${result.projects_count}\n外部服务数: ${result.services_count}`);
                    location.reload();
                }
            } catch (e) {
                alert('导入失败: ' + e.message);
            }
            
            event.target.value = '';
        }

        function scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); }
        function scrollToBottom() { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); }

        function updateConnectionStatus(ok) {
            const statusEl = document.getElementById('connectionStatus');
            if (ok) {
                statusEl.innerHTML = '<div class="w-2 h-2 rounded-full bg-green-400 animate-pulse"></div><span class="text-green-400 text-xs">已连接</span>';
                statusEl.className = 'flex items-center gap-1 px-2 py-1 rounded bg-green-500/20';
            } else {
                statusEl.innerHTML = '<div class="w-2 h-2 rounded-full bg-red-400"></div><span class="text-red-400 text-xs">断开</span>';
                statusEl.className = 'flex items-center gap-1 px-2 py-1 rounded bg-red-500/20';
            }
        }

        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }

        // 初始化
        loadProjects();
        loadWebSettings(); // 加载网页设置
        
        let refreshIntervalMs = 30 * 1000; // 默认值
        let refreshTimer = setInterval(() => { if (currentTab === 'projects') loadProjects(); }, refreshIntervalMs);
        
        // 加载网页设置
        async function loadWebSettings() {
            try {
                const response = await fetch('/api/web-settings');
                const settings = await response.json();
                
                if (settings.external_ip) {
                    externalIp = settings.external_ip;
                    document.getElementById('externalIp').value = settings.external_ip;
                }
                if (settings.refresh_interval) {
                    document.getElementById('refreshInterval').value = settings.refresh_interval;
                    clearInterval(refreshTimer);
                    refreshIntervalMs = settings.refresh_interval * 1000;
                    refreshTimer = setInterval(() => { if (currentTab === 'projects') loadProjects(); }, refreshIntervalMs);
                }
            } catch (e) {
                console.error('加载网页设置失败:', e);
            }
        }
        
        function updateRefreshInterval() {
            const seconds = Math.max(5, Math.min(300, parseInt(document.getElementById('refreshInterval').value) || 30));
            document.getElementById('refreshInterval').value = seconds;
            
            // 保存到服务器
            fetch('/api/web-settings', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ external_ip: externalIp, refresh_interval: seconds })
            }).catch(e => console.error('保存失败:', e));
            
            clearInterval(refreshTimer);
            refreshTimer = setInterval(() => { if (currentTab === 'projects') loadProjects(); }, seconds * 1000);
        }

        setInterval(() => {
            fetch('/api/projects').then(() => updateConnectionStatus(true)).catch(() => updateConnectionStatus(false));
        }, 10000);
    </script>
</body>
</html>