MVP-0: Field Console + Health Dashboard
- 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
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user