// API 基础 URL const API_BASE_URL = '/api'; // 当前时间范围 let currentTimeRange = '24h'; // 自定义时间范围 let customStartTime = ''; let customEndTime = ''; // 当前选中的设备ID let currentDeviceID = 'default'; // 当前时间区间(默认10秒) let currentInterval = '10s'; // WebSocket连接 let ws = null; // WebSocket重连次数 let wsReconnectAttempts = 0; // WebSocket最大重连次数 const wsMaxReconnectAttempts = 5; // WebSocket重连延迟(毫秒) let wsReconnectDelay = 1000; // 图表实例对象 const charts = {}; // 格式化字节数,根据用户要求:只在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'; } } } // 格式化时间,根据时间范围动态调整格式 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'); // 根据当前时间范围动态调整时间格式 if (currentTimeRange === '5s' || currentTimeRange === '10s' || currentTimeRange === '15s' || currentTimeRange === '30s') { // 短时间范围(秒级),显示完整时间格式 return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } else if (currentTimeRange === '1h' || currentTimeRange === '3h' || currentTimeRange === '5h') { // 中等时间范围(小时级),显示到分钟,不显示秒 return `${year}-${month}-${day} ${hours}:${minutes}`; } else { // 长时间范围(天级),显示到小时,不显示分钟和秒 return `${year}-${month}-${day} ${hours}:00`; } } // 初始化应用 function initApp() { // 初始化页面切换 initPageSwitch(); // 绑定事件 bindEvents(); // 加载设备列表 loadDevices(); // 初始化WebSocket连接 initWebSocket(); // 初始化图表 initCharts(); // 初始化首页图表 initHomeCharts(); // 加载首页数据 loadHomeData(); // 加载服务器数量 loadServerCount(); // 设置定时刷新(每30秒) setInterval(loadMetrics, 30000); setInterval(loadServerCount, 30000); } // 加载服务器数量 async function loadServerCount() { try { const response = await fetch(`${API_BASE_URL}/devices/`); if (!response.ok) { throw new Error('Failed to fetch devices'); } const data = await response.json(); const devices = data.devices; // 更新服务器卡片上的服务器数量 const serverCountElement = document.getElementById('serverCount'); if (serverCountElement) { serverCountElement.textContent = devices.length; } // 加载所有服务器列表 loadAllServers(); } catch (error) { console.error('Failed to load server count:', error); // 使用模拟数据 const serverCountElement = document.getElementById('serverCount'); if (serverCountElement) { serverCountElement.textContent = '2'; } // 加载模拟服务器列表 loadMockServers(); } } // 初始化页面切换 function initPageSwitch() { // 页面加载时初始化页面显示 switchPage(); // 监听hashchange事件,当URL的hash值变化时触发页面切换 window.addEventListener('hashchange', switchPage); } // 页面切换函数 function switchPage() { // 获取当前URL的hash值 const hash = window.location.hash; // 隐藏所有内容区域 document.getElementById('homeContent').classList.add('hidden'); document.getElementById('serversContent').classList.add('hidden'); document.getElementById('serverMonitorContent').classList.add('hidden'); document.getElementById('devicesContent').classList.add('hidden'); // 根据hash值显示对应的内容区域 if (hash === '#servers') { // 显示所有服务器列表 document.getElementById('serversContent').classList.remove('hidden'); // 加载所有服务器列表 loadAllServers(); } else if (hash === '#serverMonitor') { // 显示服务器监控界面 document.getElementById('serverMonitorContent').classList.remove('hidden'); // 加载设备列表 loadDevices(); // 加载监控数据 loadMetrics(); } else if (hash === '#devices') { // 显示设备管理页面 document.getElementById('devicesContent').classList.remove('hidden'); // 加载设备管理列表 loadDeviceManagementList(); } else { // 显示首页内容 document.getElementById('homeContent').classList.remove('hidden'); } } // 初始化首页图表 function initHomeCharts() { // 近一周告警/跟进走势图表 const alarmTrendCtx = document.getElementById('alarmTrendChart'); if (alarmTrendCtx) { new Chart(alarmTrendCtx, { 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, }, { label: '登记', data: [80, 85, 90, 95, 100, 100, 100], borderColor: '#eab308', backgroundColor: 'rgba(234, 179, 8, 0.1)', borderWidth: 2, fill: true, tension: 0.4, }, ], }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false, }, }, scales: { x: { grid: { display: false, }, }, y: { beginAtZero: true, grid: { color: 'rgba(0, 0, 0, 0.05)', }, }, }, }, }); } } // 加载首页数据 function loadHomeData() { // 加载业务视图数据 loadBusinessViewData(); // 加载告警列表数据 loadAlarmListData(); } // 加载业务视图数据 function loadBusinessViewData() { // 模拟数据 const businessData = [ { name: 'LIS', ip: '192.129.6.108', os: 'Linux', status: 'P1', cpuAlarm: 5, deviceId: 'device-1' }, { name: '互联网医院(苹果站)', ip: '192.129.6.57', os: 'Linux', status: 'P3', cpuAlarm: 3, deviceId: 'device-2' }, { name: 'HIS', ip: '192.129.51.21', os: 'Windows', status: 'P2', cpuAlarm: 3, deviceId: 'device-3' }, { name: 'OA', ip: '192.129.6.42', os: 'Linux', status: 'P2', cpuAlarm: 2, deviceId: 'device-4' }, { name: 'HR数据库', ip: '192.129.17.11', os: 'Linux', status: 'P1', cpuAlarm: 2, deviceId: 'device-5' }, { name: '测试环境', ip: '192.129.7.199', os: 'Linux', status: 'P4', cpuAlarm: 2, deviceId: 'device-6' }, { name: '眼科专科', ip: '192.129.5.70', os: 'Linux', status: 'P3', cpuAlarm: 1, deviceId: 'device-7' }, { name: '两腺科', ip: '192.129.5.40', os: 'Linux', status: 'P3', cpuAlarm: 1, deviceId: 'device-8' }, ]; // 更新业务视图表格 const tableBody = document.getElementById('businessViewTableBody'); if (tableBody) { tableBody.innerHTML = ''; businessData.forEach(item => { const row = document.createElement('tr'); row.innerHTML = ` ${item.name} ${item.ip} ${item.os} ${item.status}
`; tableBody.appendChild(row); }); } } // 加载告警列表数据 function loadAlarmListData() { // 模拟数据 const alarmData = [ { level: 'warning', message: 'CPU使用率超过阈值', device: '192.129.6.108', time: '2023-11-30 14:30:00', }, { level: 'error', message: '内存使用率超过阈值', device: '192.129.6.57', time: '2023-11-30 14:28:00', }, { level: 'warning', message: '磁盘使用率超过阈值', device: '192.129.51.21', time: '2023-11-30 14:25:00', }, { level: 'info', message: '网络流量异常', device: '192.129.6.42', time: '2023-11-30 14:20:00', }, ]; // 更新告警列表 const alarmList = document.getElementById('alarmList'); if (alarmList) { alarmList.innerHTML = ''; alarmData.forEach(item => { const alarmItem = document.createElement('div'); alarmItem.className = 'p-3 bg-gray-50 rounded-md border-l-4 '; // 根据告警级别设置不同的边框颜色 let borderColor = ''; let iconColor = ''; let levelText = ''; switch (item.level) { case 'error': borderColor = 'border-red-500'; iconColor = 'text-red-500'; levelText = '错误'; break; case 'warning': borderColor = 'border-yellow-500'; iconColor = 'text-yellow-500'; levelText = '警告'; break; case 'info': borderColor = 'border-blue-500'; iconColor = 'text-blue-500'; levelText = '信息'; break; } alarmItem.className += borderColor; alarmItem.innerHTML = `

