Files
Docker-Proxy/hubcmdui/server-utils.js

334 lines
9.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 服务器实用工具函数
*/
const { exec } = require('child_process');
const os = require('os');
const fs = require('fs').promises;
const path = require('path');
const logger = require('./logger');
/**
* 安全执行系统命令
* @param {string} command - 要执行的命令
* @param {object} options - 执行选项
* @returns {Promise<string>} 命令输出结果
*/
function execCommand(command, options = { timeout: 30000 }) {
return new Promise((resolve, reject) => {
exec(command, options, (error, stdout, stderr) => {
if (error) {
if (error.killed) {
reject(new Error('执行命令超时'));
} else {
reject(error);
}
return;
}
resolve(stdout.trim() || stderr.trim());
});
});
}
/**
* 获取系统信息
* @returns {Promise<object>} 系统信息对象
*/
async function getSystemInfo() {
const platform = os.platform();
let memoryInfo = {};
let cpuInfo = {};
let diskInfo = {};
try {
// 内存信息 - 使用OS模块适用于所有平台
const totalMem = os.totalmem();
const freeMem = os.freemem();
const usedMem = totalMem - freeMem;
const memPercent = Math.round((usedMem / totalMem) * 100);
memoryInfo = {
total: formatBytes(totalMem),
free: formatBytes(freeMem),
used: formatBytes(usedMem),
percent: memPercent
};
// CPU信息 - 使用不同平台的方法
await getCpuInfo(platform).then(info => {
cpuInfo = info;
}).catch(err => {
logger.warn('获取CPU信息失败:', err);
// 降级方案使用OS模块
const cpuLoad = os.loadavg();
const cpuCores = os.cpus().length;
cpuInfo = {
model: os.cpus()[0].model,
cores: cpuCores,
load1: cpuLoad[0].toFixed(2),
load5: cpuLoad[1].toFixed(2),
load15: cpuLoad[2].toFixed(2),
percent: Math.round((cpuLoad[0] / cpuCores) * 100)
};
});
// 磁盘信息 - 根据平台调用不同方法
await getDiskInfo(platform).then(info => {
diskInfo = info;
}).catch(err => {
logger.warn('获取磁盘信息失败:', err);
diskInfo = {
filesystem: 'unknown',
size: 'unknown',
used: 'unknown',
available: 'unknown',
percent: '0%'
};
});
return {
platform,
hostname: os.hostname(),
memory: memoryInfo,
cpu: cpuInfo,
disk: diskInfo,
uptime: formatUptime(os.uptime())
};
} catch (error) {
logger.error('获取系统信息失败:', error);
throw error;
}
}
/**
* 根据平台获取CPU信息
* @param {string} platform - 操作系统平台
* @returns {Promise<object>} CPU信息
*/
async function getCpuInfo(platform) {
if (platform === 'linux') {
try {
// Linux平台使用/proc/stat和/proc/cpuinfo
const [loadData, cpuData] = await Promise.all([
execCommand("cat /proc/loadavg"),
execCommand("cat /proc/cpuinfo | grep 'model name' | head -1")
]);
const cpuLoad = loadData.split(' ').slice(0, 3).map(parseFloat);
const cpuCores = os.cpus().length;
const modelMatch = cpuData.match(/model name\s*:\s*(.*)/);
const model = modelMatch ? modelMatch[1].trim() : os.cpus()[0].model;
const percent = Math.round((cpuLoad[0] / cpuCores) * 100);
return {
model,
cores: cpuCores,
load1: cpuLoad[0].toFixed(2),
load5: cpuLoad[1].toFixed(2),
load15: cpuLoad[2].toFixed(2),
percent: percent > 100 ? 100 : percent
};
} catch (error) {
throw error;
}
} else if (platform === 'darwin') {
// macOS平台
try {
const cpuLoad = os.loadavg();
const cpuCores = os.cpus().length;
const model = os.cpus()[0].model;
const systemProfilerData = await execCommand("system_profiler SPHardwareDataType | grep 'Processor Name'");
const cpuMatch = systemProfilerData.match(/Processor Name:\s*(.*)/);
const cpuModel = cpuMatch ? cpuMatch[1].trim() : model;
return {
model: cpuModel,
cores: cpuCores,
load1: cpuLoad[0].toFixed(2),
load5: cpuLoad[1].toFixed(2),
load15: cpuLoad[2].toFixed(2),
percent: Math.round((cpuLoad[0] / cpuCores) * 100)
};
} catch (error) {
throw error;
}
} else if (platform === 'win32') {
// Windows平台
try {
// 使用wmic获取CPU信息
const cpuData = await execCommand('wmic cpu get Name,NumberOfCores /value');
const cpuLines = cpuData.split('\r\n');
let model = os.cpus()[0].model;
let cores = os.cpus().length;
cpuLines.forEach(line => {
if (line.startsWith('Name=')) {
model = line.substring(5).trim();
} else if (line.startsWith('NumberOfCores=')) {
cores = parseInt(line.substring(14).trim()) || cores;
}
});
// Windows没有直接的负载平均值使用CPU使用率作为替代
const perfData = await execCommand('wmic cpu get LoadPercentage /value');
const loadMatch = perfData.match(/LoadPercentage=(\d+)/);
const loadPercent = loadMatch ? parseInt(loadMatch[1]) : 0;
return {
model,
cores,
load1: '不适用',
load5: '不适用',
load15: '不适用',
percent: loadPercent
};
} catch (error) {
throw error;
}
}
// 默认返回OS模块的信息
const cpuLoad = os.loadavg();
const cpuCores = os.cpus().length;
return {
model: os.cpus()[0].model,
cores: cpuCores,
load1: cpuLoad[0].toFixed(2),
load5: cpuLoad[1].toFixed(2),
load15: cpuLoad[2].toFixed(2),
percent: Math.round((cpuLoad[0] / cpuCores) * 100)
};
}
/**
* 根据平台获取磁盘信息
* @param {string} platform - 操作系统平台
* @returns {Promise<object>} 磁盘信息
*/
async function getDiskInfo(platform) {
if (platform === 'linux' || platform === 'darwin') {
try {
// Linux/macOS使用df命令
const diskCommand = platform === 'linux'
? 'df -h / | tail -1'
: 'df -h / | tail -1';
const diskData = await execCommand(diskCommand);
const parts = diskData.trim().split(/\s+/);
if (parts.length >= 5) {
return {
filesystem: parts[0],
size: parts[1],
used: parts[2],
available: parts[3],
percent: parts[4]
};
} else {
throw new Error('磁盘信息格式不正确');
}
} catch (error) {
throw error;
}
} else if (platform === 'win32') {
// Windows平台
try {
// 使用wmic获取C盘信息
const diskData = await execCommand('wmic logicaldisk where DeviceID="C:" get Size,FreeSpace /value');
const lines = diskData.split(/\r\n|\n/);
let freeSpace, totalSize;
lines.forEach(line => {
if (line.startsWith('FreeSpace=')) {
freeSpace = parseInt(line.split('=')[1]);
} else if (line.startsWith('Size=')) {
totalSize = parseInt(line.split('=')[1]);
}
});
if (freeSpace !== undefined && totalSize !== undefined) {
const usedSpace = totalSize - freeSpace;
const usedPercent = Math.round((usedSpace / totalSize) * 100);
return {
filesystem: 'C:',
size: formatBytes(totalSize),
used: formatBytes(usedSpace),
available: formatBytes(freeSpace),
percent: `${usedPercent}%`
};
} else {
throw new Error('无法解析Windows磁盘信息');
}
} catch (error) {
throw error;
}
}
// 默认尝试df命令
try {
const diskData = await execCommand('df -h / | tail -1');
const parts = diskData.trim().split(/\s+/);
if (parts.length >= 5) {
return {
filesystem: parts[0],
size: parts[1],
used: parts[2],
available: parts[3],
percent: parts[4]
};
} else {
throw new Error('磁盘信息格式不正确');
}
} catch (error) {
throw error;
}
}
/**
* 将字节格式化为可读大小
* @param {number} bytes - 字节数
* @returns {string} 格式化后的字符串
*/
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* 格式化运行时间
* @param {number} seconds - 秒数
* @returns {string} 格式化后的运行时间
*/
function formatUptime(seconds) {
const days = Math.floor(seconds / 86400);
seconds %= 86400;
const hours = Math.floor(seconds / 3600);
seconds %= 3600;
const minutes = Math.floor(seconds / 60);
seconds = Math.floor(seconds % 60);
const parts = [];
if (days > 0) parts.push(`${days}`);
if (hours > 0) parts.push(`${hours}小时`);
if (minutes > 0) parts.push(`${minutes}分钟`);
if (seconds > 0 && parts.length === 0) parts.push(`${seconds}`);
return parts.join(' ');
}
module.exports = {
execCommand,
getSystemInfo,
formatBytes,
formatUptime
};