SPB Git

spb/akos

Public
4.7 KB · 32 lines tsx
1import { useState } from "react";2import { Link, useParams } from "react-router";3import { api, ApiError, type ExecutionDetail as ED } from "../api";4import { useFetch, fmtBytes, fmtDate } from "../hooks";5import { Badge, Bool, ErrorBox, JsonTree, KV, Loading, Td } from "../components/ui";67export default function ExecutionDetail() {8  const { id } = useParams();9  const { data, error, loading } = useFetch<ED>(`executions/${id}`);10  const [body, setBody] = useState<string | null>(null); const [bodyErr, setBodyErr] = useState<ApiError | null>(null);11  if (loading) return <Loading />; if (error) return <ErrorBox error={error} />; if (!data) return null;12  const e = data;13  const loadBody = async () => { setBodyErr(null); try { const t = await api<string>(`/api/v1/executions/${e.id}/body`, { raw: true }); setBody(t.length > 20000 ? t.slice(0, 20000) + `\n… (${t.length - 20000} more characters)` : t); } catch (ex) { setBodyErr(ex as ApiError); } };14  return (15    <div>16      <h1>Execution <code>{e.id.slice(0, 8)}</code> <Badge v={e.state} /> {e.provisional && <Badge v="warn" label="provisional" />} {e.observed === false && <Badge v="unobserved" label="unobserved (outside enforcement boundary)" />}</h1>17      <div className="grid c2">18        <div className="card"><h3>Request</h3><KV items={[["Method", e.method], ["URL", <code style={{ wordBreak: "break-all" }}>{e.url}</code>], ["Project", <Link to={`/projects/${e.project_id}`}>{e.project_slug}</Link>], ["Policy mode", e.policy_mode], ["API", e.api_id ? <Link to={`/apis/${e.api_id}`}>{e.api_name}</Link> : "unregistered"], ["Package", e.package_id ? <Link to={`/akps/${e.package_id}`}>{e.akp_id} v{e.akp_version}</Link> : "—"], ["Operation", e.operation_id ? <Link to={`/endpoints/${e.endpoint_id}`}>{e.operation_id} ({e.endpoint_path})</Link> : "—"], ["Purpose", e.purpose || "—"], ["Caller / agent", `${e.caller ?? "—"} / ${e.agent_name ?? "—"}`], ["Credential", e.credential_ref ? <code>{e.credential_ref} v{e.credential_version}</code> : "none"], ["Fingerprint", <code>{e.request_fingerprint}</code>], ["Correlation", <code>{e.correlation_id}</code>]]} />19          <h3>Sanitized parameters</h3><JsonTree data={e.params} open /></div>20        <div className="card"><h3>Outcome</h3><KV items={[["Status code", e.status_code ?? "—"], ["Duration", e.duration_ms !== null && e.duration_ms !== undefined ? `${e.duration_ms} ms` : "—"], ["Started / finished", `${fmtDate(e.started_at)} → ${fmtDate(e.finished_at)}`], ["Redirects", e.retries], ["Response hash", <code>{e.response_hash ?? "—"}</code>], ["Bytes", fmtBytes(e.response_bytes)], ["Media type", e.response_media_type ?? "—"], ["Snapshot", e.artifact ? <span><Bool v={e.artifact.retention === "permitted"} yes="retained" no="metadata only" /> <code>{e.artifact.sha256.slice(0, 16)}</code></span> : "none"], ["Error", e.error ? <span style={{ color: "var(--critical)" }}>{e.error}</span> : "—"]]} />21          <h3>Policy decision</h3>{e.policy_decision ? <><Badge v={e.policy_decision.decision} /> <span className="hint">mode {e.policy_decision.mode}</span><ul>{e.policy_decision.reasons.map((r, i) => <li key={i}>{r}</li>)}</ul><JsonTree data={e.policy_decision.checks} /></> : <span className="hint">none</span>}22          <h3>Response body</h3>{body === null ? <div className="row"><button className="btn sm" onClick={loadBody} disabled={!e.response_artifact_id}>View body</button>{!e.response_artifact_id && <span className="hint">no snapshot retained</span>}</div> : <pre>{body}</pre>}<ErrorBox error={bodyErr} /></div>23      </div>24      <div className="grid c2">25        <div className="card"><h3>Lineage edges</h3>{e.lineage.length ? e.lineage.map((l, i) => <div key={i} className="hint"><code>{l.from_type}:{l.from_id.slice(0, 8)}</code> —{l.relation}→ <code>{l.to_type}:{l.to_id.slice(0, 8)}</code></div>) : <span className="hint">none</span>}26          <h3>Egress log</h3>{e.egress.length ? <table className="tbl"><thead><tr><th>When</th><th>Host</th><th>Decision</th><th>Reason</th></tr></thead><tbody>{e.egress.map((g, i) => <tr key={i}><Td l="when">{fmtDate(g.ts)}</Td><Td l="host"><code>{g.host}:{g.port}</code></Td><Td l="decision"><Badge v={g.decision} /></Td><Td l="reason">{g.reason}</Td></tr>)}</tbody></table> : <span className="hint">no proxy traffic (direct gateway call)</span>}</div>27        <div className="card"><h3>Audit trail (hash-chained)</h3>{e.audit.map((a) => <details key={a.seq} className="card tight"><summary>#{a.seq} <b>{a.action}</b> <span className="hint">{fmtDate(a.ts)} · {a.actor_type}:{a.actor_id?.slice(0, 8)}</span></summary><JsonTree data={a.details} open /><div className="hint">hash <code>{a.hash.slice(0, 20)}…</code></div></details>)}</div>28      </div>29    </div>30  );31}32
Files · main
Menu

Theme