// 全局变量
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秒刷新一次
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.isSuccess ? '成功' : '失败'}
${result.isSuccess ? `
延迟: ${result.latency}ms
` : `
错误: ${result.error || '未知错误'}
`}
`).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;
}
const html = '' + nodes.map((node, index) => `
类型: ${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}ms
` : ''}
` : ''}
`).join('') + '
';
container.innerHTML = html;
}
// 加载测速结果
async function loadTestResults() {
try {
showLoading();
const nodeId = 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({
limit: 1000, // 设置一个很大的限制,获取所有结果
...(nodeId && { nodeId }),
...(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);
}
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.isSuccess ? '成功' : '失败'}
${result.isSuccess ? `
延迟: ${result.latency}ms
` : `
错误: ${result.error || '未知错误'}
`}
`).join('') + '
';
container.innerHTML = html;
}
// 加载通知记录
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.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();
}
// 搜索结果
function searchResults() {
loadTestResults();
}
// 导入节点
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 ? `
状态: ${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;
}
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);
}