| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- /**
- * 系统相关路由
- */
- const express = require('express');
- const router = express.Router();
- const os = require('os'); // 确保导入 os 模块
- const util = require('util'); // 导入 util 模块
- const { exec } = require('child_process');
- const execPromise = util.promisify(exec); // 只在这里定义一次
- const logger = require('../logger');
- const { requireLogin } = require('../middleware/auth');
- const configService = require('../services/configService');
- const { execCommand, getSystemInfo } = require('../server-utils');
- const dockerService = require('../services/dockerService');
- const path = require('path');
- const fs = require('fs').promises;
- // 获取系统状态
- async function getSystemStats(req, res) {
- try {
- let dockerAvailable = false;
- let containerCount = '0';
- let memoryUsage = '0%';
- let cpuLoad = '0%';
- let diskSpace = '0%';
- let recentActivities = [];
- // 尝试获取系统信息
- try {
- const systemInfo = await getSystemInfo();
- memoryUsage = `${systemInfo.memory.percent}%`;
- cpuLoad = systemInfo.cpu.load1;
- diskSpace = systemInfo.disk.percent;
- } catch (sysError) {
- logger.error('获取系统信息失败:', sysError);
- }
- // 尝试从Docker获取状态信息
- try {
- const docker = await dockerService.getDockerConnection();
- if (docker) {
- dockerAvailable = true;
-
- // 获取容器统计
- const containers = await docker.listContainers({ all: true });
- containerCount = containers.length.toString();
-
- // 获取最近的容器活动
- const runningContainers = containers.filter(c => c.State === 'running');
- for (let i = 0; i < Math.min(3, runningContainers.length); i++) {
- recentActivities.push({
- time: new Date(runningContainers[i].Created * 1000).toLocaleString(),
- action: '运行中',
- container: runningContainers[i].Names[0].replace(/^\//, ''),
- status: '正常'
- });
- }
- // 获取最近的Docker事件
- const events = await dockerService.getRecentEvents();
- if (events && events.length > 0) {
- recentActivities = [...events.map(event => ({
- time: new Date(event.time * 1000).toLocaleString(),
- action: event.Action,
- container: event.Actor?.Attributes?.name || '未知容器',
- status: event.status || '完成'
- })), ...recentActivities].slice(0, 10);
- }
- }
- } catch (containerError) {
- logger.error('获取容器信息失败:', containerError);
- }
- // 如果没有活动记录,添加一个默认记录
- if (recentActivities.length === 0) {
- recentActivities.push({
- time: new Date().toLocaleString(),
- action: '系统检查',
- container: '监控服务',
- status: dockerAvailable ? '正常' : 'Docker服务不可用'
- });
- }
- // 返回收集到的所有数据,即使部分数据可能不完整
- res.json({
- dockerAvailable,
- containerCount,
- memoryUsage,
- cpuLoad,
- diskSpace,
- recentActivities
- });
- } catch (error) {
- logger.error('获取系统统计数据失败:', error);
-
- // 即使出错,仍然尝试返回一些基本数据
- res.status(200).json({
- dockerAvailable: false,
- containerCount: '0',
- memoryUsage: '未知',
- cpuLoad: '未知',
- diskSpace: '未知',
- recentActivities: [{
- time: new Date().toLocaleString(),
- action: '系统错误',
- container: '监控服务',
- status: '数据获取失败'
- }],
- error: '获取系统统计数据失败',
- errorDetails: error.message
- });
- }
- }
- // 获取系统配置 - 修改版本,避免与其他路由冲突
- router.get('/system-config', async (req, res) => {
- try {
- const config = await configService.getConfig();
- res.json(config);
- } catch (error) {
- logger.error('读取配置失败:', error);
- res.status(500).json({
- error: '读取配置失败',
- details: error.message
- });
- }
- });
- // 保存系统配置 - 修改版本,避免与其他路由冲突
- router.post('/system-config', requireLogin, async (req, res) => {
- try {
- const currentConfig = await configService.getConfig();
- const newConfig = { ...currentConfig, ...req.body };
- await configService.saveConfig(newConfig);
- logger.info('系统配置已更新');
- res.json({ success: true });
- } catch (error) {
- logger.error('保存配置失败:', error);
- res.status(500).json({
- error: '保存配置失败',
- details: error.message
- });
- }
- });
- // 获取系统状态
- router.get('/stats', requireLogin, async (req, res) => {
- return await getSystemStats(req, res);
- });
- // 获取磁盘空间信息
- router.get('/disk-space', requireLogin, async (req, res) => {
- try {
- const systemInfo = await getSystemInfo();
- res.json({
- diskSpace: `${systemInfo.disk.used}/${systemInfo.disk.size}`,
- usagePercent: parseInt(systemInfo.disk.percent)
- });
- } catch (error) {
- logger.error('获取磁盘空间信息失败:', error);
- res.status(500).json({ error: '获取磁盘空间信息失败', details: error.message });
- }
- });
- // 网络测试
- router.post('/network-test', requireLogin, async (req, res) => {
- const { type, domain } = req.body;
-
- // 验证输入
- function validateInput(input, type) {
- if (type === 'domain') {
- return /^[a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(input);
- }
- return false;
- }
-
- if (!validateInput(domain, 'domain')) {
- return res.status(400).json({ error: '无效的域名格式' });
- }
-
- try {
- const result = await execCommand(`${type === 'ping' ? 'ping -c 4' : 'traceroute -m 10'} ${domain}`, { timeout: 30000 });
- res.send(result);
- } catch (error) {
- if (error.killed) {
- return res.status(408).send('测试超时');
- }
- logger.error(`执行网络测试命令错误:`, error);
- res.status(500).send('测试执行失败: ' + error.message);
- }
- });
- // 获取用户统计信息
- router.get('/user-stats', requireLogin, async (req, res) => {
- try {
- const userService = require('../services/userService');
- const username = req.session.user.username;
- const userStats = await userService.getUserStats(username);
-
- res.json(userStats);
- } catch (error) {
- logger.error('获取用户统计信息失败:', error);
- res.status(500).json({
- loginCount: '0',
- lastLogin: '未知',
- accountAge: '0'
- });
- }
- });
- // 获取系统状态信息 (旧版,可能与 getSystemStats 重复,可以考虑移除)
- router.get('/system-status', requireLogin, async (req, res) => {
- logger.warn('Accessing potentially deprecated /api/system-status route.');
- try {
- // 检查 Docker 可用性
- let dockerAvailable = true;
- let containerCount = 0;
- try {
- // 避免直接执行命令计算,依赖 dockerService
- const docker = await dockerService.getDockerConnection();
- if (docker) {
- const containers = await docker.listContainers({ all: true });
- containerCount = containers.length;
- } else {
- dockerAvailable = false;
- }
- } catch (dockerError) {
- dockerAvailable = false;
- containerCount = 0;
- logger.warn('Docker可能未运行或无法访问 (in /system-status):', dockerError.message);
- }
-
- // 获取内存使用信息
- const totalMem = os.totalmem();
- const freeMem = os.freemem();
- const usedMem = totalMem - freeMem;
- const memoryUsage = `${Math.round(usedMem / totalMem * 100)}%`;
-
- // 获取CPU负载
- const [load1] = os.loadavg();
- const cpuCount = os.cpus().length || 1; // 避免除以0
- const cpuLoad = `${(load1 / cpuCount * 100).toFixed(1)}%`;
-
- // 获取磁盘空间 - 简单版
- let diskSpace = '未知';
- try {
- if (os.platform() === 'darwin' || os.platform() === 'linux') {
- const { stdout } = await execPromise('df -h / | tail -n 1'); // 使用 -n 1
- const parts = stdout.trim().split(/\s+/);
- if (parts.length >= 5) diskSpace = parts[4];
- } else if (os.platform() === 'win32') {
- const { stdout } = await execPromise('wmic logicaldisk get size,freespace,caption | findstr /B /L /V "Caption" ');
- const lines = stdout.trim().split(/\r?\n/);
- if (lines.length > 0) {
- const parts = lines[0].trim().split(/\s+/);
- if (parts.length >= 2) {
- const free = parseInt(parts[0], 10);
- const total = parseInt(parts[1], 10);
- if (!isNaN(total) && !isNaN(free) && total > 0) {
- diskSpace = `${Math.round(((total - free) / total) * 100)}%`;
- }
- }
- }
- }
- } catch (diskError) {
- logger.warn('获取磁盘空间失败 (in /system-status):', diskError.message);
- diskSpace = '未知';
- }
- // 格式化系统运行时间
- const uptime = formatUptime(os.uptime());
- res.json({
- dockerAvailable,
- containerCount,
- memoryUsage,
- cpuLoad,
- diskSpace,
- systemUptime: uptime
- });
- } catch (error) {
- logger.error('获取系统状态失败 (in /system-status):', error);
- res.status(500).json({
- error: '获取系统状态失败',
- message: error.message
- });
- }
- });
- // 添加新的API端点,提供完整系统资源信息
- router.get('/system-resources', requireLogin, async (req, res) => {
- logger.info('Received request for /api/system-resources');
- let cpuInfoData = null, memoryData = null, diskInfoData = null, systemData = null;
-
- // --- 获取 CPU 信息 (独立 try...catch) ---
- try {
- const cpuInfo = os.cpus();
- const [load1, load5, load15] = os.loadavg();
- const cpuCount = cpuInfo.length || 1;
- const cpuUsage = (load1 / cpuCount * 100).toFixed(1);
- cpuInfoData = {
- cores: cpuCount,
- model: cpuInfo[0]?.model || '未知',
- speed: `${cpuInfo[0]?.speed || '未知'} MHz`,
- loadAvg: {
- '1min': load1.toFixed(2),
- '5min': load5.toFixed(2),
- '15min': load15.toFixed(2)
- },
- usage: parseFloat(cpuUsage)
- };
- logger.info('Successfully retrieved CPU info.');
- } catch (cpuError) {
- logger.error('Error getting CPU info:', cpuError.message);
- cpuInfoData = { error: '获取 CPU 信息失败', message: cpuError.message }; // 返回错误信息
- }
-
- // --- 获取内存信息 (独立 try...catch) ---
- try {
- const totalMem = os.totalmem();
- const freeMem = os.freemem();
- const usedMem = totalMem - freeMem;
- const memoryUsagePercent = totalMem > 0 ? Math.round(usedMem / totalMem * 100) : 0;
- memoryData = {
- total: formatBytes(totalMem), // 可能出错
- free: formatBytes(freeMem), // 可能出错
- used: formatBytes(usedMem), // 可能出错
- usedPercentage: memoryUsagePercent
- };
- logger.info('Successfully retrieved Memory info.');
- } catch (memError) {
- logger.error('Error getting Memory info:', memError.message);
- memoryData = { error: '获取内存信息失败', message: memError.message }; // 返回错误信息
- }
-
- // --- 获取磁盘信息 (独立 try...catch) ---
- try {
- let diskResult = { total: '未知', free: '未知', used: '未知', usedPercentage: '未知' };
- logger.info(`Getting disk info for platform: ${os.platform()}`);
- if (os.platform() === 'darwin' || os.platform() === 'linux') {
- try {
- // 使用 -k 获取 KB 单位,方便计算
- const { stdout } = await execPromise('df -k / | tail -n 1', { timeout: 5000 });
- logger.info(`'df -k' command output: ${stdout}`);
- const parts = stdout.trim().split(/\s+/);
- // 索引通常是 1=Total, 2=Used, 3=Available, 4=Use%
- if (parts.length >= 4) {
- const total = parseInt(parts[1], 10) * 1024; // KB to Bytes
- const used = parseInt(parts[2], 10) * 1024; // KB to Bytes
- const free = parseInt(parts[3], 10) * 1024; // KB to Bytes
- // 优先使用命令输出的百分比,更准确
- let usedPercentage = parseInt(parts[4].replace('%', ''), 10);
-
- // 如果解析失败或百分比无效,则尝试计算
- if (isNaN(usedPercentage) && !isNaN(total) && !isNaN(used) && total > 0) {
- usedPercentage = Math.round((used / total) * 100);
- }
- if (!isNaN(total) && !isNaN(used) && !isNaN(free) && !isNaN(usedPercentage)) {
- diskResult = {
- total: formatBytes(total), // 可能出错
- free: formatBytes(free), // 可能出错
- used: formatBytes(used), // 可能出错
- usedPercentage: usedPercentage
- };
- logger.info('Successfully parsed disk info (Linux/Darwin).');
- } else {
- logger.warn('Failed to parse numbers from df output:', parts);
- diskResult = { ...diskResult, error: '解析 df 输出失败' }; // 添加错误标记
- }
- } else {
- logger.warn('Unexpected output format from df:', stdout);
- diskResult = { ...diskResult, error: 'df 输出格式不符合预期' }; // 添加错误标记
- }
- } catch (dfError) {
- logger.error(`Error executing or parsing 'df -k': ${dfError.message}`);
- if (dfError.killed) logger.error("'df -k' command timed out.");
- diskResult = { error: '获取磁盘信息失败 (df)', message: dfError.message }; // 标记错误
- }
- } else if (os.platform() === 'win32') {
- try {
- // 获取 C 盘信息 (可以修改为获取所有盘符或特定盘符)
- const { stdout } = await execPromise(`wmic logicaldisk where "DeviceID='C:'" get size,freespace /value`, { timeout: 5000 });
- logger.info(`'wmic' command output: ${stdout}`);
- const lines = stdout.trim().split(/\r?\n/);
- let free = NaN, total = NaN;
- lines.forEach(line => {
- if (line.startsWith('FreeSpace=')) {
- free = parseInt(line.split('=')[1], 10);
- } else if (line.startsWith('Size=')) {
- total = parseInt(line.split('=')[1], 10);
- }
- });
- if (!isNaN(total) && !isNaN(free) && total > 0) {
- const used = total - free;
- const usedPercentage = Math.round((used / total) * 100);
- diskResult = {
- total: formatBytes(total), // 可能出错
- free: formatBytes(free), // 可能出错
- used: formatBytes(used), // 可能出错
- usedPercentage: usedPercentage
- };
- logger.info('Successfully parsed disk info (Windows - C:).');
- } else {
- logger.warn('Failed to parse numbers from wmic output:', stdout);
- diskResult = { ...diskResult, error: '解析 wmic 输出失败' }; // 添加错误标记
- }
- } catch (wmicError) {
- logger.error(`Error executing or parsing 'wmic': ${wmicError.message}`);
- if (wmicError.killed) logger.error("'wmic' command timed out.");
- diskResult = { error: '获取磁盘信息失败 (wmic)', message: wmicError.message }; // 标记错误
- }
- }
- diskInfoData = diskResult;
- } catch (diskErrorOuter) {
- logger.error('Unexpected error during disk info gathering:', diskErrorOuter.message);
- diskInfoData = { error: '获取磁盘信息时发生意外错误', message: diskErrorOuter.message }; // 返回错误信息
- }
-
- // --- 获取其他系统信息 (独立 try...catch) ---
- try {
- systemData = {
- platform: os.platform(),
- release: os.release(),
- hostname: os.hostname(),
- uptime: formatUptime(os.uptime()) // 可能出错
- };
- logger.info('Successfully retrieved general system info.');
- } catch (sysInfoError) {
- logger.error('Error getting general system info:', sysInfoError.message);
- systemData = { error: '获取常规系统信息失败', message: sysInfoError.message }; // 返回错误信息
- }
- // --- 包装 Helper 函数调用以捕获潜在错误 ---
- const safeFormatBytes = (bytes) => {
- try {
- return formatBytes(bytes);
- } catch (e) {
- logger.error(`formatBytes failed for value ${bytes}:`, e.message);
- return '格式化错误';
- }
- };
- const safeFormatUptime = (seconds) => {
- try {
- return formatUptime(seconds);
- } catch (e) {
- logger.error(`formatUptime failed for value ${seconds}:`, e.message);
- return '格式化错误';
- }
- };
- // --- 构建最终响应数据,使用安全的 Helper 函数 ---
- const finalCpuData = cpuInfoData?.error ? cpuInfoData : {
- ...cpuInfoData
- // CPU 不需要格式化
- };
- const finalMemoryData = memoryData?.error ? memoryData : {
- ...memoryData,
- total: safeFormatBytes(os.totalmem()),
- free: safeFormatBytes(os.freemem()),
- used: safeFormatBytes(os.totalmem() - os.freemem())
- };
- const finalDiskData = diskInfoData?.error ? diskInfoData : {
- ...diskInfoData,
- // 如果 diskInfoData 内部有 total/free/used (字节数),则格式化
- // 否则保持 '未知' 或已格式化的字符串
- total: (diskInfoData?.total && typeof diskInfoData.total === 'number') ? safeFormatBytes(diskInfoData.total) : diskInfoData?.total || '未知',
- free: (diskInfoData?.free && typeof diskInfoData.free === 'number') ? safeFormatBytes(diskInfoData.free) : diskInfoData?.free || '未知',
- used: (diskInfoData?.used && typeof diskInfoData.used === 'number') ? safeFormatBytes(diskInfoData.used) : diskInfoData?.used || '未知'
- };
- const finalSystemData = systemData?.error ? systemData : {
- ...systemData,
- uptime: safeFormatUptime(os.uptime())
- };
- const responseData = {
- cpu: finalCpuData,
- memory: finalMemoryData,
- diskSpace: finalDiskData,
- system: finalSystemData
- };
-
- logger.info('Sending response for /api/system-resources:', JSON.stringify(responseData));
- res.status(200).json(responseData);
- });
- // 格式化系统运行时间
- 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);
-
- let result = '';
- if (days > 0) result += `${days}天 `;
- if (hours > 0 || days > 0) result += `${hours}小时 `;
- if (minutes > 0 || hours > 0 || days > 0) result += `${minutes}分钟 `;
- result += `${seconds}秒`;
-
- return result;
- }
- // 获取系统资源详情
- router.get('/system-resource-details', requireLogin, async (req, res) => {
- try {
- const { type } = req.query;
-
- let data = {};
-
- switch (type) {
- case 'memory':
- const totalMem = os.totalmem();
- const freeMem = os.freemem();
- const usedMem = totalMem - freeMem;
-
- data = {
- totalMemory: formatBytes(totalMem),
- usedMemory: formatBytes(usedMem),
- freeMemory: formatBytes(freeMem),
- memoryUsage: `${Math.round(usedMem / totalMem * 100)}%`
- };
- break;
-
- case 'cpu':
- const cpuInfo = os.cpus();
- const [load1, load5, load15] = os.loadavg();
-
- data = {
- cpuCores: cpuInfo.length,
- cpuModel: cpuInfo[0].model,
- cpuSpeed: `${cpuInfo[0].speed} MHz`,
- loadAvg1: load1.toFixed(2),
- loadAvg5: load5.toFixed(2),
- loadAvg15: load15.toFixed(2),
- cpuLoad: `${(load1 / cpuInfo.length * 100).toFixed(1)}%`
- };
- break;
-
- case 'disk':
- try {
- const { stdout: dfOutput } = await execPromise('df -h / | tail -n 1');
- const parts = dfOutput.trim().split(/\s+/);
-
- if (parts.length >= 5) {
- data = {
- totalSpace: parts[1],
- usedSpace: parts[2],
- freeSpace: parts[3],
- diskUsage: parts[4]
- };
- } else {
- throw new Error('解析磁盘信息失败');
- }
- } catch (diskError) {
- logger.warn('获取磁盘信息失败:', diskError.message);
- data = {
- error: '获取磁盘信息失败',
- message: diskError.message
- };
- }
- break;
-
- default:
- return res.status(400).json({ error: '无效的资源类型' });
- }
-
- res.json(data);
- } catch (error) {
- logger.error('获取系统资源详情失败:', error);
- res.status(500).json({ error: '获取系统资源详情失败', message: error.message });
- }
- });
- // 格式化字节数为可读格式
- function formatBytes(bytes, decimals = 2) {
- if (bytes === 0) return '0 Bytes';
-
- const k = 1024;
- const dm = decimals < 0 ? 0 : decimals;
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
-
- const i = Math.floor(Math.log(bytes) / Math.log(k));
-
- return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
- }
- module.exports = router; // 只导出 router
|