- Remove hardcoded database password fallback - Add encryption salt validation (min 32 chars) - Separate EMAIL_ENCRYPTION_KEY from JWT_SECRET - Fix command injection in status.service.js (use execFileSync) - Remove unnecessary SQL injection regex middleware - Create shared utilities (queryBuilder, pagination, emailAccountHelper) - Fix N+1 query problems in contact and todo services - Merge duplicate JMAP config functions - Add database indexes migration - Standardize error responses with error codes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
189 lines
4.5 KiB
JavaScript
189 lines
4.5 KiB
JavaScript
import os from 'os';
|
|
import path from 'path';
|
|
import { execFileSync } from 'child_process';
|
|
import fs from 'fs';
|
|
import { pool } from '../config/database.js';
|
|
|
|
/**
|
|
* Get CPU usage percentage
|
|
*/
|
|
const getCpuUsage = () => {
|
|
const cpus = os.cpus();
|
|
let totalIdle = 0;
|
|
let totalTick = 0;
|
|
|
|
cpus.forEach((cpu) => {
|
|
for (const type in cpu.times) {
|
|
totalTick += cpu.times[type];
|
|
}
|
|
totalIdle += cpu.times.idle;
|
|
});
|
|
|
|
const idle = totalIdle / cpus.length;
|
|
const total = totalTick / cpus.length;
|
|
const percent = Math.round(((total - idle) / total) * 100);
|
|
|
|
return { percent };
|
|
};
|
|
|
|
/**
|
|
* Get memory usage (Linux - reads /proc/meminfo for accurate values)
|
|
*/
|
|
const getMemoryUsage = () => {
|
|
try {
|
|
// Read /proc/meminfo for accurate memory stats (like htop)
|
|
const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
|
|
const lines = meminfo.split('\n');
|
|
|
|
const getValue = (key) => {
|
|
const line = lines.find(l => l.startsWith(key));
|
|
if (!line) return 0;
|
|
const match = line.match(/(\d+)/);
|
|
return match ? parseInt(match[1]) : 0;
|
|
};
|
|
|
|
const totalKB = getValue('MemTotal');
|
|
const freeKB = getValue('MemFree');
|
|
const buffersKB = getValue('Buffers');
|
|
const cachedKB = getValue('Cached');
|
|
const sreclaimableKB = getValue('SReclaimable');
|
|
|
|
// Available = Free + Buffers + Cached + SReclaimable (like htop calculates)
|
|
const availableKB = freeKB + buffersKB + cachedKB + sreclaimableKB;
|
|
const usedKB = totalKB - availableKB;
|
|
|
|
// Convert to GB
|
|
const totalGB = Math.round((totalKB / (1024 ** 2)) * 10) / 10;
|
|
const usedGB = Math.round((usedKB / (1024 ** 2)) * 10) / 10;
|
|
|
|
return {
|
|
used: usedGB,
|
|
total: totalGB,
|
|
unit: 'GB',
|
|
};
|
|
} catch {
|
|
// Fallback to os module
|
|
const totalBytes = os.totalmem();
|
|
const freeBytes = os.freemem();
|
|
const usedBytes = totalBytes - freeBytes;
|
|
|
|
return {
|
|
used: Math.round((usedBytes / (1024 ** 3)) * 10) / 10,
|
|
total: Math.round((totalBytes / (1024 ** 3)) * 10) / 10,
|
|
unit: 'GB',
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get disk usage (Linux/macOS)
|
|
*/
|
|
const getDiskUsage = () => {
|
|
try {
|
|
const output = execFileSync('df', ['-BG', '/'], { encoding: 'utf8' });
|
|
const lines = output.trim().split('\n');
|
|
const parts = lines[lines.length - 1].split(/\s+/);
|
|
|
|
const totalGB = parseInt(parts[1]) || 0;
|
|
const usedGB = parseInt(parts[2]) || 0;
|
|
|
|
return {
|
|
used: usedGB,
|
|
total: totalGB,
|
|
unit: 'GB',
|
|
};
|
|
} catch {
|
|
return { used: 0, total: 0, unit: 'GB' };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get Node.js backend process stats (CPU + RAM)
|
|
*/
|
|
const getBackendStats = () => {
|
|
const memUsage = process.memoryUsage();
|
|
|
|
// Get CPU usage for this process
|
|
const cpuUsage = process.cpuUsage();
|
|
const cpuPercent = Math.round(((cpuUsage.user + cpuUsage.system) / 1000000) * 100) / 100;
|
|
|
|
return {
|
|
memoryMB: Math.round((memUsage.rss / (1024 ** 2)) * 10) / 10,
|
|
heapMB: Math.round((memUsage.heapUsed / (1024 ** 2)) * 10) / 10,
|
|
cpuTime: cpuPercent, // CPU time in seconds
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get uploads directory size
|
|
*/
|
|
const getUploadsSize = () => {
|
|
try {
|
|
const uploadsPath = path.join(process.cwd(), 'uploads');
|
|
const output = execFileSync('du', ['-sm', uploadsPath], { encoding: 'utf8' });
|
|
const sizeMB = parseInt(output.split('\t')[0]) || 0;
|
|
|
|
return {
|
|
sizeMB,
|
|
unit: 'MB',
|
|
};
|
|
} catch {
|
|
return { sizeMB: 0, unit: 'MB' };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get database table count
|
|
*/
|
|
const getTableCount = async () => {
|
|
try {
|
|
const result = await pool.query(`
|
|
SELECT COUNT(*) as count
|
|
FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_type = 'BASE TABLE'
|
|
`);
|
|
return parseInt(result.rows[0]?.count) || 0;
|
|
} catch {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get system uptime in seconds
|
|
*/
|
|
const getSystemUptime = () => {
|
|
return Math.floor(os.uptime());
|
|
};
|
|
|
|
/**
|
|
* Get Node.js process uptime in seconds
|
|
*/
|
|
const getProcessUptime = () => {
|
|
return Math.floor(process.uptime());
|
|
};
|
|
|
|
/**
|
|
* Get complete server status
|
|
*/
|
|
export const getServerStatus = async () => {
|
|
const tableCount = await getTableCount();
|
|
|
|
return {
|
|
cpu: getCpuUsage(),
|
|
memory: getMemoryUsage(),
|
|
disk: getDiskUsage(),
|
|
backend: getBackendStats(),
|
|
uploads: getUploadsSize(),
|
|
database: {
|
|
tableCount,
|
|
},
|
|
systemUptimeSec: getSystemUptime(),
|
|
backendUptimeSec: getProcessUptime(),
|
|
hostname: os.hostname(),
|
|
platform: os.platform(),
|
|
nodeVersion: process.version,
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
};
|