feat(demo): add reset UI and wire truthful integration status into pages

Add a "Reset demo data" action to the sidebar (Operations Manager only,
explicit confirmation, progress, error handling) -- POST /api/v1/demo/reset
already existed and was already role-gated server-side, but had no UI
trigger. Reset invalidates the acting session server-side, so the flow
signs the user out and returns them to login afterward.

Wire the new GET /api/v1/integrations/status into Automation.tsx and
Dashboard.tsx so both show the aggregate n8n state instead of the most
recent event's status, and the MCP Hub card reflects the actual
registration_enabled setting instead of a hardcoded "not configured" label.
This commit is contained in:
NuklearRabbit
2026-08-02 06:42:27 +02:00
parent 4437b8792a
commit 1867828a9d
3 changed files with 112 additions and 6 deletions
+48 -3
View File
@@ -1,10 +1,18 @@
import { useCallback, useEffect, useState } from "react";
import { api, ApiError } from "../api/client";
import type { AutomationRun, KnowledgeHealth } from "../api/types";
import type { AutomationRun, IntegrationStatus, KnowledgeHealth } from "../api/types";
import { StatusBadge } from "../components/Badge";
import { useAuth } from "../context/AuthContext";
import { ErrorState, IntegrationMark, LoadingState, PageHeader, SectionHeading } from "../components/PageChrome";
const N8N_STATE_LABEL: Record<IntegrationStatus["n8n"]["state"], string> = {
disabled: "disabled",
unavailable: "unavailable",
degraded: "degraded",
operational: "available",
no_evidence: "no_events",
};
export function Automation() {
const { user } = useAuth();
const [runs, setRuns] = useState<AutomationRun[] | null>(null);
@@ -13,6 +21,7 @@ export function Automation() {
const [retryError, setRetryError] = useState<string | null>(null);
const [retrying, setRetrying] = useState<string | null>(null);
const [knowledge, setKnowledge] = useState<KnowledgeHealth | null>(null);
const [integrationStatus, setIntegrationStatus] = useState<IntegrationStatus | null>(null);
const load = useCallback(() => {
setRuns(null);
@@ -39,12 +48,24 @@ export function Automation() {
api.get<KnowledgeHealth>("/api/v1/knowledge/status").then(setKnowledge).catch(() => setKnowledge(null));
}, []);
const loadIntegrationStatus = useCallback(() => {
api
.get<IntegrationStatus>("/api/v1/integrations/status")
.then(setIntegrationStatus)
.catch(() => setIntegrationStatus(null));
}, []);
useEffect(() => {
loadIntegrationStatus();
}, [loadIntegrationStatus]);
async function handleRetry(eventId: string) {
setRetryError(null);
setRetrying(eventId);
try {
await api.post(`/api/v1/workflows/${eventId}/retry`);
load();
loadIntegrationStatus();
} catch (err) {
setRetryError(err instanceof ApiError ? err.message : "Could not retry this delivery.");
} finally {
@@ -66,9 +87,33 @@ export function Automation() {
<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="n8n" />
<div>
<span className="integration-kicker">Orchestration</span>
<h2>n8n delivery</h2>
<p>
{integrationStatus
? `${integrationStatus.n8n.succeeded} succeeded · ${integrationStatus.n8n.failed} failed · ${integrationStatus.n8n.pending} pending · ${integrationStatus.n8n.delivering} delivering.`
: "Return events are committed locally first and then delivered through the outbox."}
</p>
</div>
<StatusBadge status={integrationStatus ? N8N_STATE_LABEL[integrationStatus.n8n.state] : (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>
<article>
<IntegrationMark kind="mcp" />
<div>
<span className="integration-kicker">Tool gateway</span>
<h2>MCP Hub</h2>
<p>
{integrationStatus?.mcp_hub.registration_enabled
? "Registration is enabled for this deployment."
: "No active MobilityOps adapter is configured in this proof of concept."}
</p>
</div>
<StatusBadge status={integrationStatus?.mcp_hub.state ?? "not_configured"} />
</article>
</section>
<SectionHeading title="Delivery ledger" description="Persisted outbox attempts with the latest failure evidence." />
+40 -3
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import { api } from "../api/client";
import type { Dashboard as DashboardData, KnowledgeHealth } from "../api/types";
import type { Dashboard as DashboardData, IntegrationStatus, KnowledgeHealth } from "../api/types";
import { useAuth } from "../context/AuthContext";
import { SeverityBadge, StatusBadge } from "../components/Badge";
import { Icon } from "../components/Icons";
@@ -30,6 +30,7 @@ export function Dashboard() {
const canSeeAutomation = user?.role === "operations_manager";
const [data, setData] = useState<DashboardData | null>(null);
const [knowledge, setKnowledge] = useState<KnowledgeHealth | null>(null);
const [integrationStatus, setIntegrationStatus] = useState<IntegrationStatus | null>(null);
const [error, setError] = useState<string | null>(null);
const [severity, setSeverity] = useState("all");
const [query, setQuery] = useState("");
@@ -39,6 +40,14 @@ export function Dashboard() {
api.get<KnowledgeHealth>("/api/v1/knowledge/status").then(setKnowledge).catch(() => setKnowledge(null));
}, []);
useEffect(() => {
if (!canSeeAutomation) return;
api
.get<IntegrationStatus>("/api/v1/integrations/status")
.then(setIntegrationStatus)
.catch(() => setIntegrationStatus(null));
}, [canSeeAutomation]);
const attention = useMemo(() => data?.attention_items.filter((item) => {
const matchesSeverity = severity === "all" || item.severity === severity;
const haystack = `${item.title} ${item.detail} ${item.link_ref}`.toLowerCase();
@@ -122,9 +131,37 @@ export function Dashboard() {
<section className="work-panel integration-panel" aria-labelledby="integration-heading">
<SectionHeading title="Integration pulse" description="Current evidence from connected services" action={canSeeAutomation ? <Link to="/automation">System detail <Icon name="chevron" /></Link> : undefined} />
<ul className="integration-list">
<li><IntegrationMark kind="n8n" /><div><strong>n8n delivery</strong><span>{latestRun ? `Latest event ${latestRun.aggregate_ref}` : "No workflow evidence recorded"}</span></div><StatusBadge status={n8nState.toLowerCase().replace(/ /g, "_")} /></li>
<li>
<IntegrationMark kind="n8n" />
<div>
<strong>n8n delivery</strong>
<span>
{integrationStatus
? `${integrationStatus.n8n.succeeded} succeeded · ${integrationStatus.n8n.failed} failed`
: latestRun
? `Latest event ${latestRun.aggregate_ref}`
: "No workflow evidence recorded"}
</span>
</div>
<StatusBadge
status={
integrationStatus
? integrationStatus.n8n.state === "operational"
? "available"
: integrationStatus.n8n.state
: n8nState.toLowerCase().replace(/ /g, "_")
}
/>
</li>
<li><IntegrationMark kind="rag" /><div><strong>RAGcore knowledge</strong><span>{knowledge ? `${knowledge.document_count} procedures indexed` : "Health check unavailable"}</span></div><StatusBadge status={knowledge?.available ? "available" : "unavailable"} /></li>
<li><IntegrationMark kind="mcp" /><div><strong>MCP Hub</strong><span>No active adapter in this PoC</span></div><StatusBadge status="not_configured" /></li>
<li>
<IntegrationMark kind="mcp" />
<div>
<strong>MCP Hub</strong>
<span>{integrationStatus?.mcp_hub.registration_enabled ? "Registration enabled" : "No active adapter in this PoC"}</span>
</div>
<StatusBadge status={integrationStatus?.mcp_hub.state ?? "not_configured"} />
</li>
</ul>
</section>