e669155bc9
- Field Console: 4 tabs (Upload, Queue, Artifact Viewer, Timeline) - Health Dashboard: system pulse for 6 engines - No more ADR documents until MVP-0 proven with real mission Stoppregel: Ingen ny arkitektur förrän första verkliga uppdraget. Next: Pilot 001 — First real field upload
107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
/**
|
|
* Health Dashboard
|
|
*
|
|
* System pulse. Not BI. Not statistics.
|
|
* Just: which engines are running?
|
|
*/
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
interface EngineStatus {
|
|
name: string;
|
|
status: 'running' | 'stopped' | 'error';
|
|
lastHeartbeat: string;
|
|
}
|
|
|
|
function HealthDashboard() {
|
|
const [engines, setEngines] = useState<EngineStatus[]>([
|
|
{ name: 'Reality', status: 'running', lastHeartbeat: new Date().toISOString() },
|
|
{ name: 'Knowledge', status: 'running', lastHeartbeat: new Date().toISOString() },
|
|
{ name: 'Decision', status: 'stopped', lastHeartbeat: '-' },
|
|
{ name: 'Mission', status: 'stopped', lastHeartbeat: '-' },
|
|
{ name: 'Economic', status: 'running', lastHeartbeat: new Date().toISOString() },
|
|
{ name: 'Learning', status: 'stopped', lastHeartbeat: '-' },
|
|
]);
|
|
|
|
useEffect(() => {
|
|
// Poll health status
|
|
const interval = setInterval(() => {
|
|
fetch('/health')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
// Update engine status based on health response
|
|
setEngines(prev => prev.map(engine => ({
|
|
...engine,
|
|
status: data.status === 'ok' ? 'running' : 'error',
|
|
lastHeartbeat: new Date().toISOString(),
|
|
})));
|
|
})
|
|
.catch(() => {
|
|
setEngines(prev => prev.map(engine => ({
|
|
...engine,
|
|
status: 'error',
|
|
})));
|
|
});
|
|
}, 30000); // Every 30 seconds
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
return (
|
|
<div>
|
|
<h2>Health Dashboard</h2>
|
|
<p style={{ color: '#666', marginBottom: 20 }}>System pulse — not statistics</p>
|
|
|
|
<div style={{ display: 'grid', gap: 16 }}>
|
|
{engines.map(engine => (
|
|
<div key={engine.name} style={{
|
|
border: '1px solid #ccc',
|
|
padding: 16,
|
|
borderRadius: 8,
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
}}>
|
|
<div>
|
|
<h3 style={{ margin: 0 }}>{engine.name} Engine</h3>
|
|
<p style={{ margin: '4px 0 0', color: '#666', fontSize: 12 }}>
|
|
Last heartbeat: {engine.lastHeartbeat === '-' ? '-' : new Date(engine.lastHeartbeat).toLocaleTimeString()}
|
|
</p>
|
|
</div>
|
|
<StatusIndicator status={engine.status} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function StatusIndicator({ status }: { status: 'running' | 'stopped' | 'error' }) {
|
|
const colors = {
|
|
running: '#32cd32',
|
|
stopped: '#ccc',
|
|
error: '#dc143c',
|
|
};
|
|
|
|
const labels = {
|
|
running: '✓ Running',
|
|
stopped: '○ Stopped',
|
|
error: '✗ Error',
|
|
};
|
|
|
|
return (
|
|
<span style={{
|
|
padding: '8px 16px',
|
|
borderRadius: 16,
|
|
background: colors[status],
|
|
color: 'white',
|
|
fontWeight: 'bold',
|
|
fontSize: 14,
|
|
}}>
|
|
{labels[status]}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export default HealthDashboard;
|