${item.message}

${item.device}

${item.time}
`; alarmList.appendChild(alarmItem); }); } } // 绑定事件 function bindEvents() { // 设备选择事件 document.getElementById('deviceSelect')?.addEventListener('change', function() { currentDeviceID = this.value; loadMetrics(); }); // 时间设置选择器事件 document.getElementById('timeSettings')?.addEventListener('change', function() { // 获取选择的值 let selectedValue = this.value; // 转换时间范围,将1d、3d、5d转换为对应的小时数 switch (selectedValue) { case '1d': currentTimeRange = '24h'; break; case '3d': currentTimeRange = '72h'; break; case '5d': currentTimeRange = '120h'; break; default: currentTimeRange = selectedValue; } // 根据时间范围自动调整时间区间 if (currentTimeRange === '5s') { // 5秒时间范围,使用5秒时间区间 currentInterval = '5s'; } else if (currentTimeRange === '10s' || currentTimeRange === '15s' || currentTimeRange === '30s') { // 10秒到30秒时间范围,使用10秒时间区间 currentInterval = '10s'; } else if (currentTimeRange === '1h' || currentTimeRange === '3h' || currentTimeRange === '5h') { // 中等时间范围,使用中等时间区间 currentInterval = '1m'; } else if (currentTimeRange === '24h' || currentTimeRange === '72h' || currentTimeRange === '120h') { // 长时间范围,使用较大的时间区间 currentInterval = '1h'; } else { // 自定义时间范围,默认使用1小时区间 currentInterval = '1h'; } // 隐藏自定义时间范围选择器 document.getElementById('customTimeRange')?.classList.add('hidden'); // 加载数据 loadMetrics(); }); // 刷新按钮事件 document.getElementById('refreshBtn')?.addEventListener('click', function() { loadMetrics(); }); } // 初始化WebSocket连接 function initWebSocket() { // 动态生成WebSocket URL,确保协议一致性 const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/api/ws`; console.log(`正在连接WebSocket: ${wsUrl}`); // 创建WebSocket连接 ws = new WebSocket(wsUrl); // 连接打开事件 ws.onopen = function() { console.log('WebSocket连接已打开'); // 重置重连计数和延迟 wsReconnectAttempts = 0; wsReconnectDelay = 1000; }; // 接收消息事件 ws.onmessage = function(event) { try { // 解析消息 const message = JSON.parse(event.data); // 处理不同类型的消息 switch(message.type) { case 'metrics_update': handleMetricsUpdate(message); break; default: console.log('未知消息类型:', message.type); } } catch (error) { console.error('解析WebSocket消息失败:', error); } }; // 连接关闭事件 ws.onclose = function(event) { console.log(`WebSocket连接已关闭: ${event.code} - ${event.reason}`); // 尝试重连 if (wsReconnectAttempts < wsMaxReconnectAttempts) { wsReconnectAttempts++; // 指数退避重连 wsReconnectDelay *= 2; console.log(`尝试重新连接WebSocket (${wsReconnectAttempts}/${wsMaxReconnectAttempts}),${wsReconnectDelay}ms后重试`); setTimeout(initWebSocket, wsReconnectDelay); } else { console.error('WebSocket重连失败,已达到最大重连次数'); } }; // 连接错误事件 ws.onerror = function(error) { console.error('WebSocket连接错误:', error); }; } // 处理指标更新消息 function handleMetricsUpdate(message) { const { device_id, metrics } = message; // 如果是当前选中的设备,更新状态卡片和图表 if (device_id === currentDeviceID) { // 更新状态卡片 updateStatusCardsFromWebSocket(metrics); // 重新加载指标数据,更新图表 loadMetrics(); } } // 从WebSocket更新状态卡片 function updateStatusCardsFromWebSocket(metrics) { // 更新CPU状态卡片 if (metrics.cpu !== undefined) { document.getElementById('cpuValue').textContent = `${metrics.cpu.toFixed(1)}%`; } // 更新内存状态卡片 if (metrics.memory !== undefined) { document.getElementById('memoryValue').textContent = `${metrics.memory.toFixed(1)}%`; } // 更新磁盘状态卡片 if (metrics.disk !== undefined) { // 如果是按挂载点分组的对象 if (typeof metrics.disk === 'object' && metrics.disk !== null && !Array.isArray(metrics.disk)) { // 计算所有挂载点的平均使用率 let totalUsage = 0; let mountpointCount = 0; for (const mountpoint in metrics.disk) { const usage = metrics.disk[mountpoint]; totalUsage += usage; mountpointCount++; } if (mountpointCount > 0) { const averageUsage = totalUsage / mountpointCount; document.getElementById('diskValue').textContent = `${averageUsage.toFixed(1)}%`; } } else { // 兼容旧格式,直接使用数值 document.getElementById('diskValue').textContent = `${metrics.disk.toFixed(1)}%`; } } // 更新网络状态卡片 if (metrics.network) { const sentMB = (metrics.network.bytes_sent / (1024 * 1024)).toFixed(2); const receivedMB = (metrics.network.bytes_received / (1024 * 1024)).toFixed(2); document.getElementById('networkSent').textContent = sentMB; document.getElementById('networkReceived').textContent = receivedMB; document.getElementById('networkValue').textContent = `${Math.max(parseFloat(sentMB), parseFloat(receivedMB)).toFixed(2)} MB/s`; } } // 加载设备列表 async function loadDevices() { try { const response = await fetch(`${API_BASE_URL}/devices/`); if (!response.ok) { throw new Error('Failed to fetch devices'); } const data = await response.json(); const devices = data.devices; // 更新设备选择器 const deviceSelect = document.getElementById('deviceSelect'); if (deviceSelect) { deviceSelect.innerHTML = ''; // 添加设备选项 devices.forEach(device => { const option = document.createElement('option'); option.value = device.id; option.textContent = device.name || device.id; if (device.id === currentDeviceID) { option.selected = true; } deviceSelect.appendChild(option); }); // 如果设备列表为空,显示提示信息 if (devices.length === 0) { console.warn('No devices available'); // 使用模拟数据 const mockDevices = [ { id: 'default', name: '默认设备' }, { id: 'device-1', name: '服务器1' }, { id: 'device-2', name: '服务器2' } ]; mockDevices.forEach(device => { const option = document.createElement('option'); option.value = device.id; option.textContent = device.name; if (device.id === currentDeviceID) { option.selected = true; } deviceSelect.appendChild(option); }); } // 如果没有选中的设备(即currentDeviceID不存在于设备列表中),则自动选择第一个设备 if (!deviceSelect.value) { currentDeviceID = devices[0]?.id || 'default'; deviceSelect.value = currentDeviceID; } } } catch (error) { console.error('Failed to load devices:', error); // 使用模拟数据 const deviceSelect = document.getElementById('deviceSelect'); if (deviceSelect) { deviceSelect.innerHTML = ''; const mockDevices = [ { id: 'default', name: '默认设备' }, { id: 'device-1', name: '服务器1' }, { id: 'device-2', name: '服务器2' } ]; mockDevices.forEach(device => { const option = document.createElement('option'); option.value = device.id; option.textContent = device.name; if (device.id === currentDeviceID) { option.selected = true; } deviceSelect.appendChild(option); }); } } } // 加载所有监控指标 async function loadMetrics() { try { // 检查currentDeviceID是否有效 if (!currentDeviceID || currentDeviceID === 'default') { console.warn('No valid device selected, skipping metrics load'); return; } // 并行加载所有指标 const [cpuData, memoryData, diskData, networkSumData, networkRateData] = await Promise.all([ fetchMetric('cpu'), fetchMetric('memory'), fetchMetric('disk'), fetchMetric('network', 'sum'), // 获取总流量数据 fetchMetric('network', 'average') // 获取速率数据 ]); // 更新状态卡片(同时使用总流量和速率数据) updateStatusCards(cpuData, memoryData, diskData, networkSumData, networkRateData); // 更新图表 updateCharts(cpuData, memoryData, diskData, networkSumData, networkRateData); } catch (error) { console.error('Failed to load metrics:', error); // 使用模拟数据 loadMockMetrics(); } } // 获取单个指标数据 async function fetchMetric(metricType, aggregation = 'average') { // 构建查询参数 const params = new URLSearchParams(); // 设置设备ID params.append('device_id', currentDeviceID); // 设置时间范围参数 if (currentTimeRange === 'custom') { if (customStartTime && customEndTime) { params.append('start_time', customStartTime); params.append('end_time', customEndTime); } } else { params.append('start_time', `-${currentTimeRange}`); params.append('end_time', 'now()'); } // 设置聚合方式 params.append('aggregation', aggregation); // 设置时间区间 params.append('interval', currentInterval); // 发送请求 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(); return data.data; } // 更新状态卡片 function updateStatusCards(cpuData, memoryData, diskData, networkSumData, networkRateData) { // 更新CPU状态卡片 if (cpuData && cpuData.length > 0) { const latestCPU = cpuData[cpuData.length - 1].value; document.getElementById('cpuValue').textContent = `${latestCPU.toFixed(1)}%`; } // 更新内存状态卡片 if (memoryData && memoryData.length > 0) { const latestMemory = memoryData[memoryData.length - 1].value; document.getElementById('memoryValue').textContent = `${latestMemory.toFixed(1)}%`; } // 更新磁盘状态卡片 if (diskData && typeof diskData === 'object') { // 计算所有挂载点的平均使用率 let totalUsage = 0; let mountpointCount = 0; // 如果是按挂载点分组的map if (diskData.constructor === Object && !Array.isArray(diskData)) { for (const mountpoint in diskData) { const data = diskData[mountpoint]; if (data && data.length > 0) { const latestValue = data[data.length - 1].value; totalUsage += latestValue; mountpointCount++; } } } else if (Array.isArray(diskData) && diskData.length > 0) { // 兼容旧格式,直接使用第一个值 const latestDisk = diskData[diskData.length - 1].value; document.getElementById('diskValue').textContent = `${latestDisk.toFixed(1)}%`; return; } // 计算平均值 if (mountpointCount > 0) { const averageUsage = totalUsage / mountpointCount; document.getElementById('diskValue').textContent = `${averageUsage.toFixed(1)}%`; } } // 更新网络状态卡片 if (networkSumData && networkSumData.sent && networkSumData.received && networkRateData && networkRateData.sent && networkRateData.received) { // 获取最新的总流量数据 const latestSentSum = networkSumData.sent[networkSumData.sent.length - 1].value; const latestReceivedSum = networkSumData.received[networkSumData.received.length - 1].value; // 计算当前时间段内的平均速率 const calculateAverageRate = (data) => { if (data.length === 0) return 0; let sum = 0; for (const item of data) { sum += item.y; } return sum / data.length; }; const avgSentRate = calculateAverageRate(networkRateData.sent); const avgReceivedRate = calculateAverageRate(networkRateData.received); // 格式化总流量和速率 const sentSumFormatted = formatBytes(latestSentSum, 2, false); const receivedSumFormatted = formatBytes(latestReceivedSum, 2, false); const sentRateFormatted = formatBytes(avgSentRate * 1024 * 1024, 2, true); const receivedRateFormatted = formatBytes(avgReceivedRate * 1024 * 1024, 2, true); // 大字区域显示总流量(显示较大的那个值) const maxSum = Math.max(latestSentSum, latestReceivedSum); document.getElementById('networkValue').textContent = formatBytes(maxSum, 2, false); // 小字区域显示当前时间段内的平均速率 document.getElementById('networkSent').textContent = sentRateFormatted; document.getElementById('networkReceived').textContent = receivedRateFormatted; } } // 初始化图表 function initCharts() { // 图表配置 const chartConfig = { type: 'line', options: { responsive: true, maintainAspectRatio: false, interaction: { intersect: false, mode: 'index', }, plugins: { legend: { display: true, position: 'top', }, tooltip: { mode: 'index', intersect: false, callbacks: { label: function(context) { let label = context.dataset.label || ''; if (label) { label += ': '; } if (context.parsed.y !== null) { // 根据数据集标签判断是流量还是速率 const isRate = label.includes('速率'); // 注意:context.parsed.y 已经是转换为MB的值,所以需要转换回bytes label += formatBytes(context.parsed.y * 1024 * 1024, 2, isRate); } return label; } } }, }, scales: { x: { type: 'category', grid: { display: false, }, ticks: { maxRotation: 0, // 水平显示标签,减少占用空间 minRotation: 0, maxTicksLimit: 8, // 减少最大刻度数量,避免过于密集 autoSkip: true, // 自动跳过标签 autoSkipPadding: 10, // 标签之间的最小间距 callback: function(value, index, values) { // 进一步优化,只显示部分标签 // 对于长时间范围,只显示每n个标签中的一个 if (currentTimeRange === '24h' || currentTimeRange === '72h' || currentTimeRange === '120h') { // 对于天级时间范围,每4个标签显示一个 return index % 4 === 0 ? value : ''; } else if (currentTimeRange === '1h' || currentTimeRange === '3h' || currentTimeRange === '5h') { // 对于小时级时间范围,每2个标签显示一个 return index % 2 === 0 ? value : ''; } // 短时间范围,显示所有标签 return value; } }, }, y: { beginAtZero: true, grid: { color: 'rgba(0, 0, 0, 0.05)', }, ticks: { callback: function(value) { // 根据数据集标签判断是流量还是速率 const dataset = this.chart.data.datasets[0]; const isRate = dataset.label.includes('速率'); // 注意:value 已经是转换为MB的值,所以需要转换回bytes return formatBytes(value * 1024 * 1024, 2, isRate); }, maxTicksLimit: 8, // 限制y轴刻度数量 } }, }, animation: { duration: 750, }, }, }; // CPU 图表 const cpuCtx = document.getElementById('cpuChart'); if (cpuCtx) { charts.cpu = new Chart(cpuCtx, { ...chartConfig, data: { datasets: [{ label: 'CPU 使用率', data: [], borderColor: '#3b82f6', // 蓝色 backgroundColor: 'rgba(59, 130, 246, 0.1)', borderWidth: 2, fill: true, tension: 0.4, }], }, options: { ...chartConfig.options, scales: { ...chartConfig.options.scales, y: { ...chartConfig.options.scales.y, max: 100, ticks: { callback: function(value) { return value + '%'; }, }, }, }, }, }); } // 内存 图表 const memoryCtx = document.getElementById('memoryChart'); if (memoryCtx) { charts.memory = new Chart(memoryCtx, { ...chartConfig, data: { datasets: [{ label: '内存使用率', data: [], borderColor: '#10b981', // 绿色 backgroundColor: 'rgba(16, 185, 129, 0.1)', borderWidth: 2, fill: true, tension: 0.4, }], }, options: { ...chartConfig.options, scales: { ...chartConfig.options.scales, y: { ...chartConfig.options.scales.y, max: 100, ticks: { callback: function(value) { return value + '%'; }, }, }, }, }, }); } // 磁盘 图表,支持多个挂载点 const diskCtx = document.getElementById('diskChart'); if (diskCtx) { charts.disk = new Chart(diskCtx, { ...chartConfig, data: { datasets: [], }, options: { ...chartConfig.options, scales: { ...chartConfig.options.scales, y: { ...chartConfig.options.scales.y, max: 100, ticks: { callback: function(value) { return value + '%'; }, }, }, }, }, }); } // 网络 图表 const networkCtx = document.getElementById('networkChart'); if (networkCtx) { charts.network = new Chart(networkCtx, { ...chartConfig, data: { datasets: [{ label: '发送流量', data: [], borderColor: '#8b5cf6', // 紫色 backgroundColor: 'rgba(139, 92, 246, 0.1)', borderWidth: 2, fill: true, tension: 0.4, }, { label: '接收流量', data: [], borderColor: '#ec4899', // 粉色 backgroundColor: 'rgba(236, 72, 153, 0.1)', borderWidth: 2, fill: true, tension: 0.4, }], }, }); } } // 更新图表数据 function updateCharts(cpuData, memoryData, diskData, networkSumData, networkRateData) { // 数据点排序函数 const sortDataByTime = (data) => { return [...data].sort((a, b) => { return new Date(a.time) - new Date(b.time); }); }; // 更新CPU图表 if (cpuData && cpuData.length > 0) { const sortedData = sortDataByTime(cpuData); charts.cpu.data.datasets[0].data = sortedData.map(item => ({ x: formatTime(item.time), y: item.value })); charts.cpu.update(); } // 更新内存图表 if (memoryData && memoryData.length > 0) { const sortedData = sortDataByTime(memoryData); charts.memory.data.datasets[0].data = sortedData.map(item => ({ x: formatTime(item.time), y: item.value })); charts.memory.update(); } // 更新磁盘图表,支持多个挂载点 if (diskData && typeof diskData === 'object') { // 定义不同的颜色,用于区分不同的挂载点 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; for (const [mountpoint, data] of Object.entries(diskData)) { if (data && data.length > 0) { // 获取颜色 const color = colors[colorIndex % colors.length]; colorIndex++; // 排序数据 const sortedData = sortDataByTime(data); // 创建数据集 const dataset = { label: `磁盘使用率 (${mountpoint})`, data: sortedData.map(item => ({ x: formatTime(item.time), y: item.value })), borderColor: color.border, backgroundColor: color.background, borderWidth: 2, fill: true, tension: 0.4, }; // 添加数据集 charts.disk.data.datasets.push(dataset); } } // 更新图表 charts.disk.update(); } // 更新网络流量趋势图表(总流量) if (networkSumData && networkSumData.sent && networkSumData.received) { if (networkSumData.sent.length > 0) { const sortedData = sortDataByTime(networkSumData.sent); charts.network.data.datasets[0].data = sortedData.map(item => ({ x: formatTime(item.time), y: item.value / (1024 * 1024) // 转换为MB,图表会自动格式化 })); } if (networkSumData.received.length > 0) { const sortedData = sortDataByTime(networkSumData.received); charts.network.data.datasets[1].data = sortedData.map(item => ({ x: formatTime(item.time), y: item.value / (1024 * 1024) // 转换为MB,图表会自动格式化 })); } charts.network.update(); } } // 加载所有设备状态概览 async function loadAllDevicesStatus() { try { const response = await fetch(`${API_BASE_URL}/devices/status`); if (!response.ok) { throw new Error('Failed to fetch devices status'); } const data = await response.json(); const devices = data.devices; // 更新设备概览表格 const devicesTableBody = document.getElementById('devicesTableBody'); if (devicesTableBody) { devicesTableBody.innerHTML = ''; devices.forEach(device => { const row = document.createElement('tr'); // 优先显示设备名称,如果没有则显示设备IP地址 const displayName = device.name || device.ip || device.device_id; row.innerHTML = ` ${displayName} ${device.device_id} ${device.status.cpu ? device.status.cpu.toFixed(1) : 'N/A'}% ${device.status.memory ? device.status.memory.toFixed(1) : 'N/A'}% ${device.status.disk ? device.status.disk.toFixed(1) : 'N/A'}% ${device.status.network_sent ? (device.status.network_sent / (1024 * 1024)).toFixed(2) : 'N/A'} MB/s ${device.status.network_received ? (device.status.network_received / (1024 * 1024)).toFixed(2) : 'N/A'} MB/s 在线 `; devicesTableBody.appendChild(row); }); } } catch (error) { console.error('Failed to load devices status:', error); } } // 加载所有服务器列表 async function loadAllServers() { try { const response = await fetch(`${API_BASE_URL}/devices/`); if (!response.ok) { throw new Error('Failed to fetch devices'); } const data = await response.json(); const devices = data.devices; // 更新服务器网格 const serversGrid = document.getElementById('serversGrid'); if (serversGrid) { serversGrid.innerHTML = ''; devices.forEach(device => { const serverCard = createServerCard(device); serversGrid.appendChild(serverCard); }); } } catch (error) { console.error('Failed to load servers:', error); // 使用模拟数据 loadMockServers(); } } // 创建服务器卡片 function createServerCard(device) { const card = document.createElement('div'); card.className = 'status-card bg-white rounded-xl shadow-lg p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1 cursor-pointer'; card.onclick = () => goToServerMonitor(device.id); card.innerHTML = `

