rakka-dashboard 0.2.1

Live web UI over a running rakka system — REST + WebSocket + embedded React SPA, Prometheus / OTLP exporters.
import { useMemo } from "react";
import ReactFlow, {
  Background,
  Controls,
  type Edge,
  type Node,
  MiniMap,
  Position,
} from "reactflow";
import "reactflow/dist/style.css";

import type { ActorTreeNode } from "@/lib/api";

interface Layout {
  nodes: Node[];
  edges: Edge[];
}

function layout(roots: ActorTreeNode[]): Layout {
  const nodes: Node[] = [];
  const edges: Edge[] = [];
  const hGap = 220;
  const vGap = 80;

  let depthCursor: number[] = [];

  function walk(n: ActorTreeNode, depth: number, parentId?: string) {
    const id = n.path;
    depthCursor[depth] = (depthCursor[depth] ?? -1) + 1;
    const y = depth * vGap;
    const x = depthCursor[depth] * hGap;
    nodes.push({
      id,
      position: { x, y },
      sourcePosition: Position.Bottom,
      targetPosition: Position.Top,
      data: {
        label: (
          <div className="flex flex-col items-start">
            <span className="font-mono text-[11px] text-muted-foreground">
              {n.name}
            </span>
            <span className="text-[10px]">{n.actor_type}</span>
            {n.mailbox_depth > 0 && (
              <span className="text-[10px] text-amber-500">
                mailbox: {n.mailbox_depth}
              </span>
            )}
          </div>
        ),
      },
      style: {
        border: "1px solid hsl(var(--border))",
        borderRadius: 8,
        padding: 8,
        background: "hsl(var(--card))",
        color: "hsl(var(--card-foreground))",
        fontSize: 12,
      },
    });
    if (parentId) {
      edges.push({ id: `${parentId}->${id}`, source: parentId, target: id });
    }
    for (const child of n.children) walk(child, depth + 1, id);
  }

  for (const r of roots) walk(r, 0);
  return { nodes, edges };
}

export function ActorTreeFlow({
  roots,
  onSelect,
}: {
  roots: ActorTreeNode[];
  onSelect?: (path: string) => void;
}) {
  const { nodes, edges } = useMemo(() => layout(roots), [roots]);
  return (
    <div className="h-[60vh] md:h-[70vh] w-full rounded-lg border bg-card/40">
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodeClick={(_, n) => onSelect?.(n.id)}
        fitView
        proOptions={{ hideAttribution: true }}
      >
        <Background />
        <Controls position="bottom-right" />
        <MiniMap zoomable pannable className="hidden md:block" />
      </ReactFlow>
    </div>
  );
}