@kodori/topology
v0.1.0
Published
React components for visualising a service topology with live health checks.
Readme
topology
A React component library for visualizing service topologies with live health checks.
Drag-and-drop nodes, automatic axis-aligned connection routing, dark/light themes, and an interactive editor for authoring configs visually.
npm install @komadori/topologyimport { Topology, type TopologyConfig } from '@komadori/topology';
import '@komadori/topology/style.css';
const config: TopologyConfig = {
nodes: [
{ id: 'api', label: 'REST API', color: '#a78bfa', icon: 'server',
x: 100, y: 100, healthUrl: 'https://api.example.com/health' },
{ id: 'db', label: 'Postgres', color: '#4ade80', icon: 'database',
x: 100, y: 300, healthUrl: 'https://api.example.com/db/health' },
],
connections: [
{ id: 'c1', from: 'api', to: 'db', label: 'reads / writes' },
],
};
export default function StatusPage() {
return <div style={{ width: '100vw', height: '100vh' }}><Topology config={config} /></div>;
}Features
- Live health checks — HTTP-status mode or JSON-path mode (extracts a value from a JSON response and matches it).
- Three real health states —
healthy(green),error(yellow, e.g. service returned 503),unhealthy(red, network/timeout/CORS). - Pan + scroll-to-zoom — cursor-anchored zoom, fit-to-screen with reserved insets so chrome doesn't overlap content.
- Auto-routed connections — every line is built from axis-aligned segments. Three possible shapes: straight, L-bend, or Z-bend (derived from endpoint positions, no curves, no diagonals).
- Slot-snapped endpoints — connection endpoints snap to one of 5 positions on each of 4 sides of a node. Drag to slide.
- Per-segment dragging — Z-bends have a draggable middle handle that slides the perpendicular segment.
- Auto-sized groups — groups grow and shrink with their members; drag the group label to move all members at once.
- Dark / light themes, with smooth runtime switching.
- Visual editor —
src/editor/is a demo-only authoring tool. Build configs visually, copy the resulting JSON.
Project layout
src/
├── components/ React components (public lib)
│ ├── Topology/ Main component + CSS
│ ├── Node/ Single service card
│ ├── Connections/ Connection lines + endpoint/mid handles
│ ├── Icon/ 30 built-in icons
│ └── Form/ Internal form primitives (used by editor)
├── routing/ Pure routing math, no React
│ ├── geometry.ts Rect helpers, side helpers, types
│ ├── anchors.ts Slot snapping, fan-out, anchor resolution
│ └── path.ts Polyline construction with bend offset support
├── utils/ Small pure helpers (jsonPath, snap, time)
├── hooks/ useHealthChecks, usePanZoom
├── types.ts All public TypeScript types
├── constants.ts NODE_W, NODE_H, defaults
├── index.ts Public library entry point
├── editor/ Demo-only visual editor (not bundled)
└── demo/ Demo runner (not bundled)
└── docs/ Tutorial content — Markdown + TOC derivationSee DEVELOPMENT.md for the routing algorithm and how to extend the library.
Health checks
Status-code mode (default) — set healthUrl or healthCheck.url. Healthy when the response status matches expectStatus (default [200]). A response with a non-matching code shows as error (yellow). A missing response (network, timeout, CORS) shows as unhealthy (red).
JSON-path mode — set healthCheck.jsonPath to extract a value from the response body. Healthy when the value matches expectValue (default ['ok','healthy','up'], case-insensitive).
healthCheck: {
url: '/api/diagnostics',
jsonPath: 'databases.pipeline.status',
expectValue: ['ok', 'healthy'],
timeoutMs: 5_000,
}Path syntax accepts dot, bracket, and quoted segments: 'a.b.c', 'a["b"]["c"]', 'items[0].name'.
CORS: health checks run from the browser, so /health endpoints must respond with permissive CORS headers — otherwise the browser blocks the response and the node shows the error code network.
Connections
Connections auto-route as axis-aligned polylines. The shape comes from the endpoint positions:
- Same exit axis + collinear → straight line.
- Same exit axis + offset → Z-bend (out, perpendicular middle, in). The middle segment is draggable.
- Different exit axes → L-bend. If the natural corner falls inside a node body, the router automatically detours around with an extra stub.
To pin where a connection attaches:
{
id: 'c1', from: 'api', to: 'db',
fromAnchor: { side: 'right', t: 0.5 }, // middle of the right side
toAnchor: { side: 'left', t: 0.5 }, // middle of the left side
}t runs 0..1 along the side; the actual rendered position snaps to one of 5 discrete slots.
To override the perpendicular position of a Z-bend's middle segment:
{ id: 'c1', from: 'api', to: 'db', bendOffset: 320 }For an h-axis Z-bend, bendOffset is the X coordinate of the middle vertical segment. For a v-axis Z-bend, it's the Y coordinate of the middle horizontal segment.
Topology props
<Topology
config={config}
theme="dark" // 'dark' | 'light' (controlled)
defaultTheme="dark" // initial theme when uncontrolled
background="dots" // 'dots' (default) | 'snap' (square grid)
snapToGrid // snap node/group drags to gridSize
gridSize={20}
autoHideChrome // bottom-right controls fade in on hover (default: true)
hideLegend // hide the live status-count panel
hideControls // hide the zoom/theme/grid control bar
/>Demo & editor
npm install
npm run devhttp://localhost:5173 — toggle View ↔ Editor ↔ Tutorial. Edits in the editor immediately show in View; reload to start over from sampleConfig.
Design system
The demo, the editor, and topology's own chrome are built on @obento/mono — buttons, inputs, tabs, modals, callouts, code blocks, and the pan/zoom canvas all come from it. Topology defines its palette as --tp-* variables and bridges them to --obento-* in Topology.css, so obento components rendered inside topology pick up its colors and fonts automatically.
The Tutorial tab is a Markdown document (src/demo/docs/tutorial.md) rendered by obento's <Markdown>, which maps every element onto a themed primitive. Its table of contents is derived from the document's own headings, so adding a ## section is the whole edit.
Working on obento at the same time? Check it out as a sibling directory and both npm run dev and tsc resolve it from source — no publish step, no manual switching:
parent/
├── obento/
└── topology/Set OBENTO_DIR if your checkout lives elsewhere. When the sibling isn't present (CI, Docker), the installed npm package is used instead.
Build the library
npm run build:libOutputs dist/topology.js, dist/style.css, and the full .d.ts tree. That directory is the whole published package: @komadori/topology resolves to the bundle, @komadori/topology/style.css to the stylesheet.
The name is scoped because the bare topology on npm is an unrelated 2013 graph library, and installing it gets you that instead of this.
build:lib also runs as the prepare script, which npm invokes both before packing a release and after installing the package straight from git. A consumer can therefore depend on the repository ("@komadori/topology": "github:komadori-dev/topology") and get a built package without a registry release.
React is a peer dependency, so the host app supplies it and there is never a second copy in the bundle. Everything else, obento included, is bundled, which is why @komadori/topology/style.css is the only stylesheet a consumer has to import.
