widgettdc-api / apps /widget-board /widgets /SystemMonitorWidget.tsx
Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
import React, { useEffect, useState } from 'react';
interface Process {
name: string;
cpu: number;
mem: number;
pid: number;
}
interface SystemInfo {
cpu: {
manufacturer: string;
brand: string;
cores: number;
physicalCores: number;
speed: number;
temperature: number | null;
};
memory: {
total: number;
used: number;
available: number;
usedPercent: number;
};
load: {
avgLoad: number;
currentLoad: number;
currentLoadUser: number;
currentLoadSystem: number;
};
}
const SystemMonitorWidget: React.FC<{ widgetId: string }> = () => {
const [processes, setProcesses] = useState<Process[]>([]);
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = async () => {
try {
const [processesRes, systemRes] = await Promise.all([
fetch('http://localhost:3001/api/sys/processes'),
fetch('http://localhost:3001/api/sys/system')
]);
if (!processesRes.ok || !systemRes.ok) {
throw new Error('Failed to fetch system data');
}
const processesData = await processesRes.json();
const systemData = await systemRes.json();
setProcesses(processesData);
setSystemInfo(systemData);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 2000); // Update every 2 seconds
return () => clearInterval(interval);
}, []);
const formatBytes = (bytes: number) => {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 Bytes';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
};
if (loading) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-gray-500 dark:text-gray-400">Loading system data...</div>
</div>
);
}
if (error) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-red-500 text-sm text-center">
<div className="mb-2">Failed to load system data</div>
<div className="text-xs text-gray-500">{error}</div>
</div>
</div>
);
}
return (
<div className="h-full flex flex-col -m-4">
<div className="p-4 flex flex-col gap-4">
{/* System Overview */}
<div className="grid grid-cols-2 gap-3">
<div className="bg-gray-100 dark:bg-gray-700 rounded-lg p-3">
<div className="text-xs text-gray-500 dark:text-gray-400 mb-1">CPU</div>
<div className="text-lg font-bold text-blue-600 dark:text-blue-400">
{systemInfo?.load.currentLoad.toFixed(1)}%
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{systemInfo?.cpu.temperature ? `${systemInfo.cpu.temperature}°C` : 'No temp sensor'}
</div>
</div>
<div className="bg-gray-100 dark:bg-gray-700 rounded-lg p-3">
<div className="text-xs text-gray-500 dark:text-gray-400 mb-1">Memory</div>
<div className="text-lg font-bold text-green-600 dark:text-green-400">
{systemInfo?.memory.usedPercent.toFixed(1)}%
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{formatBytes(systemInfo?.memory.used || 0)} / {formatBytes(systemInfo?.memory.total || 0)}
</div>
</div>
</div>
{/* Top Processes */}
<div className="flex flex-col gap-2">
<div className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
Top Processes
</div>
{/* Header */}
<div className="flex justify-between text-xs text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-600 pb-1 mb-1">
<span className="w-2/3">PROCESS</span>
<span className="w-1/3 text-right">CPU %</span>
</div>
{/* Process List */}
<div className="flex flex-col gap-1 max-h-48 overflow-y-auto">
{processes.map((process, index) => (
<div
key={`${process.pid}-${index}`}
className="flex justify-between items-center text-xs hover:bg-gray-50 dark:hover:bg-gray-700 p-2 rounded cursor-pointer transition-colors"
>
<div className="flex-1 truncate mr-2 text-gray-800 dark:text-gray-200 font-mono">
{process.name}
</div>
<div className={`font-bold text-right min-w-[40px] ${
process.cpu > 10
? 'text-red-500 dark:text-red-400'
: process.cpu > 5
? 'text-yellow-500 dark:text-yellow-400'
: 'text-green-500 dark:text-green-400'
}`}>
{process.cpu}%
</div>
</div>
))}
</div>
</div>
{/* System Details */}
{systemInfo && (
<div className="text-xs text-gray-500 dark:text-gray-400 border-t border-gray-200 dark:border-gray-600 pt-3">
<div className="grid grid-cols-2 gap-2">
<div>CPU: {systemInfo.cpu.brand}</div>
<div>Cores: {systemInfo.cpu.cores} ({systemInfo.cpu.physicalCores} physical)</div>
<div>Load Avg: {systemInfo.load.avgLoad.toFixed(2)}</div>
<div>Memory: {formatBytes(systemInfo.memory.available)} free</div>
</div>
</div>
)}
</div>
</div>
);
};
export default SystemMonitorWidget;