← Назад
/** * Health Check System for Dashboard * Monitors all projects and stores status history */ const fs = require('fs').promises; const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); const path = require('path'); const { exec } = require('child_process'); const util = require('util'); const execPromise = util.promisify(exec); const HEALTH_FILE = path.join(__dirname, '../data/health-status.json'); const HISTORY_HOURS = 24; // Keep 24 hours of history /** * Check piewell.com health */ async function checkPiewell() { const start = Date.now(); try { const response = await fetch('https://piewell.com/wp-json/', { timeout: 5000 }); const responseTime = Date.now() - start; if (response.ok) { return { project: 'piewell', status: 'healthy', responseTime, lastCheck: new Date().toISOString(), details: { url: 'https://piewell.com/wp-json/', httpStatus: response.status } }; } else { return { project: 'piewell', status: 'warning', responseTime, lastCheck: new Date().toISOString(), details: { url: 'https://piewell.com/wp-json/', httpStatus: response.status, error: `HTTP ${response.status}` } }; } } catch (error) { return { project: 'piewell', status: 'error', responseTime: Date.now() - start, lastCheck: new Date().toISOString(), details: { url: 'https://piewell.com/wp-json/', error: error.message } }; } } /** * Check futures-screener health */ async function checkFuturesScreener() { const start = Date.now(); try { // Check if process is running const { stdout } = await execPromise('pgrep -f "node.*futures-screener" | wc -l'); const processCount = parseInt(stdout.trim()); if (processCount === 0) { return { project: 'futures-screener', status: 'error', responseTime: Date.now() - start, lastCheck: new Date().toISOString(), details: { error: 'Process not running', processCount: 0 } }; } // Check HTTP endpoint const response = await fetch('http://localhost:3200/', { timeout: 3000 }); const responseTime = Date.now() - start; if (response.ok) { return { project: 'futures-screener', status: 'healthy', responseTime, lastCheck: new Date().toISOString(), details: { url: 'http://localhost:3200/', httpStatus: response.status, processCount } }; } else { return { project: 'futures-screener', status: 'warning', responseTime, lastCheck: new Date().toISOString(), details: { url: 'http://localhost:3200/', httpStatus: response.status, processCount } }; } } catch (error) { return { project: 'futures-screener', status: 'error', responseTime: Date.now() - start, lastCheck: new Date().toISOString(), details: { error: error.message } }; } } /** * Check OpenClaw gateway health */ async function checkOpenClaw() { const start = Date.now(); try { // Check if gateway process is running const { stdout } = await execPromise('pgrep -f "openclaw gateway" | wc -l'); const processCount = parseInt(stdout.trim()); // Check if port 18789 is listening const { stdout: portCheck } = await execPromise('ss -tlnp 2>/dev/null | grep 18789 | wc -l'); const portOpen = parseInt(portCheck.trim()) > 0; const responseTime = Date.now() - start; if (processCount > 0 && portOpen) { return { project: 'openclaw', status: 'healthy', responseTime, lastCheck: new Date().toISOString(), details: { port: 18789, portOpen, processCount } }; } else if (processCount > 0) { return { project: 'openclaw', status: 'warning', responseTime, lastCheck: new Date().toISOString(), details: { port: 18789, portOpen, processCount, warning: 'Process running but port not listening' } }; } else { return { project: 'openclaw', status: 'error', responseTime, lastCheck: new Date().toISOString(), details: { error: 'Gateway not running', processCount: 0 } }; } } catch (error) { return { project: 'openclaw', status: 'error', responseTime: Date.now() - start, lastCheck: new Date().toISOString(), details: { error: error.message } }; } } /** * Check dashboard backend health (self-check) */ async function checkDashboard() { const start = Date.now(); try { // Check if backend is responding (we're already running, so this is a self-check) const uptime = process.uptime(); const memoryUsage = process.memoryUsage(); const responseTime = Date.now() - start; // Consider healthy if uptime > 10s and memory < 500MB const isHealthy = uptime > 10 && (memoryUsage.rss / 1024 / 1024) < 500; return { project: 'dashboard', status: isHealthy ? 'healthy' : 'warning', responseTime, lastCheck: new Date().toISOString(), details: { uptime: Math.floor(uptime), memoryMB: Math.floor(memoryUsage.rss / 1024 / 1024), port: 3000 } }; } catch (error) { return { project: 'dashboard', status: 'error', responseTime: Date.now() - start, lastCheck: new Date().toISOString(), details: { error: error.message } }; } } /** * Check system health (CPU/RAM thresholds) */ async function checkSystem() { const start = Date.now(); try { const si = require('systeminformation'); const [cpu, mem, load] = await Promise.all([ si.cpu(), si.mem(), si.currentLoad() ]); const cpuLoad = load.currentLoad; const memoryPercent = (mem.used / mem.total) * 100; const responseTime = Date.now() - start; // Thresholds const cpuWarning = 80; const cpuError = 95; const memWarning = 85; const memError = 95; let status = 'healthy'; let warnings = []; if (cpuLoad > cpuError || memoryPercent > memError) { status = 'error'; if (cpuLoad > cpuError) warnings.push(`CPU at ${cpuLoad.toFixed(1)}%`); if (memoryPercent > memError) warnings.push(`Memory at ${memoryPercent.toFixed(1)}%`); } else if (cpuLoad > cpuWarning || memoryPercent > memWarning) { status = 'warning'; if (cpuLoad > cpuWarning) warnings.push(`CPU at ${cpuLoad.toFixed(1)}%`); if (memoryPercent > memWarning) warnings.push(`Memory at ${memoryPercent.toFixed(1)}%`); } return { project: 'system', status, responseTime, lastCheck: new Date().toISOString(), details: { cpu: { load: cpuLoad.toFixed(1) + '%', cores: cpu.cores }, memory: { used: (mem.used / 1024 / 1024 / 1024).toFixed(2) + ' GB', total: (mem.total / 1024 / 1024 / 1024).toFixed(2) + ' GB', percent: memoryPercent.toFixed(1) + '%' }, warnings } }; } catch (error) { return { project: 'system', status: 'error', responseTime: Date.now() - start, lastCheck: new Date().toISOString(), details: { error: error.message } }; } } /** * Run all health checks */ async function runHealthChecks() { console.log('[Health Check] Running checks at', new Date().toISOString()); const checks = await Promise.allSettled([ checkPiewell(), checkFuturesScreener(), checkOpenClaw(), checkDashboard(), checkSystem() ]); const results = checks.map((check, idx) => { if (check.status === 'fulfilled') { return check.value; } else { const projects = ['piewell', 'futures-screener', 'openclaw', 'dashboard', 'system']; return { project: projects[idx], status: 'error', responseTime: 0, lastCheck: new Date().toISOString(), details: { error: check.reason?.message || 'Health check failed' } }; } }); // Log results results.forEach(result => { const emoji = result.status === 'healthy' ? '🟢' : result.status === 'warning' ? '🟡' : '🔴'; console.log(`[Health Check] ${emoji} ${result.project}: ${result.status} (${result.responseTime}ms)`); }); return results; } /** * Load health history from file */ async function loadHealthHistory() { try { const data = await fs.readFile(HEALTH_FILE, 'utf8'); return JSON.parse(data); } catch (error) { // File doesn't exist or is corrupted, return empty history return { history: [], lastUpdate: null }; } } /** * Save health history to file */ async function saveHealthHistory(history) { try { // Ensure data directory exists const dataDir = path.dirname(HEALTH_FILE); await fs.mkdir(dataDir, { recursive: true }); // Clean up old history (keep last 24 hours) const cutoffTime = Date.now() - (HISTORY_HOURS * 60 * 60 * 1000); const cleanedHistory = history.filter(entry => { return new Date(entry.timestamp).getTime() > cutoffTime; }); const data = { history: cleanedHistory, lastUpdate: new Date().toISOString() }; await fs.writeFile(HEALTH_FILE, JSON.stringify(data, null, 2)); console.log(`[Health Check] Saved ${cleanedHistory.length} history entries`); return true; } catch (error) { console.error('[Health Check] Failed to save history:', error.message); return false; } } /** * Store health check results */ async function storeHealthResults(results) { const historyData = await loadHealthHistory(); // Add new entry const entry = { timestamp: new Date().toISOString(), checks: results }; historyData.history.push(entry); await saveHealthHistory(historyData.history); } /** * Get latest health status */ async function getLatestHealth() { const historyData = await loadHealthHistory(); if (historyData.history.length === 0) { return { status: 'no_data', message: 'No health checks run yet', checks: [] }; } const latest = historyData.history[historyData.history.length - 1]; return { status: 'ok', timestamp: latest.timestamp, checks: latest.checks }; } /** * Get health history for a specific project */ async function getProjectHistory(projectName, hours = 24) { const historyData = await loadHealthHistory(); const cutoffTime = Date.now() - (hours * 60 * 60 * 1000); const projectHistory = historyData.history .filter(entry => new Date(entry.timestamp).getTime() > cutoffTime) .map(entry => { const projectCheck = entry.checks.find(c => c.project === projectName); return { timestamp: entry.timestamp, ...projectCheck }; }) .filter(entry => entry.status); // Filter out undefined entries return projectHistory; } module.exports = { runHealthChecks, storeHealthResults, getLatestHealth, getProjectHistory, checkPiewell, checkFuturesScreener, checkOpenClaw, checkDashboard, checkSystem };