feat(ui): redesign knowledge integrations and audit
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { api, ApiError } from "../api/client";
|
||||
import type { AutomationRun } from "../api/types";
|
||||
import type { AutomationRun, KnowledgeHealth } from "../api/types";
|
||||
import { StatusBadge } from "../components/Badge";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { ErrorState, IntegrationMark, LoadingState, PageHeader, SectionHeading } from "../components/PageChrome";
|
||||
|
||||
export function Automation() {
|
||||
const { user } = useAuth();
|
||||
@@ -11,8 +12,11 @@ export function Automation() {
|
||||
const [status, setStatus] = useState("");
|
||||
const [retryError, setRetryError] = useState<string | null>(null);
|
||||
const [retrying, setRetrying] = useState<string | null>(null);
|
||||
const [knowledge, setKnowledge] = useState<KnowledgeHealth | null>(null);
|
||||
|
||||
const load = useCallback(() => {
|
||||
setRuns(null);
|
||||
setError(null);
|
||||
const params = new URLSearchParams();
|
||||
if (status) params.set("status", status);
|
||||
api
|
||||
@@ -31,6 +35,10 @@ export function Automation() {
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
api.get<KnowledgeHealth>("/api/v1/knowledge/status").then(setKnowledge).catch(() => setKnowledge(null));
|
||||
}, []);
|
||||
|
||||
async function handleRetry(eventId: string) {
|
||||
setRetryError(null);
|
||||
setRetrying(eventId);
|
||||
@@ -47,7 +55,7 @@ export function Automation() {
|
||||
if (user?.role !== "operations_manager") {
|
||||
return (
|
||||
<div className="page">
|
||||
<h1>Automation</h1>
|
||||
<PageHeader eyebrow="Assurance / Integrations" title="Integrations" description="Delivery evidence is visible to Operations Managers only." />
|
||||
<p>Automation delivery status is visible to Operations Managers only.</p>
|
||||
</div>
|
||||
);
|
||||
@@ -55,7 +63,15 @@ export function Automation() {
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<h1>Automation</h1>
|
||||
<PageHeader eyebrow="Assurance / Integrations" title="Integration control" description="Monitor delivery health, graceful degradation and retryable workflow events." />
|
||||
|
||||
<section className="integration-cards" aria-label="Integration health">
|
||||
<article><IntegrationMark kind="n8n" /><div><span className="integration-kicker">Orchestration</span><h2>n8n delivery</h2><p>Return events are committed locally first and then delivered through the outbox.</p></div><StatusBadge status={runs?.[0]?.status ?? "no_events"} /></article>
|
||||
<article><IntegrationMark kind="rag" /><div><span className="integration-kicker">Knowledge</span><h2>RAGcore</h2><p>{knowledge ? `${knowledge.document_count} procedures indexed in ${knowledge.collection}.` : "Health evidence is currently unavailable."}</p></div><StatusBadge status={knowledge?.available ? "available" : "unavailable"} /></article>
|
||||
<article><IntegrationMark kind="mcp" /><div><span className="integration-kicker">Tool gateway</span><h2>MCP Hub</h2><p>No active MobilityOps adapter is configured in this proof of concept.</p></div><StatusBadge status="not_configured" /></article>
|
||||
</section>
|
||||
|
||||
<SectionHeading title="Delivery ledger" description="Persisted outbox attempts with the latest failure evidence." />
|
||||
|
||||
<form className="filters" aria-label="Filter automation runs">
|
||||
<label>
|
||||
@@ -70,13 +86,13 @@ export function Automation() {
|
||||
</label>
|
||||
</form>
|
||||
|
||||
{error && <p className="error" role="alert">{error}</p>}
|
||||
{error && <ErrorState message={error} />}
|
||||
{retryError && <p className="error" role="alert">{retryError}</p>}
|
||||
{!error && !runs && <p>Loading automation runs…</p>}
|
||||
{!error && !runs && <LoadingState label="Loading workflow delivery ledger…" />}
|
||||
{runs && runs.length === 0 && <p>No automation runs match this filter.</p>}
|
||||
|
||||
{runs && runs.length > 0 && (
|
||||
<table className="data-table">
|
||||
<div className="table-shell"><div className="table-meta"><span>{runs.length} workflow events</span><span>Bounded retries</span></div><table className="data-table">
|
||||
<caption className="visually-hidden">Automation runs</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -93,20 +109,20 @@ export function Automation() {
|
||||
<tbody>
|
||||
{runs.map((r) => (
|
||||
<tr key={r.event_id}>
|
||||
<td className="mono">{r.event_id.slice(0, 8)}</td>
|
||||
<td>{r.event_type}</td>
|
||||
<td>{r.aggregate_ref}</td>
|
||||
<td>
|
||||
<td className="mono" data-label="Event">{r.event_id.slice(0, 8)}</td>
|
||||
<td data-label="Type">{r.event_type}</td>
|
||||
<td data-label="Booking">{r.aggregate_ref}</td>
|
||||
<td data-label="Status">
|
||||
<StatusBadge status={r.status} />
|
||||
</td>
|
||||
<td>{r.attempts}</td>
|
||||
<td>{r.last_error ?? "—"}</td>
|
||||
<td>
|
||||
<td data-label="Attempts">{r.attempts}</td>
|
||||
<td data-label="Last error">{r.last_error ?? "—"}</td>
|
||||
<td data-label="When">
|
||||
<time dateTime={r.occurred_at}>
|
||||
{new Date(r.occurred_at).toLocaleString("en-GB", { timeZone: "Europe/Brussels" })}
|
||||
</time>
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="Action">
|
||||
{r.status === "failed" ? (
|
||||
<button
|
||||
type="button"
|
||||
@@ -122,7 +138,7 @@ export function Automation() {
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</table></div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user