// 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'}
在线
`;
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();