${device.name || device.id}

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

在线

CPU使用率

0.0%

内存使用率

0.0%

磁盘使用率

0.0%

网络流量

0.0 MB/s

`; return card; } // 加载模拟服务器数据 function loadMockServers() { const mockServers = [ { id: 'device-1', name: '服务器1', ip: '192.168.1.100' }, { id: 'device-2', name: '服务器2', ip: '192.168.1.101' } ]; // 更新服务器网格 const serversGrid = document.getElementById('serversGrid'); if (serversGrid) { serversGrid.innerHTML = ''; mockServers.forEach(device => { const serverCard = createServerCard(device); serversGrid.appendChild(serverCard); }); } } // 加载模拟监控数据 function loadMockMetrics() { // 生成模拟数据 const now = new Date(); const cpuData = []; const memoryData = []; const diskData = {}; const networkSumData = { sent: [], received: [] }; // 生成过去24小时的数据,每小时一个点 for (let i = 23; i >= 0; i--) { const time = new Date(now.getTime() - i * 60 * 60 * 1000); const timeStr = time.toISOString(); // CPU数据 cpuData.push({ time: timeStr, value: Math.random() * 100 }); // 内存数据 memoryData.push({ time: timeStr, value: Math.random() * 100 }); // 磁盘数据 diskData['/'] = diskData['/'] || []; diskData['/'].push({ time: timeStr, value: Math.random() * 100 }); diskData['/boot'] = diskData['/boot'] || []; diskData['/boot'].push({ time: timeStr, value: Math.random() * 100 }); diskData['/data'] = diskData['/data'] || []; diskData['/data'].push({ time: timeStr, value: Math.random() * 100 }); diskData['/var'] = diskData['/var'] || []; diskData['/var'].push({ time: timeStr, value: Math.random() * 100 }); // 网络数据 networkSumData.sent.push({ time: timeStr, value: Math.random() * 1024 * 1024 * 1024 }); // 随机值,单位为字节 networkSumData.received.push({ time: timeStr, value: Math.random() * 1024 * 1024 * 1024 }); // 随机值,单位为字节 } // 更新状态卡片 updateStatusCards(cpuData, memoryData, diskData, networkSumData, { sent: [], received: [] }); // 更新图表 updateCharts(cpuData, memoryData, diskData, networkSumData, { sent: [], received: [] }); } // 跳转到服务器监控界面 function goToServerMonitor(deviceId) { currentDeviceID = deviceId; window.location.hash = '#serverMonitor'; } // 加载设备管理列表 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; // 更新设备管理表格 const deviceManagementTableBody = document.getElementById('deviceManagementTableBody'); if (deviceManagementTableBody) { deviceManagementTableBody.innerHTML = ''; devices.forEach(device => { const row = document.createElement('tr'); // 格式化创建时间 const createdAt = new Date(device.created_at * 1000).toLocaleString(); row.innerHTML = ` ${device.name} ${device.id} ${device.ip || 'N/A'}
${device.token}
${getStatusText(device.status)} ${createdAt} `; deviceManagementTableBody.appendChild(row); }); // 绑定设备操作事件(包括复制token) bindDeviceActionEvents(); } } catch (error) { console.error('Failed to load device management list:', error); // 使用模拟数据 loadMockDeviceManagementList(); } } // 加载模拟设备管理数据 function loadMockDeviceManagementList() { const mockDevices = [ { id: 'device-1', name: '服务器1', ip: '192.168.1.100', token: 'token-1', status: 'active', created_at: Math.floor(Date.now() / 1000) - 86400 }, { id: 'device-2', name: '服务器2', ip: '192.168.1.101', token: 'token-2', status: 'active', created_at: Math.floor(Date.now() / 1000) - 172800 } ]; // 更新设备管理表格 const deviceManagementTableBody = document.getElementById('deviceManagementTableBody'); if (deviceManagementTableBody) { deviceManagementTableBody.innerHTML = ''; mockDevices.forEach(device => { const row = document.createElement('tr'); // 格式化创建时间 const createdAt = new Date(device.created_at * 1000).toLocaleString(); row.innerHTML = ` ${device.name} ${device.id} ${device.ip || 'N/A'}
${device.token}
${getStatusText(device.status)} ${createdAt} `; deviceManagementTableBody.appendChild(row); }); // 绑定设备操作事件(包括复制token) bindDeviceActionEvents(); } } // 根据状态获取颜色类 function getStatusColorClass(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'; } } // 根据状态获取文本 function getStatusText(status) { switch (status) { case 'active': return '活跃'; case 'inactive': return '非活跃'; case 'offline': return '离线'; default: return status; } } // 绑定设备操作事件 function bindDeviceActionEvents() { // 编辑按钮事件 document.querySelectorAll('.edit-device-btn').forEach(btn => { btn.addEventListener('click', function(e) { e.stopPropagation(); const deviceId = this.getAttribute('data-id'); editDevice(deviceId); }); }); // 删除按钮事件 document.querySelectorAll('.delete-device-btn').forEach(btn => { btn.addEventListener('click', function(e) { e.stopPropagation(); const deviceId = this.getAttribute('data-id'); deleteDevice(deviceId); }); }); // 复制token按钮事件 document.querySelectorAll('.copy-token-btn').forEach(btn => { btn.addEventListener('click', function(e) { e.stopPropagation(); const token = this.getAttribute('data-token'); navigator.clipboard.writeText(token).then(() => { // 显示复制成功提示 const originalIcon = this.innerHTML; this.innerHTML = ''; setTimeout(() => { this.innerHTML = originalIcon; }, 1000); }).catch(err => { console.error('Failed to copy token:', err); }); }); }); } // 编辑设备 async function editDevice(deviceId) { // 这里可以实现编辑设备的逻辑 console.log('Edit device:', deviceId); } // 删除设备 async function deleteDevice(deviceId) { if (confirm('确定要删除该设备吗?')) { try { const response = await fetch(`${API_BASE_URL}/devices/${deviceId}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('Failed to delete device'); } // 重新加载设备列表 loadDeviceManagementList(); } catch (error) { console.error('Failed to delete device:', error); alert('删除设备失败: ' + error.message); } } } // 页面加载完成后初始化应用 document.addEventListener('DOMContentLoaded', initApp); // 初始化设备管理功能 function initDeviceManagement() { // 添加设备按钮事件 document.getElementById('addDeviceBtn')?.addEventListener('click', function() { // 这里可以实现添加设备的逻辑 console.log('Add device'); }); // 状态过滤事件 document.getElementById('statusFilter')?.addEventListener('change', function() { filterDevices(); }); // 搜索设备事件 document.getElementById('searchDevice')?.addEventListener('input', function() { filterDevices(); }); } // 过滤设备 function filterDevices() { const statusFilter = document.getElementById('statusFilter')?.value; const searchTerm = document.getElementById('searchDevice')?.value.toLowerCase(); const rows = document.querySelectorAll('#deviceManagementTableBody tr'); rows.forEach(row => { // 状态列的索引从4变成了5,因为添加了认证令牌列 const status = row.querySelector('td:nth-child(5) span')?.textContent.toLowerCase(); const name = row.querySelector('td:nth-child(1)')?.textContent.toLowerCase(); const id = row.querySelector('td:nth-child(2)')?.textContent.toLowerCase(); let showRow = true; // 状态过滤 if (statusFilter && statusFilter !== 'all') { const statusText = getStatusText(statusFilter); if (status !== statusText.toLowerCase()) { showRow = false; } } // 搜索过滤 if (searchTerm) { if (!name.includes(searchTerm) && !id.includes(searchTerm)) { showRow = false; } } // 显示或隐藏行 if (showRow) { row.classList.remove('hidden'); } else { row.classList.add('hidden'); } }); } // 初始化设备管理功能 initDeviceManagement();