feat(web): MazeNET 7b — service-level selection + inspector panel

Clicking a service tag selects it (stops node drag), extends Selection
discriminant with {type:'service',id,nodeId}, and renders an inspector
panel showing proto/port/subnet/risk chip + REMOVE SERVICE button
(gated off for observed nodes and degraded topologies). Service-tag
styling now pulls `risk` from DEFAULT_SERVICES metadata instead of
node.status alone.
This commit is contained in:
2026-04-22 15:56:55 -04:00
parent 6fbac5d057
commit 1f429cd00e
4 changed files with 101 additions and 10 deletions

View File

@@ -30,6 +30,7 @@ interface Props {
onAutoLayout?: () => void;
sseConnected?: boolean;
lastEventAt?: Date | null;
onSelectService?: (nodeId: string, slug: string) => void;
}
const fmtTime = (d: Date) =>
@@ -42,7 +43,7 @@ const Canvas = forwardRef<HTMLDivElement, Props>(function Canvas(
{ nets, nodes, edges, deployed, selection, setSelection, pan, dropTargetId, dragging, edgeDraw,
onCanvasMouseDown, onNodeMouseDown, onNetMouseDown, onNetResizeMouseDown, onPortMouseDown,
onNodeContextMenu, onNetContextMenu, onEdgeContextMenu, onCanvasContextMenu,
onResetView, onAutoLayout, sseConnected, lastEventAt },
onResetView, onAutoLayout, sseConnected, lastEventAt, onSelectService },
ref,
) {
const netById = useMemo(() => new Map(nets.map((n) => [n.id, n])), [nets]);
@@ -63,8 +64,11 @@ const Canvas = forwardRef<HTMLDivElement, Props>(function Canvas(
}, [nodes, edges]);
const selNetId = selection?.type === 'net' ? selection.id : null;
const selNodeId = selection?.type === 'node' ? selection.id : null;
const selNodeId = selection?.type === 'node' ? selection.id
: selection?.type === 'service' ? selection.nodeId : null;
const selEdgeId = selection?.type === 'edge' ? selection.id : null;
const selServiceNodeId = selection?.type === 'service' ? selection.nodeId : null;
const selServiceSlug = selection?.type === 'service' ? selection.id : null;
return (
<div
@@ -168,7 +172,9 @@ const Canvas = forwardRef<HTMLDivElement, Props>(function Canvas(
selected={n.id === selNodeId}
deployed={deployed}
dragging={dragging && n.id === selNodeId}
selectedServiceSlug={n.id === selServiceNodeId ? selServiceSlug : null}
onSelect={(id) => setSelection({ type: 'node', id })}
onSelectService={onSelectService}
onMouseDown={onNodeMouseDown}
onPortMouseDown={onPortMouseDown}
onContextMenu={onNodeContextMenu}