From 4f5f569da4576bc47ee61fcf388d9f941a9e6f22 Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Mon, 6 Jul 2026 16:44:10 -0700 Subject: [PATCH] =?UTF-8?q?feat(app):=20map=20polish=20=E2=80=94=20loading?= =?UTF-8?q?=20spinner,=20cleaner=20cards,=20reveal-on-select?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Loading spinner while the graph query is in flight. - Deployment cards drop the "consumed by N" line and external-ref chips — both now live in the inspect panel, so cards are just name/port/reach. - A node's external (reference) edges are drawn only while it's selected, then removed — no permanent web of long cross-map lines. - LAN exposure edges animate like the Internet ones (animation = live traffic). --- app/src/pages/SystemMap.tsx | 70 +++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/app/src/pages/SystemMap.tsx b/app/src/pages/SystemMap.tsx index da2d237..5e04975 100644 --- a/app/src/pages/SystemMap.tsx +++ b/app/src/pages/SystemMap.tsx @@ -126,11 +126,9 @@ function MapNode({ id, data }: NodeProps) { kind: string sub?: string dim?: boolean - hub?: number exposable?: boolean program?: string | null reach?: string | null - externals?: { name: string; host: string; protocol: string }[] focusDim?: boolean focused?: boolean onMenu?: (x: number, y: number, name: string, kind: string) => void @@ -197,25 +195,6 @@ function MapNode({ id, data }: NodeProps) { )} {d.sub &&
{d.sub}
} - {typeof d.hub === "number" && d.hub > 0 && ( -
- ← consumed by {d.hub} -
- )} - {d.externals && d.externals.length > 0 && ( -
- {d.externals.map((x) => ( - - - {x.host} - - ))} -
- )} {/* Right handle: drag OUT to connect — to a hub (expose) or another node (declare "requires"). Right target lands incoming dependency lines. */} @@ -373,7 +352,7 @@ interface MenuItem { } export function SystemMapPage() { - const { data: graph } = useGraph() + const { data: graph, isLoading: graphLoading } = useGraph() const { data: jobs } = useJobs() const { data: programs } = usePrograms() const { data: gateway } = useGateway() @@ -436,16 +415,6 @@ export function SystemMapPage() { // Consumption of an external `reference` renders as a chip on the consumer (not // a long cross-map edge). Collect them here, keyed by the consuming deployment. - const externalsOf = new Map() - for (const e of graph.edges) { - if (e.kind !== "deployment") continue - const dst = byName.get(e.dst) - if (dst?.kind !== "reference") continue - const arr = externalsOf.get(e.src) ?? [] - arr.push({ name: e.dst, host: hostOf(dst.base_url) || e.dst, protocol: protoOf(dst.base_url) }) - externalsOf.set(e.src, arr) - } - // A job shows its cron; everything else prefers its declared socket, else its // gateway http port. This is what makes raw-TCP infra (postgres :5432) visible. const subFor = (n: (typeof graph.nodes)[number]): string | undefined => { @@ -473,11 +442,9 @@ export function SystemMapPage() { kind: n.kind, sub: subFor(n), dim: lane.dim, - hub: n.depended_on_by, exposable: !!lane.exposable, program: n.program && catalog.has(n.program) ? n.program : null, reach: n.reach, - externals: externalsOf.get(n.name), }, } satisfies Node }) @@ -664,7 +631,7 @@ export function SystemMapPage() { target: isPub ? "__internet__" : "__gateway__", sourceHandle: "rs", targetHandle: "lt", - animated: isPub, + animated: true, deletable: true, style: { stroke: isPub ? "#2ea043" : "#58a6ff", strokeWidth: 1.75 }, }) @@ -745,8 +712,31 @@ export function SystemMapPage() { reachRef.current = built.reachOf focusRef.current = focus setNodes(materialize(built)) - setEdges(dimEdges(built.edges)) - }, [built, focus, setNodes, setEdges, materialize, dimEdges]) + // External consumption is drawn only while the consumer is selected — a dashed + // line to each of its reference nodes (kept off-canvas otherwise to avoid a + // permanent web of long cross-map lines). + const extEdges: Edge[] = [] + if (focus && graph) { + const refNames = new Set( + graph.nodes.filter((n) => n.kind === "reference").map((n) => n.name), + ) + for (const e of graph.edges) { + if (e.kind === "deployment" && e.src === focus && refNames.has(e.dst)) { + extEdges.push({ + id: `ext:${focus}->${e.dst}`, + source: focus, + target: e.dst, + sourceHandle: "rs", + targetHandle: "lt", + animated: true, + deletable: false, + style: { stroke: "#8b949e", strokeWidth: 1.5, strokeDasharray: "4 3" }, + }) + } + } + } + setEdges([...dimEdges(built.edges), ...extEdges]) + }, [built, focus, graph, setNodes, setEdges, materialize, dimEdges]) // Single-node selection = inspect (dim + panel); 0 or many = no focus (drag mode). const onSelectionChange = useCallback((p: { nodes: Node[] }) => { @@ -1060,6 +1050,12 @@ export function SystemMapPage() { colorMode="dark" > + {(graphLoading || !graph) && ( +
+ + Loading map… +
+ )} setLasso((v) => !v)}