feat: redesign assurance reports for production
This commit is contained in:
@@ -1,17 +1,20 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo, useState, type FormEvent, type ReactNode } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { api, h6ReportExportUrl, type H6Breakdown, type H6ReportQuery, type HarnessReportVerdict } from '../lib/api';
|
||||
import { Card, StatusBadge } from '../components/ui/Card';
|
||||
import {
|
||||
CoverageBar,
|
||||
ReportHero,
|
||||
ReportPanel,
|
||||
SignalMetric,
|
||||
reportActionPrimary,
|
||||
reportActionSecondary,
|
||||
reportTone,
|
||||
} from '../components/report/ReportPrimitives';
|
||||
import { StatusBadge } from '../components/ui/Card';
|
||||
import { api, h6ReportExportUrl, type H6Breakdown, type H6ReportQuery } from '../lib/api';
|
||||
|
||||
const money = (value: number): string => `$${value.toFixed(6)}`;
|
||||
const integer = (value: number): string => new Intl.NumberFormat('en-US').format(value);
|
||||
const verdictTone: Record<HarnessReportVerdict, string> = {
|
||||
pass: 'border-emerald-300 bg-emerald-50 text-emerald-900',
|
||||
attention: 'border-amber-300 bg-amber-50 text-amber-950',
|
||||
fail: 'border-rose-300 bg-rose-50 text-rose-950',
|
||||
no_data: 'border-slate-300 bg-slate-50 text-slate-800',
|
||||
};
|
||||
|
||||
function queryFromSearch(search: URLSearchParams): H6ReportQuery {
|
||||
return {
|
||||
@@ -24,31 +27,62 @@ function queryFromSearch(search: URLSearchParams): H6ReportQuery {
|
||||
}
|
||||
|
||||
function FilterField({ label, children }: { label: string; children: ReactNode }) {
|
||||
return <label className="block"><span className="mb-1.5 block text-[10px] font-bold uppercase tracking-[0.13em] text-slate-500">{label}</span>{children}</label>;
|
||||
}
|
||||
|
||||
function MetricCard({ label, value, sub, accent = false }: { label: string; value: string; sub?: string; accent?: boolean }) {
|
||||
return <div className={`relative overflow-hidden rounded-2xl border p-4 ${accent ? 'border-indigo-200 bg-indigo-50/70' : 'border-slate-200 bg-white'}`}>
|
||||
{accent && <span className="absolute -right-5 -top-5 h-16 w-16 rounded-full bg-indigo-200/50 blur-xl" />}
|
||||
<div className="relative text-[10px] font-bold uppercase tracking-[0.12em] text-slate-500">{label}</div>
|
||||
<div className={`relative mt-2 font-semibold tracking-tight text-slate-900 ${value.length > 9 ? 'text-base' : 'text-2xl'}`}>{value}</div>
|
||||
{sub && <div className="relative mt-1 text-xs text-slate-500">{sub}</div>}
|
||||
</div>;
|
||||
return (
|
||||
<label className="block">
|
||||
<span className="mb-2 block text-[10px] font-black uppercase tracking-[0.15em] text-slate-500">{label}</span>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function BreakdownTable({ rows, subject }: { rows: H6Breakdown[]; subject: string }) {
|
||||
return <div className="overflow-x-auto"><table className="w-full min-w-[680px] text-sm">
|
||||
<thead><tr className="border-b border-slate-200 text-left text-[10px] font-bold uppercase tracking-[0.1em] text-slate-400">
|
||||
<th className="py-2.5 pr-3">{subject}</th><th>runs</th><th>failures</th><th>avg latency</th><th>tokens</th><th>cost</th>
|
||||
</tr></thead>
|
||||
<tbody>{rows.map((row) => <tr key={row.key} className="border-b border-slate-100 transition-colors hover:bg-slate-50/80">
|
||||
<td className="max-w-[300px] py-3 pr-3 font-medium text-slate-800">{row.key}</td><td>{integer(row.runs)}</td><td>{integer(row.failures)}</td><td>{integer(row.latency_avg_ms)} ms</td><td>{row.tokens === null ? 'Unavailable' : integer(row.tokens)}</td><td>{row.cost_usd === null ? 'Unavailable' : money(row.cost_usd)}</td>
|
||||
</tr>)}{rows.length === 0 && <tr><td colSpan={6} className="py-8 text-center text-slate-500">No matching {subject.toLowerCase()} records.</td></tr>}</tbody>
|
||||
</table></div>;
|
||||
return (
|
||||
<div className="-mx-5 overflow-x-auto sm:-mx-6">
|
||||
<table className="w-full min-w-[760px] text-sm">
|
||||
<thead>
|
||||
<tr className="border-y border-slate-100 bg-slate-50 text-left text-[10px] font-black uppercase tracking-[0.12em] text-slate-500">
|
||||
<th className="px-5 py-3 sm:pl-6">{subject}</th>
|
||||
<th className="px-3 py-3 text-right">Runs</th>
|
||||
<th className="px-3 py-3 text-right">Failures</th>
|
||||
<th className="px-3 py-3 text-right">Avg latency</th>
|
||||
<th className="px-3 py-3 text-right">Tokens</th>
|
||||
<th className="px-5 py-3 text-right sm:pr-6">Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.key} className="border-b border-slate-100 transition-colors last:border-0 hover:bg-cyan-50/30">
|
||||
<td className="max-w-[320px] px-5 py-4 font-semibold text-slate-900 sm:pl-6">{row.key}</td>
|
||||
<td className="px-3 py-4 text-right font-mono text-xs text-slate-600">{integer(row.runs)}</td>
|
||||
<td className={`px-3 py-4 text-right font-mono text-xs ${row.failures > 0 ? 'font-semibold text-rose-700' : 'text-slate-600'}`}>{integer(row.failures)}</td>
|
||||
<td className="px-3 py-4 text-right font-mono text-xs text-slate-600">{integer(row.latency_avg_ms)} ms</td>
|
||||
<td className="px-3 py-4 text-right font-mono text-xs text-slate-600">{row.tokens === null ? <span className="font-sans text-amber-700">Unavailable</span> : integer(row.tokens)}</td>
|
||||
<td className="px-5 py-4 text-right font-mono text-xs text-slate-600 sm:pr-6">{row.cost_usd === null ? <span className="font-sans text-amber-700">Unavailable</span> : money(row.cost_usd)}</td>
|
||||
</tr>
|
||||
))}
|
||||
{rows.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-6 py-14 text-center">
|
||||
<div className="mx-auto flex h-10 w-10 items-center justify-center rounded-full border border-slate-200 text-slate-400">∅</div>
|
||||
<p className="mt-3 text-sm text-slate-500">No matching {subject.toLowerCase()} records.</p>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LoadingReport() {
|
||||
return <div className="space-y-4" aria-label="Loading H6 report"><div className="h-44 animate-pulse rounded-3xl bg-slate-200" /><div className="grid grid-cols-2 gap-3 lg:grid-cols-4">{Array.from({ length: 8 }, (_, index) => <div key={index} className="h-28 animate-pulse rounded-2xl bg-slate-200" />)}</div></div>;
|
||||
return (
|
||||
<div className="space-y-5" aria-label="Loading H6 report">
|
||||
<div className="h-72 animate-pulse rounded-[1.75rem] bg-slate-200" />
|
||||
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
{Array.from({ length: 4 }, (_, index) => <div key={index} className="h-36 animate-pulse rounded-2xl bg-slate-200" />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function H6ReportPage() {
|
||||
@@ -60,7 +94,11 @@ export function H6ReportPage() {
|
||||
to: searchParams.get('to') ?? '',
|
||||
run: searchParams.get('run') ?? '',
|
||||
}));
|
||||
const report = useQuery({ queryKey: ['reports', 'h6', query], queryFn: () => api.h6Report(query), retry: false });
|
||||
const report = useQuery({
|
||||
queryKey: ['reports', 'h6', query],
|
||||
queryFn: () => api.h6Report(query),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const applyFilters = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -77,80 +115,201 @@ export function H6ReportPage() {
|
||||
setSearchParams({});
|
||||
};
|
||||
|
||||
const inputClass = 'w-full rounded-xl border border-slate-300 bg-white px-3 py-2.5 text-sm text-slate-800 outline-none transition focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100';
|
||||
const inputClass = 'min-h-11 w-full rounded-xl border border-slate-300 bg-white px-3.5 py-2.5 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 hover:border-slate-400 focus:border-cyan-500 focus:ring-4 focus:ring-cyan-100';
|
||||
const data = report.data;
|
||||
|
||||
return <div className="space-y-5">
|
||||
<section className="relative overflow-hidden rounded-3xl border border-slate-800 bg-[#111827] px-5 py-6 text-white shadow-[0_22px_55px_rgba(15,23,42,0.16)] sm:px-7">
|
||||
<div className="absolute right-[-60px] top-[-90px] h-64 w-64 rounded-full bg-indigo-500/25 blur-3xl" />
|
||||
<div className="absolute bottom-[-120px] left-1/3 h-56 w-56 rounded-full bg-teal-500/10 blur-3xl" />
|
||||
<div className="relative flex flex-col justify-between gap-5 lg:flex-row lg:items-end">
|
||||
<div><div className="text-[10px] font-bold uppercase tracking-[0.2em] text-indigo-300">H6 · evidence-backed operations</div><h2 className="mt-2 text-2xl font-semibold tracking-tight sm:text-3xl">AgentOps assurance dossier</h2><p className="mt-2 max-w-2xl text-sm leading-6 text-slate-300">One report surface for runtime health, provider usage, cost provenance, failures, retries, alerts and source freshness.</p></div>
|
||||
{report.data && <div className="flex flex-wrap items-center gap-2"><span className={`rounded-full border px-3 py-2 text-xs font-black uppercase tracking-[0.12em] ${verdictTone[report.data.verdict]}`}>{report.data.verdict}</span><span className="rounded-full border border-slate-700 bg-slate-900/70 px-3 py-2 text-xs text-slate-300">{report.data.freshness.status} · {report.data.report_id}</span></div>}
|
||||
</div>
|
||||
</section>
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<ReportHero
|
||||
eyebrow="H6 · AgentOps assurance"
|
||||
title="Operational evidence, ready for a decision."
|
||||
description="A production dossier for runtime health, reliability, cost provenance and telemetry integrity—designed to answer what happened, why it matters and where the evidence lives."
|
||||
verdict={data?.verdict}
|
||||
meta={data ? (
|
||||
<>
|
||||
<span>{data.report_id}</span>
|
||||
<span>Generated {data.generated_at.replace('T', ' ').replace('Z', ' UTC')}</span>
|
||||
<span>Freshness {data.freshness.status}</span>
|
||||
</>
|
||||
) : <span>Generating from canonical evidence…</span>}
|
||||
actions={data ? (
|
||||
<>
|
||||
<a href={h6ReportExportUrl(query, 'json')} className={reportActionSecondary}>JSON evidence</a>
|
||||
<a href={h6ReportExportUrl(query, 'html')} className={reportActionPrimary}>Export dossier</a>
|
||||
</>
|
||||
) : undefined}
|
||||
/>
|
||||
|
||||
<Card title="Report scope" right={<span className="text-xs font-normal normal-case tracking-normal text-slate-400">Filters apply to API and both exports</span>}>
|
||||
<form onSubmit={applyFilters} className="grid gap-3 md:grid-cols-2 xl:grid-cols-[1fr_1fr_1fr_1.4fr_auto] xl:items-end">
|
||||
<FilterField label="Project"><select value={draft.project} onChange={(event) => setDraft((current) => ({ ...current, project: event.target.value }))} className={inputClass}><option value="">All projects</option>{report.data?.available_filters.projects.map((project) => <option key={project} value={project}>{project}</option>)}</select></FilterField>
|
||||
<FilterField label="From"><input type="date" value={draft.from} onChange={(event) => setDraft((current) => ({ ...current, from: event.target.value }))} className={inputClass} /></FilterField>
|
||||
<FilterField label="To"><input type="date" value={draft.to} onChange={(event) => setDraft((current) => ({ ...current, to: event.target.value }))} className={inputClass} /></FilterField>
|
||||
<FilterField label="Run / trace"><input list="h6-run-options" value={draft.run} onChange={(event) => setDraft((current) => ({ ...current, run: event.target.value }))} placeholder="All runs" className={inputClass} /><datalist id="h6-run-options">{report.data?.available_filters.runs.map((run) => <option key={run} value={run} />)}</datalist></FilterField>
|
||||
<div className="flex gap-2"><button type="submit" className="rounded-xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-300">Apply</button><button type="button" onClick={clearFilters} className="rounded-xl border border-slate-300 bg-white px-3 py-2.5 text-sm font-semibold text-slate-600 transition hover:bg-slate-50">Clear</button></div>
|
||||
</form>
|
||||
</Card>
|
||||
|
||||
{report.isLoading && <LoadingReport />}
|
||||
{report.isError && <div role="alert" className="rounded-2xl border border-rose-200 bg-rose-50 p-5 text-sm text-rose-800"><div className="font-semibold">The H6 report could not be generated.</div><p className="mt-1 text-rose-700">Check the selected time range and confirm the Control Panel API can read the canonical telemetry sources.</p></div>}
|
||||
|
||||
{report.data && <>
|
||||
<section className="grid grid-cols-2 gap-3 lg:grid-cols-4 xl:grid-cols-8">
|
||||
<MetricCard label="Runs" value={integer(report.data.summary.runs)} sub={`${report.data.summary.success} successful`} accent />
|
||||
<MetricCard label="Failures" value={integer(report.data.summary.failed)} sub={`${report.data.summary.failure_rate_pct}% rate`} />
|
||||
<MetricCard label="Degraded" value={integer(report.data.summary.degraded)} />
|
||||
<MetricCard label="P95 latency" value={`${integer(report.data.summary.latency_ms.p95)}ms`} sub={`P50 ${integer(report.data.summary.latency_ms.p50)}ms`} />
|
||||
<MetricCard
|
||||
label="Provider tokens"
|
||||
value={report.data.summary.coverage.token_records > 0 ? integer(report.data.summary.tokens.provider_total ?? report.data.summary.tokens.total ?? 0) : 'Unavailable'}
|
||||
sub={`${report.data.summary.coverage.token_pct}% run coverage`}
|
||||
accent
|
||||
/>
|
||||
<MetricCard
|
||||
label="Actual cost"
|
||||
value={report.data.summary.cost_usd.provider_actual !== null ? money(report.data.summary.cost_usd.provider_actual) : 'Unavailable'}
|
||||
sub={`${report.data.summary.provider_calls} provider calls`}
|
||||
/>
|
||||
<MetricCard
|
||||
label="Cost coverage"
|
||||
value={`${report.data.summary.coverage.cost_pct}%`}
|
||||
sub={`${report.data.summary.coverage.cost_records}/${report.data.summary.coverage.runtime_records} runs`}
|
||||
/>
|
||||
<MetricCard label="Alerts" value={integer(report.data.summary.alerts)} sub={`${report.data.summary.retries} retries`} />
|
||||
</section>
|
||||
|
||||
<div className="grid gap-5 xl:grid-cols-[1.15fr_0.85fr]">
|
||||
<Card title="Verdict findings" right={<StatusBadge value={report.data.verdict} />}>
|
||||
<div className="space-y-2.5">{report.data.findings.map((finding) => <div key={finding.code} className={`rounded-xl border-l-4 p-3.5 ${finding.severity === 'critical' ? 'border-rose-500 bg-rose-50' : finding.severity === 'warning' ? 'border-amber-500 bg-amber-50' : 'border-indigo-500 bg-indigo-50'}`}><div className="flex flex-wrap items-center justify-between gap-2"><span className="font-mono text-xs font-bold text-slate-800">{finding.code}</span>{finding.metric && <span className="text-[11px] text-slate-500">{finding.metric}: {String(finding.value)}{finding.threshold !== undefined ? ` · threshold ${String(finding.threshold)}` : ''}</span>}</div><p className="mt-1 text-sm leading-5 text-slate-600">{finding.message}</p></div>)}{report.data.findings.length === 0 && <div className="rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-800">No threshold breach was detected in this scope.</div>}</div>
|
||||
</Card>
|
||||
<Card title="Evidence freshness" right={<StatusBadge value={report.data.freshness.status} />}>
|
||||
<div className="space-y-3">{report.data.evidence_sources.map((source) => <div key={source.source} className="rounded-xl border border-slate-200 bg-slate-50/70 p-3"><div className="flex items-center justify-between gap-3"><span className="text-sm font-semibold capitalize text-slate-800">{source.source}</span><StatusBadge value={!source.present ? 'missing' : source.stale ? 'stale' : 'fresh'} /></div><div className="mt-2 flex justify-between text-xs text-slate-500"><span>{source.records} records</span><span>{source.age_s === null ? 'no timestamp' : `${integer(source.age_s)}s old`}</span></div><div className="mt-2 break-all font-mono text-[10px] leading-4 text-slate-400">{source.path}</div></div>)}</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 xl:grid-cols-2"><Card title="Runtime by step"><BreakdownTable rows={report.data.details.by_step} subject="Step" /></Card><Card title="Provider usage"><BreakdownTable rows={report.data.details.by_provider} subject="Provider · model" /></Card></div>
|
||||
|
||||
<div className="grid gap-5 xl:grid-cols-[0.8fr_1.2fr]">
|
||||
<Card title={`Data quality · ${report.data.data_quality.status}`}>
|
||||
<div className="mb-4 grid grid-cols-2 gap-3">
|
||||
<div className="rounded-xl border border-slate-200 bg-slate-50 p-3"><div className="text-[10px] font-bold uppercase tracking-wider text-slate-400">Token coverage</div><div className="mt-1 text-xl font-semibold text-slate-900">{report.data.summary.coverage.token_pct}%</div></div>
|
||||
<div className="rounded-xl border border-slate-200 bg-slate-50 p-3"><div className="text-[10px] font-bold uppercase tracking-wider text-slate-400">Cost coverage</div><div className="mt-1 text-xl font-semibold text-slate-900">{report.data.summary.coverage.cost_pct}%</div></div>
|
||||
<ReportPanel
|
||||
eyebrow="Scope"
|
||||
title="Evidence boundary"
|
||||
description="Filters are applied server-side to the screen, JSON contract and print-ready dossier."
|
||||
right={Object.values(draft).some(Boolean) ? <span className="rounded-full bg-cyan-50 px-3 py-1 text-[10px] font-black uppercase tracking-[0.12em] text-cyan-700">Filtered view</span> : <span className="text-xs text-slate-400">All available evidence</span>}
|
||||
>
|
||||
<form onSubmit={applyFilters} className="grid gap-4 md:grid-cols-2 xl:grid-cols-[1fr_1fr_1fr_1.45fr_auto] xl:items-end">
|
||||
<FilterField label="Project">
|
||||
<select value={draft.project} onChange={(event) => setDraft((current) => ({ ...current, project: event.target.value }))} className={inputClass}>
|
||||
<option value="">All projects</option>
|
||||
{data?.available_filters.projects.map((project) => <option key={project} value={project}>{project}</option>)}
|
||||
</select>
|
||||
</FilterField>
|
||||
<FilterField label="From">
|
||||
<input type="date" value={draft.from} onChange={(event) => setDraft((current) => ({ ...current, from: event.target.value }))} className={inputClass} />
|
||||
</FilterField>
|
||||
<FilterField label="To">
|
||||
<input type="date" value={draft.to} onChange={(event) => setDraft((current) => ({ ...current, to: event.target.value }))} className={inputClass} />
|
||||
</FilterField>
|
||||
<FilterField label="Run / trace">
|
||||
<input list="h6-run-options" value={draft.run} onChange={(event) => setDraft((current) => ({ ...current, run: event.target.value }))} placeholder="All governed runs" className={inputClass} />
|
||||
<datalist id="h6-run-options">{data?.available_filters.runs.map((run) => <option key={run} value={run} />)}</datalist>
|
||||
</FilterField>
|
||||
<div className="flex gap-2">
|
||||
<button type="submit" className="min-h-11 rounded-xl bg-slate-950 px-5 py-2.5 text-sm font-bold text-white transition hover:-translate-y-px hover:bg-slate-800 focus:outline-none focus:ring-4 focus:ring-cyan-100">Apply</button>
|
||||
<button type="button" onClick={clearFilters} className="min-h-11 rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-bold text-slate-600 transition hover:bg-slate-50">Clear</button>
|
||||
</div>
|
||||
{report.data.data_quality.warnings.length > 0 ? <ul className="space-y-2 text-sm leading-5 text-slate-600">{report.data.data_quality.warnings.map((warning) => <li key={warning} className="flex gap-2"><span className="mt-1 h-2 w-2 shrink-0 rounded-full bg-amber-400" />{warning}</li>)}</ul> : <div className="text-sm text-emerald-700">All required sources are present and no estimation warning was detected.</div>}
|
||||
</Card>
|
||||
<Card title="Independent export" right={<span className="text-xs font-normal normal-case tracking-normal text-slate-400">Generated on demand · no hard-coded score</span>}>
|
||||
<p className="text-sm leading-6 text-slate-600">Both exports use the same contract, filters, verdict rules and canonical sources as this screen. JSON is machine-auditable; HTML is print-ready for team review.</p>
|
||||
<div className="mt-4 flex flex-wrap gap-3"><a href={h6ReportExportUrl(query, 'json')} className="rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-slate-700">Download JSON</a><a href={h6ReportExportUrl(query, 'html')} className="rounded-xl border border-indigo-300 bg-indigo-50 px-4 py-2.5 text-sm font-semibold text-indigo-700 transition hover:bg-indigo-100">Download HTML</a></div>
|
||||
</Card>
|
||||
</div>
|
||||
</>}
|
||||
</div>;
|
||||
</form>
|
||||
</ReportPanel>
|
||||
|
||||
{report.isLoading && <LoadingReport />}
|
||||
{report.isError && (
|
||||
<div role="alert" className="rounded-2xl border border-rose-200 bg-rose-50 p-5 text-sm text-rose-800">
|
||||
<div className="font-semibold">The H6 dossier could not be generated.</div>
|
||||
<p className="mt-1 text-rose-700">Check the selected range and confirm that the Control Plane can read the canonical telemetry sources.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data && (
|
||||
<>
|
||||
<section aria-label="Executive signals" className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<SignalMetric label="Governed runs" value={integer(data.summary.runs)} detail={`${data.summary.success} successful · ${data.summary.degraded} degraded`} tone="info" primary />
|
||||
<SignalMetric label="Reliability" value={`${data.summary.failure_rate_pct}%`} detail={`${data.summary.failed} failed · ${data.summary.alerts} alerts`} tone={data.summary.failed > 0 ? 'danger' : 'success'} primary />
|
||||
<SignalMetric label="P95 latency" value={`${integer(data.summary.latency_ms.p95)} ms`} detail={`P50 ${integer(data.summary.latency_ms.p50)} ms`} tone={data.findings.some((finding) => finding.code.includes('LATENCY')) ? 'warning' : 'neutral'} primary />
|
||||
<SignalMetric label="Telemetry integrity" value={data.data_quality.status.replaceAll('_', ' ')} detail={`${data.summary.coverage.token_pct}% token · ${data.summary.coverage.cost_pct}% cost coverage`} tone={reportTone(data.data_quality.status)} primary />
|
||||
</section>
|
||||
|
||||
<section className="grid gap-3 rounded-2xl border border-slate-200 bg-white p-4 shadow-[0_12px_32px_rgba(15,23,42,0.04)] sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="border-b border-slate-100 p-2 sm:border-b-0 sm:border-r">
|
||||
<p className="text-[10px] font-black uppercase tracking-[0.14em] text-slate-400">Provider tokens</p>
|
||||
<p className="mt-2 font-mono text-lg font-semibold text-slate-900">{data.summary.coverage.token_records > 0 ? integer(data.summary.tokens.provider_total ?? data.summary.tokens.total ?? 0) : 'Unavailable'}</p>
|
||||
</div>
|
||||
<div className="border-b border-slate-100 p-2 sm:border-b-0 lg:border-r">
|
||||
<p className="text-[10px] font-black uppercase tracking-[0.14em] text-slate-400">Actual cost</p>
|
||||
<p className="mt-2 font-mono text-lg font-semibold text-slate-900">{data.summary.cost_usd.provider_actual !== null ? money(data.summary.cost_usd.provider_actual) : 'Unavailable'}</p>
|
||||
</div>
|
||||
<div className="border-b border-slate-100 p-2 sm:border-b-0 sm:border-r">
|
||||
<p className="text-[10px] font-black uppercase tracking-[0.14em] text-slate-400">Provider calls</p>
|
||||
<p className="mt-2 font-mono text-lg font-semibold text-slate-900">{integer(data.summary.provider_calls)}</p>
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<p className="text-[10px] font-black uppercase tracking-[0.14em] text-slate-400">Retries</p>
|
||||
<p className="mt-2 font-mono text-lg font-semibold text-slate-900">{integer(data.summary.retries)}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="grid gap-5 xl:grid-cols-[1.18fr_0.82fr]">
|
||||
<ReportPanel
|
||||
eyebrow="Decision brief"
|
||||
title="What requires attention"
|
||||
description="Threshold findings are ordered as review signals, not maturity claims."
|
||||
right={<StatusBadge value={data.verdict} />}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{data.findings.map((finding, index) => {
|
||||
const tone = finding.severity === 'critical' ? 'border-rose-500 bg-rose-50/60' : finding.severity === 'warning' ? 'border-amber-500 bg-amber-50/60' : 'border-cyan-500 bg-cyan-50/60';
|
||||
return (
|
||||
<article key={finding.code} className={`grid gap-3 rounded-xl border-l-4 p-4 sm:grid-cols-[2rem_minmax(0,1fr)] ${tone}`}>
|
||||
<div className="font-mono text-lg font-semibold text-slate-400">{String(index + 1).padStart(2, '0')}</div>
|
||||
<div>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<span className="font-mono text-xs font-bold text-slate-900">{finding.code}</span>
|
||||
{finding.metric && <span className="font-mono text-[10px] text-slate-500">{finding.metric}: {String(finding.value)}{finding.threshold !== undefined ? ` / ${String(finding.threshold)}` : ''}</span>}
|
||||
</div>
|
||||
<p className="mt-2 text-sm leading-6 text-slate-700">{finding.message}</p>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
{data.findings.length === 0 && (
|
||||
<div className="rounded-xl border border-emerald-200 bg-emerald-50 p-5 text-sm text-emerald-800">No threshold breach was detected in the selected evidence boundary.</div>
|
||||
)}
|
||||
</div>
|
||||
</ReportPanel>
|
||||
|
||||
<ReportPanel
|
||||
eyebrow="Source integrity"
|
||||
title="Evidence provenance"
|
||||
description="Freshness is evaluated independently for every canonical source."
|
||||
right={<StatusBadge value={data.freshness.status} />}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{data.evidence_sources.map((source) => {
|
||||
const state = !source.present ? 'missing' : source.stale ? 'stale' : 'fresh';
|
||||
return (
|
||||
<article key={source.source} className="rounded-xl border border-slate-200 p-4">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold capitalize text-slate-900">{source.source}</h3>
|
||||
<p className="mt-1 font-mono text-[10px] text-slate-400">{source.records} records · {source.age_s === null ? 'no timestamp' : `${integer(source.age_s)}s old`}</p>
|
||||
</div>
|
||||
<StatusBadge value={state} />
|
||||
</div>
|
||||
<p className="mt-3 break-all border-t border-slate-100 pt-3 font-mono text-[10px] leading-4 text-slate-400">{source.path}</p>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ReportPanel>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 xl:grid-cols-2">
|
||||
<ReportPanel eyebrow="Runtime" title="Execution profile by step" description="Latency, failure and attribution by governed lifecycle step.">
|
||||
<BreakdownTable rows={data.details.by_step} subject="Step" />
|
||||
</ReportPanel>
|
||||
<ReportPanel eyebrow="Provider" title="Model usage and provenance" description="Only provider-reported token and actual cost values are shown.">
|
||||
<BreakdownTable rows={data.details.by_provider} subject="Provider · model" />
|
||||
</ReportPanel>
|
||||
</div>
|
||||
|
||||
<ReportPanel
|
||||
eyebrow="Evidence quality"
|
||||
title="Coverage and disclosure"
|
||||
description="Unavailable telemetry remains null. Coverage gaps are surfaced as review findings."
|
||||
right={<StatusBadge value={data.data_quality.status} />}
|
||||
>
|
||||
<div className="grid gap-7 xl:grid-cols-[0.75fr_1.25fr]">
|
||||
<div className="space-y-6">
|
||||
<CoverageBar label="Token attribution" value={data.summary.coverage.token_pct} detail={`${data.summary.coverage.token_records}/${data.summary.coverage.runtime_records} runtime records`} />
|
||||
<CoverageBar label="Cost attribution" value={data.summary.coverage.cost_pct} detail={`${data.summary.coverage.cost_records}/${data.summary.coverage.runtime_records} runtime records`} />
|
||||
</div>
|
||||
<div className="rounded-2xl border border-slate-200 bg-slate-50 p-5">
|
||||
<p className="text-[10px] font-black uppercase tracking-[0.15em] text-slate-500">Disclosure notes</p>
|
||||
{data.data_quality.warnings.length > 0 ? (
|
||||
<ul className="mt-4 space-y-3">
|
||||
{data.data_quality.warnings.map((warning) => (
|
||||
<li key={warning} className="grid grid-cols-[0.55rem_minmax(0,1fr)] gap-3 text-sm leading-6 text-slate-700">
|
||||
<span className="mt-2 h-2 w-2 rounded-full bg-amber-500" />
|
||||
<span>{warning}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : <p className="mt-4 text-sm text-emerald-700">All required sources are present and no estimation warning was detected.</p>}
|
||||
</div>
|
||||
</div>
|
||||
</ReportPanel>
|
||||
|
||||
<div className="rounded-2xl border border-slate-800 bg-slate-950 p-5 text-white shadow-[0_18px_48px_rgba(15,23,42,0.16)] sm:flex sm:items-center sm:justify-between sm:gap-6 sm:p-6">
|
||||
<div>
|
||||
<p className="text-[10px] font-black uppercase tracking-[0.18em] text-cyan-300">Independent evidence package</p>
|
||||
<h2 className="mt-2 text-lg font-semibold">Same contract. Two review formats.</h2>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-slate-400">JSON is machine-auditable. HTML is a self-contained, print-ready assurance dossier generated only when requested.</p>
|
||||
</div>
|
||||
<div className="mt-5 flex shrink-0 flex-wrap gap-2 sm:mt-0">
|
||||
<a href={h6ReportExportUrl(query, 'json')} className={reportActionSecondary}>Download JSON</a>
|
||||
<a href={h6ReportExportUrl(query, 'html')} className={reportActionPrimary}>Download HTML</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,114 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { api, type HarnessRunRecord } from '../lib/api';
|
||||
import { Card, StatusBadge } from '../components/ui/Card';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { ReportHero, ReportPanel, SignalMetric, reportActionPrimary } from '../components/report/ReportPrimitives';
|
||||
import { TraceExplorer } from '../components/trace/TraceExplorer';
|
||||
import { StatusBadge } from '../components/ui/Card';
|
||||
import { api, type HarnessRunRecord } from '../lib/api';
|
||||
|
||||
const integer = (value: number): string => new Intl.NumberFormat('en-US').format(value);
|
||||
|
||||
export function Runs() {
|
||||
const { data, isLoading } = useQuery({ queryKey: ['runs'], queryFn: () => api.runs(100) });
|
||||
const { data, isLoading, isError } = useQuery({ queryKey: ['runs'], queryFn: () => api.runs(100) });
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const selectedTrace = searchParams.get('trace') ?? '';
|
||||
const runs = data?.runs ?? [];
|
||||
const failures = runs.filter((run) => run.status === 'failed').length;
|
||||
const attributedCost = runs.filter((run) => run.cost_estimate != null).length;
|
||||
const latencyValues = runs.map((run) => Number(run.latency_ms)).filter((value) => Number.isFinite(value) && value > 0);
|
||||
const averageLatency = latencyValues.length > 0
|
||||
? Math.round(latencyValues.reduce((sum, value) => sum + value, 0) / latencyValues.length)
|
||||
: null;
|
||||
|
||||
if (isLoading || !data) return <div className="text-slate-500">Loading…</div>;
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<ReportHero
|
||||
eyebrow="Run assurance registry"
|
||||
title="Every governed run, one evidence trail."
|
||||
description="Select a lifecycle record to reconstruct H1–H7 decisions, inspect sanitized evidence and export an independent assurance dossier."
|
||||
verdict={failures > 0 ? 'attention' : data ? 'operational' : undefined}
|
||||
meta={data ? (
|
||||
<>
|
||||
<span>{data.count} records in current registry</span>
|
||||
<span>{failures} failures</span>
|
||||
<span>{attributedCost}/{runs.length} cost-attributed</span>
|
||||
</>
|
||||
) : <span>Loading governed run registry…</span>}
|
||||
actions={<Link to="/reports/h6" className={reportActionPrimary}>Open H6 dossier</Link>}
|
||||
/>
|
||||
|
||||
{selectedTrace && <TraceExplorer traceId={selectedTrace} onClose={() => setSearchParams({})} />}
|
||||
<Card
|
||||
title={`Recent runs (${data.count})`}
|
||||
right={<span className="text-xs font-normal normal-case tracking-normal text-slate-400">Select a run to inspect H1–H7</span>}
|
||||
>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr className="border-b border-slate-200 text-left text-slate-500">
|
||||
<th className="py-2">time</th><th>step</th><th>status</th><th>latency</th><th>tokens</th><th>cost</th><th className="text-right">trace</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{data.runs.map((run: HarnessRunRecord, index: number) => (
|
||||
<tr key={`${run.trace_id ?? 'run'}-${index}`} className="border-b border-slate-100 transition-colors hover:bg-slate-50/80">
|
||||
<td className="py-2 text-slate-500">{run.timestamp?.replace('T', ' ').replace('Z', '')}</td>
|
||||
<td className="text-slate-700">{run.step ?? run.harness}</td>
|
||||
<td><StatusBadge value={run.status ?? '—'} /></td>
|
||||
<td>{run.latency_ms ?? '—'} ms</td>
|
||||
<td>{run.total_tokens ?? '—'}</td>
|
||||
<td>{run.cost_estimate == null ? <span className="font-medium text-amber-600">Unavailable</span> : `$${Number(run.cost_estimate).toFixed(5)}`}</td>
|
||||
<td className="py-2 text-right">
|
||||
{run.trace_id ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearchParams({ trace: run.trace_id ?? '' })}
|
||||
className="rounded-lg border border-blue-200 bg-blue-50 px-3 py-1.5 text-xs font-semibold text-blue-700 transition-colors hover:bg-blue-100"
|
||||
>
|
||||
Open H1–H7
|
||||
</button>
|
||||
) : <span className="text-slate-300">—</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{isLoading && (
|
||||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{Array.from({ length: 4 }, (_, index) => <div key={index} className="h-32 animate-pulse rounded-2xl bg-slate-200" />)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
{isError && <div role="alert" className="rounded-2xl border border-rose-200 bg-rose-50 p-5 text-sm text-rose-800">The governed run registry could not be loaded.</div>}
|
||||
|
||||
{data && (
|
||||
<>
|
||||
<section className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<SignalMetric label="Governed records" value={integer(data.count)} detail="Latest 100 lifecycle records" tone="info" />
|
||||
<SignalMetric label="Failures" value={integer(failures)} detail={`${data.count ? Math.round((failures / data.count) * 100) : 0}% observed failure rate`} tone={failures > 0 ? 'danger' : 'success'} />
|
||||
<SignalMetric label="Average latency" value={averageLatency === null ? 'Unavailable' : `${integer(averageLatency)} ms`} detail={`${latencyValues.length}/${runs.length} records measured`} tone="neutral" />
|
||||
<SignalMetric label="Cost attribution" value={`${runs.length ? Math.round((attributedCost / runs.length) * 100) : 0}%`} detail={`${attributedCost}/${runs.length} records`} tone={attributedCost === runs.length && runs.length > 0 ? 'success' : 'warning'} />
|
||||
</section>
|
||||
|
||||
<ReportPanel
|
||||
eyebrow="Evidence index"
|
||||
title="Recent governed runs"
|
||||
description="Operational values are shown only when present in canonical telemetry. Select a trace to open the evidence spine."
|
||||
right={<span className="text-xs text-slate-400">{data.count} total records</span>}
|
||||
>
|
||||
<div className="-mx-5 overflow-x-auto sm:-mx-6">
|
||||
<table className="w-full min-w-[900px] text-sm">
|
||||
<thead>
|
||||
<tr className="border-y border-slate-100 bg-slate-50 text-left text-[10px] font-black uppercase tracking-[0.12em] text-slate-500">
|
||||
<th className="px-5 py-3 sm:pl-6">Observed at</th>
|
||||
<th className="px-3 py-3">Lifecycle step</th>
|
||||
<th className="px-3 py-3">Verdict</th>
|
||||
<th className="px-3 py-3 text-right">Latency</th>
|
||||
<th className="px-3 py-3 text-right">Tokens</th>
|
||||
<th className="px-3 py-3 text-right">Cost</th>
|
||||
<th className="px-5 py-3 text-right sm:pr-6">Evidence</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{runs.map((run: HarnessRunRecord, index: number) => (
|
||||
<tr key={`${run.trace_id ?? 'run'}-${index}`} className="border-b border-slate-100 transition-colors last:border-0 hover:bg-cyan-50/30">
|
||||
<td className="whitespace-nowrap px-5 py-4 font-mono text-[11px] text-slate-500 sm:pl-6">{run.timestamp?.replace('T', ' ').replace('Z', ' UTC') ?? 'Unavailable'}</td>
|
||||
<td className="px-3 py-4 font-semibold text-slate-900">{run.step ?? run.harness}</td>
|
||||
<td className="px-3 py-4"><StatusBadge value={run.status ?? 'unknown'} /></td>
|
||||
<td className="px-3 py-4 text-right font-mono text-xs text-slate-600">{run.latency_ms == null ? 'Unavailable' : `${integer(Number(run.latency_ms))} ms`}</td>
|
||||
<td className="px-3 py-4 text-right font-mono text-xs text-slate-600">{run.total_tokens ?? 'Unavailable'}</td>
|
||||
<td className="px-3 py-4 text-right font-mono text-xs text-slate-600">{run.cost_estimate == null ? <span className="font-sans text-amber-700">Unavailable</span> : `$${Number(run.cost_estimate).toFixed(5)}`}</td>
|
||||
<td className="px-5 py-4 text-right sm:pr-6">
|
||||
{run.trace_id ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSearchParams({ trace: run.trace_id ?? '' });
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
className="min-h-11 rounded-xl border border-slate-300 bg-white px-4 py-2 text-xs font-bold text-slate-700 transition hover:-translate-y-px hover:border-cyan-400 hover:text-cyan-800 focus:outline-none focus:ring-4 focus:ring-cyan-100"
|
||||
>
|
||||
Inspect H1–H7
|
||||
</button>
|
||||
) : <span className="text-slate-300">—</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{runs.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={7} className="px-6 py-16 text-center text-sm text-slate-500">No governed run has been recorded yet.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ReportPanel>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user