// 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 = ` 加载业务视图数据失败 `; } } } // 渲染业务视图 function renderBusinessView(devices) { const tableBody = document.getElementById('businessViewTableBody'); if (!tableBody) return; tableBody.innerHTML = ''; devices.forEach(device => { const row = document.createElement('tr'); row.className = 'hover:bg-gray-50 transition-colors cursor-pointer'; row.innerHTML = ` ${device.name} ${device.ip} ${device.os || '未知'} 正常 `; // 为表格行添加点击事件 row.addEventListener('click', () => { goToServerMonitor(device.id); }); tableBody.appendChild(row); }); } // 加载告警列表数据 async function loadAlarmListData() { try { const response = await fetch(`${API_BASE_URL}/alarms`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); const alarmData = data.alarms || []; renderAlarmList(alarmData); } catch (error) { console.error('加载告警列表失败:', error); // 只显示错误信息,不使用模拟数据 const alarmList = document.getElementById('alarmList'); if (alarmList) { alarmList.innerHTML = `
加载告警列表失败
`; } } } // 渲染告警列表 function renderAlarmList(alarmData) { const alarmList = document.getElementById('alarmList'); if (!alarmList) return; alarmList.innerHTML = ''; alarmData.forEach(alarm => { const alarmItem = document.createElement('div'); alarmItem.className = `p-4 bg-gray-50 rounded-lg border-l-4 ${getAlarmBorderColor(alarm.level)}`; alarmItem.innerHTML = `

${alarm.message}

${alarm.device}

${alarm.time}
`; alarmList.appendChild(alarmItem); }); } // 获取告警样式 function getAlarmBorderColor(level) { switch(level) { case 'error': return 'border-red-500'; case 'warning': return 'border-yellow-500'; case 'info': return 'border-blue-500'; default: return 'border-gray-500'; } } function getAlarmIconColor(level) { switch(level) { case 'error': return 'text-red-500'; case 'warning': return 'text-yellow-500'; case 'info': return 'text-blue-500'; default: return 'text-gray-500'; } } // 加载服务器数量 async function loadServerCount() { try { const response = await fetch(`${API_BASE_URL}/devices/`); const data = await response.json(); const count = data.devices ? data.devices.length : 0; const serverCountElement = document.getElementById('serverCount'); if (serverCountElement) { serverCountElement.textContent = count; } } catch (error) { console.error('加载服务器数量失败:', error); const serverCountElement = document.getElementById('serverCount'); if (serverCountElement) { serverCountElement.textContent = '2'; } } } // 加载所有服务器 async function loadAllServers() { try { // 调用设备状态API获取详细信息 const response = await fetch(`${API_BASE_URL}/devices/status`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); const devices = data.devices || []; renderServersGrid(devices); } catch (error) { console.error('加载服务器列表失败:', error); // 只显示错误信息,不使用模拟数据 const serversGrid = document.getElementById('serversGrid'); if (serversGrid) { serversGrid.innerHTML = `

加载服务器列表失败

`; } } } // 渲染服务器网格 function renderServersGrid(devices) { const serversGrid = document.getElementById('serversGrid'); if (!serversGrid) return; serversGrid.innerHTML = ''; devices.forEach(device => { const serverCard = createServerCard(device); serversGrid.appendChild(serverCard); }); } // 跳转到服务器监控界面 function goToServerMonitor(deviceId) { // 确保deviceId有效 if (!deviceId) { console.error('Invalid device ID'); return; } // 设置URL hash window.location.hash = `#serverMonitor/${deviceId}`; // 手动调用switchPage来立即更新页面 switchPage(); } // 创建服务器卡片 function createServerCard(device) { const card = document.createElement('div'); card.className = 'bg-white rounded-xl shadow-md p-6 card-hover border border-gray-100 cursor-pointer'; // 为卡片添加点击事件 card.addEventListener('click', () => { // 确保使用正确的设备ID字段 const deviceId = device.device_id || device.id || device.ID; goToServerMonitor(deviceId); }); // 获取设备状态数据 const status = device.status || {}; const cpuUsage = status.cpu !== undefined ? status.cpu.toFixed(1) : '0.0'; const memoryUsage = status.memory !== undefined ? status.memory.toFixed(1) : '0.0'; const diskUsage = status.disk !== undefined ? status.disk.toFixed(1) : '0.0'; const networkTraffic = status.network !== undefined ? status.network.toFixed(1) : '0.0'; card.innerHTML = `

${device.name || device.device_id || device.id}

${device.ip || 'N/A'}

在线

CPU使用率

${cpuUsage}%

磁盘使用率

${diskUsage}%

内存使用率

${memoryUsage}%

网络流量

${networkTraffic} MB/s

