phase 16: React dashboard with full platform control and analytics

This commit is contained in:
Celes Renata
2026-04-11 16:19:46 -07:00
parent 25e0e386b7
commit faccb0b8db
53 changed files with 7924 additions and 13 deletions
+111
View File
@@ -0,0 +1,111 @@
import { useState } from 'react';
import { useIngestionThroughput, useIngestionSummary } from '../api/hooks';
import { LoadingSpinner, DateRangeSelector, Card } from '../components/ui';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend } from 'recharts';
export function OpsIngestionPage() {
const [hours, setHours] = useState(24);
const [bucket, setBucket] = useState('1h');
const { data: throughput, isLoading: tpLoading } = useIngestionThroughput(hours, bucket);
const { data: summary } = useIngestionSummary(hours);
if (tpLoading) return <LoadingSpinner />;
const chartData = (throughput ?? []).map((row: unknown) => {
const r = row as Record<string, unknown>;
return {
time: r.bucket_start ? new Date(r.bucket_start as string).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '',
completed: Number(r.completed ?? 0),
failed: Number(r.failed ?? 0),
items: Number(r.items_fetched ?? 0),
};
});
const s = (summary ?? {}) as Record<string, unknown>;
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-xl font-semibold text-gray-100">Ingestion Monitor</h1>
<div className="flex items-center gap-3">
<DateRangeSelector value={hours} onChange={setHours} />
<div className="inline-flex rounded-md border border-surface-700" role="group">
{['15m', '1h', '6h', '1d'].map((b) => (
<button
key={b}
onClick={() => setBucket(b)}
className={`px-2 py-1 text-xs font-medium first:rounded-l-md last:rounded-r-md ${bucket === b ? 'bg-brand-600 text-white' : 'bg-surface-900 text-gray-400 hover:bg-surface-800'}`}
>
{b}
</button>
))}
</div>
</div>
</div>
{/* Summary stats */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-5">
<StatCard label="Total Runs" value={s.total_runs} />
<StatCard label="Completed" value={s.completed} color="text-green-400" />
<StatCard label="Failed" value={s.failed} color="text-red-400" />
<StatCard label="Items Fetched" value={s.total_items_fetched} />
<StatCard label="New Items" value={s.total_items_new} />
</div>
{/* Throughput chart */}
<Card>
<h2 className="mb-3 text-sm font-medium text-gray-400">Throughput</h2>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={chartData}>
<XAxis dataKey="time" tick={{ fill: '#6b7280', fontSize: 11 }} />
<YAxis tick={{ fill: '#6b7280', fontSize: 11 }} />
<Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: 8 }} labelStyle={{ color: '#9ca3af' }} />
<Legend />
<Bar dataKey="completed" fill="#22c55e" name="Completed" />
<Bar dataKey="failed" fill="#ef4444" name="Failed" />
</BarChart>
</ResponsiveContainer>
</Card>
{/* By source type */}
{s.by_source_type && (
<Card>
<h2 className="mb-3 text-sm font-medium text-gray-400">By Source Type</h2>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-surface-700 text-left text-gray-500">
<th className="px-3 py-2">Type</th>
<th className="px-3 py-2">Runs</th>
<th className="px-3 py-2">Completed</th>
<th className="px-3 py-2">Failed</th>
<th className="px-3 py-2">Items</th>
</tr>
</thead>
<tbody>
{(s.by_source_type as Array<Record<string, unknown>>).map((row, i) => (
<tr key={i} className="border-b border-surface-700/50">
<td className="px-3 py-2 text-gray-300">{row.source_type as string}</td>
<td className="px-3 py-2 text-gray-300">{String(row.runs)}</td>
<td className="px-3 py-2 text-green-400">{String(row.completed)}</td>
<td className="px-3 py-2 text-red-400">{String(row.failed)}</td>
<td className="px-3 py-2 text-gray-300">{String(row.items_fetched)}</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
)}
</div>
);
}
function StatCard({ label, value, color = 'text-gray-100' }: { label: string; value: unknown; color?: string }) {
return (
<Card className="text-center">
<div className={`text-xl font-bold ${color}`}>{value != null ? String(value) : '—'}</div>
<div className="text-xs text-gray-500">{label}</div>
</Card>
);
}