docs: enterprise refactor plan with ralph specs
This commit is contained in:
91
frontend/src/pages/Dashboard.tsx
Normal file
91
frontend/src/pages/Dashboard.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { api } from '../hooks/useApi';
|
||||
import { useSocket } from '../hooks/useSocket';
|
||||
import { NewSessionForm } from '../components/NewSessionForm';
|
||||
import { SessionList } from '../components/SessionList';
|
||||
import { AnomalyList } from '../components/AnomalyList';
|
||||
import type { Session, AnomalySummary, Stats } from '../types';
|
||||
|
||||
export function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const [sessions, setSessions] = useState<Session[]>([]);
|
||||
const [anomalies, setAnomalies] = useState<AnomalySummary[]>([]);
|
||||
const [stats, setStats] = useState<Stats | null>(null);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
const [s, a, st] = await Promise.all([api.getSessions(), api.getAnomalies(), api.getStats()]);
|
||||
setSessions(s);
|
||||
setAnomalies(a);
|
||||
setStats(st);
|
||||
}, []);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
|
||||
useSocket(useCallback((event, _data) => {
|
||||
if (['session:started', 'session:completed', 'session:error', 'anomaly:detected'].includes(event)) {
|
||||
load();
|
||||
}
|
||||
}, [load]));
|
||||
|
||||
function handleCreated(sessionId: string) {
|
||||
setShowForm(false);
|
||||
navigate(`/sessions/${sessionId}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto px-4 py-8 space-y-8">
|
||||
<header className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">ABE Dashboard</h1>
|
||||
<p className="text-gray-400 text-sm mt-1">Autonomous Bug Explorer</p>
|
||||
</div>
|
||||
<div className="flex gap-3 items-center">
|
||||
<Link to="/settings" className="text-gray-400 hover:text-white text-sm transition-colors">Settings</Link>
|
||||
<button
|
||||
onClick={() => setShowForm((v) => !v)}
|
||||
className="bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium px-4 py-2 rounded transition-colors"
|
||||
>
|
||||
{showForm ? 'Cancel' : '+ New Exploration'}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Stats Bar */}
|
||||
{stats && (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||
<div className="bg-gray-800 rounded-lg p-4 text-center">
|
||||
<p className="text-2xl font-bold text-white">{stats.totalSessions}</p>
|
||||
<p className="text-gray-400 text-xs mt-1">Total Sessions</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 rounded-lg p-4 text-center">
|
||||
<p className="text-2xl font-bold text-white">{stats.totalAnomalies}</p>
|
||||
<p className="text-gray-400 text-xs mt-1">Total Anomalies</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 rounded-lg p-4 text-center">
|
||||
<p className={`text-2xl font-bold ${stats.criticalHighCount > 0 ? 'text-red-400' : 'text-white'}`}>
|
||||
{stats.criticalHighCount}
|
||||
</p>
|
||||
<p className="text-gray-400 text-xs mt-1">Critical / High</p>
|
||||
</div>
|
||||
<div className="bg-gray-800 rounded-lg p-4 text-center">
|
||||
<p className={`text-2xl font-bold ${stats.runningSessions > 0 ? 'text-green-400' : 'text-white'}`}>
|
||||
{stats.runningSessions}
|
||||
</p>
|
||||
<p className="text-gray-400 text-xs mt-1">Running Now</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showForm && <NewSessionForm onCreated={handleCreated} />}
|
||||
|
||||
<section>
|
||||
<h2 className="text-base font-semibold text-gray-300 mb-3">Sessions</h2>
|
||||
<SessionList sessions={sessions} />
|
||||
</section>
|
||||
|
||||
<AnomalyList anomalies={anomalies} title="Recent Anomalies" sessions={sessions} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user