Files
crm-server/src/services/status.service.js
richardtekula 73a3c6bf95 hotfix: Security, performance, and code cleanup
- 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>
2026-01-19 07:17:23 +01:00

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(),
};
};