// 全局变量 let currentPage = 'dashboard'; let currentNodesPage = 1; let currentResultsPage = 1; let currentNotificationsPage = 1; // API基础URL const API_BASE = '/api'; // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { initializeApp(); }); // 初始化应用 async function initializeApp() { try { // 设置导航事件 setupNavigation(); // 加载仪表板数据 await loadDashboard(); // 设置定时刷新 setInterval(loadDashboard, 30000); // 每30秒刷新一次 // 设置实时搜索 setupRealTimeSearch(); console.log('应用初始化完成'); } catch (error) { console.error('应用初始化失败:', error); showError('应用初始化失败: ' + error.message); } } // 设置导航 function setupNavigation() { const navLinks = document.querySelectorAll('[data-page]'); navLinks.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const page = this.getAttribute('data-page'); showPage(page); }); }); } // 显示页面 async function showPage(pageName) { // 隐藏所有页面 document.querySelectorAll('.page-content').forEach(page => { page.style.display = 'none'; }); // 移除所有导航激活状态 document.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active'); }); // 激活当前导航 document.querySelector(`[data-page="${pageName}"]`).classList.add('active'); // 显示目标页面 const targetPage = document.getElementById(pageName + '-page'); if (targetPage) { targetPage.style.display = 'block'; targetPage.classList.add('fade-in'); } // 加载页面数据 switch (pageName) { case 'dashboard': await loadDashboard(); break; case 'nodes': await loadNodes(); break; case 'speed-test': await loadTestResults(); break; case 'notifications': await loadNotifications(); break; case 'settings': await loadSettings(); break; } currentPage = pageName; } // 加载仪表板 async function loadDashboard() { try { showLoading(); // 加载统计信息 const statsResponse = await fetch(`${API_BASE}/stats`); const statsData = await statsResponse.json(); if (statsData.success) { updateDashboardStats(statsData.data); } // 加载最近测速结果 const resultsResponse = await fetch(`${API_BASE}/test/results?limit=5`); const resultsData = await resultsResponse.json(); if (resultsData.success) { updateRecentResults(resultsData.data.results); } hideLoading(); } catch (error) { console.error('加载仪表板失败:', error); hideLoading(); showError('加载仪表板失败: ' + error.message); } } // 更新仪表板统计 function updateDashboardStats(stats) { document.getElementById('total-nodes').textContent = stats.nodes.total; document.getElementById('online-nodes').textContent = stats.nodes.online; document.getElementById('success-rate').textContent = stats.tests.successRate + '%'; document.getElementById('today-tests').textContent = stats.tests.recent24h; } // 更新最近测速结果 function updateRecentResults(results) { const container = document.getElementById('recent-results'); if (results.length === 0) { container.innerHTML = '
暂无测速结果
'; return; } const html = results.map(result => `
${result.node ? result.node.name : '未知节点'}
${result.isSuccess ? '成功' : '失败'}
${result.isSuccess ? `
${(() => { const avg = result.node && result.node.averageLatency != null ? result.node.averageLatency : null; const latency = result.latency; if (avg != null) { return avg > 2000 ? '超时' : avg + 'ms'; } else { return latency > 2000 ? '超时' : latency + 'ms'; } })()}
` : `
${result.error || '未知错误'}
`}
${formatTime(result.testTime)}
`).join(''); container.innerHTML = html; } // 加载节点列表 async function loadNodes() { try { showLoading(); const search = document.getElementById('node-search')?.value || ''; const status = document.getElementById('status-filter')?.value || ''; const params = new URLSearchParams({ limit: 1000, // 设置一个很大的限制,获取所有节点 ...(search && { search }), ...(status && { status }) }); const response = await fetch(`${API_BASE}/nodes?${params}`); const data = await response.json(); if (data.success) { updateNodesList(data.data); } hideLoading(); } catch (error) { console.error('加载节点列表失败:', error); hideLoading(); showError('加载节点列表失败: ' + error.message); } } // 更新节点列表 function updateNodesList(data) { const container = document.getElementById('nodes-list'); const { nodes, pagination } = data; if (nodes.length === 0) { container.innerHTML = '
暂无节点
'; return; } // 对节点进行排序:故障节点(offline)排在前面 const sortedNodes = [...nodes].sort((a, b) => { // 首先按状态排序:offline在前,online在后 if (a.status === 'offline' && b.status === 'online') return -1; if (a.status === 'online' && b.status === 'offline') return 1; // 如果状态相同,按名称排序 return a.name.localeCompare(b.name); }); const html = '
' + sortedNodes.map((node, index) => `
${node.name}
${node.status === 'online' ? '在线' : '离线'}
类型: ${node.type}
服务器: ${node.server}
端口: ${node.port}
${node.testResults && node.testResults.length > 0 ? `
最后测速: ${formatTime(node.testResults[0].testTime)} ${node.testResults[0].isSuccess ? ` | 延迟: ${node.testResults[0].latency > 2000 ? '超时' : node.testResults[0].latency + 'ms'} ` : ''}
` : ''}
`).join('') + '
'; container.innerHTML = html; } // 更新节点统计信息 function updateNodesStats(nodes) { const totalNodes = nodes.length; const onlineNodes = nodes.filter(node => node.status === 'online').length; const offlineNodes = nodes.filter(node => node.status === 'offline').length; const onlineRate = totalNodes > 0 ? Math.round((onlineNodes / totalNodes) * 100) : 0; document.getElementById('total-nodes-count').textContent = totalNodes; document.getElementById('online-nodes-count').textContent = onlineNodes; document.getElementById('offline-nodes-count').textContent = offlineNodes; document.getElementById('online-rate').textContent = onlineRate + '%'; } // 加载测速结果 async function loadTestResults(page = 1) { try { showLoading(); const nodeName = document.getElementById('result-node-filter')?.value || ''; const isSuccess = document.getElementById('result-status-filter')?.value || ''; const startDate = document.getElementById('start-date')?.value || ''; const endDate = document.getElementById('end-date')?.value || ''; const params = new URLSearchParams({ page: page, limit: 20, // 每页显示20条记录 ...(nodeName && { nodeName }), ...(isSuccess && { isSuccess }), ...(startDate && { startDate }), ...(endDate && { endDate }) }); const response = await fetch(`${API_BASE}/test/results?${params}`); const data = await response.json(); if (data.success) { updateTestResults(data.data); currentResultsPage = page; } hideLoading(); } catch (error) { console.error('加载测速结果失败:', error); hideLoading(); showError('加载测速结果失败: ' + error.message); } } // 更新测速结果 function updateTestResults(data) { const container = document.getElementById('test-results'); const { results, pagination } = data; if (results.length === 0) { container.innerHTML = '
暂无测速结果
'; return; } const html = results.map((result, index) => `
${result.node ? result.node.name : '未知节点'}
${result.isSuccess ? '成功' : '失败'}
${result.isSuccess ? `
${(() => { const avg = result.node && result.node.averageLatency != null ? result.node.averageLatency : null; const latency = result.latency; if (avg != null) { return avg > 2000 ? '超时' : avg + 'ms'; } else { return latency > 2000 ? '超时' : latency + 'ms'; } })()}
` : `
${result.error || '未知错误'}
`}
${formatTime(result.testTime)}
`).join(''); container.innerHTML = html; // 更新分页 updatePagination('test-results-pagination', pagination, loadTestResults); } // 加载通知记录 async function loadNotifications(page = 1) { try { showLoading(); const params = new URLSearchParams({ page: page, limit: 20 }); const response = await fetch(`${API_BASE}/notifications?${params}`); const data = await response.json(); if (data.success) { updateNotificationsList(data.data); currentNotificationsPage = page; } hideLoading(); } catch (error) { console.error('加载通知记录失败:', error); hideLoading(); showError('加载通知记录失败: ' + error.message); } } // 更新通知列表 function updateNotificationsList(data) { const container = document.getElementById('notifications-list'); const { notifications, pagination } = data; if (notifications.length === 0) { container.innerHTML = '
暂无通知记录
'; return; } const html = notifications.map(notification => `
${notification.type}
${formatTime(notification.createdAt)}
状态: ${notification.isSent ? '已发送' : '发送失败'}
内容: ${notification.message.substring(0, 100)}${notification.message.length > 100 ? '...' : ''}
${notification.node ? `
节点: ${notification.node.name}
` : ''}
`).join(''); container.innerHTML = html; // 更新分页 updatePagination('notifications-pagination', pagination, loadNotifications); } // 加载设置页面 async function loadSettings() { try { showLoading(); // 加载系统状态 const statusResponse = await fetch(`${API_BASE}/status`); const statusData = await statusResponse.json(); if (statusData.success) { updateSystemStatus(statusData.data); } // 加载订阅状态 const subscriptionResponse = await fetch(`${API_BASE}/subscription/status`); const subscriptionData = await subscriptionResponse.json(); if (subscriptionData.success) { updateSubscriptionStatus(subscriptionData.data); } hideLoading(); } catch (error) { console.error('加载设置失败:', error); hideLoading(); showError('加载设置失败: ' + error.message); } } // 更新系统状态 function updateSystemStatus(status) { const container = document.getElementById('system-status'); const html = `
调度器状态
${status.isRunning ? '运行中' : '已停止'}
下次测速: ${status.nextTestTime ? formatTime(status.nextTestTime) : '未设置'}
测速间隔: ${status.testInterval ? Math.round(status.testInterval / 60000) + '分钟' : '未设置'}
系统信息
运行时间: ${formatUptime(status.uptime)}
总测速次数: ${status.totalTests || 0}
`; container.innerHTML = html; } // 更新订阅状态 function updateSubscriptionStatus(status) { const container = document.getElementById('subscription-status'); const html = `
订阅地址: ${status.subscriptionUrl || '未配置'}
更新间隔: ${status.updateInterval ? Math.round(status.updateInterval / 60000) + '分钟' : '未设置'}
自动更新: ${status.autoUpdateEnabled ? '已启用' : '已禁用'}
${status.lastUpdate ? `
最后更新: ${formatTime(status.lastUpdate)}
` : ''} `; container.innerHTML = html; } // 开始测速 async function startSpeedTest() { try { showLoading(); const response = await fetch(`${API_BASE}/test/manual`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); const data = await response.json(); if (data.success) { showSuccess(`测速已开始,共测试 ${data.data.testedNodes} 个节点`); // 刷新当前页面数据 if (currentPage === 'dashboard') { await loadDashboard(); } else if (currentPage === 'speed-test') { await loadTestResults(); } } else { showError('测速失败: ' + data.error); } hideLoading(); } catch (error) { console.error('测速失败:', error); hideLoading(); showError('测速失败: ' + error.message); } } // 测试通知 async function testNotification() { try { showLoading(); const response = await fetch(`${API_BASE}/notifications/test`, { method: 'POST' }); const data = await response.json(); if (data.success) { showSuccess('通知测试成功'); } else { showError('通知测试失败: ' + data.error); } hideLoading(); } catch (error) { console.error('通知测试失败:', error); hideLoading(); showError('通知测试失败: ' + error.message); } } // 刷新统计 async function refreshStats() { await loadDashboard(); showSuccess('统计信息已刷新'); } // 显示系统状态 async function showSystemStatus() { await showPage('settings'); } // 搜索节点 function searchNodes() { loadNodes(); } // 搜索结果 // 搜索防抖函数 let searchTimeout; function searchResults() { loadTestResults(1); } // 实时搜索功能 function setupRealTimeSearch() { const nodeFilter = document.getElementById('result-node-filter'); if (nodeFilter) { nodeFilter.addEventListener('input', function() { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { loadTestResults(1); }, 500); // 500ms 防抖延迟 }); } } // 导入节点 function importNodes() { const modal = new bootstrap.Modal(document.getElementById('importModal')); modal.show(); } // 确认导入 async function confirmImport() { try { const configPath = document.getElementById('config-path').value; const configUrl = document.getElementById('config-url').value; if (!configPath && !configUrl) { showError('请提供配置文件路径或URL'); return; } showLoading(); const response = await fetch(`${API_BASE}/import/clash`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ configPath: configPath || undefined, configUrl: configUrl || undefined }) }); const data = await response.json(); if (data.success) { showSuccess(`成功导入 ${data.data.imported} 个节点`); bootstrap.Modal.getInstance(document.getElementById('importModal')).hide(); if (currentPage === 'nodes') { await loadNodes(); } } else { showError('导入失败: ' + data.error); } hideLoading(); } catch (error) { console.error('导入失败:', error); hideLoading(); showError('导入失败: ' + error.message); } } // 更新订阅 async function updateSubscription() { try { showLoading(); const response = await fetch(`${API_BASE}/subscription/update`, { method: 'POST' }); const data = await response.json(); if (data.success) { showSuccess('订阅更新成功'); await loadSettings(); } else { showError('订阅更新失败: ' + data.error); } hideLoading(); } catch (error) { console.error('订阅更新失败:', error); hideLoading(); showError('订阅更新失败: ' + error.message); } } // 查看节点详情 async function viewNodeDetail(nodeId) { try { const response = await fetch(`${API_BASE}/nodes/${nodeId}`); const data = await response.json(); if (data.success) { const node = data.data; const modal = new bootstrap.Modal(document.getElementById('nodeDetailModal')); document.getElementById('node-detail-content').innerHTML = `
基本信息
名称:${node.name}
类型:${node.type}
服务器:${node.server}
端口:${node.port}
状态:${node.status === 'online' ? '在线' : '离线'}
最近测速结果
${node.testResults && node.testResults.length > 0 ? `
${formatTime(node.testResults[0].testTime)}
状态: ${node.testResults[0].isSuccess ? '成功' : '失败'}
${node.testResults[0].isSuccess ? `
延迟: ${node.testResults[0].latency}ms
` : `
错误: ${node.testResults[0].error || '未知错误'}
`}
` : '
暂无测速结果
'}
`; modal.show(); } } catch (error) { console.error('获取节点详情失败:', error); showError('获取节点详情失败: ' + error.message); } } // 测试单个节点 async function testSingleNode(nodeId) { try { showLoading(); const response = await fetch(`${API_BASE}/test/manual`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nodeIds: [nodeId] }) }); const data = await response.json(); if (data.success) { showSuccess('节点测速已开始'); // 刷新当前页面数据 if (currentPage === 'nodes') { await loadNodes(); } else if (currentPage === 'speed-test') { await loadTestResults(); } } else { showError('节点测速失败: ' + data.error); } hideLoading(); } catch (error) { console.error('节点测速失败:', error); hideLoading(); showError('节点测速失败: ' + error.message); } } // 更新分页 function updatePagination(containerId, pagination, loadFunction) { const container = document.getElementById(containerId); if (pagination.pages <= 1) { container.innerHTML = ''; return; } // 根据容器ID确定函数名 let functionName = 'loadTestResults'; if (containerId === 'notifications-pagination') { functionName = 'loadNotifications'; } let html = ''; container.innerHTML = html; } // 工具函数 function formatTime(timeString) { const date = new Date(timeString); return date.toLocaleString('zh-CN'); } function formatSpeed(speed) { if (!speed) return 'N/A'; if (speed < 1024) return speed + ' B/s'; if (speed < 1024 * 1024) return (speed / 1024).toFixed(1) + ' KB/s'; if (speed < 1024 * 1024 * 1024) return (speed / (1024 * 1024)).toFixed(1) + ' MB/s'; return (speed / (1024 * 1024 * 1024)).toFixed(1) + ' GB/s'; } function formatUptime(seconds) { if (!seconds) return 'N/A'; const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); return `${hours}小时${minutes}分钟`; } function showLoading() { document.getElementById('loading-overlay').style.display = 'flex'; } function hideLoading() { document.getElementById('loading-overlay').style.display = 'none'; } function showSuccess(message) { // 创建成功提示 const toast = document.createElement('div'); toast.className = 'alert alert-success alert-dismissible fade show position-fixed'; toast.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; toast.innerHTML = ` ${message} `; document.body.appendChild(toast); // 3秒后自动移除 setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 3000); } function showError(message) { // 创建错误提示 const toast = document.createElement('div'); toast.className = 'alert alert-danger alert-dismissible fade show position-fixed'; toast.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; toast.innerHTML = ` ${message} `; document.body.appendChild(toast); // 5秒后自动移除 setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 5000); }