// API 基础 URL const API_BASE_URL = '/api'; // 全局状态 let state = { currentTimeRange: '1h', // 与UI默认值保持一致 customStartTime: '', customEndTime: '', currentInterval: '3m', // 固定10分钟区间 currentDeviceID: '', // 当前选中的服务器ID historyMetrics: {}, // 存储历史指标数据 autoRefreshEnabled: false, // 自动刷新开关状态,默认关闭 lastMetricsUpdate: null, // 记录上次指标更新时间 lastChartUpdate: 0, // 记录上次图表更新时间 chartUpdateThrottle: 1000, // 图表更新节流时间(毫秒) pendingMetricsUpdate: null, // 待处理的指标更新 pendingChartUpdate: null, // 待处理的图表更新 isUpdatingDOM: false, // 标记是否正在更新DOM // 自动刷新相关配置 autoRefreshInterval: 30000, // 默认自动刷新间隔(毫秒) minAutoRefreshInterval: 5000, // 最小自动刷新间隔(毫秒) maxAutoRefreshInterval: 60000, // 最大自动刷新间隔(毫秒) autoRefreshTimer: null, // 自动刷新定时器 lastAutoRefreshTime: 0 // 上次自动刷新时间 } // WebSocket连接 let ws = null; let wsReconnectAttempts = 0; const wsMaxReconnectAttempts = 10; // 增加最大重连次数 let wsReconnectDelay = 1000; const wsMaxReconnectDelay = 30000; // 添加最大重连延迟(30秒) let wsReconnectTimeout = null; // 重连定时器 // 图表实例 const charts = {}; // 初始化应用 function initApp() { initCustomTimeRange(); bindEvents(); initPageSwitch(); loadHomeData(); initCharts(); initWebSocket(); // 初始化自动刷新机制 setupAutoRefresh(); // 设置服务器数量定时刷新 setInterval(loadServerCount, 30000); // 初始化网卡列表 loadNetworkInterfaces(); } // 设置自动刷新机制 function setupAutoRefresh() { // 清除现有的定时器 if (state.autoRefreshTimer) { clearInterval(state.autoRefreshTimer); state.autoRefreshTimer = null; } // 如果启用了自动刷新,设置新的定时器 if (state.autoRefreshEnabled) { // 立即执行一次,然后开始定时执行 loadMetrics(); // 设置定时器 state.autoRefreshTimer = setInterval(() => { loadMetrics(); state.lastAutoRefreshTime = Date.now(); // 动态调整刷新间隔 updateAutoRefreshInterval(); }, state.autoRefreshInterval); } } // 动态调整自动刷新间隔 function updateAutoRefreshInterval() { // 这里可以根据实际情况调整刷新间隔 // 例如,根据系统负载、网络延迟或数据变化频率来动态调整 // 简单实现:如果最近数据变化频繁,缩短刷新间隔;否则延长刷新间隔 const now = Date.now(); if (state.lastMetricsUpdate && (now - state.lastMetricsUpdate) < state.autoRefreshInterval / 2) { // 数据变化频繁,缩短刷新间隔,但不低于最小值 state.autoRefreshInterval = Math.max( state.autoRefreshInterval - 2000, state.minAutoRefreshInterval ); } else if (state.lastMetricsUpdate && (now - state.lastMetricsUpdate) > state.autoRefreshInterval * 2) { // 数据变化缓慢,延长刷新间隔,但不高于最大值 state.autoRefreshInterval = Math.min( state.autoRefreshInterval + 5000, state.maxAutoRefreshInterval ); } // 更新定时器 setupAutoRefresh(); } // 初始化自定义时间范围 function initCustomTimeRange() { const now = new Date(); // 默认显示过去1小时 const oneHourAgo = new Date(now.getTime() - 1 * 60 * 60 * 1000); // 直接使用ISO字符串,包含完整的时区信息 state.customStartTime = oneHourAgo.toISOString(); state.customEndTime = now.toISOString(); // 更新日期选择器输入框的值 const startTimeInput = document.getElementById('customStartTime'); const endTimeInput = document.getElementById('customEndTime'); if (startTimeInput && endTimeInput) { // 将ISO字符串转换为datetime-local格式(YYYY-MM-DDTHH:MM) startTimeInput.value = oneHourAgo.toISOString().slice(0, 16); endTimeInput.value = now.toISOString().slice(0, 16); } } // 页面切换 function initPageSwitch() { window.addEventListener('hashchange', switchPage); switchPage(); } function switchPage() { const hash = window.location.hash; // 隐藏所有内容 hideAllContent(); // 显示对应内容 if (hash === '#servers') { showContent('serversContent'); loadAllServers(); // 清除当前设备ID,避免在服务器列表页显示特定服务器的数据 state.currentDeviceID = ''; } else if (hash === '#devices') { showContent('devicesContent'); loadDeviceManagementList(); // 清除当前设备ID state.currentDeviceID = ''; } else if (hash === '#serverMonitor' || hash.startsWith('#serverMonitor/')) { showContent('serverMonitorContent'); // 提取设备ID let deviceId = ''; if (hash.startsWith('#serverMonitor/')) { deviceId = hash.split('/')[1]; // 检查deviceId是否为'undefined'字符串 if (deviceId === 'undefined') { deviceId = ''; } } // 直接设置当前设备ID,确保loadMetrics能使用正确的设备ID state.currentDeviceID = deviceId; // 加载服务器信息 if (deviceId) { loadServerInfo(deviceId).then(() => { // 确保服务器信息加载完成后再加载指标数据 loadMetrics(); }); } else { // 如果没有设备ID,显示错误信息 const serverInfoDisplay = document.getElementById('serverInfoDisplay'); if (serverInfoDisplay) { serverInfoDisplay.innerHTML = `
无效的服务器ID
`; } } } else { showContent('homeContent'); loadHomeData(); // 清除当前设备ID state.currentDeviceID = ''; } } function hideAllContent() { document.getElementById('homeContent').classList.add('hidden'); document.getElementById('serversContent').classList.add('hidden'); document.getElementById('serverMonitorContent').classList.add('hidden'); document.getElementById('devicesContent').classList.add('hidden'); } function showContent(contentId) { document.getElementById(contentId).classList.remove('hidden'); } // 加载首页数据 async function loadHomeData() { try { // 加载业务视图数据 await loadBusinessViewData(); // 加载告警列表数据 loadAlarmListData(); // 加载服务器数量 loadServerCount(); } catch (error) { console.error('加载首页数据失败:', error); } } // 加载业务视图数据 async function loadBusinessViewData() { try { const response = await fetch(`${API_BASE_URL}/devices/`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); const devices = data.devices || []; renderBusinessView(devices); } catch (error) { console.error('加载业务视图数据失败:', error); // 只显示错误信息,不使用模拟数据 const tableBody = document.getElementById('businessViewTableBody'); if (tableBody) { tableBody.innerHTML = `${alarm.message}
${alarm.device}
加载服务器列表失败
${device.ip || 'N/A'}
CPU使用率
${cpuUsage}%
磁盘使用率
${diskUsage}%
内存使用率
${memoryUsage}%
网络流量
${networkTraffic} MB/s
服务器名称: ${deviceData.name || deviceId} | IP地址: ${deviceData.ip || '未知'}
`; } } catch (error) { console.error('Failed to load server info:', error); // 即使请求失败,也要将设备ID存储到全局状态 state.currentDeviceID = deviceId; // 显示错误信息,不使用模拟数据 const serverInfoDisplay = document.getElementById('serverInfoDisplay'); if (serverInfoDisplay) { serverInfoDisplay.innerHTML = `加载服务器信息失败
`; } } } // 处理指标更新 function handleMetricsUpdate(message) { const { device_id, metrics } = message; // 只处理当前选中设备的WebSocket消息 if (state.currentDeviceID && device_id !== state.currentDeviceID) { return; } // 格式化数据,确保updateStatusCards函数能正确处理 const formattedMetrics = { cpu: metrics.cpu, cpu_hz: metrics.cpu_hz, // 添加cpu_hz字段,确保CPU频率能显示 memory: metrics.memory, disk: formatDiskDataForCards(metrics.disk), network: formatNetworkDataForCards(metrics.network) }; // 直接更新统计卡片,始终实时更新 updateStatusCards(formattedMetrics); // 根据自动刷新开关状态决定是否更新图表 if (state.autoRefreshEnabled) { // 直接使用WebSocket数据更新图表,避免不必要的API请求 updateCharts(metrics.cpu, metrics.memory, metrics.disk, metrics.network); } } // 更新刷新状态指示器 function updateRefreshStatus() { // 更新最后刷新时间 const now = new Date(); const formattedTime = now.toLocaleTimeString(); // 更新状态指示器和时间显示 const statusIndicator = document.getElementById('refreshStatusIndicator'); const lastRefreshTime = document.getElementById('lastRefreshTime'); if (statusIndicator && lastRefreshTime) { // 短暂显示绿色,表示数据已更新 statusIndicator.className = 'w-2 h-2 bg-green-500 rounded-full'; lastRefreshTime.textContent = `上次刷新: ${formattedTime}`; // 1秒后恢复为黄色,表示正常状态 setTimeout(() => { if (statusIndicator) { statusIndicator.className = 'w-2 h-2 bg-yellow-500 rounded-full'; } }, 1000); } } // 更新状态卡片 function updateStatusCards(metrics) { // 更新历史指标数据 updateHistoryMetrics(metrics); // 保存待处理的指标更新 state.pendingMetricsUpdate = metrics; // 使用requestAnimationFrame优化DOM更新 if (!state.isUpdatingDOM) { state.isUpdatingDOM = true; requestAnimationFrame(() => { _updateStatusCards(state.pendingMetricsUpdate); state.isUpdatingDOM = false; state.lastMetricsUpdate = Date.now(); }); } } // 内部DOM更新函数,由requestAnimationFrame调用 function _updateStatusCards(metrics) { // 使用历史数据或当前数据 const displayMetrics = { cpu: metrics.cpu || state.historyMetrics.cpu, cpu_hz: metrics.cpu_hz || state.historyMetrics.cpu_hz, memory: metrics.memory || state.historyMetrics.memory, disk: metrics.disk || state.historyMetrics.disk, network: metrics.network || state.historyMetrics.network }; // 更新CPU状态卡片 if (displayMetrics.cpu) { let cpuUsage = 0; let cpuGhz = 0; let cpuLoad = 0; // 解析CPU数据 if (Array.isArray(displayMetrics.cpu) && displayMetrics.cpu.length > 0) { // 数组格式:获取最新的数据点 cpuUsage = displayMetrics.cpu[displayMetrics.cpu.length - 1].value; } else if (typeof displayMetrics.cpu === 'number') { // 数值格式 cpuUsage = displayMetrics.cpu; } else if (typeof displayMetrics.cpu === 'object' && displayMetrics.cpu.usage) { // 对象格式,包含usage, frequency, load cpuUsage = displayMetrics.cpu.usage; cpuGhz = displayMetrics.cpu.frequency || 0; cpuLoad = displayMetrics.cpu.load || 0; } // 从单独的cpu_hz字段获取CPU频率(如果有) if (displayMetrics.cpu_hz && typeof displayMetrics.cpu_hz === 'number') { cpuGhz = displayMetrics.cpu_hz / 1000; // 转换为GHz } // 更新显示 const cpuElement = document.getElementById('cpuValue'); const cpuDetailsElement = document.getElementById('cpuDetails'); if (cpuElement) { cpuElement.textContent = `${cpuUsage.toFixed(1)}%`; // 设置红色显示如果达到顶峰,同时保留原有的样式类 cpuElement.className = `text-3xl font-bold metric-value ${cpuUsage > 90 ? 'text-red-500' : 'text-gray-900'}`; } if (cpuDetailsElement) { cpuDetailsElement.className = 'text-xs text-gray-500 mt-1'; cpuDetailsElement.textContent = `${cpuGhz.toFixed(2)} GHz | 负载: ${cpuLoad.toFixed(2)}`; } } // 更新内存状态卡片 if (displayMetrics.memory) { let memoryUsage = 0; let memoryUsed = 0; let memoryTotal = 0; // 解析内存数据 if (Array.isArray(displayMetrics.memory) && displayMetrics.memory.length > 0) { // 数组格式:获取最新的数据点 memoryUsage = displayMetrics.memory[displayMetrics.memory.length - 1].value; } else if (typeof displayMetrics.memory === 'number') { // 数值格式 memoryUsage = displayMetrics.memory; } else if (typeof displayMetrics.memory === 'object') { // 对象格式,包含usage, used, total memoryUsage = displayMetrics.memory.usage || 0; memoryUsed = displayMetrics.memory.used || 0; memoryTotal = displayMetrics.memory.total || 0; } // 如果只有使用率,没有使用量和总量,尝试从其他地方获取 if (memoryTotal === 0 && state.historyMetrics.memory && typeof state.historyMetrics.memory === 'object') { memoryUsed = state.historyMetrics.memory.used || 0; memoryTotal = state.historyMetrics.memory.total || 0; } // 如果内存总量仍为0,尝试使用默认值或从API获取 if (memoryTotal === 0) { // 尝试从系统获取内存总量(如果浏览器支持) if (navigator.deviceMemory) { memoryTotal = navigator.deviceMemory * 1024 * 1024 * 1024; // 转换为字节 } else { // 使用默认值16GB memoryTotal = 16 * 1024 * 1024 * 1024; } } // 计算使用量(如果只有使用率) if (memoryUsed === 0 && memoryTotal > 0) { memoryUsed = (memoryTotal * memoryUsage) / 100; } // 更新显示 const memoryElement = document.getElementById('memoryValue'); const memoryDetailsElement = document.getElementById('memoryDetails'); if (memoryElement) { memoryElement.textContent = `${memoryUsage.toFixed(1)}%`; // 设置红色显示如果达到顶峰,同时保留原有的样式类 memoryElement.className = `text-3xl font-bold metric-value ${memoryUsage > 90 ? 'text-red-500' : 'text-gray-900'}`; } if (memoryDetailsElement) { memoryDetailsElement.className = 'text-xs text-gray-500 mt-1'; memoryDetailsElement.textContent = `${formatBytes(memoryUsed)} / ${formatBytes(memoryTotal)}`; } } // 更新磁盘状态卡片 if (displayMetrics.disk) { let totalUsed = 0; let totalSize = 0; let usagePercent = 0; // 过滤掉不需要的挂载点 const excludedMountpoints = ['/usr', '/boot', '/boot/efi']; // 解析磁盘数据 if (typeof displayMetrics.disk === 'object' && displayMetrics.disk !== null && !Array.isArray(displayMetrics.disk)) { // 按挂载点分组的数据 let hasValidData = false; for (const mountpoint in displayMetrics.disk) { // 跳过排除的挂载点 if (excludedMountpoints.includes(mountpoint)) { continue; } const data = displayMetrics.disk[mountpoint]; if (data && typeof data === 'object') { if (data.used_percent !== undefined && data.total !== undefined) { // 新格式:包含used_percent和total字段 // 计算已使用大小(total * used_percent / 100) const used = (data.total * data.used_percent) / 100; totalUsed += used; totalSize += data.total; hasValidData = true; } else if (data.used !== undefined && data.total !== undefined) { // 旧格式:包含used和total字段 totalUsed += data.used; totalSize += data.total; hasValidData = true; } } } // 如果没有有效数据,尝试使用历史数据 if (!hasValidData && state.historyMetrics.disk && typeof state.historyMetrics.disk === 'object' && !Array.isArray(state.historyMetrics.disk)) { for (const mountpoint in state.historyMetrics.disk) { if (excludedMountpoints.includes(mountpoint)) { continue; } const data = state.historyMetrics.disk[mountpoint]; if (data && typeof data === 'object') { if (data.used_percent !== undefined && data.total !== undefined) { const used = (data.total * data.used_percent) / 100; totalUsed += used; totalSize += data.total; } else if (data.used !== undefined && data.total !== undefined) { totalUsed += data.used; totalSize += data.total; } } } } } else if (typeof displayMetrics.disk === 'object' && displayMetrics.disk.used !== undefined && displayMetrics.disk.total !== undefined) { // 单磁盘数据 totalUsed = displayMetrics.disk.used; totalSize = displayMetrics.disk.total; } // 如果只有使用率,没有使用量和总量,尝试计算 if (totalSize === 0 && displayMetrics.disk && typeof displayMetrics.disk === 'number') { // 如果disk是一个数字,尝试从历史数据获取总量 if (state.historyMetrics.disk && typeof state.historyMetrics.disk === 'object' && !Array.isArray(state.historyMetrics.disk)) { for (const mountpoint in state.historyMetrics.disk) { if (excludedMountpoints.includes(mountpoint)) { continue; } const data = state.historyMetrics.disk[mountpoint]; if (data && typeof data === 'object') { if (data.total !== undefined) { totalSize += data.total; } } } // 计算使用量 if (totalSize > 0) { totalUsed = (totalSize * displayMetrics.disk) / 100; } } } // 计算使用率 if (totalSize > 0) { usagePercent = (totalUsed / totalSize) * 100; } else if (typeof displayMetrics.disk === 'number') { // 如果disk是一个数字,直接使用它作为使用率 usagePercent = displayMetrics.disk; } // 更新显示 const diskElement = document.getElementById('diskValue'); const diskDetailsElement = document.getElementById('diskDetails'); if (diskElement) { diskElement.textContent = `${usagePercent.toFixed(1)}%`; // 设置红色显示如果达到顶峰,同时保留原有的样式类 diskElement.className = `text-3xl font-bold metric-value ${usagePercent > 90 ? 'text-red-500' : 'text-gray-900'}`; } if (diskDetailsElement) { diskDetailsElement.className = 'text-xs text-gray-500 mt-1'; diskDetailsElement.textContent = `${formatBytes(totalUsed)} / ${formatBytes(totalSize)}`; } } // 更新网络流量状态卡片 if (displayMetrics.network) { // 解析网络数据 const rxRate = displayMetrics.network.bytes_received || 0; // 接收速率 (bytes/s) const txRate = displayMetrics.network.bytes_sent || 0; // 发送速率 (bytes/s) // 计算速率比值 let ratio = 0; let ratioSymbol = ''; let ratioText = ''; let symbolColor = ''; if (rxRate === 0 && txRate === 0) { // 如果接收速率和发送速率都为0,显示无穷符号 ratioText = '∞'; ratioSymbol = ''; symbolColor = 'text-gray-500'; } else if (txRate === 0) { // 如果发送速率为0,显示无穷符号和接收箭头 ratioText = '∞'; ratioSymbol = '↓'; symbolColor = 'text-green-500'; } else if (rxRate === 0) { // 如果接收速率为0,显示无穷符号和发送箭头 ratioText = '∞'; ratioSymbol = '↑'; symbolColor = 'text-red-500'; } else { // 计算接收速率与发送速率的比值 ratio = rxRate / txRate; ratioText = ratio.toFixed(2); // 根据比值判断箭头方向和颜色 if (ratio > 10) { // 接收速率远高于发送,显示绿色↓ ratioSymbol = '↓'; symbolColor = 'text-green-500'; } else if (ratio < 0.1) { // 发送速率远高于接收,显示红色↑ ratioSymbol = '↑'; symbolColor = 'text-red-500'; } else if (ratio >= 0.5 && ratio <= 2) { // 收发速率均衡,显示蓝色↔ ratioSymbol = '↔'; symbolColor = 'text-blue-500'; } else if (ratio > 1) { // 接收速率高于发送,显示绿色↓ ratioSymbol = '↓'; symbolColor = 'text-green-500'; } else { // 发送速率高于接收,显示红色↑ ratioSymbol = '↑'; symbolColor = 'text-red-500'; } } // 格式化速率为MB/s const formatRate = (bytesPerSec) => { return (bytesPerSec / (1024 * 1024)).toFixed(2); }; const rxRateMB = formatRate(rxRate); const txRateMB = formatRate(txRate); // 更新显示 const networkElement = document.getElementById('networkValue'); const networkDetailsElement = document.getElementById('networkDetails'); if (networkElement) { // 大数字显示比值和箭头 networkElement.innerHTML = `${ratioText} ${ratioSymbol}`; networkElement.className = 'text-3xl font-bold metric-value text-gray-900 flex items-center'; } if (networkDetailsElement) { // 速率显示 networkDetailsElement.className = 'text-xs text-gray-500 mt-1'; networkDetailsElement.textContent = `${rxRateMB} MB/s | ${txRateMB} MB/s`; } } } // 更新历史指标数据 function updateHistoryMetrics(metrics) { // 只更新有有效数据的指标 if (metrics.cpu) { state.historyMetrics.cpu = metrics.cpu; } if (metrics.cpu_hz) { state.historyMetrics.cpu_hz = metrics.cpu_hz; } if (metrics.memory) { state.historyMetrics.memory = metrics.memory; } if (metrics.disk) { state.historyMetrics.disk = metrics.disk; } if (metrics.network) { state.historyMetrics.network = metrics.network; } } // 更新图表数据 function updateCharts(cpuData, memoryData, diskData, networkData) { // 确保数据格式正确 const safeCpuData = Array.isArray(cpuData) ? cpuData : []; const safeMemoryData = Array.isArray(memoryData) ? memoryData : []; const safeDiskData = typeof diskData === 'object' && diskData !== null ? diskData : {}; const safeNetworkData = typeof networkData === 'object' && networkData !== null ? networkData : {}; // 保存待处理的图表更新数据 state.pendingChartUpdate = { cpuData: safeCpuData, memoryData: safeMemoryData, diskData: safeDiskData, networkData: safeNetworkData }; // 使用节流机制更新图表 _updateChartsThrottled(); } // 带节流功能的图表更新函数 function _updateChartsThrottled() { const now = Date.now(); if (now - state.lastChartUpdate < state.chartUpdateThrottle) { // 如果距离上次更新时间不足节流时间,延迟执行 setTimeout(_updateChartsThrottled, state.chartUpdateThrottle - (now - state.lastChartUpdate)); return; } // 执行实际的图表更新 _updateCharts(state.pendingChartUpdate); state.lastChartUpdate = now; } // 内部图表更新函数,执行实际的图表渲染 function _updateCharts({ cpuData, memoryData, diskData, networkData }) { // 数据点排序函数 const sortDataByTime = (data) => { return [...data].sort((a, b) => { return new Date(a.time) - new Date(b.time); }); }; // 计算固定份数X轴数据 const getFixedPointsData = (data) => { if (!Array.isArray(data) || data.length === 0) { return []; } // 根据时间范围计算需要的份数(每份10分钟) let expectedPoints = 6; // 默认1小时,6份 let timeRange = state.currentTimeRange || '1h'; // 如果使用了自定义时间,检查是否是24小时范围 if (state.customStartTime && state.customEndTime) { const startTime = new Date(state.customStartTime); const endTime = new Date(state.customEndTime); const durationHours = (endTime - startTime) / (1000 * 60 * 60); // 根据实际时长设置预期点数 if (durationHours <= 0.5) { timeRange = '30m'; } else if (durationHours <= 1) { timeRange = '1h'; } else if (durationHours <= 2) { timeRange = '2h'; } else if (durationHours <= 6) { timeRange = '6h'; } else if (durationHours <= 12) { timeRange = '12h'; } else { timeRange = '24h'; } } switch(timeRange) { case '30m': expectedPoints = 3; // 30分钟,3份 break; case '1h': expectedPoints = 6; // 1小时,6份 break; case '2h': expectedPoints = 12; // 2小时,12份 break; case '6h': expectedPoints = 36; // 6小时,36份 break; case '12h': expectedPoints = 72; // 12小时,72份 break; case '24h': expectedPoints = 144; // 24小时,144份 break; } // 排序数据 const sortedData = sortDataByTime(data); // 如果数据点足够,直接返回 if (sortedData.length <= expectedPoints) { return sortedData; } // 计算采样步长 const step = Math.ceil(sortedData.length / expectedPoints); const sampled = []; // 采样数据,确保得到期望的份数 for (let i = 0; i < sortedData.length; i += step) { sampled.push(sortedData[i]); if (sampled.length >= expectedPoints) { break; } } // 确保包含最后一个数据点 if (sampled.length < expectedPoints && sortedData.length > 0) { // 如果采样点不够,从末尾补充 const remaining = expectedPoints - sampled.length; for (let i = 1; i <= remaining; i++) { const index = Math.max(0, sortedData.length - i); if (!sampled.some(item => item.time === sortedData[index].time)) { sampled.push(sortedData[index]); } } } // 再次排序,确保时间顺序 return sortDataByTime(sampled); }; // 更新CPU图表 if (cpuData && Array.isArray(cpuData) && cpuData.length > 0 && charts.cpu) { const fixedData = getFixedPointsData(cpuData); charts.cpu.data.datasets[0].data = fixedData.map(item => ({ x: formatTime(item.time), y: item.value })); charts.cpu.update(); } // 更新内存图表 if (memoryData && Array.isArray(memoryData) && memoryData.length > 0 && charts.memory) { const fixedData = getFixedPointsData(memoryData); charts.memory.data.datasets[0].data = fixedData.map(item => ({ x: formatTime(item.time), y: item.value })); charts.memory.update(); } // 更新磁盘图表,支持多个挂载点 if (diskData && charts.disk) { // 定义不同的颜色,用于区分不同的挂载点 const colors = [ { border: '#f59e0b', background: 'rgba(245, 158, 11, 0.1)' }, // 黄色 { border: '#ef4444', background: 'rgba(239, 68, 68, 0.1)' }, // 红色 { border: '#10b981', background: 'rgba(16, 185, 129, 0.1)' }, // 绿色 { border: '#3b82f6', background: 'rgba(59, 130, 246, 0.1)' }, // 蓝色 { border: '#8b5cf6', background: 'rgba(139, 92, 246, 0.1)' }, // 紫色 { border: '#ec4899', background: 'rgba(236, 72, 153, 0.1)' }, // 粉色 ]; // 清空现有的数据集 charts.disk.data.datasets = []; // 为每个挂载点创建独立的数据集 let colorIndex = 0; if (typeof diskData === 'object' && diskData !== null && !Array.isArray(diskData)) { // 处理按挂载点分组的数据 for (const [mountpoint, data] of Object.entries(diskData)) { if (data && Array.isArray(data) && data.length > 0) { // 获取颜色 const color = colors[colorIndex % colors.length]; colorIndex++; // 排序数据 const sortedData = sortDataByTime(data); // 使用固定份数X轴数据计算 const fixedPointsData = getFixedPointsData(sortedData); // 创建数据集 const dataset = { label: `磁盘使用率 (${mountpoint})`, data: fixedPointsData.map(item => ({ x: formatTime(item.time), y: item.value })), borderColor: color.border, backgroundColor: color.background, borderWidth: 2, fill: true, tension: 0.7, pointRadius: 0, pointHoverRadius: 3, }; // 添加数据集 charts.disk.data.datasets.push(dataset); } } } else if (Array.isArray(diskData) && diskData.length > 0) { // 处理单一磁盘数据 const sortedData = sortDataByTime(diskData); // 使用固定份数X轴数据计算 const fixedPointsData = getFixedPointsData(sortedData); const dataset = { label: '磁盘使用率', data: fixedPointsData.map(item => ({ x: formatTime(item.time), y: item.value })), borderColor: '#f59e0b', backgroundColor: 'rgba(245, 158, 11, 0.1)', borderWidth: 2, fill: true, tension: 0.7, pointRadius: 0, pointHoverRadius: 3, }; charts.disk.data.datasets.push(dataset); } // 更新图表 charts.disk.update(); } // 保存当前选中的网卡 state.currentInterface = state.currentInterface || 'all'; // 数据求和辅助函数:计算所有网卡在同一时间点的数据之和 const sumAllInterfacesData = (networkData, metricType) => { if (typeof networkData !== 'object' || networkData === null) { return []; } // 收集所有时间点 const allTimes = new Set(); const interfaceDatas = []; // 遍历所有网卡 for (const iface in networkData) { if (networkData.hasOwnProperty(iface) && typeof networkData[iface] === 'object') { const ifaceData = networkData[iface][metricType]; if (Array.isArray(ifaceData)) { interfaceDatas.push(ifaceData); // 收集所有时间点 ifaceData.forEach(item => { allTimes.add(item.time); }); } } } // 如果没有数据,返回空数组 if (interfaceDatas.length === 0) { return []; } // 将时间点转换为数组并排序 const sortedTimes = Array.from(allTimes).sort((a, b) => new Date(a) - new Date(b)); // 计算每个时间点的总和 const summedData = sortedTimes.map(time => { let sum = 0; // 遍历所有网卡数据,累加同一时间点的值 interfaceDatas.forEach(ifaceData => { // 查找当前时间点的数据 const dataPoint = ifaceData.find(item => item.time === time); if (dataPoint) { sum += dataPoint.value; } }); return { time: time, value: sum }; }); return summedData; }; // 更新网络流量趋势图表(发送总和和接收总和) if (networkData && charts.network) { let txBytesData, rxBytesData; // 如果是按网卡分组的数据 if (typeof networkData === 'object' && networkData.sent === undefined && networkData.received === undefined) { if (state.currentInterface === 'all') { // 计算所有网卡的发送累积总流量 txBytesData = sumAllInterfacesData(networkData, 'tx_bytes'); // 计算所有网卡的接收累积总流量 rxBytesData = sumAllInterfacesData(networkData, 'rx_bytes'); } else { // 选择当前选中的网卡数据 const selectedNetworkData = networkData[state.currentInterface] || networkData['all'] || {}; txBytesData = selectedNetworkData.tx_bytes || []; rxBytesData = selectedNetworkData.rx_bytes || []; } } else { // 直接使用数据 txBytesData = networkData.tx_bytes || []; rxBytesData = networkData.rx_bytes || []; } if (txBytesData.length > 0 || rxBytesData.length > 0) { // 使用发送累积总流量数据 if (Array.isArray(txBytesData) && txBytesData.length > 0) { // 排序发送数据 const sortedTxBytes = sortDataByTime(txBytesData); // 转换为MB const txBytesSumData = sortedTxBytes.map(item => ({ time: item.time, value: item.value / (1024 * 1024) // 直接转换为MB })); // 使用固定份数X轴数据计算 const fixedPointsTxBytesSum = getFixedPointsData(txBytesSumData); charts.network.data.datasets[0].data = fixedPointsTxBytesSum.map(item => ({ x: formatTime(item.time), y: item.value })); } // 使用接收累积总流量数据 if (Array.isArray(rxBytesData) && rxBytesData.length > 0) { // 排序接收数据 const sortedRxBytes = sortDataByTime(rxBytesData); // 转换为MB const rxBytesSumData = sortedRxBytes.map(item => ({ time: item.time, value: item.value / (1024 * 1024) // 直接转换为MB })); // 使用固定份数X轴数据计算 const fixedPointsRxBytesSum = getFixedPointsData(rxBytesSumData); charts.network.data.datasets[1].data = fixedPointsRxBytesSum.map(item => ({ x: formatTime(item.time), y: item.value })); } charts.network.update(); } } // 更新网速趋势图表 if (networkData && charts.speed) { let sentData, receivedData; // 如果是按网卡分组的数据 if (typeof networkData === 'object' && networkData.sent === undefined && networkData.received === undefined) { if (state.currentInterface === 'all') { // 计算所有网卡的发送速率总和 sentData = sumAllInterfacesData(networkData, 'sent'); // 计算所有网卡的接收速率总和 receivedData = sumAllInterfacesData(networkData, 'received'); } else { // 选择当前选中的网卡数据 const selectedNetworkData = networkData[state.currentInterface] || networkData['all'] || {}; sentData = selectedNetworkData.sent || []; receivedData = selectedNetworkData.received || []; } } else { // 直接使用数据 sentData = networkData.sent || []; receivedData = networkData.received || []; } if (sentData.length > 0 || receivedData.length > 0) { // 更新发送流量 if (Array.isArray(sentData) && sentData.length > 0) { const sortedData = sortDataByTime(sentData); // 使用固定份数X轴数据计算 const fixedPointsData = getFixedPointsData(sortedData); charts.speed.data.datasets[0].data = fixedPointsData.map(item => ({ x: formatTime(item.time), y: item.value / (1024 * 1024) // 转换为MB/s })); } // 更新接收流量 if (Array.isArray(receivedData) && receivedData.length > 0) { const sortedData = sortDataByTime(receivedData); // 使用固定份数X轴数据计算 const fixedPointsData = getFixedPointsData(sortedData); charts.speed.data.datasets[1].data = fixedPointsData.map(item => ({ x: formatTime(item.time), y: item.value / (1024 * 1024) // 转换为MB/s })); } charts.speed.update(); } } // 更新网卡选择下拉框 updateInterfaceDropdown(networkData); // 初始化图表(如果尚未初始化) initDetailedCharts(); } // 更新网卡选择下拉框 function updateInterfaceDropdown(networkData) { // 创建或获取网卡选择容器 let interfaceContainer = document.getElementById('interfaceSelectorContainer'); if (!interfaceContainer) { // 获取图表选项卡导航容器 const chartTabs = document.getElementById('chartTabs'); if (!chartTabs) { return; } // 创建网卡选择容器 interfaceContainer = document.createElement('div'); interfaceContainer.id = 'interfaceSelectorContainer'; interfaceContainer.className = 'flex items-center gap-2 ml-4'; // 创建标签 const label = document.createElement('label'); label.htmlFor = 'interfaceSelector'; label.className = 'text-sm text-gray-600'; label.textContent = '网卡:'; // 创建下拉框 const select = document.createElement('select'); select.id = 'interfaceSelector'; select.className = 'px-3 py-1 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm'; // 添加默认选项 const defaultOption = document.createElement('option'); defaultOption.value = 'all'; defaultOption.textContent = '所有网卡'; select.appendChild(defaultOption); // 添加事件监听 select.addEventListener('change', (e) => { state.currentInterface = e.target.value; // 重新加载指标 loadMetrics(); }); // 组装容器 interfaceContainer.appendChild(label); interfaceContainer.appendChild(select); // 将容器添加到图表选项卡导航中 const tabsContainer = chartTabs.querySelector('.flex.flex-wrap'); if (tabsContainer) { tabsContainer.appendChild(interfaceContainer); } } // 更新下拉框选项 const select = document.getElementById('interfaceSelector'); if (select) { // 保存当前选中的值 const currentValue = select.value; // 清空现有选项 select.innerHTML = ''; // 添加默认选项 const defaultOption = document.createElement('option'); defaultOption.value = 'all'; defaultOption.textContent = '所有网卡'; select.appendChild(defaultOption); // 添加所有网卡选项 // 检查是否有按网卡分组的网络数据 if (typeof networkData === 'object' && networkData.sent === undefined && networkData.received === undefined) { // 获取所有网卡名称 const interfaces = Object.keys(networkData); // 添加所有网卡选项 interfaces.forEach(iface => { // 只添加实际的网卡名称,不添加"all"选项 if (iface !== 'all') { const option = document.createElement('option'); option.value = iface; option.textContent = iface; select.appendChild(option); } }); } else { // 如果没有按网卡分组的数据,显示"所有接口"选项 const option = document.createElement('option'); option.value = 'all'; option.textContent = '所有接口'; select.appendChild(option); } // 恢复当前选中的值 select.value = currentValue; } // 检查当前选中的选项卡,如果不是"网络"或"网速",隐藏网卡选择下拉框 const activeTab = document.querySelector('.chart-tab.active'); if (activeTab && activeTab.dataset.tab !== 'network' && activeTab.dataset.tab !== 'speed') { interfaceContainer.classList.add('hidden'); } else { interfaceContainer.classList.remove('hidden'); } } // 尝试重连WebSocket function attemptReconnect() { // 清除可能存在的重连定时器 if (wsReconnectTimeout) { clearTimeout(wsReconnectTimeout); wsReconnectTimeout = null; } if (wsReconnectAttempts < wsMaxReconnectAttempts) { wsReconnectAttempts++; // 指数退避,但不超过最大延迟 wsReconnectDelay = Math.min(wsReconnectDelay * 2, wsMaxReconnectDelay); // 添加随机抖动,防止多个客户端同时重连 const jitter = Math.random() * 1000; const delay = wsReconnectDelay + jitter; wsReconnectTimeout = setTimeout(() => { console.log(`尝试重新连接WebSocket (${wsReconnectAttempts}/${wsMaxReconnectAttempts}),延迟 ${Math.round(delay)}ms`); initWebSocket(); }, delay); } else { console.error('WebSocket重连失败,已达到最大重连次数'); // 30秒后重置重连状态,允许再次尝试 setTimeout(() => { wsReconnectAttempts = 0; wsReconnectDelay = 1000; console.log('WebSocket重连状态已重置,准备再次尝试连接'); attemptReconnect(); }, 30000); } } // 打开模态框 function openModal(isEdit = false, deviceData = null) { const modal = document.getElementById('deviceModal'); const modalContent = document.getElementById('modalContent'); const modalTitle = document.getElementById('modalTitle'); const deviceId = document.getElementById('deviceId'); const deviceName = document.getElementById('deviceName'); const deviceIp = document.getElementById('deviceIp'); const deviceStatus = document.getElementById('deviceStatus'); // 重置表单 document.getElementById('deviceForm').reset(); // 设置模态框标题和数据 if (isEdit && deviceData) { modalTitle.textContent = '编辑设备'; deviceId.value = deviceData.id; deviceName.value = deviceData.name || ''; deviceIp.value = deviceData.ip || ''; deviceStatus.value = deviceData.status || 'inactive'; } else { modalTitle.textContent = '添加设备'; deviceId.value = ''; } // 显示模态框并添加动画 modal.classList.remove('hidden'); // 触发重排后再添加动画类 setTimeout(() => { modalContent.classList.remove('scale-95', 'opacity-0'); modalContent.classList.add('scale-100', 'opacity-100'); }, 10); } // 关闭模态框 function closeModal() { const modal = document.getElementById('deviceModal'); const modalContent = document.getElementById('modalContent'); // 先应用离开动画 modalContent.classList.remove('scale-100', 'opacity-100'); modalContent.classList.add('scale-95', 'opacity-0'); // 动画结束后隐藏模态框 setTimeout(() => { modal.classList.add('hidden'); }, 300); } // 编辑设备 function editDevice(deviceId) { // 直接从API获取设备详情,确保数据最新 fetch(`${API_BASE_URL}/devices/${deviceId}`) .then(response => { if (!response.ok) { throw new Error('Failed to fetch device details'); } return response.json(); }) .then(data => { // 提取设备数据(API返回的是 {"device": {...}} 格式) const deviceData = data.device; openModal(true, deviceData); }) .catch(error => { console.error('Failed to get device details:', error); showToast('获取设备详情失败', 'error'); }); } // 处理设备表单提交 async function handleDeviceFormSubmit(event) { event.preventDefault(); const deviceId = document.getElementById('deviceId').value; const deviceName = document.getElementById('deviceName').value; const deviceIp = document.getElementById('deviceIp').value; const deviceStatus = document.getElementById('deviceStatus').value; // 生成或使用现有ID let idToUse = deviceId; if (!idToUse) { // 为新设备生成ID idToUse = 'device-' + Date.now(); } // 获取当前时间戳 const timestamp = Math.floor(Date.now() / 1000); // 为新设备生成token let token = ''; if (!deviceId) { token = 'token-' + Math.random().toString(36).substring(2, 15); } // 构建完整的设备数据对象,包含所有必需字段 const deviceData = { id: idToUse, name: deviceName, ip: deviceIp, status: deviceStatus, token: token, // 新设备生成token,编辑时不提供(由后端保持原值) created_at: timestamp, // 新设备的创建时间 updated_at: timestamp // 更新时间 }; try { let url = `${API_BASE_URL}/devices/`; let method = 'POST'; // 如果是编辑操作,修改URL和方法 if (deviceId) { url = `${API_BASE_URL}/devices/${deviceId}`; method = 'PUT'; } // 发送请求 const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(deviceData) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); // 显示成功提示 showToast(deviceId ? '设备更新成功' : '设备添加成功', 'success'); // 关闭模态框 closeModal(); // 重新加载设备列表 await loadDeviceManagementList(); } catch (error) { console.error('保存设备失败:', error); showToast(deviceId ? '设备更新失败' : '设备添加失败', 'error'); } } // 绑定事件 function bindEvents() { // 添加设备按钮事件 const addDeviceBtn = document.getElementById('addDeviceBtn'); if (addDeviceBtn) { addDeviceBtn.addEventListener('click', () => { openModal(false); }); } // 关闭模态框按钮 const closeModalBtn = document.getElementById('closeModalBtn'); if (closeModalBtn) { closeModalBtn.addEventListener('click', closeModal); } // 取消按钮 const cancelModalBtn = document.getElementById('cancelModalBtn'); if (cancelModalBtn) { cancelModalBtn.addEventListener('click', closeModal); } // 设备表单提交 const deviceForm = document.getElementById('deviceForm'); if (deviceForm) { deviceForm.addEventListener('submit', handleDeviceFormSubmit); } // 点击模态框背景关闭 const modal = document.getElementById('deviceModal'); if (modal) { modal.addEventListener('click', (event) => { if (event.target === modal) { closeModal(); } }); } // 设备搜索事件 const deviceSearch = document.getElementById('deviceSearch'); if (deviceSearch) { deviceSearch.addEventListener('input', applyDeviceFilters); } // 设备状态筛选事件 const deviceStatusFilter = document.getElementById('deviceStatusFilter'); if (deviceStatusFilter) { deviceStatusFilter.addEventListener('change', applyDeviceFilters); } // 清除筛选按钮事件 const clearFilterBtn = document.getElementById('clearFilterBtn'); if (clearFilterBtn) { clearFilterBtn.addEventListener('click', clearDeviceFilters); } // 自定义时间查询事件 const customTimeQuery = document.getElementById('customTimeQuery'); if (customTimeQuery) { customTimeQuery.addEventListener('click', () => { const startTimeInput = document.getElementById('customStartTime'); const endTimeInput = document.getElementById('customEndTime'); if (startTimeInput && endTimeInput) { // 获取本地时间 const localStartTime = new Date(startTimeInput.value); const localEndTime = new Date(endTimeInput.value); // 转换为ISO字符串(UTC时间) state.customStartTime = localStartTime.toISOString(); state.customEndTime = localEndTime.toISOString(); // 清空当前时间范围状态,只使用自定义时间 state.currentTimeRange = ''; // 重新加载数据 loadMetrics(); } }); } // 缩放控件事件处理 const zoomOutBtn = document.getElementById('zoomOutBtn'); const zoomInBtn = document.getElementById('zoomInBtn'); const resetZoomBtn = document.getElementById('resetZoomBtn'); const currentTimeRangeDisplay = document.getElementById('currentTimeRangeDisplay'); if (zoomOutBtn && zoomInBtn && currentTimeRangeDisplay) { // 时间范围选项列表,用于缩放 const timeRanges = ['30m', '1h', '2h', '6h', '12h', '24h']; } // 网卡选择和刷新按钮事件 const networkInterfaceSelect = document.getElementById('networkInterface'); const refreshInterfacesBtn = document.getElementById('refreshInterfacesBtn'); if (networkInterfaceSelect) { networkInterfaceSelect.addEventListener('change', (e) => { state.currentInterface = e.target.value; loadMetrics(); // 切换网卡后重新加载数据 }); } if (refreshInterfacesBtn) { refreshInterfacesBtn.addEventListener('click', loadNetworkInterfaces); } // 自动刷新开关事件 const autoRefreshToggle = document.getElementById('autoRefreshToggle'); if (autoRefreshToggle) { autoRefreshToggle.addEventListener('change', (e) => { state.autoRefreshEnabled = e.target.checked; // 如果开启了自动刷新,立即刷新一次数据 if (state.autoRefreshEnabled) { loadMetrics(); } }); } } // 加载网卡列表 async function loadNetworkInterfaces() { try { // 获取设备ID const hash = window.location.hash; let deviceId = ''; if (hash.startsWith('#serverMonitor/')) { deviceId = hash.split('/')[1]; } // 从网络指标API获取网卡列表 const response = await fetch(`${API_BASE_URL}/metrics/network?device_id=${deviceId}`); const data = await response.json(); // 从返回的数据中提取网卡列表 const interfaces = Object.keys(data.data || {}); // 更新下拉选择框 const selectElement = document.getElementById('networkInterface'); if (selectElement) { // 清空现有选项 selectElement.innerHTML = ''; // 添加新选项 interfaces.forEach(iface => { if (iface !== 'all') { const option = document.createElement('option'); option.value = iface; option.textContent = iface; selectElement.appendChild(option); } }); // 如果当前选中的网卡不存在,重置为'all' if (!interfaces.includes(state.currentInterface) && state.currentInterface !== 'all') { state.currentInterface = 'all'; } // 设置当前选中的值 selectElement.value = state.currentInterface; } } catch (error) { console.error('加载网卡列表失败:', error); // 显示友好的错误提示 showToast('加载网卡列表失败,请稍后重试', 'error'); } } // 更新当前时间范围显示 const updateTimeRangeDisplay = () => { let displayText = ''; // 计算实际的开始时间和结束时间 let startTime, endTime; if (state.customStartTime && state.customEndTime) { // 使用自定义时间范围 startTime = new Date(state.customStartTime); endTime = new Date(state.customEndTime); displayText = `${formatTime(state.customStartTime)} 至 ${formatTime(state.customEndTime)}`; } else { // 使用预设时间范围 const now = new Date(); endTime = now; // 根据预设时间范围计算开始时间 switch(state.currentTimeRange) { case '30m': startTime = new Date(now.getTime() - 30 * 60 * 1000); displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`; break; case '1h': startTime = new Date(now.getTime() - 60 * 60 * 1000); displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`; break; case '2h': startTime = new Date(now.getTime() - 2 * 60 * 60 * 1000); displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`; break; case '6h': startTime = new Date(now.getTime() - 6 * 60 * 60 * 1000); displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`; break; case '12h': startTime = new Date(now.getTime() - 12 * 60 * 60 * 1000); displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`; break; case '24h': startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000); displayText = `${formatTime(startTime.toISOString())} 至 ${formatTime(endTime.toISOString())}`; break; default: displayText = '自定义时间范围'; } } // 更新所有图表的时间范围显示 const displays = [ document.getElementById('cpuCurrentTimeRangeDisplay'), document.getElementById('currentTimeRangeDisplay'), document.getElementById('diskCurrentTimeRangeDisplay'), document.getElementById('networkCurrentTimeRangeDisplay'), document.getElementById('speedCurrentTimeRangeDisplay') ]; displays.forEach(display => { if (display) display.textContent = displayText; }); }; // 初始化显示 updateTimeRangeDisplay(); // 放大事件 const zoomInHandler = () => { // 只在使用预设时间范围时生效 if (state.customStartTime && state.customEndTime) { // 使用自定义时间时,先清除自定义时间 state.customStartTime = ''; state.customEndTime = ''; state.currentTimeRange = '1h'; } else { // 查找当前时间范围在列表中的索引 const currentIndex = timeRanges.indexOf(state.currentTimeRange); if (currentIndex > 0) { // 放大:使用更小的时间范围 state.currentTimeRange = timeRanges[currentIndex - 1]; } } // 更新显示 updateTimeRangeDisplay(); // 重新加载数据 loadMetrics(); }; // 为所有放大按钮添加事件监听器 document.getElementById('cpuZoomInBtn')?.addEventListener('click', zoomInHandler); document.getElementById('memoryZoomInBtn')?.addEventListener('click', zoomInHandler); document.getElementById('diskZoomInBtn')?.addEventListener('click', zoomInHandler); document.getElementById('networkZoomInBtn')?.addEventListener('click', zoomInHandler); document.getElementById('speedZoomInBtn')?.addEventListener('click', zoomInHandler); // 缩小事件 const zoomOutHandler = () => { // 只在使用预设时间范围时生效 if (state.customStartTime && state.customEndTime) { // 使用自定义时间时,先清除自定义时间 state.customStartTime = ''; state.customEndTime = ''; state.currentTimeRange = '1h'; } else { // 查找当前时间范围在列表中的索引 const currentIndex = timeRanges.indexOf(state.currentTimeRange); if (currentIndex < timeRanges.length - 1) { // 缩小:使用更大的时间范围 state.currentTimeRange = timeRanges[currentIndex + 1]; } } // 更新显示 updateTimeRangeDisplay(); // 重新加载数据 loadMetrics(); }; // 为所有缩小按钮添加事件监听器 document.getElementById('cpuZoomOutBtn')?.addEventListener('click', zoomOutHandler); document.getElementById('memoryZoomOutBtn')?.addEventListener('click', zoomOutHandler); document.getElementById('diskZoomOutBtn')?.addEventListener('click', zoomOutHandler); document.getElementById('networkZoomOutBtn')?.addEventListener('click', zoomOutHandler); document.getElementById('speedZoomOutBtn')?.addEventListener('click', zoomOutHandler); // 重置缩放处理函数 const resetZoomHandler = () => { // 重置时间范围 state.currentTimeRange = '1h'; state.customStartTime = ''; state.customEndTime = ''; // 更新显示 updateTimeRangeDisplay(); // 重新加载数据 loadMetrics(); // 重置所有图表的缩放 Object.values(charts).forEach(chart => { if (chart && typeof chart.resetZoom === 'function') { chart.resetZoom(); } }); }; // 为所有重置按钮添加事件监听器 document.getElementById('cpuResetZoomBtn')?.addEventListener('click', resetZoomHandler); document.getElementById('memoryResetZoomBtn')?.addEventListener('click', resetZoomHandler); document.getElementById('diskResetZoomBtn')?.addEventListener('click', resetZoomHandler); document.getElementById('networkResetZoomBtn')?.addEventListener('click', resetZoomHandler); document.getElementById('speedResetZoomBtn')?.addEventListener('click', resetZoomHandler); // 工具函数 function showContent(contentId) { const element = document.getElementById(contentId); if (element) { element.classList.remove('hidden'); } } // 注意:此函数已被更新的goToServerMonitor函数替代,保留用于兼容 // 初始化图表选项卡 function initChartTabs() { const tabs = document.querySelectorAll('.chart-tab'); if (tabs.length === 0) return; tabs.forEach(tab => { tab.addEventListener('click', () => { // 移除所有选项卡的激活状态 tabs.forEach(t => { t.classList.remove('active', 'text-blue-600', 'border-blue-600'); t.classList.add('text-gray-600', 'border-transparent'); }); // 添加当前选项卡的激活状态 tab.classList.add('active', 'text-blue-600', 'border-blue-600'); tab.classList.remove('text-gray-600', 'border-transparent'); // 隐藏所有图表容器 const tabId = tab.dataset.tab; const chartContainers = document.querySelectorAll('.chart-container'); chartContainers.forEach(container => { container.classList.add('hidden'); }); // 显示当前选中的图表容器 const activeContainer = document.getElementById(`${tabId}ChartContainer`); if (activeContainer) { activeContainer.classList.remove('hidden'); console.log(`Shown chart container: ${tabId}ChartContainer`); } else { console.error(`Chart container not found: ${tabId}ChartContainer`); } // 显示/隐藏进程信息、磁盘详细信息和系统日志 const processInfoContainer = document.getElementById('processInfoContainer'); const diskDetailsContainer = document.getElementById('diskDetailsContainer'); const logInfoContainer = document.getElementById('logInfoContainer'); // 隐藏所有附加信息容器 if (processInfoContainer) { processInfoContainer.classList.add('hidden'); } if (diskDetailsContainer) { diskDetailsContainer.classList.add('hidden'); } if (logInfoContainer) { logInfoContainer.classList.add('hidden'); } // 根据选项卡显示相应的附加信息 if (tabId === 'cpu') { // 显示进程信息 if (processInfoContainer) { processInfoContainer.classList.remove('hidden'); // 加载进程信息 loadProcessInfo(); } } else if (tabId === 'disk') { // 显示磁盘详细信息 if (diskDetailsContainer) { diskDetailsContainer.classList.remove('hidden'); // 加载磁盘详细信息 loadDiskDetails(); } } else if (tabId === 'logs') { // 显示系统日志 if (logInfoContainer) { logInfoContainer.classList.remove('hidden'); // 加载系统日志 loadSystemLogs(); } } // 显示/隐藏网卡选择下拉框 const interfaceContainer = document.getElementById('interfaceSelectorContainer'); if (interfaceContainer) { // 只有在点击"网络"或"网速"选项卡时,显示网卡选择下拉框 if (tabId === 'network' || tabId === 'speed') { interfaceContainer.classList.remove('hidden'); } else { interfaceContainer.classList.add('hidden'); } } }); }); // 初始状态:根据当前选中的选项卡显示/隐藏网卡选择下拉框和加载数据 const activeTab = document.querySelector('.chart-tab.active'); const interfaceContainer = document.getElementById('interfaceSelectorContainer'); if (activeTab) { const tabId = activeTab.dataset.tab; // 显示当前选中的图表容器 const activeContainer = document.getElementById(`${tabId}ChartContainer`); if (activeContainer) { activeContainer.classList.remove('hidden'); } else { console.error(`Initial chart container not found: ${tabId}ChartContainer`); } // 显示/隐藏进程信息、磁盘详细信息和系统日志 const processInfoContainer = document.getElementById('processInfoContainer'); const diskDetailsContainer = document.getElementById('diskDetailsContainer'); const logInfoContainer = document.getElementById('logInfoContainer'); // 隐藏所有附加信息容器 if (processInfoContainer) { processInfoContainer.classList.add('hidden'); } if (diskDetailsContainer) { diskDetailsContainer.classList.add('hidden'); } if (logInfoContainer) { logInfoContainer.classList.add('hidden'); } // 根据选项卡显示相应的附加信息 if (tabId === 'cpu') { // 显示进程信息 if (processInfoContainer) { processInfoContainer.classList.remove('hidden'); } loadProcessInfo(); } else if (tabId === 'disk') { // 显示磁盘详细信息 if (diskDetailsContainer) { diskDetailsContainer.classList.remove('hidden'); } loadDiskDetails(); } else if (tabId === 'logs') { // 显示系统日志 if (logInfoContainer) { logInfoContainer.classList.remove('hidden'); } loadSystemLogs(); } // 显示/隐藏网卡选择下拉框 if (interfaceContainer) { if (tabId === 'network' || tabId === 'speed') { interfaceContainer.classList.remove('hidden'); } else { interfaceContainer.classList.add('hidden'); } } } } // 页面加载完成后初始化 window.addEventListener('DOMContentLoaded', () => { initApp(); // 初始化图表选项卡 initChartTabs(); // 添加路由监听,处理hash变化 window.addEventListener('hashchange', handleHashChange); // 初始检查hash handleHashChange(); // 为进程信息显示数量下拉框添加事件监听 const processPageSizeSelect = document.getElementById('processPageSize'); if (processPageSizeSelect) { processPageSizeSelect.addEventListener('change', () => { loadProcessInfo(1); // 重置为第一页 }); // 设置初始值 processPageSizeSelect.value = processPagination.itemsPerPage; } }); // 进程信息分页状态 let processPagination = { currentPage: 1, itemsPerPage: 5, totalItems: 0, totalPages: 0, allProcesses: [], lastDeviceID: '' // 上次请求数据的设备ID }; // 系统日志分页状态 let logPagination = { currentPage: 1, itemsPerPage: 5, totalItems: 0, totalPages: 0, allLogs: [], lastDeviceID: '' // 上次请求数据的设备ID }; // 加载进程信息 async function loadProcessInfo(page = 1) { const processTableBody = document.getElementById('processTableBody'); const processPaginationContainer = document.getElementById('processPaginationContainer'); const processPageSizeSelect = document.getElementById('processPageSize'); if (!processTableBody) return; // 获取并设置每页显示数量 if (processPageSizeSelect) { const selectedPageSize = parseInt(processPageSizeSelect.value, 10); if (selectedPageSize !== processPagination.itemsPerPage) { processPagination.itemsPerPage = selectedPageSize; // 重置为第一页 page = 1; processPagination.currentPage = 1; } } try { // 构建查询参数 const params = new URLSearchParams(); if (state.currentDeviceID) { params.append('device_id', state.currentDeviceID); } // 检查设备ID是否变化 if (processPagination.lastDeviceID !== state.currentDeviceID) { // 设备ID变化,清空旧数据 processPagination.allProcesses = []; processPagination.totalItems = 0; processPagination.totalPages = 0; processPagination.currentPage = 1; } // 如果是第一次加载或设备ID变化,获取全部数据 if (processPagination.allProcesses.length === 0) { // 发送请求 const response = await fetch(`${API_BASE_URL}/metrics/processes?${params.toString()}`); if (!response.ok) { throw new Error('Failed to fetch process info'); } const data = await response.json(); processPagination.allProcesses = data.data || []; processPagination.totalItems = processPagination.allProcesses.length; processPagination.totalPages = Math.ceil(processPagination.totalItems / processPagination.itemsPerPage); // 更新上次请求数据的设备ID processPagination.lastDeviceID = state.currentDeviceID; } // 更新当前页码 processPagination.currentPage = page; // 清空表格 processTableBody.innerHTML = ''; if (processPagination.totalItems === 0) { // 没有进程数据,显示提示 processTableBody.innerHTML = `暂无磁盘详细信息
加载磁盘详细信息失败