react-dashstream
v0.5.8
Published
Dashstream is a holographic 3D infrastructure monitoring dashboard for React. Pure CSS-3D — no WebGL, no canvas, no dependencies beyond React.
Maintainers
Readme
React DashStream
Holographic 3D infrastructure monitoring dashboard for React. Pure CSS-3D — no WebGL, no canvas, no dependencies beyond React.
npm install react-dashstreamQuick start
import "react-dashstream/dist/index.css";
import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
import type { ServiceMeta } from "react-dashstream";
const services: ServiceMeta[] = [
{
name: "My Service",
status: "online",
metrics: [
{ label: "Service Health", value: "99.9%", color: "#00ff88" },
{ label: "Avg Response Time", value: "14ms", color: "#00e5ff" },
],
alerts: [{ level: "info", message: "All Systems Nominal" }],
},
];
export default function App() {
return (
<AIOPsDashboard brandName="MY DASHBOARD" services={services}>
<Service
name="My Service"
status="online"
connections={[
{ from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
{ from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
]}
>
<ServerNode
ex={200}
ey={380}
compactOffset={{ x: -30, y: -20 }}
zIndex={8}
name="SRV-01"
subLabel="APP SERVER"
status="online"
cpuLoad={42}
memLoad={60}
/>
<DatabaseNode
ex={460}
ey={380}
compactOffset={{ x: 30, y: -20 }}
zIndex={7}
name="DB-01"
subLabel="PRIMARY"
status="online"
capacity={55}
/>
</Service>
</AIOPsDashboard>
);
}Click a service to expand its topology. Click a component to drill into its internals.
Carousel layout options
Use these AIOPsDashboard props when service names are long or the dashboard runs on a wide display:
<AIOPsDashboard
brandName="MY DASHBOARD"
services={services}
serviceNameMaxLines={2}
serviceNameWidth={420}
alternateServiceNameRows={true}
carouselRadiusX={560}
>
{/* services */}
</AIOPsDashboard>| Prop | Type | Default | Notes |
| ---- | ---- | ------- | ----- |
| serviceNameMaxLines | 1 \| 2 | 1 | Allows compact carousel service names to wrap to two lines. Expanded titles stay on one line. |
| serviceNameWidth | number | 300 | Compact carousel service-name text box width in pixels. Increase this when names wrap too early. |
| alternateServiceNameRows | boolean | false | Offsets alternating compact service names up/down to reduce horizontal label gaps. |
| serviceNameRowOffset | number | 34 | Pixel distance used by alternateServiceNameRows. |
| carouselRadiusX | number | 420 | Horizontal compact orbit radius. Increase this on wide screens for a wider oval movement. |
| carouselRadiusY | number | 160 | Vertical compact orbit radius. |
The same props are available on the lower-level Carousel component for custom shells.
AI assistant skills
Published with the package under skills/ — each folder is a small, topic-focused reference for coding agents (Cursor, OpenCode, etc.):
| Skill | Focus |
| ----- | ----- |
| dashstream-core | Package overview, install, theming, exports, types, pitfalls |
| dashstream-3d-dashboard | AIOPsDashboard, services, nodes, positioning, connections |
| dashstream-live-data | DataProvider, bindings, hooks, endpoint contract |
| dashstream-component-dialogs | Drill-down metrics, internals, sparklines, alerts |
| dashstream-event-view | EventView, event API, event-to-dashboard bridge |
| dashstream-datacenter-view | DatacenterView, topology, geography, buildings |
| dashstream-ai-assistant | Collapsible AI chat panel, navigation tools, PromQL guide, gen-sitemap CLI |
Theme (light / dark)
The dashboard ships with light and dark visual modes. AIOPsDashboard defaults to light; the header includes a control to toggle modes.
Background images
| Prop | When used |
| ---------------------- | -------------------------------------------------- |
| backgroundImage | Dark mode, and light mode if no light asset is set |
| lightBackgroundImage | Light mode when provided |
Controlled theme (sync with your app shell)
Pass theme and onThemeChange to drive the dashboard from parent state (for example, to keep tabs, sidebars, and EventView in sync):
import { useState } from "react";
import { AIOPsDashboard, ThemeProvider, type DashboardTheme, type ServiceMeta } from "react-dashstream";
import lightBg from "./light.webp";
import darkBg from "./dark.webp";
const services: ServiceMeta[] = [
/* …same shape as Quick start… */
];
export default function App() {
const [theme, setTheme] = useState<DashboardTheme>("light");
return (
<ThemeProvider value={theme}>
<AIOPsDashboard
theme={theme}
onThemeChange={setTheme}
lightBackgroundImage={lightBg}
backgroundImage={darkBg}
brandName="MY DASHBOARD"
services={services}
>
{/* Service / node tree — see Quick start */}
</AIOPsDashboard>
</ThemeProvider>
);
}When both props are set, the dashboard is controlled; the header toggle calls onThemeChange. Omit them to use internal theme state.
ThemeProvider, EventView, and the credentials modal
ServiceDialog, ComponentDialog, EventView, and CredentialsModal (access-key prompt) read the active mode from useTheme().
- Wrap any subtree that includes
EventViewor standalone credential prompts inThemeProviderwith the samevalueas your dashboard so the event console and lock screen match. - Optionally pass
theme="light"|"dark"onEventViewto override context (useful when embedding without a provider).
Exports: ThemeProvider, useTheme, type DashboardTheme.
Themed colours (ThemedColor)
Several props accept a ThemedColor — either a plain hex string (same colour in both themes) or a { dark, light } object for per-theme control:
type ThemedColor = string | { dark: string; light: string };
// Same in both themes
color="#00e5ff"
// Different per theme
color={{ dark: "#00e5ff", light: "#0e7490" }}Use resolveThemedColor(color, theme) to resolve a ThemedColor to a concrete string at render time.
Components and props that accept ThemedColor:
| Component / Type | Prop(s) | Notes |
| ----------------------- | ----------------------------- | ------------------------------------------ |
| Human3D / HumanNode | color | Accent colour for the SVG silhouette |
| SyncBridge | syncedColor, lagColor | Status colours for the replication bridge |
| ServiceMetricBinding | color | Accent for live service dialog metrics |
| ServiceDialogMetric | color | Accent for static service dialog metrics |
All components that accept ThemedColor auto-adapt to the active theme by default. The override props are optional and only needed when you want custom colours.
Full example — multi-service, multi-layer
See example/Dashboard.tsx in this package for a complete two-service example with Payment Gateway and Auth Service topologies rotating in a 3D carousel.
Datacenter topology map (DatacenterView)
DatacenterView renders an SVG topology (or optional geographic outline if you pass an SVG path d string), CSS 3D datacenter markers, link lines, and a zoom transition into a nested AIOPsDashboard for the selected building. Define multiple buildings under one siteId to get a single site marker, aggregated KPIs, and a multi-building cluster. Metrics can stay mock (dataCenters[].metrics) or use PromQL via metricBindings (building id → metric key → DataBinding) together with DataProvider and extractDatacenterMetricQueries. The map view itself supports light / dark themes and includes a built-in theme toggle when you do not fully hide the map header.
The demo app (src/App.tsx) uses example/SaudiMapView.tsx, a thin wrapper around DatacenterView that composes the map with DatacenterSite / shape components (TowerDC, FlatDC, …). Scenario data (configs and drill-down services) lives in example/saudiMapDemoData.tsx; optional mock metric sliders use example/DemoMetricPanel.tsx + example/DemoMetricPanel.css (not part of the published package).
Agent-oriented reference: see skills/dashstream-datacenter-view/SKILL.md for props, types, mock vs live data, and composition with DataProvider. Other topics live under skills/<name>/SKILL.md — see AI assistant skills below.
Minimal import
import "react-dashstream/dist/index.css";
import { DatacenterView, DataProvider, extractDatacenterMetricQueries } from "react-dashstream";
import type { DatacenterBuildingConfig, DatacenterMetricBindings } from "react-dashstream";
const dataCenters: DatacenterBuildingConfig[] = [
{
id: "dc-a",
name: "DC A",
subtitle: "Primary",
x: 30,
y: 40,
status: "online",
variant: "tower",
metrics: {
carbonEmissions: 100,
powerUtilization: 5000,
cooling: 1200,
pue: 1.2,
uptime: 99.9,
activeServers: 200,
temperature: 22,
networkThroughput: 100,
},
services: [],
renderServices: () => null,
},
];
const metricBindings: DatacenterMetricBindings = {
"dc-a": { powerUtilization: 'avg(scrape_duration_seconds)' },
};
const queries = extractDatacenterMetricQueries(metricBindings);
<DataProvider config={{ endpoint: "https://prom.example.com/api/v1/query", queries }}>
<DatacenterView dataCenters={dataCenters} metricBindings={metricBindings} />
</DataProvider>;Event Console
The EventView component provides a full-screen operations event console with severity filtering, sortable columns, search, and pagination — styled to match the holographic theme. It fetches events from an external API using the same access-key / access-secret-key authentication as the 3D dashboard.
Quick start — API mode
import "react-dashstream/dist/index.css";
import { EventView } from "react-dashstream";
import type { EventApiConfig } from "react-dashstream";
const eventApiConfig: EventApiConfig = {
baseUrl: "https://your-monitoring-server.example.com",
// endpoint defaults to "/tsws/monitoring/api/v1.0/events/search"
payload: {
filter: {},
sortBy: "date_reception",
sortOrder: "DESC",
},
fieldMapping: {
id: "mc_ueid",
occurrence: "date_reception",
severityLastModified: "severity_last_modified",
severity: "severity",
source: "event_source",
owner: "owner",
class: "class",
host: "mc_host",
message: "msg",
remedySupportGroup: "ara_remedy_support_group",
incidentId: "ara_incident_id",
smsStatus: "ara_sms_status",
onCallNumber: "ara_on_call_number",
hostedApplication: "ara_hosted_application",
monitoringCategory: "ara_hosted_app_monitoring",
applicationSupportUnit: "ara_application_support_unit",
},
};
export default function Events() {
return <EventView apiConfig={eventApiConfig} />;
}On first load a credentials modal appears (same as the 3D dashboard). After authentication, EventView sends a POST to {baseUrl}/tsws/monitoring/api/v1.0/events/search with the payload and polls every 60 seconds.
How it works
- Authentication — uses
access-keyandaccess-secret-keyHTTP headers, identical to the 3D dashboard. If EventView is inside aDataProvider(e.g. alongsideAIOPsDashboardwithliveData), it reuses those credentials automatically — no second login. - POST request — sends
apiConfig.payloadas the JSON body. - Response format — expects:
{ "eventSeverityCount": { "MAJOR": 1, "CRITICAL": 2, "MINOR": 3 }, "totalCount": 6, "eventList": [{ "mc_ueid": "...", "msg": "...", ... }] } - Field mapping — each object in
eventListis mapped to anAIOpsEventusingapiConfig.fieldMapping. EveryAIOpsEventfield must have a corresponding API field name (includingsourcefor the event’s domain or product area). - Severity mapping — raw severity strings (e.g.
"CRITICAL") are mapped toEventSeverityviaapiConfig.severityMap(defaults to{ CRITICAL: "Critical", MAJOR: "Major", MINOR: "Minor" }).
Pre-fetched events mode
If you already have events data, pass them directly — no API call is made:
<EventView events={myEvents} title="My Event Console" />EventView props
| Prop | Type | Default | Description |
| -------------------- | --------------------- | ----------------- | ---------------------------------------------------------------------------- |
| apiConfig | EventApiConfig | — | API configuration (base URL, payload, field mapping). Required for API mode |
| events | AIOpsEvent[] | — | Pre-fetched events. When provided, apiConfig is not used |
| credentials | Credentials | — | Explicit credentials. Falls back to DataProvider context, then modal |
| columnWidthsCookie | string | "ev_col_widths" | Cookie name used to persist user-defined column widths |
| title | string | "Event Console" | Title displayed in the component header |
| theme | "light" | "dark" | — | Optional override; otherwise uses ThemeProvider / defaults to dark context |
EventApiConfig
| Field | Type | Default | Description |
| ----------------- | ------------------------------- | ---------------------------------------------------------- | ------------------------------------------------- |
| baseUrl | string | (required) | Protocol + host, e.g. "https://mon.example.com" |
| endpoint | string | "/tsws/monitoring/api/v1.0/events/search" | Path appended to baseUrl |
| payload | Record<string, unknown> | (required) | POST body sent with every request |
| fieldMapping | EventFieldMapping | (required) | Maps API field names → AIOpsEvent fields |
| severityMap | Record<string, EventSeverity> | { CRITICAL: "Critical", MAJOR: "Major", MINOR: "Minor" } | Maps raw severity → EventSeverity |
| refreshInterval | number | 60000 | Polling interval in milliseconds |
Field mapping
The fieldMapping object maps every AIOpsEvent property to the key name the API returns. All fields are required:
const fieldMapping: EventFieldMapping = {
id: "mc_ueid", // unique event identifier
occurrence: "date_reception", // datetime
severityLastModified: "severity_last_modified",
severity: "severity", // mapped through severityMap
source: "event_source", // e.g. Networking, Computing, APM — any string from the API
owner: "owner",
class: "class", // Self-monitoring, SCOM, etc.
host: "mc_host",
message: "msg",
remedySupportGroup: "ara_remedy_support_group",
incidentId: "ara_incident_id",
smsStatus: "ara_sms_status", // "", "SUCCESS", "FAILED"
onCallNumber: "ara_on_call_number",
hostedApplication: "ara_hosted_application",
monitoringCategory: "ara_hosted_app_monitoring", // "24/7" or "Office Hours"
applicationSupportUnit: "ara_application_support_unit",
};Credential resolution order
credentialsprop (if provided)DataProvidercontext credentials (when insideAIOPsDashboardwithliveData)- Built-in credentials modal (same UI as the 3D dashboard)
Severity colors
| Severity | Color | Usage |
| -------- | --------- | ------------------ |
| Minor | #ffb800 | Amber, low urgency |
| Major | #ff6600 | Orange, attention |
| Critical | #ff2255 | Red, immediate |
Features
- Live API polling — POST to configurable endpoint with auto-refresh
- Field mapping — maps arbitrary API response keys to typed event columns
- Shared authentication — reuses credentials from
DataProvidercontext when available - Severity filter chips — toggle Critical, Major, Minor with live counts
- Free-text search — filters across message, host, owner, and incident ID
- Sortable columns — click any header to cycle ascending / descending / none
- Infinite scroll — all matching events render in a single scrollable table
- Resizable columns — drag column edges to resize; widths are persisted in cookies
- Loading / error states — spinner, error badge, last-refresh timestamp, manual refresh button
- Theming — dark holographic styling or light mode (panels, table, and credentials modal follow
ThemeProvider/ optionalthemeprop)
AI Assistant chat panel
DashStream ships with an opt-in collapsible holographic chat panel that connects to any OpenAI-compatible Chat Completions endpoint (OpenAI, Azure, OpenRouter, Groq, vLLM, llama.cpp, your own backend proxy).
The assistant can:
- Answer questions about the current view using a structured JSON snapshot — including normally-hidden hover-only fields like a datacenter's carbon emissions, PUE, and temperature. No screenshots; the LLM is text-only.
- Drill up / drill down by calling tools that mutate the same React state the human's clicks use. When the AI drills, the human sees the same view change.
- Read open events from
EventAlertsContextand already-fetched PromQL values fromDataProvider. - Run ad-hoc PromQL queries via the
runPromQLtool — with a just-in-timegetPromQLGuidetool that delivers the endpoint constraints + the dashboard's existing bindings as worked examples (so token usage stays low). - Suggest view-aware starter chips and emit collapsed tool-call cards in the transcript.
Quick start
import { AIOPsDashboard } from "react-dashstream";
import "react-dashstream/dist/index.css";
<AIOPsDashboard
aiAssistant={{
endpoint: "/api/ai", // your backend proxy (recommended)
model: "gpt-4o-mini",
}}
/* ...rest of props... */
>
{/* services */}
</AIOPsDashboard>;A floating "AI Assistant" pill appears bottom-right. Click it (or Cmd/Ctrl+K) to open the panel. It respects the dashboard's light / dark theme automatically.
When aiAssistant is omitted, no panel mounts and no AI code paths run.
Streaming vs non-streaming
stream defaults to false (single-shot JSON response). Set stream: true to enable progressive token rendering when your endpoint supports SSE.
Security
apiKey is sent from the browser. Use only with a backend proxy you control or in a trusted intranet. Keys are never persisted to storage. Recommended pattern:
// Express snippet — your backend handles the real key.
app.post("/api/ai/chat/completions", async (req, res) => {
const upstream = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.OPENAI_KEY}` },
body: JSON.stringify(req.body),
});
upstream.body.pipe(res);
});Site map (the agent's world model)
The AI sees a hybrid site map (datacenters → services → nodes) merged from three sources:
Runtime auto-derivation — walks the React tree at render time. Always on.
Pre-generated JSON — author once with the CLI, edit by hand, commit to git:
npx react-dashstream gen-sitemap path/to/Dashboard.tsx --out dashstream.sitemap.jsonimport siteMap from "./dashstream.sitemap.json"; <AIOPsDashboard aiAssistant={{ endpoint, model, siteMap }}>Re-running the CLI merges with the existing file: edited
summary/runbookUrl/owner/tags/roleare preserved; entities removed from the dashboard get a_stale: trueflag so you can prune intentionally.Inline hints — lightweight escape hatch:
aiAssistant: { endpoint, model, siteMapHints: { services: { "Payment Gateway": { summary: "PCI-scoped checkout flow.", owner: "Platform · Payments" } }, }, }
Live fields (status, metrics, alerts) always come from runtime. Descriptive fields (summary, owner, runbook) come from the JSON / hints when set.
Tools the model can call
| Tool | Purpose |
| ---- | ------- |
| getSiteMap | Full merged site map. |
| getCurrentView | Where the user is now. |
| getViewSnapshot | Structured JSON of the visible view + normally-hidden hover-only fields. |
| getOpenEvents | Filterable open alerts. |
| getCachedMetrics | PromQL values already polled — free, no extra fetch. |
| getPromQLGuide | Just-in-time docs: endpoint constraints + worked example queries from this dashboard. |
| runPromQL | Execute an ad-hoc PromQL query (must resolve to one number; rate-limited). |
| goToMap / drillIntoDatacenter / drillIntoService / drillIntoComponent / drillUp | Navigation. |
| highlightNode | Briefly pulse a node in the 3D scene. |
PromQL tools are omitted entirely when no endpoint is configured.
Imperative API
The same context the AI uses is exposed for your own code via the useNavigation() hook:
import { useNavigation } from "react-dashstream";
const nav = useNavigation();
await nav.drillIntoComponent({ serviceName: "Payment Gateway", nodeName: "SRV-01" });Every action returns a Promise<NavigationResult> that resolves once the dashboard's animation settles.
For the full configuration reference, tool details, theming, persistence, and pitfalls see skills/dashstream-ai-assistant/SKILL.md.
Event-to-dashboard bridge
When the same EventApiConfig is passed to AIOPsDashboard, events are fetched in the background and automatically highlight nodes in the 3D topology whose hostname matches an open event. No extra code, no manual wiring — just pass the config.
Quick start
<AIOPsDashboard
brandName="MY DASHBOARD"
services={services}
liveData
dataEndpoint="https://prometheus.example.com/api/v1/query"
dataBindings={dataBindings}
eventApiConfig={eventApiConfig} // ← same config used for EventView
>
<MyService name="My Service" />
</AIOPsDashboard>How it works
AIOPsDashboardwraps its children in anEventAlertsProviderthat fetches events in the background (using the same polling interval and credentials as the Event Console).- Each fetched event has a
hostfield (mapped viafieldMapping). - Every
ServiceNodein the topology checks if itscomponentInfo.namematches any event host (case-insensitive). - If a match is found, the node's severity is updated and a
NodeCalloutappears with the event message.
Severity mapping
| Event severity | Dashboard status | Callout color |
| -------------- | ---------------- | ------------- |
| Critical | "critical" | Red |
| Major | "warning" | Orange |
| Minor | "warning" | Orange |
When multiple events exist on the same host, the highest severity wins and the callout shows "N events – <message>".
The event alert is a third severity source alongside the node's own status prop and metric threshold breaches. The highest severity among all three always wins.
Node name matching
For the bridge to work, the name prop on your nodes must match the host field in the event data:
// If your events API returns host: "SAP-PRD-APP01", name your node:
<ServerNode name="SAP-PRD-APP01" ... />
// Matching is case-insensitive, so these also work:
<ServerNode name="sap-prd-app01" ... />
<ServerNode name="Sap-Prd-App01" ... />Credential resolution
The EventAlertsProvider inside AIOPsDashboard resolves credentials in this order:
- Parent
DataProvidercontext (whenliveDatais enabled — same credentials, single login) - Built-in credentials modal (when
liveDatais not enabled)
Opt-in behavior
- Without
eventApiConfig— no events are fetched, no alerts are injected, zero overhead. - Without
EventView— the bridge still works. You can use event-based alerts on the 3D dashboard without ever rendering anEventView.
External data source monitoring
DashStream can connect to any HTTP monitoring endpoint (Prometheus, Grafana, custom APIs) and feed live values into your dashboard. This section covers every data path — from node props to service dialogs, component dialogs, alerts, drill-down internals, and graphs.
How it works
- Set
liveData={true}onAIOPsDashboardwithdataEndpointanddataBindings. - A credentials modal appears on first load asking for
access-keyandaccess-secret-key(stored in memory only). - The dashboard polls
GET <endpoint>?query=<encodedQuery>for each unique query with credentials as HTTP headers. - Resolved values are injected as props into child service components matched by
name. - The header shows "DATA REFRESH FAILED" if any queries fail.
Live data props
| Prop | Type | Default | Description |
| --------------------- | ---------------------------------------- | ------------- | ------------------------------------------------------ |
| liveData | boolean | false | Enable the live data pipeline. |
| dataEndpoint | string | — | Base URL. Queries sent as GET <url>?query=<encoded>. |
| dataBindings | DataBindings | — | Maps service → prop → query. See below. |
| dataTransform | (raw) => unknown | Numeric parse | Global transform for raw responses. |
| dataRefreshInterval | number | 60000 | Polling interval in ms. |
| serviceDataBindings | Record<string, ServiceMetricBinding[]> | — | Live metrics for the service stats dialog. |
Endpoint contract
| Aspect | Requirement |
| ------------ | --------------------------------------------------------------- |
| Method | GET |
| URL | <dataEndpoint>?query=<urlEncodedQuery> |
| Headers | access-key and access-secret-key |
| Response | Plain text body (trimmed). Default transform parses as number. |
| Errors | Non-2xx → counted as failure. Partial failures shown in header. |
For JSON responses (e.g. Prometheus), provide a custom dataTransform:
dataTransform={(raw) => {
const parsed = JSON.parse(String(raw));
return parsed?.data?.result?.[0]?.value?.[1] ?? raw;
}}1. Data bindings — injecting live props into nodes
dataBindings maps service name → prop name → query. Each key in the inner object must match a prop on your service component. The service name must match the child's name prop.
A binding can be a bare query string or an object with a custom transform:
type DataBinding = string | { query: string; transform?: (raw: unknown) => unknown };
type DataBindings = Record<string, Record<string, DataBinding>>;Example — one service with a server and database:
import "react-dashstream/dist/index.css";
import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
function statusFromValue(raw: unknown) {
const n = Number(raw);
if (n >= 85) return "critical";
if (n >= 70) return "warning";
return "online";
}
export default function App() {
return (
<AIOPsDashboard
brandName="LIVE DASHBOARD"
services={[{ name: "My Service", status: "online" }]}
liveData={true}
dataEndpoint="https://prometheus.example.com/api/v1/query"
dataRefreshInterval={10000}
dataBindings={{
"My Service": {
// bare string → parsed as number automatically
cpuLoad: 'cpu_usage{instance="srv-01"}',
memLoad: 'memory_usage{instance="srv-01"}',
// object with transform → convert number to status string
status: {
query: 'up{instance="srv-01"}',
transform: statusFromValue,
},
// bind database props too
dbCapacity: 'disk_capacity{instance="db-01"}',
dbStatus: {
query: 'disk_capacity{instance="db-01"}',
transform: statusFromValue,
},
},
}}
>
<MyService name="My Service" />
</AIOPsDashboard>
);
}The component receives cpuLoad, memLoad, status, dbCapacity, and dbStatus as live props, overriding their defaults.
Multi-component services
When a service has multiple nodes of the same type (e.g. three servers), use a flat naming convention with prefixes. Each binding maps one prop to one query — there is no array or object binding support.
// Service component — accepts prefixed props for each server
interface ServiceXProps {
name: string;
status?: ComponentStatus;
srv1CpuLoad?: number;
srv1MemLoad?: number;
srv1Status?: ComponentStatus;
srv2CpuLoad?: number;
srv2MemLoad?: number;
srv2Status?: ComponentStatus;
srv3CpuLoad?: number;
srv3MemLoad?: number;
srv3Status?: ComponentStatus;
dbCapacity?: number;
dbStatus?: ComponentStatus;
}
function ServiceX({
name, status = "online",
srv1CpuLoad = 54, srv1MemLoad = 58, srv1Status = "online",
srv2CpuLoad = 63, srv2MemLoad = 66, srv2Status = "online",
srv3CpuLoad = 78, srv3MemLoad = 71, srv3Status = "online",
dbCapacity = 68, dbStatus = "online",
}: ServiceXProps) {
return (
<Service name={name} status={status} connections={[/* ... */]}>
<ServerNode name="SRV-X1" status={srv1Status}
cpuLoad={srv1CpuLoad} memLoad={srv1MemLoad} ... />
<ServerNode name="SRV-X2" status={srv2Status}
cpuLoad={srv2CpuLoad} memLoad={srv2MemLoad} ... />
<ServerNode name="SRV-X3" status={srv3Status}
cpuLoad={srv3CpuLoad} memLoad={srv3MemLoad} ... />
<DatabaseNode name="DB-X1" status={dbStatus}
capacity={dbCapacity} ... />
</Service>
);
}
// Dashboard — bind each prefixed prop to its query
<AIOPsDashboard
liveData={true}
dataEndpoint="https://prometheus.example.com/api/v1/query"
dataBindings={{
ServiceX: {
status: { query: 'status{instance="svcx"}', transform: statusFromValue },
srv1CpuLoad: 'cpu_usage{instance="srvx-01"}',
srv1MemLoad: 'memory_usage{instance="srvx-01"}',
srv1Status: { query: 'status{instance="srvx-01"}', transform: statusFromValue },
srv2CpuLoad: 'cpu_usage{instance="srvx-02"}',
srv2MemLoad: 'memory_usage{instance="srvx-02"}',
srv2Status: { query: 'status{instance="srvx-02"}', transform: statusFromValue },
srv3CpuLoad: 'cpu_usage{instance="srvx-03"}',
srv3MemLoad: 'memory_usage{instance="srvx-03"}',
srv3Status: { query: 'status{instance="srvx-03"}', transform: statusFromValue },
dbCapacity: 'disk_capacity{instance="dbx-01"}',
dbStatus: { query: 'status{instance="dbx-01"}', transform: statusFromValue },
},
}}
services={[{ name: "ServiceX", status: "online" }]}
>
<ServiceX name="ServiceX" />
</AIOPsDashboard>Use a consistent naming convention like srv1CpuLoad, srv2CpuLoad, etc. The dashboard injects each prefixed prop individually. The service component maps each prop to the correct node.
2. Service dialog — live KPI metrics
The ServiceDialog is the stats panel that appears when you click a service. It shows KPI rows and alerts.
Static way — pass ServiceMeta with hardcoded values:
const services: ServiceMeta[] = [
{
name: "My Service",
status: "online",
metrics: [
{ label: "Uptime", value: "99.99%", color: "#00ff88" },
{ label: "Avg Latency", value: "8ms", color: "#00e5ff" },
{ label: "Error Rate", value: "0.02%", color: "#00ff88" },
],
alerts: [{ level: "info", message: "All Systems Nominal" }],
},
];Live way — use serviceDataBindings to fetch values from your endpoint:
<AIOPsDashboard
services={[{ name: "My Service", status: "online" }]}
liveData={true}
dataEndpoint="https://prometheus.example.com/api/v1/query"
dataBindings={{ /* ... */ }}
serviceDataBindings={{
"My Service": [
{ label: "CPU Load", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" },
{ label: "Memory", query: 'memory_usage{instance="srv-01"}', unit: "%", color: "#bb55ff" },
{ label: "Disk", query: 'disk_capacity{instance="db-01"}', unit: "%", color: "#ff8c00" },
],
}}
>Each ServiceMetricBinding:
interface ServiceMetricBinding {
label: string; // Row label
query: string; // PromQL query
unit?: string; // Suffix (e.g. "%", "ms")
color?: ThemedColor; // Accent color — string or { dark, light }
transform?: (raw: unknown) => string; // Custom formatter
}When serviceDataBindings is provided for a service, live values replace the static ServiceMeta.metrics.
3. Component dialog — custom gauges
The ComponentDialog appears when you click a node (server, database, etc.) inside an expanded service. By default it shows CPU, Memory, and Storage gauges derived from the node's props.
Override these gauges with the dialogMetrics prop on any compound node:
<ServerNode
ex={200}
ey={380}
compactOffset={{ x: -30, y: -20 }}
zIndex={8}
name="SRV-01"
subLabel="APP SERVER"
status="online"
cpuLoad={67}
memLoad={72}
dialogMetrics={[
{ id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67, unit: "%" },
{ id: "mem", label: "MEMORY", sublabel: "HEAP USAGE", value: 72, unit: "%" },
{ id: "iops", label: "IOPS", sublabel: "DISK OPS", value: 45, unit: "k/s", icon: "disk" },
{
id: "threads",
label: "THREADS",
sublabel: "ACTIVE",
value: 82,
unit: "%",
warnAt: 60,
critAt: 80,
icon: "cpu",
},
]}
/>Each ComponentDialogMetric:
interface ComponentDialogMetric {
id: string; // Unique key
label: string; // Upper label (e.g. "CPU")
sublabel: string; // Lower label (e.g. "PROCESSOR")
value: number; // 0–100 gauge value
unit?: string; // Suffix (default "%")
icon?: "cpu" | "mem" | "disk"; // Gauge icon (default "cpu")
warnAt?: number; // Orange threshold (default 70)
critAt?: number; // Red threshold (default 85)
color?: string; // Override bar color (bypasses thresholds)
}All compound nodes (ServerNode, DatabaseNode, WebDispatcherNode, MessageServerNode, LoadBalancerNode, GatewayNode, VirtualIPNode) accept dialogMetrics.
Live component dialog metrics — to feed live values into custom gauges, expose each metric value as a prop on your service component and use dataBindings to inject them. Then build the dialogMetrics array from those live props:
// 1. Define your service component with props for each custom metric
import { Service, ServerNode } from "react-dashstream";
import type { ComponentStatus } from "react-dashstream";
interface MyServiceProps {
name: string;
status?: ComponentStatus;
cpuLoad?: number;
memLoad?: number;
iops?: number;
threadCount?: number;
}
function MyService({
name,
status = "online",
cpuLoad = 42,
memLoad = 60,
iops = 20,
threadCount = 45,
}: MyServiceProps) {
return (
<Service
name={name}
status={status}
connections={
[
/* ... */
]
}
>
<ServerNode
ex={200}
ey={380}
compactOffset={{ x: -30, y: -20 }}
zIndex={8}
name="SRV-01"
subLabel="APP SERVER"
status={status}
cpuLoad={cpuLoad}
memLoad={memLoad}
dialogMetrics={[
{ id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: cpuLoad },
{ id: "mem", label: "MEMORY", sublabel: "HEAP", value: memLoad },
{
id: "iops",
label: "IOPS",
sublabel: "DISK OPS",
value: iops,
icon: "disk",
warnAt: 50,
critAt: 80,
},
{
id: "threads",
label: "THREADS",
sublabel: "ACTIVE",
value: threadCount,
icon: "cpu",
warnAt: 60,
critAt: 85,
},
]}
alert={{ offsetX: -160, offsetY: -60, align: "left" }}
/>
</Service>
);
}
// 2. Bind each prop to a live query
<AIOPsDashboard
liveData={true}
dataEndpoint="https://prometheus.example.com/api/v1/query"
dataBindings={{
"My Service": {
cpuLoad: 'cpu_usage{instance="srv-01"}',
memLoad: 'memory_usage{instance="srv-01"}',
iops: 'disk_iops{instance="srv-01"}',
threadCount: 'active_threads{instance="srv-01"}',
status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
},
}}
services={[{ name: "My Service", status: "online" }]}
>
<MyService name="My Service" />
</AIOPsDashboard>;The dashboard injects cpuLoad, memLoad, iops, and threadCount as live props into MyService. Those flow into the dialogMetrics array, so the component dialog gauges update automatically on every poll cycle. This works for any number of custom metrics — just add a prop for each one.
4. Alerts — automatic threshold detection
Nodes automatically show alert callouts when metrics breach thresholds:
- Warning at 70% (orange callout)
- Critical at 85% (red callout)
This works from cpuLoad, memLoad, traffic, queueDepth, or capacity — no extra code needed:
// This server shows a critical alert automatically because cpuLoad > 85
<ServerNode
ex={200}
ey={380}
compactOffset={{ x: -30, y: -20 }}
zIndex={8}
name="SRV-01"
subLabel="PROCESSOR"
status="online"
cpuLoad={92}
memLoad={64}
/>Position the callout:
<ServerNode
cpuLoad={92}
alert={{ offsetX: -160, offsetY: -60, align: "left" }}
...
/>Custom alert message:
<ServerNode
cpuLoad={92}
alert={{ msg: "CPU overload — scale out", offsetX: -160, offsetY: -60, align: "left" }}
...
/>Custom thresholds via dialogMetrics:
<ServerNode
dialogMetrics={[
{ id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67,
warnAt: 50, critAt: 75 }, // Alert triggers at 67% because critAt is 75
]}
alert={{ offsetX: -160, offsetY: -60, align: "left" }}
...
/>Alert align options: "left", "right", "top", "bottom".
5. Drill-down internals — custom sub-components
When you click a node in expanded view, a drill-down opens showing internal sub-components (CPUs, memory sticks, drives, etc.). By default these are auto-generated based on node type.
Provide custom sub-components via the subComponents prop:
import { ServerNode, CPU3D, Memory3D, DriveBay3D, ThreadPool3D } from "react-dashstream";
import type { SubComponentConfig } from "react-dashstream";
const internals: SubComponentConfig[] = [
{
id: "cpu-0",
label: "CPU-0",
status: "online",
element: <CPU3D label="CPU-0" load={67} color="#00e5ff" />,
},
{
id: "cpu-1",
label: "CPU-1",
status: "warning",
element: <CPU3D label="CPU-1" load={88} color="#ff8c00" status="warning" />,
},
{
id: "heap",
label: "HEAP",
status: "online",
element: <Memory3D label="HEAP" usedPercent={72} color="#8855ee" />,
},
{
id: "drive",
label: "DRIVE-0",
status: "online",
element: <DriveBay3D label="DRIVE-0" color="#00e5ff" activity={true} />,
},
{
id: "threads",
label: "THREADS",
status: "online",
element: <ThreadPool3D label="THREADS" color="#00e5ff" />,
},
];
<ServerNode
ex={200}
ey={380}
compactOffset={{ x: -30, y: -20 }}
zIndex={8}
name="SRV-01"
subLabel="APP SERVER"
status="online"
cpuLoad={67}
memLoad={72}
subComponents={internals}
/>;Each SubComponentConfig:
interface SubComponentConfig {
id: string; // Unique key
label: string; // Display name
status: ComponentStatus; // Drives LED color
detail?: string; // Shown on fault
element: ReactNode; // The 3D element to render
}Available internal 3D components: CPU3D, Memory3D, DriveBay3D, NetworkBlock3D, ThreadPool3D, Platter3D, Port3D.
6. Graph series — custom sparklines
The drill-down also shows a historical graph panel with sparklines. By default, graphs are auto-generated from mock data.
Provide custom graph data via the graphSeries prop:
import type { GraphSeries } from "react-dashstream";
const graphs: GraphSeries[] = [
{ id: "cpu", label: "CPU-0", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68, 55, 62] },
{ id: "mem", label: "HEAP", unit: "%", color: "#8855ee", data: [60, 65, 72, 78, 82, 75, 70] },
{ id: "iops", label: "DISK", unit: "k/s", color: "#ff8c00", data: [12, 15, 22, 18, 25, 20, 16] },
];
<ServerNode
ex={200}
ey={380}
compactOffset={{ x: -30, y: -20 }}
zIndex={8}
name="SRV-01"
subLabel="APP SERVER"
status="online"
cpuLoad={67}
memLoad={72}
graphSeries={graphs}
/>;Each GraphSeries:
interface GraphSeries {
id: string; // Unique key
label: string; // Label above the sparkline
unit: string; // Suffix (e.g. "%", "kbps")
color: string; // Sparkline color
data: number[]; // Data points (most recent last)
}7. Putting it all together
A complete example with live data, service dialog metrics, component dialog gauges, sub-components, graphs, and alert positioning:
import "react-dashstream/dist/index.css";
import { AIOPsDashboard, Service, ServerNode, DatabaseNode, CPU3D, Memory3D } from "react-dashstream";
import type { ServiceMeta, DataBindings, ServiceMetricBinding } from "react-dashstream";
const services: ServiceMeta[] = [
{
name: "My Service",
status: "online",
metrics: [{ label: "Uptime", value: "99.99%", color: "#00ff88" }],
alerts: [{ level: "info", message: "All Systems Nominal" }],
},
];
function statusFromValue(raw: unknown) {
const n = Number(raw);
if (n >= 85) return "critical";
if (n >= 70) return "warning";
return "online";
}
const dataBindings: DataBindings = {
"My Service": {
cpuLoad: 'cpu_usage{instance="srv-01"}',
memLoad: 'memory_usage{instance="srv-01"}',
status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
dbCapacity: 'disk_capacity{instance="db-01"}',
dbStatus: { query: 'disk_capacity{instance="db-01"}', transform: statusFromValue },
},
};
const serviceDataBindings: Record<string, ServiceMetricBinding[]> = {
"My Service": [
{ label: "CPU Load", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" },
{ label: "Memory", query: 'memory_usage{instance="srv-01"}', unit: "%", color: "#bb55ff" },
{ label: "Disk", query: 'disk_capacity{instance="db-01"}', unit: "%", color: "#ff8c00" },
],
};
function MyService({ name, status, cpuLoad, memLoad, dbCapacity, dbStatus }: any) {
return (
<Service
name={name}
status={status ?? "online"}
connections={[
{ from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
{ from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
]}
>
<ServerNode
ex={200}
ey={380}
compactOffset={{ x: -30, y: -20 }}
zIndex={8}
name="SRV-01"
subLabel="APP SERVER"
status={status ?? "online"}
cpuLoad={cpuLoad ?? 42}
memLoad={memLoad ?? 60}
alert={{ offsetX: -160, offsetY: -60, align: "left" }}
subComponents={[
{
id: "cpu",
label: "CPU-0",
status: "online",
element: <CPU3D label="CPU-0" load={cpuLoad ?? 42} />,
},
{
id: "mem",
label: "HEAP",
status: "online",
element: <Memory3D label="HEAP" usedPercent={memLoad ?? 60} />,
},
]}
graphSeries={[
{ id: "cpu", label: "CPU", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68] },
]}
/>
<DatabaseNode
ex={460}
ey={380}
compactOffset={{ x: 30, y: -20 }}
zIndex={7}
name="DB-01"
subLabel="PRIMARY"
status={dbStatus ?? "online"}
capacity={dbCapacity ?? 55}
alert={{ offsetX: 160, offsetY: -60, align: "right" }}
/>
</Service>
);
}
export default function App() {
return (
<AIOPsDashboard
brandName="MY DASHBOARD"
services={services}
liveData={true}
dataEndpoint="https://prometheus.example.com/api/v1/query"
dataRefreshInterval={10000}
dataBindings={dataBindings}
serviceDataBindings={serviceDataBindings}
>
<MyService name="My Service" />
</AIOPsDashboard>
);
}8. Data hooks
Access live data anywhere inside the dashboard tree:
import { useAIOpsData, useAIOpsDataOptional, useQueryResult } from "react-dashstream";
function CustomWidget() {
const { data, isRefreshing, lastRefreshError } = useAIOpsData();
const cpu = useQueryResult('cpu_usage{instance="srv-01"}');
return (
<div>
{isRefreshing && <span>Refreshing...</span>}
{lastRefreshError && <span>{lastRefreshError}</span>}
<span>CPU: {String(cpu)}</span>
</div>
);
}| Hook | Returns | Throws? |
| ------------------------ | -------------------------- | ---------------------------- |
| useAIOpsData() | Full DataContextValue | Yes — outside DataProvider |
| useAIOpsDataOptional() | DataContextValue \| null | No |
| useQueryResult(query) | Raw response or null | Yes — outside DataProvider |
9. Standalone DataProvider
Use the data layer without the full dashboard shell:
import { DataProvider, useAIOpsData } from "react-dashstream";
function MyApp() {
return (
<DataProvider
config={{
endpoint: "https://prometheus.example.com/api/v1/query",
queries: ['cpu_usage{instance="srv-01"}'],
refreshInterval: 15000,
}}
>
<MyContent />
</DataProvider>
);
}Components
Layout
| Component | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------- |
| AIOPsDashboard | Full dashboard shell — header, carousel, and state management. Drop services in as children. |
| Service | Service container — positions child nodes on a 3D orbit and draws connection lines between them. |
| ServiceNode | Low-level positioned wrapper with floating animation, scan line, and labels. Used by the compound nodes below. |
Compound nodes (recommended)
These combine ServiceNode + 3D model + componentInfo into a single element:
| Component | Key props | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| ServerNode | status, cpuLoad, memLoad, brandLabel, dialogMetrics, subComponents, graphSeries, alert | App server tower with LEDs and CPU/memory bars. |
| DatabaseNode | status, capacity, dialogMetrics, subComponents, graphSeries, alert | Three-platter database cylinder with capacity bar. |
| WebDispatcherNode | status, traffic, activeRoutes, dialogMetrics, subComponents, graphSeries, alert | Network appliance with 8 port LEDs and traffic metrics. |
| MessageServerNode | status, queueDepth, msgsPerSec, instances, dialogMetrics, subComponents, graphSeries, alert | Message server with instance LEDs and queue metrics. |
| LoadBalancerNode | status, throughput, activeConnections, healthyTargets, totalTargets, algorithm, dialogMetrics, subComponents, graphSeries, alert | Network appliance with animated traffic distribution flow lanes. |
| GatewayNode | status, throughput, activeConnections, healthyTargets, totalTargets, algorithm, dialogMetrics, subComponents, graphSeries, alert | Two-tier octagonal gateway hub (ingress → distribution ring). |
| VirtualIPNode | variant, address, status, traffic, throughput, activeConnections, healthyTargets, totalTargets, latencyMs, packetLoss, failoverState, dialogMetrics, subComponents, graphSeries, alert | Service vIP / virtual IP with three CSS-3D options: VirtualIPA orbital core, VirtualIPB failover beacon, or VirtualIPC routing fabric. |
| HumanNode | status, scale, color | SVG wireframe person icon for user/actor nodes. |
All compound nodes share: ex, ey, compactOffset, zIndex, name, subLabel, color, delay, visibleAtPhase.
3D models (low-level)
If you need full control, use the raw 3D models inside a ServiceNode:
Server3D, Database3D, WebDispatcher3D, MessageServer3D, LoadBalancer3D, Gateway3D, VirtualIP3D, VirtualIPA, VirtualIPB, VirtualIPC, Human3D
All 3D models accept: rotateX, rotateY, rotateZ, scale, autoRotate.
Status indicators
| Component | Props | Description |
| --------------- | ------------------------------------ | ---------------------------------------------------------------- |
| SyncBridge | synced, latencyMs, syncedColor, lagColor | Database replication bridge between primary and standby. |
| NodeCallout | status, title, msg, ex, ey | Alert callout with leader line (auto-rendered by ServiceNode). |
| HoloBase | size, color, widthRatio | Neon holographic base platform (auto-rendered by Service). |
| SvgConnection | x1, y1, x2, y2, show | Animated dashed SVG connection line. |
Dialogs
| Component | Description |
| ----------------- | ----------------------------------------------------------------------------------------------- |
| ServiceDialog | Service-level stats panel — shows metrics and alerts. Auto-rendered when a service is expanded. |
| ComponentDialog | Component drill-down with sub-component internals and sparkline graphs. |
Drill-down internals
Rendered inside ComponentDialog when a component is inspected:
CPU3D, Memory3D, DriveBay3D, NetworkBlock3D, ThreadPool3D, Platter3D, Port3D, HistoricalGraphPanel, ComponentDrillView
Pre-built services
| Component | Topology |
| ----------------- | --------------------------------------------------------------------------------- |
| SAPService | Users → Web Dispatcher + Message Server → 3 App Servers → Primary DB + Standby DB |
| ExchangeService | Users → Dispatcher → 3 App Servers → Primary DB + Standby DB |
Building a custom service
Compose compound nodes inside a Service container. Each node needs:
ex,ey— Position in the expanded topology (pixels from top-left of scene).compactOffset— Offset from the service center in the compact carousel view.zIndex— Stacking order (higher tiers get higher values).visibleAtPhase— When the node fades in during expansion (0–6). Use2for top-tier,3for middle, etc.
Define connection lines between nodes via the connections prop on Service:
connections={[
{ from: [x1, y1], to: [x2, y2], visibleAtPhase: 3 },
{ from: [x1, y1], to: [x2, y2], visibleAtPhase: 4, color: "#ff8c00" },
]}Optionally customise the SVG container that draws the connection lines via connectionsSvg:
<Service
connectionsSvg={{ width: "100%", height: 800, overflow: "hidden" }}
...
/>| Property | Type | Default |
| ---------- | ------------------ | ----------- |
| width | number \| string | 660 |
| height | number \| string | "100%" |
| overflow | string | "visible" |
Status types
type ComponentStatus = "online" | "warning" | "critical" | "offline";| Status | Color | Glow |
| ---------- | --------- | -------- |
| online | #00e5ff | cyan |
| warning | #ff8c00 | orange |
| critical | #ff2255 | red |
| offline | #1e3a5a | dim blue |
Theme constants
import {
STATUS_CFG,
HOLO_CYAN,
HOLO_BLUE,
HOLO_SURFACE,
HOLO_GLASS,
makeFaceStyles,
resolveThemedColor,
ThemeProvider,
useTheme,
type DashboardTheme,
type ThemedColor,
} from "react-dashstream";STATUS_CFG— status-to-color lookup tableHOLO_CYAN/HOLO_BLUE— accent colorsHOLO_SURFACE/HOLO_GLASS— CSS gradient backgrounds for 3D facesmakeFaceStyles(W, H, D)— generates CSS transforms for the 6 faces of a 3D boxThemeProvider,useTheme,DashboardTheme— light/dark for dashboard UI, dialogs,EventView, and credentials modal (see Theme (light / dark) above)ThemedColor,resolveThemedColor— per-theme colour support (see Themed colours below)
Contact
For questions, suggestions, or feedback, reach out at [email protected].
License
MIT
