|
1 | 1 | import { useEffect, useState } from "react"; |
2 | | -import { api, type PolicyDoc, type CedarPolicyEntry, type OPAPolicyDoc, type PIIAction } from "../api.ts"; |
| 2 | +import { api, type PolicyDoc, type CedarPolicyEntry, type OPAPolicyDoc, type PIIAction, type SrsHealth } from "../api.ts"; |
3 | 3 | import { useAuth } from "../context/AuthContext.tsx"; |
4 | 4 |
|
5 | 5 | /** |
@@ -49,6 +49,7 @@ export function PoliciesPage() { |
49 | 49 | <div> |
50 | 50 | <div className="text-base font-semibold">Policies</div> |
51 | 51 | <div className="text-[11px] text-gray-500">SRS-managed · Cedar + OPA</div> |
| 52 | + <SrsStatus /> |
52 | 53 | </div> |
53 | 54 | {canWrite && ( |
54 | 55 | <button |
@@ -645,6 +646,66 @@ function RegoPoliciesModal({ |
645 | 646 | ); |
646 | 647 | } |
647 | 648 |
|
| 649 | +/** |
| 650 | + * SRS connectivity pill. Polls `/srs/health` on mount, on every refresh, and |
| 651 | + * every 30s — both signals are measured from agentos-server (the browser has |
| 652 | + * no route to SRS): raw reachability + the authenticated proxy path the |
| 653 | + * Policies UI itself uses. Click to re-check immediately. |
| 654 | + */ |
| 655 | +function SrsStatus() { |
| 656 | + const [health, setHealth] = useState<SrsHealth | null>(null); |
| 657 | + const [agentosUp, setAgentosUp] = useState<boolean | null>(null); // the fetch itself = SPA → agentos |
| 658 | + const [checking, setChecking] = useState(false); |
| 659 | + |
| 660 | + const check = () => { |
| 661 | + setChecking(true); |
| 662 | + api.srsHealth() |
| 663 | + .then((h) => { setHealth(h); setAgentosUp(true); }) |
| 664 | + .catch(() => { setHealth(null); setAgentosUp(false); }) |
| 665 | + .finally(() => setChecking(false)); |
| 666 | + }; |
| 667 | + useEffect(() => { |
| 668 | + check(); |
| 669 | + const t = setInterval(check, 30_000); |
| 670 | + return () => clearInterval(t); |
| 671 | + }, []); |
| 672 | + |
| 673 | + const probeState = (p: { configured: boolean; reachable: boolean; authenticated: boolean; latencyMs: number | null; error?: string } | null | undefined, fallback: string) => { |
| 674 | + if (!p) return { dot: "bg-gray-500", text: fallback }; |
| 675 | + if (!p.configured) return { dot: "bg-gray-500", text: "not configured" }; |
| 676 | + if (p.authenticated) return { dot: "bg-emerald-500", text: `connected · ${p.latencyMs}ms` }; |
| 677 | + if (p.reachable) return { dot: "bg-amber-500", text: "reachable, auth failing" }; |
| 678 | + return { dot: "bg-red-500", text: "unreachable" }; |
| 679 | + }; |
| 680 | + |
| 681 | + const rows: Array<{ label: string; dot: string; text: string; title?: string }> = [ |
| 682 | + agentosUp === false |
| 683 | + ? { label: "AgentOS", dot: "bg-red-500", text: "unreachable" } |
| 684 | + : { label: "AgentOS", dot: agentosUp ? "bg-emerald-500" : "bg-gray-500", text: agentosUp ? "connected" : "checking…" }, |
| 685 | + { label: "AgentOS → SRS", ...probeState(health?.agentos, "checking…"), title: health?.agentos?.error }, |
| 686 | + health && !health.cas.reachable |
| 687 | + ? { label: "CAS → SRS", dot: "bg-red-500", text: "CAS unreachable", title: health.cas.error } |
| 688 | + : { label: "CAS → SRS", ...probeState(health?.cas.srs, health ? "no /srs/health (old CAS?)" : "checking…"), title: health?.cas.error ?? health?.cas.srs?.error }, |
| 689 | + ]; |
| 690 | + |
| 691 | + return ( |
| 692 | + <button |
| 693 | + type="button" |
| 694 | + onClick={check} |
| 695 | + title="Click to re-check connectivity" |
| 696 | + className={`mt-1.5 block text-left space-y-0.5 ${checking ? "opacity-60" : ""}`} |
| 697 | + > |
| 698 | + {rows.map((r) => ( |
| 699 | + <span key={r.label} title={r.title} className="flex items-center gap-1.5 text-[10px] text-gray-500 hover:text-gray-300"> |
| 700 | + <span className={`h-1.5 w-1.5 rounded-full ${r.dot}`} /> |
| 701 | + <span className="w-24 text-left">{r.label}</span> |
| 702 | + <span>{r.text}</span> |
| 703 | + </span> |
| 704 | + ))} |
| 705 | + </button> |
| 706 | + ); |
| 707 | +} |
| 708 | + |
648 | 709 | /** |
649 | 710 | * Click-to-copy policy id. Shown wherever a policy surfaces so users can paste |
650 | 711 | * the id straight into the SDK (`ComputerAgent(policy_id="…")`). `short` shows |
|
0 commit comments