← Назад/**
* 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
};