npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

Readme

React DashStream

Holographic 3D infrastructure monitoring dashboard for React. Pure CSS-3D — no WebGL, no canvas, no dependencies beyond React.

npm install react-dashstream

Quick 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 EventView or standalone credential prompts in ThemeProvider with the same value as your dashboard so the event console and lock screen match.
  • Optionally pass theme="light" | "dark" on EventView to 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

  1. Authentication — uses access-key and access-secret-key HTTP headers, identical to the 3D dashboard. If EventView is inside a DataProvider (e.g. alongside AIOPsDashboard with liveData), it reuses those credentials automatically — no second login.
  2. POST request — sends apiConfig.payload as the JSON body.
  3. Response format — expects:
    {
        "eventSeverityCount": { "MAJOR": 1, "CRITICAL": 2, "MINOR": 3 },
        "totalCount": 6,
        "eventList": [{ "mc_ueid": "...", "msg": "...", ... }]
    }
  4. Field mapping — each object in eventList is mapped to an AIOpsEvent using apiConfig.fieldMapping. Every AIOpsEvent field must have a corresponding API field name (including source for the event’s domain or product area).
  5. Severity mapping — raw severity strings (e.g. "CRITICAL") are mapped to EventSeverity via apiConfig.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

  1. credentials prop (if provided)
  2. DataProvider context credentials (when inside AIOPsDashboard with liveData)
  3. 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 DataProvider context 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 / optional theme prop)

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 EventAlertsContext and already-fetched PromQL values from DataProvider.
  • Run ad-hoc PromQL queries via the runPromQL tool — with a just-in-time getPromQLGuide tool 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:

  1. Runtime auto-derivation — walks the React tree at render time. Always on.

  2. 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.json
    import siteMap from "./dashstream.sitemap.json";
    <AIOPsDashboard aiAssistant={{ endpoint, model, siteMap }}>

    Re-running the CLI merges with the existing file: edited summary / runbookUrl / owner / tags / role are preserved; entities removed from the dashboard get a _stale: true flag so you can prune intentionally.

  3. 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

  1. AIOPsDashboard wraps its children in an EventAlertsProvider that fetches events in the background (using the same polling interval and credentials as the Event Console).
  2. Each fetched event has a host field (mapped via fieldMapping).
  3. Every ServiceNode in the topology checks if its componentInfo.name matches any event host (case-insensitive).
  4. If a match is found, the node's severity is updated and a NodeCallout appears 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:

  1. Parent DataProvider context (when liveData is enabled — same credentials, single login)
  2. Built-in credentials modal (when liveData is 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 an EventView.

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

  1. Set liveData={true} on AIOPsDashboard with dataEndpoint and dataBindings.
  2. A credentials modal appears on first load asking for access-key and access-secret-key (stored in memory only).
  3. The dashboard polls GET <endpoint>?query=<encodedQuery> for each unique query with credentials as HTTP headers.
  4. Resolved values are injected as props into child service components matched by name.
  5. 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). Use 2 for top-tier, 3 for 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 table
  • HOLO_CYAN / HOLO_BLUE — accent colors
  • HOLO_SURFACE / HOLO_GLASS — CSS gradient backgrounds for 3D faces
  • makeFaceStyles(W, H, D) — generates CSS transforms for the 6 faces of a 3D box
  • ThemeProvider, 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