mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-08 17:31:56 -10:00
shell-redesign V1 polish slice Phase 5.1 P5.1-5: pod-scope graph view
V1 founder-walk surfaced a missing graph view at the pod scope
(/topology/pod/$rigId/$podName previously rendered a placeholder
without the live graph render). Per Phase 5.1 ACK §6 implementation
plan, pod-scope graph was the "investigate; park if new-build" item.
Investigation showed it can ride on the existing RigGraph data
(single rig) with a small filter prop — NOT new build. Shipping inline.
EXTENDED packages/ui/src/components/RigGraph.tsx:
- New optional `podScope?: string` prop. When set, the rawNodes /
rawEdges streams from useRigGraph(rigId) are filtered to:
* podGroup/group container nodes whose data.podNamespace OR data.podId
matches the pod name
* agent nodes whose data.podNamespace / data.podId / inferPodName
falls in the pod
* edges whose source AND target are both kept
- Cast inline to RigNodeShape / RigEdgeShape since useRigGraph's hook
data is typed as unknown[] (matching existing downstream pattern).
REFACTORED packages/ui/src/components/topology/ScopePages.tsx:
- PodScopePage default tab moved from "table" → "graph" so the new
graph view-mode is the landing surface.
- Graph view-mode: <RigGraph rigId={rigId} podScope={podName} ... />.
Pod-scoped subset renders with same paper-grid + tactical aesthetic.
- Mobile graph→table degradation pattern replicated from HostScopePage
+ RigScopePage (P5-9): at <lg viewport, graph effectively renders
table with degraded hint; useShellViewport gate.
- Table + Terminal + Overview view-modes unchanged.
Verification:
- pnpm -F @openrig/ui exec tsc --noEmit: clean
- vitest rig-graph.test.tsx: 61/61 PASS (existing tests unaffected by
podScope addition since tests don't pass the prop)
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
1eb71fc0b8
commit
971170daf0
2 files changed
+64
-8
No files matched your search
@@ -96,15 +96,55 @@ export function RigGraph({
|
||||
rigId,
|
||||
rigName = null,
|
||||
showDiscovered = true,
|
||||
podScope,
|
||||
}: {
|
||||
rigId: string | null;
|
||||
rigName?: string | null;
|
||||
showDiscovered?: boolean;
|
||||
/** V1 polish slice Phase 5.1 P5.1-5: pod-scope filter. When set, the
|
||||
* graph renders only nodes/edges/podGroups whose pod matches this
|
||||
* name (matched via inferPodName + node.podId/podNamespace). Other
|
||||
* rig nodes are filtered out so the graph reads as a single-pod
|
||||
* subset. Used by /topology/pod/$rigId/$podName graph view-mode. */
|
||||
podScope?: string;
|
||||
}) {
|
||||
const { data, isPending: loading, error: queryError } = useRigGraph(rigId ?? "");
|
||||
const discoveredSessions = useDiscoveredSessionsConditional(showDiscovered);
|
||||
const rawNodes = data?.nodes ?? [];
|
||||
const rawEdges = data?.edges ?? [];
|
||||
const allRawNodes = data?.nodes ?? [];
|
||||
const allRawEdges = data?.edges ?? [];
|
||||
|
||||
// P5.1-5 pod-scope filter: when podScope set, restrict nodes to those
|
||||
// whose pod matches; restrict edges to those between filtered nodes.
|
||||
// Hook data is typed as unknown[]; cast inline to known shape.
|
||||
const { rawNodes, rawEdges } = useMemo(() => {
|
||||
if (!podScope) return { rawNodes: allRawNodes, rawEdges: allRawEdges };
|
||||
type RigNodeShape = {
|
||||
id: string;
|
||||
type?: string;
|
||||
data?: { logicalId?: string; podId?: string | null; podNamespace?: string | null };
|
||||
};
|
||||
type RigEdgeShape = { source: string; target: string };
|
||||
const allowedNodeIds = new Set<string>();
|
||||
const filteredNodes = (allRawNodes as RigNodeShape[]).filter((n) => {
|
||||
if (n.type === "podGroup" || n.type === "group") {
|
||||
const matches =
|
||||
(n.data?.podNamespace ?? n.data?.podId) === podScope;
|
||||
if (matches) allowedNodeIds.add(n.id);
|
||||
return matches;
|
||||
}
|
||||
const inferredPod =
|
||||
n.data?.podNamespace ??
|
||||
n.data?.podId ??
|
||||
inferPodName(n.data?.logicalId ?? null);
|
||||
const matches = inferredPod === podScope;
|
||||
if (matches) allowedNodeIds.add(n.id);
|
||||
return matches;
|
||||
});
|
||||
const filteredEdges = (allRawEdges as RigEdgeShape[]).filter(
|
||||
(e) => allowedNodeIds.has(e.source) && allowedNodeIds.has(e.target),
|
||||
);
|
||||
return { rawNodes: filteredNodes, rawEdges: filteredEdges };
|
||||
}, [allRawNodes, allRawEdges, podScope]);
|
||||
const error = queryError?.message ?? null;
|
||||
const { reconnecting } = useRigEvents(rigId);
|
||||
const edgeActivity = useTopologyEdgeActivity();
|
||||
|
||||
@@ -253,9 +253,17 @@ function RigOverviewTab({ rigId, rigName }: { rigId: string; rigName: string | n
|
||||
}
|
||||
|
||||
export function PodScopePage() {
|
||||
// V1 polish slice Phase 5.1 P5.1-5: pod-scope graph wires through
|
||||
// RigGraph's new podScope prop (filters nodes + edges + pod groups
|
||||
// to the matching pod only). Default tab moved to "graph" so the
|
||||
// graph view-mode is the landing surface (matches host/rig scope
|
||||
// pattern; founder direction "I noticed that there's no graph view
|
||||
// when you click on a pod").
|
||||
const { rigId, podName } = useParams({ from: "/topology/pod/$rigId/$podName" });
|
||||
const [active, setActive] = useState<TopologyRigPodScopeTab>("table");
|
||||
const [active, setActive] = useState<TopologyRigPodScopeTab>("graph");
|
||||
const { isWideLayout } = useShellViewport();
|
||||
useOverlayForActiveTab(active);
|
||||
const effectiveActive = !isWideLayout && active === "graph" ? "table" : active;
|
||||
|
||||
return (
|
||||
<ScopeShell
|
||||
@@ -263,17 +271,25 @@ export function PodScopePage() {
|
||||
title={`${rigId} / ${podName}`}
|
||||
tabsNav={<TopologyViewModeTabs tabs={RIG_POD_SCOPE_TABS} active={active} onSelect={setActive} testIdPrefix="topology-pod" />}
|
||||
>
|
||||
{active === "graph" ? (
|
||||
<div className="p-6">
|
||||
<EmptyState label="POD GRAPH" description="Pod-scoped graph view (Phase 5 polish)." variant="card" />
|
||||
{effectiveActive === "graph" ? (
|
||||
<div className="flex-1 min-h-0 relative">
|
||||
<RigGraph rigId={rigId} rigName={null} showDiscovered={false} podScope={podName} />
|
||||
</div>
|
||||
) : null}
|
||||
{active === "table" ? (
|
||||
{effectiveActive === "table" ? (
|
||||
<div className="px-6 pb-6">
|
||||
{!isWideLayout && active === "graph" ? (
|
||||
<p
|
||||
data-testid="topology-mobile-graph-degraded"
|
||||
className="font-mono text-[9px] text-on-surface-variant italic mb-2"
|
||||
>
|
||||
Graph view degrades to table on narrow viewports.
|
||||
</p>
|
||||
) : null}
|
||||
<TopologyTableView rigIdScope={rigId} />
|
||||
</div>
|
||||
) : null}
|
||||
{active === "terminal" ? (
|
||||
{effectiveActive === "terminal" ? (
|
||||
<TopologyTerminalView scope="pod" rigId={rigId} podName={podName} />
|
||||
) : null}
|
||||
{active === "overview" ? (
|
||||
|
||||
Reference in new issue
Block a user