SPB Git

spb/akos

Public
6.2 KB · 37 lines tsx
1import { useState } from "react";2import { Link, useParams } from "react-router";3import { api, type ApiDetail as AD, type Project } from "../api";4import { useFetch, fmtDate, short } from "../hooks";5import { Badge, Bool, Empty, ErrorBox, JobStatus, JsonTree, KV, Loading, Tabs, Td } from "../components/ui";6import { DebtTable, EndpointRow, EvidenceList, LifecycleButtons } from "../components/shared";78export default function ApiDetail() {9  const { id } = useParams();10  const { data, error, loading, reload } = useFetch<AD>(`apis/${id}`);11  const projects = useFetch<{ projects: Project[] }>("projects");12  const [tab, setTab] = useState("Endpoints");13  const [driftJob, setDriftJob] = useState<string | null>(null);14  if (loading) return <Loading />; if (error) return <ErrorBox error={error} />; if (!data) return null;15  const a = data;16  const projs = projects.data?.projects.map((p) => ({ slug: p.slug, name: `${p.name} (${p.policy_mode})` })) ?? [{ slug: "default", name: "default" }];17  return (18    <div>19      <div className="row between"><div><h1>{a.name} {a.opaque_internal_implementation && <Badge v="critical" label="opaque internal implementation" />}</h1><div className="hint"><Link to={`/apis?provider=${a.provider_slug}`}>{a.provider_name}</Link> · {a.kind} · <Badge v={a.lifecycle} /> <Badge v={a.knowledge_state} label={`knowledge: ${a.knowledge_state}`} /></div></div>20        <div className="row">{a.current_version_id && <button className="btn sm" onClick={async () => { const r = await api<{ job_id: string }>("drift/check", { body: { api_version_id: a.current_version_id } }); setDriftJob(r.job_id); }}>Check drift</button>}</div></div>21      <JobStatus jobId={driftJob} label="drift check" onDone={reload} />22      <div className="card"><KV items={[["Base URL", <code>{a.base_url ?? "—"}</code>], ["Docs", a.docs_url ? <a href={a.docs_url} target="_blank" rel="noreferrer">{a.docs_url}</a> : "—"], ["Auth", <code>{JSON.stringify(a.auth_scheme)}</code>], ["Provider license", (a.provider_license?.name as string) ?? <Badge v="warn" label="unresolved" />], ["Snapshots", (a.provider_license?.snapshots as string) ?? "metadata_only"], ["Description", a.description ?? "—"], ["Tags", a.tags.join(", ") || "—"], ["Current version", a.current_version_id ? <Link to={`/versions/${a.current_version_id}`}>{short(a.current_version_id)}</Link> : "—"], ["Created", fmtDate(a.created_at)]]} />23        <h3>Lifecycle transitions (state machine enforced server-side; evidence required for tested/registered)</h3>24        <LifecycleButtons allowed={a.allowed_transitions} onTransition={async (to, reason) => { await api(`apis/${a.id}/lifecycle`, { body: { to, reason } }); reload(); }} /></div>25      <Tabs tabs={["Endpoints", "Variables", "Versions", "Packages", "Evidence", "Debt", "Drift", "Executions"]} active={tab} onChange={setTab} />26      {tab === "Endpoints" && (a.endpoints?.length ? <div className="stack">{a.endpoints.map((e) => <EndpointRow key={e.id} e={e} apiId={a.id} projects={projs} />)}</div> : <Empty>No endpoints in the current version.</Empty>)}27      {tab === "Variables" && (a.variables?.length ? <table className="tbl"><thead><tr><th>Name</th><th>Path</th><th>Type</th><th>Unit</th><th>Concept</th><th>Description</th></tr></thead><tbody>{a.variables.map((v) => <tr key={v.id}><Td l="name"><code>{v.name}</code></Td><Td l="path"><code>{v.json_path}</code></Td><Td l="type">{v.data_type}</Td><Td l="unit">{v.unit ?? "—"}</Td><Td l="concept">{v.semantic_concept ?? "—"}</Td><Td l="description" wide><span className="hint">{v.description}</span></Td></tr>)}</tbody></table> : <Empty>No variables extracted.</Empty>)}28      {tab === "Versions" && <table className="tbl"><thead><tr><th>Version</th><th>Lifecycle</th><th>Source</th><th>Endpoints</th><th>Contract hash</th><th>Created</th></tr></thead><tbody>{a.versions.map((v) => <tr key={v.id}><Td l="version"><Link to={`/versions/${v.id}`}>{v.version}</Link>{v.id === a.current_version_id && <Badge v="accent" label="current" />}</Td><Td l="lifecycle"><Badge v={v.lifecycle} /></Td><Td l="source">{v.source_kind}{v.source_url && <div className="hint truncate">{v.source_url}</div>}</Td><Td l="endpoints" className="num">{v.endpoints}</Td><Td l="hash"><code>{short(v.contract_hash, 16)}</code></Td><Td l="created">{fmtDate(v.created_at)}</Td></tr>)}</tbody></table>}29      {tab === "Packages" && (a.packages.length ? <table className="tbl"><thead><tr><th>AKP</th><th>Version</th><th>Lifecycle</th><th>Knowledge</th><th>Hash</th><th>Credential</th><th>Created</th></tr></thead><tbody>{a.packages.map((k) => <tr key={k.id}><Td l="akp"><Link to={`/akps/${k.id}`}>{k.akp_id}</Link>{k.superseded_by && <Badge v="deprecated" label="superseded" />}</Td><Td l="version">v{k.version}</Td><Td l="lifecycle"><Badge v={k.lifecycle} /></Td><Td l="knowledge"><Badge v={k.knowledge_state} /></Td><Td l="hash"><code>{short(k.hash, 12)}</code></Td><Td l="credential"><Bool v={!!k.credential_ref_id} yes="ref" no="none" /></Td><Td l="created">{fmtDate(k.created_at)}</Td></tr>)}</tbody></table> : <Empty>No knowledge package compiled yet.</Empty>)}30      {tab === "Evidence" && <EvidenceList evidence={a.evidence} />}31      {tab === "Debt" && <DebtTable debt={a.debt} onChange={reload} />}32      {tab === "Drift" && (a.drift.length ? <div className="stack">{a.drift.map((d) => <details key={d.id} className="card tight"><summary><Badge v={d.status} /> <b>{d.kind}</b> <span className="hint">{fmtDate(d.detected_at)} · affected projects {d.affected_projects.length}</span></summary><JsonTree data={d.details} open /></details>)}</div> : <Empty>No drift alerts.</Empty>)}33      {tab === "Executions" && (a.executions.length ? <table className="tbl"><thead><tr><th>When</th><th>State</th><th>Mode</th><th>Request</th><th>Status</th><th>ms</th></tr></thead><tbody>{a.executions.map((e) => <tr key={e.id}><Td l="when"><Link to={`/executions/${e.id}`}>{fmtDate(e.created_at)}</Link></Td><Td l="state"><Badge v={e.state} /></Td><Td l="mode">{e.policy_mode}</Td><Td l="request" wide><span className="truncate mono">{e.method} {e.url}</span></Td><Td l="status">{e.status_code ?? "—"}</Td><Td l="ms" className="num">{e.duration_ms ?? "—"}</Td></tr>)}</tbody></table> : <Empty>No executions for this API.</Empty>)}34    </div>35  );36}37
Files · da58f809c01c422e65c786b05d6ffa3079c9feba
Menu

Theme