`; // 为"查看详情"按钮添加点击事件 const viewDetailBtn = card.querySelector('button'); viewDetailBtn.addEventListener('click', (e) => { e.stopPropagation(); // 阻止事件冒泡 // 确保使用正确的设备ID字段 const deviceId = device.device_id || device.id || device.ID; goToServerMonitor(deviceId); }); return card; } // 存储原始设备列表,用于搜索和筛选 let originalDeviceList = []; // 设备管理 async function loadDeviceManagementList() { try { const response = await fetch(`${API_BASE_URL}/devices/all`); if (!response.ok) { throw new Error('Failed to fetch devices'); } const data = await response.json(); const devices = data.devices || []; if (devices.length === 0) { console.warn('No devices available'); originalDeviceList = []; renderDeviceManagementList([]); return; } // 处理设备数据,转换为所需格式 const processedDevices = devices.map(device => ({ id: device.id, name: device.name || device.id, ip: device.ip || 'N/A', status: device.status || 'unknown', created_at: device.created_at ? new Date(device.created_at * 1000).toLocaleString() : 'N/A', token: device.token || 'N/A' })); // 存储原始设备列表 originalDeviceList = processedDevices; // 应用当前筛选条件 applyDeviceFilters(); } catch (error) { console.error('加载设备管理列表失败:', error); // 只显示错误信息,不使用模拟数据 const deviceListBody = document.getElementById('deviceListBody'); if (deviceListBody) { deviceListBody.innerHTML = ` 加载设备列表失败 `; } } } // 应用设备筛选条件 function applyDeviceFilters() { const searchTerm = document.getElementById('deviceSearch').value.toLowerCase(); const statusFilter = document.getElementById('deviceStatusFilter').value; // 复制原始列表 let filteredDevices = [...originalDeviceList]; // 应用搜索筛选 if (searchTerm) { filteredDevices = filteredDevices.filter(device => device.name.toLowerCase().includes(searchTerm) || device.id.toLowerCase().includes(searchTerm) || device.ip.toLowerCase().includes(searchTerm) ); } // 应用状态筛选 if (statusFilter && statusFilter !== 'all') { filteredDevices = filteredDevices.filter(device => device.status === statusFilter); } // 渲染筛选后的设备列表 renderDeviceManagementList(filteredDevices); // 显示或隐藏无数据提示 const noDataMessage = document.getElementById('noDataMessage'); if (noDataMessage) { if (filteredDevices.length === 0) { noDataMessage.classList.remove('hidden'); } else { noDataMessage.classList.add('hidden'); } } } // 清除筛选条件 function clearDeviceFilters() { document.getElementById('deviceSearch').value = ''; document.getElementById('deviceStatusFilter').value = 'all'; applyDeviceFilters(); } // 复制文本到剪贴板 function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { // 可以添加一个提示,如"已复制"的临时消息 showToast('复制成功'); }).catch(err => { console.error('复制失败:', err); showToast('复制失败'); }); } // 显示临时提示消息 function showToast(message, type = 'success') { // 检查是否已存在toast元素 let toast = document.getElementById('toast'); if (!toast) { toast = document.createElement('div'); toast.id = 'toast'; document.body.appendChild(toast); } // 设置toast样式根据类型 let bgColor = 'bg-gray-800'; switch(type) { case 'success': bgColor = 'bg-green-800'; break; case 'error': bgColor = 'bg-red-800'; break; case 'warning': bgColor = 'bg-yellow-800'; break; } toast.className = `${bgColor} text-white px-4 py-2 rounded opacity-0 transition-opacity duration-300 z-50 fixed bottom-4 right-4`; toast.textContent = message; toast.style.opacity = '1'; // 2秒后自动隐藏 setTimeout(() => { toast.style.opacity = '0'; }, 2000); } // 切换设备状态(激活/停用) async function toggleDeviceStatus(deviceId, newStatus) { try { // 先获取设备详情 const deviceResponse = await fetch(`${API_BASE_URL}/devices/${deviceId}`); if (!deviceResponse.ok) { throw new Error('Failed to fetch device details'); } const deviceData = await deviceResponse.json(); const device = deviceData.device; // 更新设备状态 const updateResponse = await fetch(`${API_BASE_URL}/devices/${deviceId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus, name: device.name, ip: device.ip }) }); if (!updateResponse.ok) { throw new Error(`HTTP error! status: ${updateResponse.status}`); } // 重新加载设备列表 await loadDeviceManagementList(); // 显示成功提示 const statusText = newStatus === 'active' ? '激活' : '停用'; showToast(`设备已成功${statusText}`, 'success'); } catch (error) { console.error('Failed to toggle device status:', error); const statusText = newStatus === 'active' ? '激活' : '停用'; showToast(`设备${statusText}失败`, 'error'); } } // 编辑设备 function editDevice(deviceId) { // 这里可以实现编辑设备的逻辑,比如打开模态框等 console.log('编辑设备:', deviceId); // 可以添加模态框或跳转到编辑页面的逻辑 } // 删除设备 function deleteDevice(deviceId) { if (confirm('确定要删除这个设备吗?')) { fetch(`${API_BASE_URL}/devices/${deviceId}`, { method: 'DELETE' }).then(response => { if (response.ok) { showToast('设备删除成功'); loadDeviceManagementList(); // 重新加载设备列表 } else { throw new Error('删除失败'); } }).catch(err => { console.error('删除设备失败:', err); showToast('删除失败'); }); } } function renderDeviceManagementList(devices) { const tableBody = document.getElementById('deviceManagementTableBody'); if (!tableBody) return; tableBody.innerHTML = ''; devices.forEach(device => { const row = document.createElement('tr'); row.className = 'hover:bg-gray-50 transition-colors'; // 根据设备状态获取显示样式 const getStatusStyle = (status) => { switch (status) { case 'active': return 'bg-green-100 text-green-800'; case 'inactive': return 'bg-yellow-100 text-yellow-800'; case 'offline': return 'bg-red-100 text-red-800'; default: return 'bg-gray-100 text-gray-800'; } }; row.innerHTML = ` ${device.name} ${device.id} ${device.ip}
${device.token}
${device.status} ${device.created_at} `; tableBody.appendChild(row); }); } // 初始化图表 function initCharts() { initAlarmTrendChart(); initDetailedCharts(); } // 初始化详细监控图表 function initDetailedCharts() { // 初始化CPU图表 const cpuCtx = document.getElementById('cpuChart'); if (cpuCtx && !charts.cpu) { charts.cpu = new Chart(cpuCtx, { type: 'line', data: { labels: [], datasets: [{ label: 'CPU使用率 (%)', data: [], borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', borderWidth: 2, fill: true, tension: 0.7, pointRadius: 0, pointHoverRadius: 3 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' }, tooltip: { mode: 'index', intersect: false } }, scales: { y: { beginAtZero: true, max: 100, ticks: { callback: function(value) { return value + '%'; } } } }, // 启用缩放功能 interaction: { intersect: false, mode: 'index' }, // 允许显示空白区域 spanGaps: true, // 配置缩放和拖拽 plugins: { legend: { position: 'top' }, tooltip: { mode: 'index', intersect: false }, zoom: { pan: { enabled: true, mode: 'x' }, zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'x', onZoom: function({chart}) { console.log(chart.getZoomLevel()); } } } } } }); } // 初始化内存图表 const memoryCtx = document.getElementById('memoryChart'); if (memoryCtx && !charts.memory) { charts.memory = new Chart(memoryCtx, { type: 'line', data: { labels: [], datasets: [{ label: '内存使用率 (%)', data: [], borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', borderWidth: 2, fill: true, tension: 0.7, pointRadius: 0, pointHoverRadius: 3 }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, spanGaps: true, plugins: { legend: { position: 'top' }, tooltip: { mode: 'index', intersect: false, callbacks: { label: function(context) { const label = context.dataset.label || ''; const value = context.parsed.y; // 第二行显示实际值 return [ `${label}: ${value}%`, `实际值: ${value}%` ]; } } }, zoom: { pan: { enabled: true, mode: 'x' }, zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'x', onZoom: function({chart}) { console.log(chart.getZoomLevel()); } } } }, scales: { y: { beginAtZero: true, max: 100, ticks: { callback: function(value) { return value + '%'; } } } } } }); } // 初始化磁盘图表 const diskCtx = document.getElementById('diskChart'); if (diskCtx && !charts.disk) { charts.disk = new Chart(diskCtx, { type: 'line', data: { labels: [], datasets: [] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, spanGaps: true, plugins: { legend: { position: 'top' }, tooltip: { mode: 'index', intersect: false, callbacks: { label: function(context) { const label = context.dataset.label || ''; const value = context.parsed.y; // 第二行显示实际值 return [ `${label}: ${value}%`, `实际值: ${value}%` ]; } } }, zoom: { pan: { enabled: true, mode: 'x' }, zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'x', onZoom: function({chart}) { console.log(chart.getZoomLevel()); } } } }, scales: { y: { beginAtZero: true, max: 100, ticks: { callback: function(value) { return value + '%'; } } } } } }); } // 初始化网络流量图表(发送总和和接收总和) const networkCtx = document.getElementById('networkChart'); if (networkCtx && !charts.network) { charts.network = new Chart(networkCtx, { type: 'line', data: { labels: [], datasets: [ { label: '发送总和 (MB)', data: [], borderColor: '#f59e0b', backgroundColor: 'rgba(245, 158, 11, 0.1)', borderWidth: 2, fill: true, tension: 0.7, pointRadius: 0, pointHoverRadius: 3 }, { label: '接收总和 (MB)', data: [], borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', borderWidth: 2, fill: true, tension: 0.7, pointRadius: 0, pointHoverRadius: 3 } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, spanGaps: true, plugins: { legend: { position: 'top' }, tooltip: { mode: 'index', intersect: false, callbacks: { label: function(context) { const label = context.dataset.label || ''; const value = context.parsed.y; // 第二行显示实际值 return [ `${label}: ${value} MB`, `实际值: ${value} MB` ]; } } }, zoom: { pan: { enabled: true, mode: 'x' }, zoom: { wheel: { enabled: true, }, pinch: { enabled: true }, mode: 'x', onZoom: function({chart}) { console.log(chart.getZoomLevel()); } } } }, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return value + ' MB'; } } } } } }); } // 初始化网速趋势图表 const speedCtx = document.getElementById('speedChart'); if (speedCtx && !charts.speed) { charts.speed = new Chart(speedCtx, { type: 'line', data: { labels: [], datasets: [ { label: '发送速率 (MB/s)', data: [], borderColor: '#f59e0b', backgroundColor: 'rgba(245, 158, 11, 0.1)', borderWidth: 2, fill: true, tension: 0.7, pointRadius: 0, pointHoverRadius: 3 }, { label: '接收速率 (MB/s)', data: [], borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', borderWidth: 2, fill: true, tension: 0.7, pointRadius: 0, pointHoverRadius: 3 } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index' }, spanGaps: true, plugins: { legend: { position: 'top' }, tooltip: { mode: 'index', intersect: false, callbacks: { label: function(context) { const label = context.dataset.label || ''; const value = context.parsed.y; // 第二行显示实际值 return [ `${label}: ${value} MB/s`, `实际值: ${value} MB/s` ]; } } }, zoom: { pan: { enabled: true, mode: 'x' }, zoom: { wheel: { enabled: true, }, pinch: { enabled: true }, mode: 'x', onZoom: function({chart}) { console.log(chart.getZoomLevel()); } } } }, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return value + ' MB/s'; } } } } } }); } } // 初始化告警趋势图 function initAlarmTrendChart() { const ctx = document.getElementById('alarmTrendChart'); if (!ctx) return; charts.alarmTrend = new Chart(ctx, { type: 'line', data: { labels: ['11-24', '11-25', '11-26', '11-27', '11-28', '11-29', '11-30'], datasets: [ { label: '报警', data: [50, 60, 75, 80, 90, 100, 112], borderColor: '#ef4444', backgroundColor: 'rgba(239, 68, 68, 0.1)', borderWidth: 2, fill: true, tension: 0.4 }, { label: '提醒', data: [20, 25, 30, 35, 38, 38, 38], borderColor: '#f59e0b', backgroundColor: 'rgba(245, 158, 11, 0.1)', borderWidth: 2, fill: true, tension: 0.4 } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top' } }, scales: { y: { beginAtZero: true } } } }); } // 格式化字节数,根据用户要求:只在MB和GB之间转换 function formatBytes(bytes, decimals = 2, isRate = false) { if (bytes === 0) { return isRate ? '0 MB/s' : '0 MB'; } const mb = 1024 * 1024; const gb = mb * 1024; const dm = decimals < 0 ? 0 : decimals; // 对于速率(bytes/s):默认显示MB/s,超过1024MB/s显示GB/s if (isRate) { if (bytes >= gb) { return parseFloat((bytes / gb).toFixed(dm)) + ' GB/s'; } else { return parseFloat((bytes / mb).toFixed(dm)) + ' MB/s'; } } // 对于流量(bytes):默认显示MB,超过1024MB显示GB else { if (bytes >= gb) { return parseFloat((bytes / gb).toFixed(dm)) + ' GB'; } else { return parseFloat((bytes / mb).toFixed(dm)) + ' MB'; } } } // 获取单个指标数据 async function fetchMetric(metricType, aggregation = 'average') { // 构建查询参数 const params = new URLSearchParams(); // 设置设备ID参数(如果存在) if (state.currentDeviceID) { params.append('device_id', state.currentDeviceID); } // 设置时间范围参数 if (state.customStartTime && state.customEndTime) { // 自定义时间范围 params.append('start_time', state.customStartTime); params.append('end_time', state.customEndTime); } else { // 使用状态中的时间范围设置 params.append('start_time', `-${state.currentTimeRange}`); params.append('end_time', 'now()'); } // 设置聚合方式 params.append('aggregation', aggregation); // 固定时间区间为10分钟 params.append('interval', '10m'); // 发送请求 const response = await fetch(`${API_BASE_URL}/metrics/${metricType}?${params.toString()}`); if (!response.ok) { throw new Error(`Failed to fetch ${metricType} metrics`); } const data = await response.json(); // 确保返回的数据是数组格式 if (metricType === 'disk') { // 磁盘数据可能是对象格式,需要特殊处理 return data.data || {}; } else if (metricType === 'network') { // 网络数据可能是对象格式,需要特殊处理 return data.data || {}; } else { // 其他数据应该是数组格式 return Array.isArray(data.data) ? data.data : []; } } // 格式化时间,统一格式避免图表误解 function formatTime(timeStr) { // timeStr是ISO格式的UTC时间字符串,如"2025-12-02T01:53:19Z" const date = new Date(timeStr); // 格式化年、月、日(使用本地时间) const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); // 格式化时、分、秒(使用本地时间) const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); // 总是显示完整的日期和时间,方便区分不同日期的数据 return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } // 加载监控指标 async function loadMetrics() { try { // 并行加载所有指标 const [cpuData, memoryData, diskData, networkSumData] = await Promise.all([ fetchMetric('cpu'), fetchMetric('memory'), fetchMetric('disk'), fetchMetric('network') ]); // 格式化数据,确保updateStatusCards函数能正确处理 const formattedMetrics = { cpu: cpuData, memory: memoryData, disk: formatDiskDataForCards(diskData), network: formatNetworkDataForCards(networkSumData) }; // 更新状态卡片 updateStatusCards(formattedMetrics); // 更新图表 updateCharts(cpuData, memoryData, diskData, networkSumData); // 更新刷新状态指示器 updateRefreshStatus(); // 更新进程信息 loadProcessInfo(); // 更新系统日志 loadSystemLogs(); } catch (error) { console.error('Failed to load metrics:', error); // 显示友好的错误提示 showToast('加载监控数据失败,请稍后重试', 'error'); // 更新刷新状态指示器为错误状态 const statusIndicator = document.getElementById('refreshStatusIndicator'); const lastRefreshTime = document.getElementById('lastRefreshTime'); if (statusIndicator && lastRefreshTime) { statusIndicator.className = 'w-2 h-2 bg-red-500 rounded-full animate-pulse'; lastRefreshTime.textContent = `上次刷新: 失败`; } // 即使发生错误,也要尝试初始化图表,避免页面空白 initDetailedCharts(); } } // 格式化磁盘数据,用于状态卡片显示 function formatDiskDataForCards(diskData) { // 如果diskData是空对象,返回空对象 if (!diskData || typeof diskData !== 'object' || Array.isArray(diskData)) { return {}; } const formattedDiskData = {}; // 遍历每个挂载点 for (const mountpoint in diskData) { const mountpointData = diskData[mountpoint]; // 如果挂载点数据是数组,获取最新的数据点 if (Array.isArray(mountpointData) && mountpointData.length > 0) { // 最新的数据点是数组的最后一个元素 const latestData = mountpointData[mountpointData.length - 1]; formattedDiskData[mountpoint] = { used_percent: latestData.value, // 尝试从历史数据获取总容量 total: state.historyMetrics.disk && state.historyMetrics.disk[mountpoint] && state.historyMetrics.disk[mountpoint].total || 0 }; } } return formattedDiskData; } // 格式化网络数据,用于状态卡片显示 function formatNetworkDataForCards(networkData) { // 初始化返回数据结构 const formattedNetworkData = { bytes_sent: 0, bytes_received: 0, tx_bytes: 0, rx_bytes: 0 }; // 如果没有数据,返回初始值 if (!networkData || typeof networkData === 'undefined') { return formattedNetworkData; } // 处理数组格式数据 if (Array.isArray(networkData)) { // 数组格式:直接处理最新的数据点 if (networkData.length > 0) { // 最新的数据点是数组的最后一个元素 const latestData = networkData[networkData.length - 1]; // 检查是否包含速率数据 if (latestData.sent !== undefined) { formattedNetworkData.bytes_sent = latestData.sent; } else if (latestData.bytes_sent !== undefined) { formattedNetworkData.bytes_sent = latestData.bytes_sent; } if (latestData.received !== undefined) { formattedNetworkData.bytes_received = latestData.received; } else if (latestData.bytes_received !== undefined) { formattedNetworkData.bytes_received = latestData.bytes_received; } // 检查是否包含总量数据 if (latestData.tx_bytes !== undefined) { formattedNetworkData.tx_bytes = latestData.tx_bytes; } if (latestData.rx_bytes !== undefined) { formattedNetworkData.rx_bytes = latestData.rx_bytes; } } } // 处理对象格式数据 else if (typeof networkData === 'object') { // 检查是否为WebSocket直接返回的总流量格式 if (networkData.bytes_sent !== undefined && networkData.bytes_received !== undefined) { // WebSocket消息格式 - 总流量 formattedNetworkData.bytes_sent = networkData.bytes_sent; formattedNetworkData.bytes_received = networkData.bytes_received; formattedNetworkData.tx_bytes = networkData.tx_bytes || 0; formattedNetworkData.rx_bytes = networkData.rx_bytes || 0; } // 按网卡分组的数据 else { // 遍历每个网卡 for (const iface in networkData) { const ifaceData = networkData[iface]; if (typeof ifaceData === 'object') { // 处理速率数据 if (ifaceData.sent && ifaceData.received) { // 如果sent和received是数组,获取最新的数据点 if (Array.isArray(ifaceData.sent) && ifaceData.sent.length > 0 && Array.isArray(ifaceData.received) && ifaceData.received.length > 0) { // 最新的数据点是数组的最后一个元素 const latestSent = ifaceData.sent[ifaceData.sent.length - 1].value; const latestReceived = ifaceData.received[ifaceData.received.length - 1].value; // 累加速率 formattedNetworkData.bytes_sent += latestSent; formattedNetworkData.bytes_received += latestReceived; } // 如果sent和received是数值 else if (typeof ifaceData.sent === 'number' && typeof ifaceData.received === 'number') { formattedNetworkData.bytes_sent += ifaceData.sent; formattedNetworkData.bytes_received += ifaceData.received; } } // 直接使用bytes_sent和bytes_received字段 else if (ifaceData.bytes_sent !== undefined && ifaceData.bytes_received !== undefined) { formattedNetworkData.bytes_sent += ifaceData.bytes_sent; formattedNetworkData.bytes_received += ifaceData.bytes_received; } // 检查是否有总量数据(多种可能的字段名) // 1. 标准字段名:tx_bytes, rx_bytes if (ifaceData.tx_bytes !== undefined) { formattedNetworkData.tx_bytes += ifaceData.tx_bytes; } if (ifaceData.rx_bytes !== undefined) { formattedNetworkData.rx_bytes += ifaceData.rx_bytes; } // 2. 旧格式字段名:bytes_sent_total, bytes_received_total if (ifaceData.bytes_sent_total !== undefined) { formattedNetworkData.tx_bytes += ifaceData.bytes_sent_total; } if (ifaceData.bytes_received_total !== undefined) { formattedNetworkData.rx_bytes += ifaceData.bytes_received_total; } // 3. 可能的其他格式:bytes_sent, bytes_received作为总量数据 // 仅当没有其他总量数据字段时使用 if (formattedNetworkData.tx_bytes === 0 && ifaceData.bytes_sent !== undefined && typeof ifaceData.bytes_sent === 'number') { formattedNetworkData.tx_bytes += ifaceData.bytes_sent; } if (formattedNetworkData.rx_bytes === 0 && ifaceData.bytes_received !== undefined && typeof ifaceData.bytes_received === 'number') { formattedNetworkData.rx_bytes += ifaceData.bytes_received; } } } } } return formattedNetworkData; } // WebSocket初始化 function initWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/api/ws`; ws = new WebSocket(wsUrl); ws.onopen = () => { console.log('WebSocket连接已打开'); wsReconnectAttempts = 0; wsReconnectDelay = 1000; }; ws.onmessage = (event) => { try { const message = JSON.parse(event.data); handleWebSocketMessage(message); } catch (error) { console.error('解析WebSocket消息失败:', error); } }; ws.onclose = (event) => { console.log(`WebSocket连接已关闭: ${event.code} - ${event.reason}`); attemptReconnect(); }; ws.onerror = (error) => { console.error('WebSocket连接错误:', error); }; } // 处理WebSocket消息 function handleWebSocketMessage(message) { if (message.type === 'metrics_update') { handleMetricsUpdate(message); } } // 加载服务器信息 async function loadServerInfo(deviceId) { try { // 将设备ID存储到全局状态 state.currentDeviceID = deviceId; const response = await fetch(`${API_BASE_URL}/devices/${deviceId}`); if (!response.ok) { throw new Error('Failed to fetch server info'); } const data = await response.json(); const deviceData = data.device; // 更新服务器信息显示 const serverInfoDisplay = document.getElementById('serverInfoDisplay'); if (serverInfoDisplay) { serverInfoDisplay.innerHTML = `

