← back to Omega Watches 2
src/app/(dashboard)/page.tsx
429 lines
"use client";
import { useEffect, useState } from "react";
import { api } from "@/lib/api-client";
import { formatDateTime, formatCurrency } from "@/lib/utils";
import { CollectionPriceChart, MarketActivityChart, TopMoversChart, SourceHealthDonut } from "@/components/charts";
interface HealthData {
status: string;
version: string;
uptime_seconds: number;
database: { ok: boolean; latency: number; connections: { total: number; idle: number; waiting: number } };
timestamp: string;
}
function StatCard({ label, value, sub, color = "text-gray-100" }: { label: string; value: string | number; sub?: string; color?: string }) {
return (
<div className="card border-navy-700">
<div className="text-xs uppercase tracking-wider text-gray-500">{label}</div>
<div className={`mt-1 text-2xl font-bold ${color}`}>{value}</div>
{sub && <div className="mt-1 text-xs text-gray-500">{sub}</div>}
</div>
);
}
export default function SystemHealthPage() {
const [health, setHealth] = useState<HealthData | null>(null);
const [sources, setSources] = useState<any[]>([]);
const [summary, setSummary] = useState<any>(null);
const [dashboard, setDashboard] = useState<any>(null);
const [chartData, setChartData] = useState<any>(null);
const [refreshing, setRefreshing] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
async function load() {
try {
const token = localStorage.getItem("omega_token");
const headers = { Authorization: `Bearer ${token}` };
const [h, s, refRes, dashRes, chartRes] = await Promise.all([
api.health(),
api.getSources(),
fetch("/api/references", { headers }).then((r) => r.json()),
fetch("/api/dashboard", { headers }).then((r) => r.json()),
api.getDashboardChartData().catch(() => null),
]);
setHealth(h);
setSources(s);
if (refRes.success) setSummary(refRes.summary);
if (dashRes.success) setDashboard(dashRes.data);
if (chartRes) setChartData(chartRes);
} catch (e: any) {
setError(e.message);
}
}
load();
const interval = setInterval(load, 30000);
return () => clearInterval(interval);
}, []);
async function refreshViews() {
setRefreshing(true);
try {
const token = localStorage.getItem("omega_token");
await fetch("/api/admin/refresh-views", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
});
} catch {}
setRefreshing(false);
}
if (error) {
return <div className="rounded bg-red-900/30 p-4 text-red-300">{error}</div>;
}
const uptimeMin = health ? Math.round(health.uptime_seconds / 60) : 0;
const uptimeStr = uptimeMin >= 60 ? `${Math.floor(uptimeMin / 60)}h ${uptimeMin % 60}m` : `${uptimeMin}m`;
const activeSources = sources.filter((s: any) => s.is_active).length;
const totalRuns = sources.reduce((acc: number, s: any) => acc + (parseInt(s.total_runs) || 0), 0);
const failedRuns = sources.reduce((acc: number, s: any) => acc + (parseInt(s.failed_runs) || 0), 0);
const totalRecords = sources.reduce((acc: number, s: any) => acc + (parseInt(s.total_records_inserted) || 0), 0);
const s24 = dashboard?.stats_24h;
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold text-gray-100">Omega Watches 2.0 — Control Plane</h1>
<div className="flex items-center gap-3">
<button
onClick={refreshViews}
disabled={refreshing}
className="btn-secondary text-xs disabled:opacity-50"
>
{refreshing ? "Refreshing..." : "Refresh Views"}
</button>
<span className={`badge ${health?.status === "healthy" ? "badge-success" : "badge-warning"}`}>
{health?.status || "loading..."}
</span>
</div>
</div>
{/* Primary KPIs */}
<div className="grid grid-cols-2 gap-4 md:grid-cols-5">
<StatCard
label="Watch References"
value={summary?.total_references || "—"}
sub="Across all collections"
color="text-omega-400"
/>
<StatCard
label="Market Events"
value={summary?.total_events || "—"}
sub="Auctions, sales, asks"
color="text-blue-400"
/>
<StatCard
label="MSRP Snapshots"
value={summary?.total_snapshots || "—"}
sub="Daily price observations"
color="text-green-400"
/>
<StatCard
label="Active Sources"
value={activeSources}
sub={`${sources.length} total configured`}
/>
<StatCard
label="Collector Runs"
value={totalRuns}
sub={failedRuns > 0 ? `${failedRuns} failed` : "All healthy"}
color={failedRuns > 0 ? "text-yellow-400" : "text-gray-100"}
/>
</div>
{/* 24-Hour Stats + System Health */}
<div className="grid gap-4 md:grid-cols-4">
<StatCard
label="Database"
value={health?.database.ok ? "Connected" : "Down"}
sub={health ? `${health.database.latency}ms latency` : ""}
color={health?.database.ok ? "text-green-400" : "text-red-400"}
/>
<StatCard label="Uptime" value={uptimeStr} sub={health ? formatDateTime(health.timestamp) : ""} />
<StatCard
label="24h Inserted"
value={s24?.inserted_24h || 0}
sub={s24 ? `${s24.runs_24h} runs (${s24.failed_24h} failed)` : ""}
color="text-blue-400"
/>
<StatCard
label="24h Quarantined"
value={s24?.quarantined_24h || 0}
sub="Flagged for review"
color={parseInt(s24?.quarantined_24h || "0") > 0 ? "text-yellow-400" : "text-green-400"}
/>
</div>
{/* Charts Grid */}
<div className="grid gap-4 md:grid-cols-2">
<CollectionPriceChart
data={(dashboard?.collection_summary || [])
.filter((c: any) => c.avg_market_price)
.map((c: any) => ({
collection: c.collection,
avg: parseFloat(c.avg_market_price),
min: parseFloat(c.min_price),
max: parseFloat(c.max_price),
count: parseInt(c.event_count),
}))}
title="Collection Price Comparison"
/>
<MarketActivityChart
data={chartData?.daily_activity || []}
title="Market Activity (30 Days)"
/>
<TopMoversChart
data={(dashboard?.top_movers || []).map((m: any) => ({
label: m.reference_number,
collection: m.collection,
model: m.model_name,
stddev: parseFloat(m.price_stddev) || 0,
avg_price: parseFloat(m.avg_price),
events: parseInt(m.events_30d),
}))}
title="Top Price Movers (30d Volatility)"
/>
<SourceHealthDonut
data={chartData?.source_health || []}
title="Source Health Overview"
/>
</div>
{/* Collection Price Summary + Recent Activity */}
<div className="grid gap-4 md:grid-cols-2">
{/* Collection Summary */}
<div className="card border-navy-700">
<h2 className="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400">
Collection Price Summary
</h2>
<div className="space-y-2">
{(dashboard?.collection_summary || []).map((c: any) => (
<div key={c.collection} className="flex items-center justify-between rounded bg-navy-900 p-3">
<div>
<div className="text-sm font-medium text-gray-200">{c.collection}</div>
<div className="text-xs text-gray-500">
{c.ref_count} refs · {c.event_count} events
</div>
</div>
<div className="text-right">
{c.avg_market_price ? (
<>
<div className="text-sm font-bold text-omega-400">
{formatCurrency(parseFloat(c.avg_market_price))}
</div>
<div className="text-xs text-gray-500">
{formatCurrency(parseFloat(c.min_price))} – {formatCurrency(parseFloat(c.max_price))}
</div>
</>
) : (
<span className="text-xs text-gray-500">No market data</span>
)}
</div>
</div>
))}
{(!dashboard?.collection_summary || dashboard.collection_summary.length === 0) && (
<div className="p-4 text-center text-sm text-gray-500">No collection data yet</div>
)}
</div>
</div>
{/* Recent Activity */}
<div className="card border-navy-700">
<h2 className="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400">
Recent Collector Runs
</h2>
<div className="space-y-2">
{(dashboard?.recent_runs || []).slice(0, 8).map((r: any) => (
<div key={r.run_id} className="flex items-center justify-between rounded bg-navy-900 p-2">
<div className="flex items-center gap-2">
<span className={`badge text-xs ${
r.status === "completed" ? "badge-success" :
r.status === "running" ? "badge-info" :
r.status === "failed" ? "badge-error" : "badge-warning"
}`}>
{r.status}
</span>
<div>
<span className="text-sm text-gray-200">{r.source_name}</span>
<span className="ml-2 text-xs text-gray-500">{r.job_type}</span>
</div>
</div>
<div className="text-right">
<div className="text-xs text-gray-400">
+{r.records_inserted || 0} records
{r.records_quarantined > 0 && (
<span className="ml-1 text-yellow-400">({r.records_quarantined} flagged)</span>
)}
</div>
<div className="text-xs text-gray-600">
{r.started_at ? formatDateTime(r.started_at) : "—"}
</div>
</div>
</div>
))}
{(!dashboard?.recent_runs || dashboard.recent_runs.length === 0) && (
<div className="p-4 text-center text-sm text-gray-500">No runs yet</div>
)}
</div>
</div>
</div>
{/* Top Movers + Quarantine Alerts */}
<div className="grid gap-4 md:grid-cols-2">
{/* Top Movers */}
<div className="card border-navy-700">
<h2 className="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400">
Top Price Movers (30d)
</h2>
<div className="space-y-2">
{(dashboard?.top_movers || []).map((m: any) => (
<div key={m.reference_number} className="flex items-center justify-between rounded bg-navy-900 p-3">
<div>
<div className="font-mono text-sm text-omega-400">{m.reference_number}</div>
<div className="text-xs text-gray-500">{m.collection} — {m.model_name}</div>
</div>
<div className="text-right">
<div className="text-sm text-gray-200">{formatCurrency(parseFloat(m.avg_price))}</div>
<div className="text-xs text-gray-500">
{m.events_30d} events · {m.price_stddev ? `\u00B1${formatCurrency(parseFloat(m.price_stddev))}` : "—"}
</div>
</div>
</div>
))}
{(!dashboard?.top_movers || dashboard.top_movers.length === 0) && (
<div className="p-4 text-center text-sm text-gray-500">Not enough data for variance analysis</div>
)}
</div>
</div>
{/* Quarantine Alerts */}
<div className="card border-navy-700">
<h2 className="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400">
Quarantine Alerts
</h2>
<div className="space-y-2">
{(dashboard?.recent_alerts || []).map((a: any) => (
<div key={a.id} className="flex items-center justify-between rounded bg-navy-900 p-3">
<div className="flex items-center gap-2">
<span className={`badge text-xs ${
a.severity === "critical" ? "badge-error" :
a.severity === "high" ? "badge-warning" : "badge-info"
}`}>
{a.severity}
</span>
<div>
<div className="text-sm text-gray-200">{a.field_name}</div>
<div className="text-xs text-gray-500">
{a.reference_number_raw} · {a.source_name}
</div>
</div>
</div>
<div className="text-xs text-gray-600">
{a.created_at ? formatDateTime(a.created_at) : "—"}
</div>
</div>
))}
{(!dashboard?.recent_alerts || dashboard.recent_alerts.length === 0) && (
<div className="p-4 text-center text-sm text-gray-500">No unresolved alerts</div>
)}
</div>
</div>
</div>
{/* Source Catalog */}
<div className="card border-navy-700">
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400">
Source Catalog ({sources.length})
</h2>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-navy-700 text-left text-xs uppercase text-gray-500">
<th className="pb-2 pr-4">Source</th>
<th className="pb-2 pr-4">Type</th>
<th className="pb-2 pr-4">Access</th>
<th className="pb-2 pr-4">Reliability</th>
<th className="pb-2 pr-4">Runs</th>
<th className="pb-2 pr-4">Success</th>
<th className="pb-2 pr-4">Records</th>
<th className="pb-2 pr-4">ToS</th>
<th className="pb-2">Status</th>
</tr>
</thead>
<tbody>
{sources.map((s: any) => (
<tr key={s.id} className="border-b border-navy-800 hover:bg-navy-900">
<td className="py-2 pr-4 font-medium text-gray-200">{s.display_name}</td>
<td className="py-2 pr-4 text-gray-400">{s.source_type}</td>
<td className="py-2 pr-4">
<span className={`badge ${s.access_method === "api" ? "badge-success" : s.access_method === "manual" ? "badge-warning" : "badge-info"}`}>
{s.access_method}
</span>
</td>
<td className="py-2 pr-4">
<div className="flex gap-0.5">
{[1, 2, 3, 4, 5].map((n) => (
<span key={n} className={`inline-block h-2 w-2 rounded-full ${n <= s.reliability_score ? "bg-omega-400" : "bg-navy-700"}`} />
))}
</div>
</td>
<td className="py-2 pr-4 text-gray-400">{s.total_runs || 0}</td>
<td className="py-2 pr-4">
<span className={parseInt(s.success_rate_pct) >= 90 ? "text-green-400" : parseInt(s.success_rate_pct) >= 70 ? "text-yellow-400" : "text-red-400"}>
{s.success_rate_pct || 0}%
</span>
</td>
<td className="py-2 pr-4 text-gray-400">{s.total_records_inserted || 0}</td>
<td className="py-2 pr-4">
{s.tos_reviewed ? (
s.tos_allows_collection === false ? (
<span className="badge-error">Blocked</span>
) : (
<span className="badge-success">OK</span>
)
) : (
<span className="badge-warning">Pending</span>
)}
</td>
<td className="py-2">
<span className={`badge ${s.is_active ? "badge-success" : "badge-error"}`}>
{s.is_active ? "Active" : "Disabled"}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Database Connections */}
<div className="card border-navy-700">
<h2 className="mb-3 text-sm font-semibold uppercase tracking-wider text-gray-400">
Database Connections
</h2>
{health && (
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-2xl font-bold text-gray-200">{health.database.connections.total}</div>
<div className="text-xs text-gray-500">Total</div>
</div>
<div>
<div className="text-2xl font-bold text-green-400">{health.database.connections.idle}</div>
<div className="text-xs text-gray-500">Idle</div>
</div>
<div>
<div className="text-2xl font-bold text-yellow-400">{health.database.connections.waiting}</div>
<div className="text-xs text-gray-500">Waiting</div>
</div>
</div>
)}
</div>
</div>
);
}