Skip to main content

Overview

psys provides an interactive network diagram that visualizes the relationships between listening processes and their network connections using React Flow.

Diagram Architecture

The visualization follows a left-to-right layout:
  • Left side: Listener nodes (processes listening on ports)
  • Right side: Target nodes (remote addresses being connected to)
  • Edges: Connections between listeners and targets

Node Types

psys implements two custom node types:

Listener Nodes

Listener nodes represent processes that are listening on network ports:
function ListenerNode({ data }: NodeProps<{ 
  label: string; 
  port: number; 
  pid?: number; 
  processIconType?: string; 
  isDocker?: boolean 
}>) {
  return (
    <div className="rounded-lg border-2 border-primary/30 bg-card px-3 py-2 shadow-sm">
      <Handle type="source" position={Position.Right} className="!w-2 !h-2" />
      <div className="font-medium text-sm flex items-center gap-1.5 flex-wrap">
        <ProcessIcon type={data.processIconType} size={14} />
        {data.isDocker && <DockerIcon size={12} className="text-[#2496ed]" />}
        {labelMain}
      </div>
      <div className="text-xs text-muted-foreground">port {data.port}</div>
    </div>
  );
}
Listener nodes display process icons (Node.js, Redis, MongoDB, etc.) and Docker indicators for containerized processes.

Target Nodes

Target nodes represent remote addresses that listeners are connecting to:
function TargetNode({ data }: NodeProps<{ 
  label: string; 
  address: string; 
  port: number 
}>) {
  return (
    <div className="rounded-lg border-2 border-muted bg-muted/50 px-3 py-2 shadow-sm">
      <Handle type="target" position={Position.Left} className="!w-2 !h-2" />
      <div className="font-medium text-sm">{data.label}</div>
      <div className="text-xs text-muted-foreground">{data.address}:{data.port}</div>
    </div>
  );
}

Flow Building Logic

The buildFlow function converts connection data into React Flow nodes and edges:
function buildFlow(data: ConnectionsData): { nodes: Node[]; edges: Edge[] } {
  const nodes: Node[] = [];
  const edges: Edge[] = [];
  const targetIds = new Map<string, string>();
  const listenerIdByPidPort = new Map<string, string>();

  // Create listener nodes on the left
  data.listeners.forEach((l, i) => {
    const id = `listen-${i}-${l.pid}-${l.port}`;
    nodes.push({
      id,
      type: "listener",
      position: { x: 40, y: 40 + i * (NODE_HEIGHT + GAP) },
      data: {
        label: l.serviceLabel || l.processName || `:${l.port}`,
        port: l.port,
        pid: l.pid,
        processIconType: l.processIconType,
        isDocker: !!l.containerName,
      },
    });
  });

  // Create target nodes on the right and edges
  data.connections.forEach((c) => {
    const fromId = listenerIdByPidPort.get(`${c.fromPid}-${c.fromPort}`);
    if (!fromId) return;
    
    const toKey = `${c.toAddress}:${c.toPort}`;
    let toId = targetIds.get(toKey);
    
    if (!toId) {
      // Create new target node
      toId = `target-${toKey.replace(/[.:]/g, "_")}`;
      nodes.push({
        id: toId,
        type: "target",
        position: {
          x: 40 + LISTENER_NODE_WIDTH + 120,
          y: 40 + targetIndex * (NODE_HEIGHT + GAP),
        },
        data: { label, address: c.toAddress, port: c.toPort },
      });
    }
    
    // Create edge
    edges.push({
      id: `e-${fromId}-${toId}-${edges.length}`,
      source: fromId,
      target: toId,
    });
  });

  return { nodes, edges };
}

Interactive Controls

The diagram includes React Flow’s built-in interactive features:
<ReactFlow
  nodes={nodes}
  edges={edges}
  onNodesChange={onNodesChange}
  onEdgesChange={onEdgesChange}
  nodeTypes={nodeTypes}
  fitView
  fitViewOptions={{ padding: 0.2 }}
  proOptions={{ hideAttribution: true }}
>
  <Background />
  <Controls />
  <MiniMap />
</ReactFlow>

Available Controls

Zoom

Use mouse wheel or zoom controls to zoom in/out

Pan

Click and drag to pan around the diagram

Minimap

Overview of the entire network topology

Layout Configuration

The layout uses fixed spacing constants:
const LISTENER_NODE_WIDTH = 180;
const NODE_HEIGHT = 56;
const GAP = 24;
  • Listener nodes are positioned vertically on the left at x=40
  • Target nodes are positioned at x=40 + LISTENER_NODE_WIDTH + 120
  • Vertical spacing between nodes is NODE_HEIGHT + GAP (80px total)
The diagram automatically fits to view on load with fitView and 20% padding around the edges.

Auto-Refresh

The diagram updates automatically every 5 seconds:
useEffect(() => {
  fetchData();
  const t = setInterval(fetchData, 5000);
  return () => clearInterval(t);
}, [fetchData]);
When new data arrives, nodes and edges are rebuilt and React Flow handles the smooth transition.