服务器名称: ${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 lastTimeRange: '', // 上次请求数据的时间范围 lastCustomStartTime: '', // 上次请求数据的自定义开始时间 lastCustomEndTime: '' // 上次请求数据的自定义结束时间 }; // 系统日志分页状态 let logPagination = { currentPage: 1, itemsPerPage: 5, totalItems: 0, totalPages: 0, allLogs: [], lastDeviceID: '', // 上次请求数据的设备ID lastTimeRange: '', // 上次请求数据的时间范围 lastCustomStartTime: '', // 上次请求数据的自定义开始时间 lastCustomEndTime: '' // 上次请求数据的自定义结束时间 }; // 加载进程信息 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); } // 设置时间范围参数(与fetchMetric函数保持一致) if (state.customStartTime && state.customEndTime) { // 自定义时间范围 params.append('start_time', state.customStartTime); params.append('end_time', state.customEndTime); } else { // 使用状态中的时间范围设置 params.append('start_time', `-${state.currentTimeRange}`); params.append('end_time', 'now()'); } // 检查设备ID或时间范围是否变化 const timeRangeChanged = processPagination.lastTimeRange !== state.currentTimeRange || processPagination.lastCustomStartTime !== state.customStartTime || processPagination.lastCustomEndTime !== state.customEndTime; if (processPagination.lastDeviceID !== state.currentDeviceID || timeRangeChanged) { // 设备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.lastTimeRange = state.currentTimeRange; processPagination.lastCustomStartTime = state.customStartTime; processPagination.lastCustomEndTime = state.customEndTime; } // 更新当前页码 processPagination.currentPage = page; // 清空表格 processTableBody.innerHTML = ''; if (processPagination.totalItems === 0) { // 没有进程数据,显示提示 processTableBody.innerHTML = ` 暂无进程数据 `; // 隐藏分页控件 if (processPaginationContainer) { processPaginationContainer.innerHTML = ''; } return; } // 计算分页数据 const startIndex = (processPagination.currentPage - 1) * processPagination.itemsPerPage; const endIndex = Math.min(startIndex + processPagination.itemsPerPage, processPagination.totalItems); const paginatedProcesses = processPagination.allProcesses.slice(startIndex, endIndex); // 填充表格数据 paginatedProcesses.forEach((proc, index) => { const row = document.createElement('tr'); row.innerHTML = ` ${proc.process_name || 'N/A'} ${proc.username || 'N/A'} ${proc.pid || 'N/A'} ${proc.cpu_usage ? parseFloat(proc.cpu_usage).toFixed(2) : '0.00'} ${proc.memory_usage ? parseFloat(proc.memory_usage).toFixed(2) : '0.00'} ${proc.path || 'N/A'} ${proc.cmdline || 'N/A'} ${proc.ports || 'N/A'} `; processTableBody.appendChild(row); }); // 创建分页控件 if (processPaginationContainer) { createPaginationControls(processPaginationContainer, processPagination, loadProcessInfo); } } catch (error) { console.error('Error loading process info:', error); processTableBody.innerHTML = ` 加载进程信息失败 `; if (processPaginationContainer) { processPaginationContainer.innerHTML = ''; } } } // 磁盘信息分页状态 let diskPagination = { currentPage: 1, itemsPerPage: 5, totalItems: 0, totalPages: 0, allDisks: [], lastDeviceID: '' // 上次请求数据的设备ID }; // 创建分页控件 function createPaginationControls(container, paginationState, loadFunction) { // 清空容器 container.innerHTML = ''; // 如果只有一页,不需要分页 if (paginationState.totalPages <= 1) { return; } const pagination = document.createElement('div'); pagination.className = 'flex justify-between items-center mt-4'; // 页码信息 const info = document.createElement('div'); info.className = 'text-sm text-gray-500'; info.textContent = `显示 ${(paginationState.currentPage - 1) * paginationState.itemsPerPage + 1} 至 ${Math.min(paginationState.currentPage * paginationState.itemsPerPage, paginationState.totalItems)} 条,共 ${paginationState.totalItems} 条`; // 分页按钮容器 const buttons = document.createElement('div'); buttons.className = 'flex items-center space-x-2'; // 上一页按钮 const prevButton = document.createElement('button'); prevButton.className = `px-3 py-1 rounded border ${paginationState.currentPage === 1 ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-gray-700 hover:bg-gray-50'}`; prevButton.innerHTML = ''; prevButton.disabled = paginationState.currentPage === 1; prevButton.addEventListener('click', () => { if (paginationState.currentPage > 1) { loadFunction(paginationState.currentPage - 1); } }); // 页码按钮 const pageButtons = []; // 总是显示第一页 if (paginationState.currentPage > 3) { pageButtons.push(1); pageButtons.push('...'); } // 显示当前页附近的页码 const startPage = Math.max(1, paginationState.currentPage - 2); const endPage = Math.min(paginationState.totalPages, paginationState.currentPage + 2); for (let i = startPage; i <= endPage; i++) { pageButtons.push(i); } // 总是显示最后一页 if (paginationState.currentPage < paginationState.totalPages - 2) { pageButtons.push('...'); pageButtons.push(paginationState.totalPages); } // 创建页码按钮 pageButtons.forEach(page => { if (page === '...') { const ellipsis = document.createElement('span'); ellipsis.className = 'px-2 text-gray-500'; ellipsis.textContent = '...'; buttons.appendChild(ellipsis); } else { const pageButton = document.createElement('button'); pageButton.className = `px-3 py-1 rounded border ${page === paginationState.currentPage ? 'bg-blue-50 text-blue-600 border-blue-300' : 'bg-white text-gray-700 hover:bg-gray-50'}`; pageButton.textContent = page; pageButton.addEventListener('click', () => { loadFunction(page); }); buttons.appendChild(pageButton); } }); // 下一页按钮 const nextButton = document.createElement('button'); nextButton.className = `px-3 py-1 rounded border ${paginationState.currentPage === paginationState.totalPages ? 'bg-gray-100 text-gray-400 cursor-not-allowed' : 'bg-white text-gray-700 hover:bg-gray-50'}`; nextButton.innerHTML = ''; nextButton.disabled = paginationState.currentPage === paginationState.totalPages; nextButton.addEventListener('click', () => { if (paginationState.currentPage < paginationState.totalPages) { loadFunction(paginationState.currentPage + 1); } }); // 添加到按钮容器 buttons.appendChild(prevButton); // 跳转页面功能 const jumpContainer = document.createElement('div'); jumpContainer.className = 'flex items-center ml-4 space-x-2'; const jumpText = document.createElement('span'); jumpText.className = 'text-sm text-gray-500'; jumpText.textContent = '前往'; const jumpInput = document.createElement('input'); jumpInput.type = 'number'; jumpInput.min = 1; jumpInput.max = paginationState.totalPages; jumpInput.value = paginationState.currentPage; jumpInput.className = 'w-12 px-2 py-1 text-sm border rounded text-center'; jumpInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { let page = parseInt(jumpInput.value); if (!isNaN(page) && page >= 1 && page <= paginationState.totalPages) { loadFunction(page); } else { jumpInput.value = paginationState.currentPage; } } }); const totalText = document.createElement('span'); totalText.className = 'text-sm text-gray-500'; totalText.textContent = `页,共 ${paginationState.totalPages} 页`; jumpContainer.appendChild(jumpText); jumpContainer.appendChild(jumpInput); jumpContainer.appendChild(totalText); buttons.appendChild(jumpContainer); buttons.appendChild(nextButton); // 添加到分页容器 pagination.appendChild(info); pagination.appendChild(buttons); // 添加到页面 container.appendChild(pagination); } // 加载磁盘详细信息 async function loadDiskDetails(page = 1) { const diskDetailsContent = document.getElementById('diskDetailsContent'); const diskPaginationContainer = document.getElementById('diskPaginationContainer'); if (!diskDetailsContent) return; try { // 构建查询参数 const params = new URLSearchParams(); if (state.currentDeviceID) { params.append('device_id', state.currentDeviceID); } // 检查设备ID是否变化 if (diskPagination.lastDeviceID !== state.currentDeviceID) { // 设备ID变化,清空旧数据 diskPagination.allDisks = []; diskPagination.totalItems = 0; diskPagination.totalPages = 0; diskPagination.currentPage = 1; } // 如果是第一次加载或设备ID变化,获取全部数据 if (diskPagination.allDisks.length === 0) { // 发送请求 const response = await fetch(`${API_BASE_URL}/metrics/disk_details?${params.toString()}`); if (!response.ok) { throw new Error('Failed to fetch disk details'); } const data = await response.json(); diskPagination.allDisks = data.data || []; diskPagination.totalItems = diskPagination.allDisks.length; diskPagination.totalPages = Math.ceil(diskPagination.totalItems / diskPagination.itemsPerPage); // 更新上次请求数据的设备ID diskPagination.lastDeviceID = state.currentDeviceID; } // 更新当前页码 diskPagination.currentPage = page; // 清空内容 diskDetailsContent.innerHTML = ''; if (diskPagination.totalItems === 0) { // 没有磁盘数据,显示提示 diskDetailsContent.innerHTML = `

暂无磁盘详细信息

`; // 隐藏分页控件 if (diskPaginationContainer) { diskPaginationContainer.innerHTML = ''; } return; } // 计算分页数据 const startIndex = (diskPagination.currentPage - 1) * diskPagination.itemsPerPage; const endIndex = Math.min(startIndex + diskPagination.itemsPerPage, diskPagination.totalItems); const paginatedDisks = diskPagination.allDisks.slice(startIndex, endIndex); // 填充磁盘详细信息卡片 paginatedDisks.forEach(disk => { const diskCard = document.createElement('div'); diskCard.className = 'bg-white rounded-lg shadow-md p-6 border border-gray-100'; diskCard.innerHTML = `

${disk.device_id || 'Unknown Device'}

状态: ${disk.status || 'Unknown'}
类型: ${disk.type || 'Unknown'}
大小: ${disk.size_gb ? parseFloat(disk.size_gb).toFixed(2) : '0.00'} GB
型号: ${disk.model || 'Unknown'}
接口类型: ${disk.interface_type || 'Unknown'}
描述: ${disk.description || 'Unknown'}
`; diskDetailsContent.appendChild(diskCard); }); // 创建分页控件 if (diskPaginationContainer) { createPaginationControls(diskPaginationContainer, diskPagination, loadDiskDetails); } } catch (error) { console.error('Error loading disk details:', error); diskDetailsContent.innerHTML = `

加载磁盘详细信息失败

`; if (diskPaginationContainer) { diskPaginationContainer.innerHTML = ''; } } } // 格式化时间为易识别的日期样式 function formatLogTime(timeString) { if (!timeString) return new Date().toLocaleString('zh-CN'); try { const date = new Date(timeString); if (isNaN(date.getTime())) { // 如果时间解析失败,返回原始字符串 return timeString; } // 格式化为:2025-12-04 16:02:28 return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); } catch (error) { console.error('Error formatting log time:', error); return timeString; } } // 加载系统日志 async function loadSystemLogs(page = 1) { console.log('loadSystemLogs function called'); const logTableBody = document.getElementById('logTableBody'); const logPaginationContainer = document.getElementById('logPaginationContainer'); if (!logTableBody) { console.error('logTableBody element not found'); return; } try { // 构建查询参数 const params = new URLSearchParams(); if (state.currentDeviceID) { params.append('device_id', state.currentDeviceID); } // 设置时间范围参数(与fetchMetric函数保持一致) if (state.customStartTime && state.customEndTime) { // 自定义时间范围 params.append('start_time', state.customStartTime); params.append('end_time', state.customEndTime); } else { // 使用状态中的时间范围设置 params.append('start_time', `-${state.currentTimeRange}`); params.append('end_time', 'now()'); } // 检查设备ID或时间范围是否变化 const timeRangeChanged = logPagination.lastTimeRange !== state.currentTimeRange || logPagination.lastCustomStartTime !== state.customStartTime || logPagination.lastCustomEndTime !== state.customEndTime; if (logPagination.lastDeviceID !== state.currentDeviceID || timeRangeChanged) { // 设备ID或时间范围变化,清空旧数据 logPagination.allLogs = []; logPagination.totalItems = 0; logPagination.totalPages = 0; logPagination.currentPage = 1; } // 如果是第一次加载或设备ID/时间范围变化,获取全部数据 if (logPagination.allLogs.length === 0) { console.log('Fetching logs from:', `${API_BASE_URL}/metrics/logs?${params.toString()}`); // 发送请求 const response = await fetch(`${API_BASE_URL}/metrics/logs?${params.toString()}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log('Logs response data:', data); // 处理后端返回格式,支持两种格式:{"logs": [...]} 和 {"data": [...], "total": ...} logPagination.allLogs = data.data || data.logs || []; logPagination.totalItems = data.total || logPagination.allLogs.length; logPagination.totalPages = Math.ceil(logPagination.totalItems / logPagination.itemsPerPage); // 更新上次请求数据的设备ID和时间范围 logPagination.lastDeviceID = state.currentDeviceID; logPagination.lastTimeRange = state.currentTimeRange; logPagination.lastCustomStartTime = state.customStartTime; logPagination.lastCustomEndTime = state.customEndTime; } // 更新当前页码 logPagination.currentPage = page; // 清空表格 logTableBody.innerHTML = ''; if (logPagination.totalItems === 0) { // 没有日志数据,显示提示 logTableBody.innerHTML = ` 暂无系统日志 `; // 隐藏分页控件 if (logPaginationContainer) { logPaginationContainer.innerHTML = ''; } return; } // 计算分页数据 const startIndex = (logPagination.currentPage - 1) * logPagination.itemsPerPage; const endIndex = Math.min(startIndex + logPagination.itemsPerPage, logPagination.totalItems); const paginatedLogs = logPagination.allLogs.slice(startIndex, endIndex); // 填充表格数据 paginatedLogs.forEach((log, index) => { const row = document.createElement('tr'); row.innerHTML = ` ${startIndex + index + 1} ${log.source || 'System'} ${formatLogTime(log.time)} ${log.message || 'No message'} `; logTableBody.appendChild(row); }); // 创建分页控件 if (logPaginationContainer) { createPaginationControls(logPaginationContainer, logPagination, loadSystemLogs); } } catch (error) { console.error('Error loading system logs:', error); logTableBody.innerHTML = ` 加载系统日志失败: ${error.message} `; if (logPaginationContainer) { logPaginationContainer.innerHTML = ''; } } } // 处理hash变化 function handleHashChange() { const hash = window.location.hash; if (hash === '#serverMonitor' || hash.startsWith('#serverMonitor/')) { // 延迟一下,确保DOM已经渲染完成 setTimeout(() => { // 加载当前选项卡对应的数据 const activeTab = document.querySelector('.chart-tab.active'); if (activeTab) { const tabId = activeTab.dataset.tab; // 显示/隐藏附加信息容器 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 === 'logs') { // 显示系统日志 if (logInfoContainer) { logInfoContainer.classList.remove('hidden'); } // 加载系统日志 loadSystemLogs(); } else { // 加载其他监控数据 loadMetrics(); // 根据选项卡加载附加信息 if (tabId === 'cpu') { // 显示进程信息 if (processInfoContainer) { processInfoContainer.classList.remove('hidden'); } loadProcessInfo(); } else if (tabId === 'disk') { // 显示磁盘详细信息 if (diskDetailsContainer) { diskDetailsContainer.classList.remove('hidden'); } loadDiskDetails(); } } } else { // 如果没有找到激活的选项卡,默认加载metrics loadMetrics(); } }, 300); } }