react-dashstream 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,9 +11,9 @@ npm install react-dashstream
11
11
  ## Quick start
12
12
 
13
13
  ```tsx
14
- import "dashstream/dist/index.css";
15
- import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "dashstream";
16
- import type { ServiceMeta } from "dashstream";
14
+ import "react-dashstream/dist/index.css";
15
+ import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
16
+ import type { ServiceMeta } from "react-dashstream";
17
17
 
18
18
  const services: ServiceMeta[] = [
19
19
  {
@@ -71,130 +71,619 @@ Click a service to expand its topology. Click a component to drill into its inte
71
71
 
72
72
  ## Full example — multi-service, multi-layer
73
73
 
74
- Two services with different topologies rotating in a 3D carousel.
75
- This is the example included in `example/Dashboard.tsx` inside this package.
74
+ 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.
75
+
76
+ ---
77
+
78
+ ## External data source monitoring
79
+
80
+ 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.
81
+
82
+ ### How it works
83
+
84
+ 1. Set `liveData={true}` on `AIOPsDashboard` with `dataEndpoint` and `dataBindings`.
85
+ 2. A **credentials modal** appears on first load asking for `access-key` and `access-secret-key` (stored in memory only).
86
+ 3. The dashboard polls `GET <endpoint>?query=<encodedQuery>` for each unique query with credentials as HTTP headers.
87
+ 4. Resolved values are injected as **props** into child service components matched by `name`.
88
+ 5. The header shows **"DATA REFRESH FAILED"** if any queries fail.
89
+
90
+ ### Live data props
91
+
92
+ | Prop | Type | Default | Description |
93
+ | --------------------- | ---------------------------------------- | ------------- | ------------------------------------------------------ |
94
+ | `liveData` | `boolean` | `false` | Enable the live data pipeline. |
95
+ | `dataEndpoint` | `string` | — | Base URL. Queries sent as `GET <url>?query=<encoded>`. |
96
+ | `dataBindings` | `DataBindings` | — | Maps service → prop → query. See below. |
97
+ | `dataTransform` | `(raw) => unknown` | Numeric parse | Global transform for raw responses. |
98
+ | `dataRefreshInterval` | `number` | `60000` | Polling interval in ms. |
99
+ | `serviceDataBindings` | `Record<string, ServiceMetricBinding[]>` | — | Live metrics for the service stats dialog. |
100
+
101
+ ### Endpoint contract
102
+
103
+ | Aspect | Requirement |
104
+ | ------------ | --------------------------------------------------------------- |
105
+ | **Method** | `GET` |
106
+ | **URL** | `<dataEndpoint>?query=<urlEncodedQuery>` |
107
+ | **Headers** | `access-key` and `access-secret-key` |
108
+ | **Response** | Plain text body (trimmed). Default transform parses as number. |
109
+ | **Errors** | Non-2xx → counted as failure. Partial failures shown in header. |
110
+
111
+ For JSON responses (e.g. Prometheus), provide a custom `dataTransform`:
112
+
113
+ ```tsx
114
+ dataTransform={(raw) => {
115
+ const parsed = JSON.parse(String(raw));
116
+ return parsed?.data?.result?.[0]?.value?.[1] ?? raw;
117
+ }}
118
+ ```
119
+
120
+ ---
121
+
122
+ ### 1. Data bindings — injecting live props into nodes
123
+
124
+ `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.
125
+
126
+ A binding can be a bare query string or an object with a custom transform:
127
+
128
+ ```ts
129
+ type DataBinding = string | { query: string; transform?: (raw: unknown) => unknown };
130
+ type DataBindings = Record<string, Record<string, DataBinding>>;
131
+ ```
132
+
133
+ **Example — one service with a server and database:**
134
+
135
+ ```tsx
136
+ import "react-dashstream/dist/index.css";
137
+ import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
138
+
139
+ function statusFromValue(raw: unknown) {
140
+ const n = Number(raw);
141
+ if (n >= 85) return "critical";
142
+ if (n >= 70) return "warning";
143
+ return "online";
144
+ }
145
+
146
+ export default function App() {
147
+ return (
148
+ <AIOPsDashboard
149
+ brandName="LIVE DASHBOARD"
150
+ services={[{ name: "My Service", status: "online" }]}
151
+ liveData={true}
152
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
153
+ dataRefreshInterval={10000}
154
+ dataBindings={{
155
+ "My Service": {
156
+ // bare string → parsed as number automatically
157
+ cpuLoad: 'cpu_usage{instance="srv-01"}',
158
+ memLoad: 'memory_usage{instance="srv-01"}',
159
+
160
+ // object with transform → convert number to status string
161
+ status: {
162
+ query: 'up{instance="srv-01"}',
163
+ transform: statusFromValue,
164
+ },
165
+
166
+ // bind database props too
167
+ dbCapacity: 'disk_capacity{instance="db-01"}',
168
+ dbStatus: {
169
+ query: 'disk_capacity{instance="db-01"}',
170
+ transform: statusFromValue,
171
+ },
172
+ },
173
+ }}
174
+ >
175
+ <MyService name="My Service" />
176
+ </AIOPsDashboard>
177
+ );
178
+ }
179
+ ```
180
+
181
+ The component receives `cpuLoad`, `memLoad`, `status`, `dbCapacity`, and `dbStatus` as live props, overriding their defaults.
182
+
183
+ #### Multi-component services
184
+
185
+ 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.
76
186
 
77
187
  ```tsx
78
- import "dashstream/dist/index.css";
79
- import { AIOPsDashboard, Service, HumanNode, WebDispatcherNode, ServerNode, DatabaseNode } from "dashstream";
80
- import type { ServiceMeta } from "dashstream";
188
+ // Service component — accepts prefixed props for each server
189
+ interface ServiceXProps {
190
+ name: string;
191
+ status?: ComponentStatus;
192
+ srv1CpuLoad?: number;
193
+ srv1MemLoad?: number;
194
+ srv1Status?: ComponentStatus;
195
+ srv2CpuLoad?: number;
196
+ srv2MemLoad?: number;
197
+ srv2Status?: ComponentStatus;
198
+ srv3CpuLoad?: number;
199
+ srv3MemLoad?: number;
200
+ srv3Status?: ComponentStatus;
201
+ dbCapacity?: number;
202
+ dbStatus?: ComponentStatus;
203
+ }
204
+
205
+ function ServiceX({
206
+ name, status = "online",
207
+ srv1CpuLoad = 54, srv1MemLoad = 58, srv1Status = "online",
208
+ srv2CpuLoad = 63, srv2MemLoad = 66, srv2Status = "online",
209
+ srv3CpuLoad = 78, srv3MemLoad = 71, srv3Status = "online",
210
+ dbCapacity = 68, dbStatus = "online",
211
+ }: ServiceXProps) {
212
+ return (
213
+ <Service name={name} status={status} connections={[/* ... */]}>
214
+ <ServerNode name="SRV-X1" status={srv1Status}
215
+ cpuLoad={srv1CpuLoad} memLoad={srv1MemLoad} ... />
216
+ <ServerNode name="SRV-X2" status={srv2Status}
217
+ cpuLoad={srv2CpuLoad} memLoad={srv2MemLoad} ... />
218
+ <ServerNode name="SRV-X3" status={srv3Status}
219
+ cpuLoad={srv3CpuLoad} memLoad={srv3MemLoad} ... />
220
+ <DatabaseNode name="DB-X1" status={dbStatus}
221
+ capacity={dbCapacity} ... />
222
+ </Service>
223
+ );
224
+ }
225
+
226
+ // Dashboard — bind each prefixed prop to its query
227
+ <AIOPsDashboard
228
+ liveData={true}
229
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
230
+ dataBindings={{
231
+ ServiceX: {
232
+ status: { query: 'status{instance="svcx"}', transform: statusFromValue },
233
+ srv1CpuLoad: 'cpu_usage{instance="srvx-01"}',
234
+ srv1MemLoad: 'memory_usage{instance="srvx-01"}',
235
+ srv1Status: { query: 'status{instance="srvx-01"}', transform: statusFromValue },
236
+ srv2CpuLoad: 'cpu_usage{instance="srvx-02"}',
237
+ srv2MemLoad: 'memory_usage{instance="srvx-02"}',
238
+ srv2Status: { query: 'status{instance="srvx-02"}', transform: statusFromValue },
239
+ srv3CpuLoad: 'cpu_usage{instance="srvx-03"}',
240
+ srv3MemLoad: 'memory_usage{instance="srvx-03"}',
241
+ srv3Status: { query: 'status{instance="srvx-03"}', transform: statusFromValue },
242
+ dbCapacity: 'disk_capacity{instance="dbx-01"}',
243
+ dbStatus: { query: 'status{instance="dbx-01"}', transform: statusFromValue },
244
+ },
245
+ }}
246
+ services={[{ name: "ServiceX", status: "online" }]}
247
+ >
248
+ <ServiceX name="ServiceX" />
249
+ </AIOPsDashboard>
250
+ ```
251
+
252
+ 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.
253
+
254
+ ---
81
255
 
82
- // ── Service metadata (drives the stats dialog) ──────────────────────────
256
+ ### 2. Service dialog live KPI metrics
83
257
 
258
+ The **ServiceDialog** is the stats panel that appears when you click a service. It shows KPI rows and alerts.
259
+
260
+ **Static way** — pass `ServiceMeta` with hardcoded values:
261
+
262
+ ```tsx
84
263
  const services: ServiceMeta[] = [
85
264
  {
86
- name: "Payment Gateway",
87
- status: "critical",
88
- metrics: [
89
- { label: "Service Health", value: "76.3%", color: "#ff8c00" },
90
- { label: "Avg Response Time", value: "243ms", color: "#ff8c00" },
91
- { label: "Active Connections", value: "1,847", color: "#bb55ff" },
92
- { label: "Request Throughput", value: "3.2k req/s", color: "#00e5ff" },
93
- { label: "Error Rate", value: "3.81%", color: "#ff2255" },
94
- ],
95
- alerts: [{ level: "critical", message: "SRV-01 CPU at 99% — immediate action required" }],
96
- },
97
- {
98
- name: "Auth Service",
265
+ name: "My Service",
99
266
  status: "online",
100
267
  metrics: [
101
- { label: "Service Health", value: "99.9%", color: "#00ff88" },
102
- { label: "Avg Response Time", value: "11ms", color: "#00e5ff" },
103
- { label: "Active Sessions", value: "8,421", color: "#bb55ff" },
104
- { label: "Auth Rate", value: "920 req/s", color: "#00e5ff" },
105
- { label: "Failed Logins", value: "0.03%", color: "#00ff88" },
268
+ { label: "Uptime", value: "99.99%", color: "#00ff88" },
269
+ { label: "Avg Latency", value: "8ms", color: "#00e5ff" },
270
+ { label: "Error Rate", value: "0.02%", color: "#00ff88" },
106
271
  ],
107
272
  alerts: [{ level: "info", message: "All Systems Nominal" }],
108
273
  },
109
274
  ];
275
+ ```
276
+
277
+ **Live way** — use `serviceDataBindings` to fetch values from your endpoint:
278
+
279
+ ```tsx
280
+ <AIOPsDashboard
281
+ services={[{ name: "My Service", status: "online" }]}
282
+ liveData={true}
283
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
284
+ dataBindings={{ /* ... */ }}
285
+ serviceDataBindings={{
286
+ "My Service": [
287
+ { label: "CPU Load", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" },
288
+ { label: "Memory", query: 'memory_usage{instance="srv-01"}', unit: "%", color: "#bb55ff" },
289
+ { label: "Disk", query: 'disk_capacity{instance="db-01"}', unit: "%", color: "#ff8c00" },
290
+ ],
291
+ }}
292
+ >
293
+ ```
294
+
295
+ Each `ServiceMetricBinding`:
296
+
297
+ ```ts
298
+ interface ServiceMetricBinding {
299
+ label: string; // Row label
300
+ query: string; // PromQL query
301
+ unit?: string; // Suffix (e.g. "%", "ms")
302
+ color?: string; // Accent color
303
+ transform?: (raw: unknown) => string; // Custom formatter
304
+ }
305
+ ```
110
306
 
111
- // ── Payment Gateway 4-layer topology ──────────────────────────────────
307
+ When `serviceDataBindings` is provided for a service, live values **replace** the static `ServiceMeta.metrics`.
112
308
 
113
- function PaymentGateway({ name }: { name: string }) {
309
+ ---
310
+
311
+ ### 3. Component dialog — custom gauges
312
+
313
+ 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.
314
+
315
+ **Override these gauges** with the `dialogMetrics` prop on any compound node:
316
+
317
+ ```tsx
318
+ <ServerNode
319
+ ex={200}
320
+ ey={380}
321
+ compactOffset={{ x: -30, y: -20 }}
322
+ zIndex={8}
323
+ name="SRV-01"
324
+ subLabel="APP SERVER"
325
+ status="online"
326
+ cpuLoad={67}
327
+ memLoad={72}
328
+ dialogMetrics={[
329
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67, unit: "%" },
330
+ { id: "mem", label: "MEMORY", sublabel: "HEAP USAGE", value: 72, unit: "%" },
331
+ { id: "iops", label: "IOPS", sublabel: "DISK OPS", value: 45, unit: "k/s", icon: "disk" },
332
+ {
333
+ id: "threads",
334
+ label: "THREADS",
335
+ sublabel: "ACTIVE",
336
+ value: 82,
337
+ unit: "%",
338
+ warnAt: 60,
339
+ critAt: 80,
340
+ icon: "cpu",
341
+ },
342
+ ]}
343
+ />
344
+ ```
345
+
346
+ Each `ComponentDialogMetric`:
347
+
348
+ ```ts
349
+ interface ComponentDialogMetric {
350
+ id: string; // Unique key
351
+ label: string; // Upper label (e.g. "CPU")
352
+ sublabel: string; // Lower label (e.g. "PROCESSOR")
353
+ value: number; // 0–100 gauge value
354
+ unit?: string; // Suffix (default "%")
355
+ icon?: "cpu" | "mem" | "disk"; // Gauge icon (default "cpu")
356
+ warnAt?: number; // Orange threshold (default 70)
357
+ critAt?: number; // Red threshold (default 85)
358
+ color?: string; // Override bar color (bypasses thresholds)
359
+ }
360
+ ```
361
+
362
+ All compound nodes (`ServerNode`, `DatabaseNode`, `WebDispatcherNode`, `MessageServerNode`) accept `dialogMetrics`.
363
+
364
+ **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:
365
+
366
+ ```tsx
367
+ // 1. Define your service component with props for each custom metric
368
+ import { Service, ServerNode } from "react-dashstream";
369
+ import type { ComponentStatus } from "react-dashstream";
370
+
371
+ interface MyServiceProps {
372
+ name: string;
373
+ status?: ComponentStatus;
374
+ cpuLoad?: number;
375
+ memLoad?: number;
376
+ iops?: number;
377
+ threadCount?: number;
378
+ }
379
+
380
+ function MyService({
381
+ name,
382
+ status = "online",
383
+ cpuLoad = 42,
384
+ memLoad = 60,
385
+ iops = 20,
386
+ threadCount = 45,
387
+ }: MyServiceProps) {
114
388
  return (
115
389
  <Service
116
390
  name={name}
117
- status="critical"
118
- connections={[
119
- { from: [330, 100], to: [330, 230], visibleAtPhase: 2, color: "#00e5ff" },
120
- { from: [330, 230], to: [200, 380], visibleAtPhase: 3 },
121
- { from: [330, 230], to: [460, 380], visibleAtPhase: 3 },
122
- { from: [200, 380], to: [330, 520], visibleAtPhase: 4, color: "#ff8c00" },
123
- { from: [460, 380], to: [330, 520], visibleAtPhase: 4, color: "#ff8c00" },
124
- ]}
391
+ status={status}
392
+ connections={
393
+ [
394
+ /* ... */
395
+ ]
396
+ }
125
397
  >
126
- <HumanNode
127
- ex={330}
128
- ey={100}
129
- compactOffset={{ x: 0, y: -50 }}
130
- zIndex={10}
131
- visibleAtPhase={2}
132
- color="#00e5ff"
133
- scale={1.5}
134
- />
135
- <WebDispatcherNode
136
- ex={330}
137
- ey={230}
138
- compactOffset={{ x: 0, y: -25 }}
139
- zIndex={9}
140
- name="GATEWAY"
141
- subLabel="HTTP LAYER"
142
- status="online"
143
- traffic={82}
144
- activeRoutes={6}
145
- visibleAtPhase={2}
146
- />
147
398
  <ServerNode
148
399
  ex={200}
149
400
  ey={380}
150
- compactOffset={{ x: -30, y: 10 }}
401
+ compactOffset={{ x: -30, y: -20 }}
151
402
  zIndex={8}
152
403
  name="SRV-01"
153
- subLabel="PROCESSOR"
154
- status="critical"
155
- cpuLoad={99}
156
- memLoad={64}
404
+ subLabel="APP SERVER"
405
+ status={status}
406
+ cpuLoad={cpuLoad}
407
+ memLoad={memLoad}
408
+ dialogMetrics={[
409
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: cpuLoad },
410
+ { id: "mem", label: "MEMORY", sublabel: "HEAP", value: memLoad },
411
+ {
412
+ id: "iops",
413
+ label: "IOPS",
414
+ sublabel: "DISK OPS",
415
+ value: iops,
416
+ icon: "disk",
417
+ warnAt: 50,
418
+ critAt: 80,
419
+ },
420
+ {
421
+ id: "threads",
422
+ label: "THREADS",
423
+ sublabel: "ACTIVE",
424
+ value: threadCount,
425
+ icon: "cpu",
426
+ warnAt: 60,
427
+ critAt: 85,
428
+ },
429
+ ]}
157
430
  alert={{ offsetX: -160, offsetY: -60, align: "left" }}
158
431
  />
159
- <ServerNode
160
- ex={460}
161
- ey={380}
162
- compactOffset={{ x: 30, y: 10 }}
163
- zIndex={8}
164
- name="SRV-02"
165
- subLabel="PROCESSOR"
166
- delay="0.4s"
167
- status="online"
168
- cpuLoad={38}
169
- memLoad={51}
170
- />
171
- <DatabaseNode
172
- ex={330}
173
- ey={520}
174
- compactOffset={{ x: 0, y: 40 }}
175
- zIndex={7}
176
- name="PG-DB"
177
- subLabel="PRIMARY"
178
- color="#ff8c00"
179
- status="online"
180
- capacity={61}
181
- />
182
432
  </Service>
183
433
  );
184
434
  }
185
435
 
186
- // ── Auth Service 2-layer topology ─────────────────────────────────────
436
+ // 2. Bind each prop to a live query
437
+ <AIOPsDashboard
438
+ liveData={true}
439
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
440
+ dataBindings={{
441
+ "My Service": {
442
+ cpuLoad: 'cpu_usage{instance="srv-01"}',
443
+ memLoad: 'memory_usage{instance="srv-01"}',
444
+ iops: 'disk_iops{instance="srv-01"}',
445
+ threadCount: 'active_threads{instance="srv-01"}',
446
+ status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
447
+ },
448
+ }}
449
+ services={[{ name: "My Service", status: "online" }]}
450
+ >
451
+ <MyService name="My Service" />
452
+ </AIOPsDashboard>;
453
+ ```
454
+
455
+ 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.
456
+
457
+ ---
458
+
459
+ ### 4. Alerts — automatic threshold detection
460
+
461
+ Nodes **automatically** show alert callouts when metrics breach thresholds:
462
+
463
+ - **Warning** at 70% (orange callout)
464
+ - **Critical** at 85% (red callout)
465
+
466
+ This works from `cpuLoad`, `memLoad`, `traffic`, `queueDepth`, or `capacity` — no extra code needed:
467
+
468
+ ```tsx
469
+ // This server shows a critical alert automatically because cpuLoad > 85
470
+ <ServerNode
471
+ ex={200}
472
+ ey={380}
473
+ compactOffset={{ x: -30, y: -20 }}
474
+ zIndex={8}
475
+ name="SRV-01"
476
+ subLabel="PROCESSOR"
477
+ status="online"
478
+ cpuLoad={92}
479
+ memLoad={64}
480
+ />
481
+ ```
482
+
483
+ **Position the callout:**
484
+
485
+ ```tsx
486
+ <ServerNode
487
+ cpuLoad={92}
488
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
489
+ ...
490
+ />
491
+ ```
492
+
493
+ **Custom alert message:**
494
+
495
+ ```tsx
496
+ <ServerNode
497
+ cpuLoad={92}
498
+ alert={{ msg: "CPU overload — scale out", offsetX: -160, offsetY: -60, align: "left" }}
499
+ ...
500
+ />
501
+ ```
502
+
503
+ **Custom thresholds** via `dialogMetrics`:
504
+
505
+ ```tsx
506
+ <ServerNode
507
+ dialogMetrics={[
508
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67,
509
+ warnAt: 50, critAt: 75 }, // Alert triggers at 67% because critAt is 75
510
+ ]}
511
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
512
+ ...
513
+ />
514
+ ```
515
+
516
+ Alert `align` options: `"left"`, `"right"`, `"top"`, `"bottom"`.
517
+
518
+ ---
519
+
520
+ ### 5. Drill-down internals — custom sub-components
521
+
522
+ 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.
523
+
524
+ Provide **custom sub-components** via the `subComponents` prop:
525
+
526
+ ```tsx
527
+ import { ServerNode, CPU3D, Memory3D, DriveBay3D, ThreadPool3D } from "react-dashstream";
528
+ import type { SubComponentConfig } from "react-dashstream";
529
+
530
+ const internals: SubComponentConfig[] = [
531
+ {
532
+ id: "cpu-0",
533
+ label: "CPU-0",
534
+ status: "online",
535
+ element: <CPU3D label="CPU-0" load={67} color="#00e5ff" />,
536
+ },
537
+ {
538
+ id: "cpu-1",
539
+ label: "CPU-1",
540
+ status: "warning",
541
+ element: <CPU3D label="CPU-1" load={88} color="#ff8c00" status="warning" />,
542
+ },
543
+ {
544
+ id: "heap",
545
+ label: "HEAP",
546
+ status: "online",
547
+ element: <Memory3D label="HEAP" usedPercent={72} color="#8855ee" />,
548
+ },
549
+ {
550
+ id: "drive",
551
+ label: "DRIVE-0",
552
+ status: "online",
553
+ element: <DriveBay3D label="DRIVE-0" color="#00e5ff" activity={true} />,
554
+ },
555
+ {
556
+ id: "threads",
557
+ label: "THREADS",
558
+ status: "online",
559
+ element: <ThreadPool3D label="THREADS" color="#00e5ff" />,
560
+ },
561
+ ];
562
+
563
+ <ServerNode
564
+ ex={200}
565
+ ey={380}
566
+ compactOffset={{ x: -30, y: -20 }}
567
+ zIndex={8}
568
+ name="SRV-01"
569
+ subLabel="APP SERVER"
570
+ status="online"
571
+ cpuLoad={67}
572
+ memLoad={72}
573
+ subComponents={internals}
574
+ />;
575
+ ```
576
+
577
+ Each `SubComponentConfig`:
578
+
579
+ ```ts
580
+ interface SubComponentConfig {
581
+ id: string; // Unique key
582
+ label: string; // Display name
583
+ status: ComponentStatus; // Drives LED color
584
+ detail?: string; // Shown on fault
585
+ element: ReactNode; // The 3D element to render
586
+ }
587
+ ```
588
+
589
+ Available internal 3D components: `CPU3D`, `Memory3D`, `DriveBay3D`, `NetworkBlock3D`, `ThreadPool3D`, `Platter3D`, `Port3D`.
590
+
591
+ ---
592
+
593
+ ### 6. Graph series — custom sparklines
594
+
595
+ The drill-down also shows a **historical graph panel** with sparklines. By default, graphs are auto-generated from mock data.
596
+
597
+ Provide **custom graph data** via the `graphSeries` prop:
598
+
599
+ ```tsx
600
+ import type { GraphSeries } from "react-dashstream";
601
+
602
+ const graphs: GraphSeries[] = [
603
+ { id: "cpu", label: "CPU-0", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68, 55, 62] },
604
+ { id: "mem", label: "HEAP", unit: "%", color: "#8855ee", data: [60, 65, 72, 78, 82, 75, 70] },
605
+ { id: "iops", label: "DISK", unit: "k/s", color: "#ff8c00", data: [12, 15, 22, 18, 25, 20, 16] },
606
+ ];
607
+
608
+ <ServerNode
609
+ ex={200}
610
+ ey={380}
611
+ compactOffset={{ x: -30, y: -20 }}
612
+ zIndex={8}
613
+ name="SRV-01"
614
+ subLabel="APP SERVER"
615
+ status="online"
616
+ cpuLoad={67}
617
+ memLoad={72}
618
+ graphSeries={graphs}
619
+ />;
620
+ ```
621
+
622
+ Each `GraphSeries`:
623
+
624
+ ```ts
625
+ interface GraphSeries {
626
+ id: string; // Unique key
627
+ label: string; // Label above the sparkline
628
+ unit: string; // Suffix (e.g. "%", "kbps")
629
+ color: string; // Sparkline color
630
+ data: number[]; // Data points (most recent last)
631
+ }
632
+ ```
633
+
634
+ ---
635
+
636
+ ### 7. Putting it all together
637
+
638
+ A complete example with live data, service dialog metrics, component dialog gauges, sub-components, graphs, and alert positioning:
639
+
640
+ ```tsx
641
+ import "react-dashstream/dist/index.css";
642
+ import { AIOPsDashboard, Service, ServerNode, DatabaseNode, CPU3D, Memory3D } from "react-dashstream";
643
+ import type { ServiceMeta, DataBindings, ServiceMetricBinding } from "react-dashstream";
644
+
645
+ const services: ServiceMeta[] = [
646
+ {
647
+ name: "My Service",
648
+ status: "online",
649
+ metrics: [{ label: "Uptime", value: "99.99%", color: "#00ff88" }],
650
+ alerts: [{ level: "info", message: "All Systems Nominal" }],
651
+ },
652
+ ];
653
+
654
+ function statusFromValue(raw: unknown) {
655
+ const n = Number(raw);
656
+ if (n >= 85) return "critical";
657
+ if (n >= 70) return "warning";
658
+ return "online";
659
+ }
660
+
661
+ const dataBindings: DataBindings = {
662
+ "My Service": {
663
+ cpuLoad: 'cpu_usage{instance="srv-01"}',
664
+ memLoad: 'memory_usage{instance="srv-01"}',
665
+ status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
666
+ dbCapacity: 'disk_capacity{instance="db-01"}',
667
+ dbStatus: { query: 'disk_capacity{instance="db-01"}', transform: statusFromValue },
668
+ },
669
+ };
670
+
671
+ const serviceDataBindings: Record<string, ServiceMetricBinding[]> = {
672
+ "My Service": [
673
+ { label: "CPU Load", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" },
674
+ { label: "Memory", query: 'memory_usage{instance="srv-01"}', unit: "%", color: "#bb55ff" },
675
+ { label: "Disk", query: 'disk_capacity{instance="db-01"}', unit: "%", color: "#ff8c00" },
676
+ ],
677
+ };
187
678
 
188
- function AuthService({ name }: { name: string }) {
679
+ function MyService({ name, status, cpuLoad, memLoad, dbCapacity, dbStatus }: any) {
189
680
  return (
190
681
  <Service
191
682
  name={name}
192
- status="online"
683
+ status={status ?? "online"}
193
684
  connections={[
194
- { from: [200, 200], to: [200, 380], visibleAtPhase: 3 },
195
- { from: [460, 200], to: [460, 380], visibleAtPhase: 3 },
196
- { from: [200, 380], to: [330, 520], visibleAtPhase: 4, color: "#ff8c00" },
197
- { from: [460, 380], to: [330, 520], visibleAtPhase: 4, color: "#ff8c00" },
685
+ { from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
686
+ { from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
198
687
  ]}
199
688
  >
200
689
  <ServerNode
@@ -202,46 +691,57 @@ function AuthService({ name }: { name: string }) {
202
691
  ey={380}
203
692
  compactOffset={{ x: -30, y: -20 }}
204
693
  zIndex={8}
205
- name="AUTH-01"
206
- subLabel="IDENTITY"
207
- status="online"
208
- cpuLoad={34}
209
- memLoad={48}
694
+ name="SRV-01"
695
+ subLabel="APP SERVER"
696
+ status={status ?? "online"}
697
+ cpuLoad={cpuLoad ?? 42}
698
+ memLoad={memLoad ?? 60}
699
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
700
+ subComponents={[
701
+ {
702
+ id: "cpu",
703
+ label: "CPU-0",
704
+ status: "online",
705
+ element: <CPU3D label="CPU-0" load={cpuLoad ?? 42} />,
706
+ },
707
+ {
708
+ id: "mem",
709
+ label: "HEAP",
710
+ status: "online",
711
+ element: <Memory3D label="HEAP" usedPercent={memLoad ?? 60} />,
712
+ },
713
+ ]}
714
+ graphSeries={[
715
+ { id: "cpu", label: "CPU", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68] },
716
+ ]}
210
717
  />
211
- <ServerNode
718
+ <DatabaseNode
212
719
  ex={460}
213
720
  ey={380}
214
721
  compactOffset={{ x: 30, y: -20 }}
215
- zIndex={8}
216
- name="AUTH-02"
217
- subLabel="SESSION"
218
- delay="0.4s"
219
- status="online"
220
- cpuLoad={29}
221
- memLoad={42}
222
- />
223
- <DatabaseNode
224
- ex={330}
225
- ey={520}
226
- compactOffset={{ x: 0, y: 20 }}
227
722
  zIndex={7}
228
- name="AUTH-DB"
723
+ name="DB-01"
229
724
  subLabel="PRIMARY"
230
- color="#ff8c00"
231
- status="online"
232
- capacity={37}
725
+ status={dbStatus ?? "online"}
726
+ capacity={dbCapacity ?? 55}
727
+ alert={{ offsetX: 160, offsetY: -60, align: "right" }}
233
728
  />
234
729
  </Service>
235
730
  );
236
731
  }
237
732
 
238
- // ── Dashboard ───────────────────────────────────────────────────────────
239
-
240
733
  export default function App() {
241
734
  return (
242
- <AIOPsDashboard brandName="DASHSTREAM" brandTag="EXAMPLE" services={services}>
243
- <PaymentGateway name="Payment Gateway" />
244
- <AuthService name="Auth Service" />
735
+ <AIOPsDashboard
736
+ brandName="MY DASHBOARD"
737
+ services={services}
738
+ liveData={true}
739
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
740
+ dataRefreshInterval={10000}
741
+ dataBindings={dataBindings}
742
+ serviceDataBindings={serviceDataBindings}
743
+ >
744
+ <MyService name="My Service" />
245
745
  </AIOPsDashboard>
246
746
  );
247
747
  }
@@ -249,6 +749,57 @@ export default function App() {
249
749
 
250
750
  ---
251
751
 
752
+ ### 8. Data hooks
753
+
754
+ Access live data anywhere inside the dashboard tree:
755
+
756
+ ```tsx
757
+ import { useAIOpsData, useAIOpsDataOptional, useQueryResult } from "react-dashstream";
758
+
759
+ function CustomWidget() {
760
+ const { data, isRefreshing, lastRefreshError } = useAIOpsData();
761
+ const cpu = useQueryResult('cpu_usage{instance="srv-01"}');
762
+
763
+ return (
764
+ <div>
765
+ {isRefreshing && <span>Refreshing...</span>}
766
+ {lastRefreshError && <span>{lastRefreshError}</span>}
767
+ <span>CPU: {String(cpu)}</span>
768
+ </div>
769
+ );
770
+ }
771
+ ```
772
+
773
+ | Hook | Returns | Throws? |
774
+ | ------------------------ | -------------------------- | ---------------------------- |
775
+ | `useAIOpsData()` | Full `DataContextValue` | Yes — outside `DataProvider` |
776
+ | `useAIOpsDataOptional()` | `DataContextValue \| null` | No |
777
+ | `useQueryResult(query)` | Raw response or `null` | Yes — outside `DataProvider` |
778
+
779
+ ### 9. Standalone DataProvider
780
+
781
+ Use the data layer without the full dashboard shell:
782
+
783
+ ```tsx
784
+ import { DataProvider, useAIOpsData } from "react-dashstream";
785
+
786
+ function MyApp() {
787
+ return (
788
+ <DataProvider
789
+ config={{
790
+ endpoint: "https://prometheus.example.com/api/v1/query",
791
+ queries: ['cpu_usage{instance="srv-01"}'],
792
+ refreshInterval: 15000,
793
+ }}
794
+ >
795
+ <MyContent />
796
+ </DataProvider>
797
+ );
798
+ }
799
+ ```
800
+
801
+ ---
802
+
252
803
  ## Components
253
804
 
254
805
  ### Layout
@@ -263,15 +814,15 @@ export default function App() {
263
814
 
264
815
  These combine `ServiceNode` + 3D model + `componentInfo` into a single element:
265
816
 
266
- | Component | Key props | Description |
267
- | ------------------- | ------------------------------------------------- | ------------------------------------------------------- |
268
- | `ServerNode` | `status`, `cpuLoad`, `memLoad`, `brandLabel` | Application server tower with LEDs and CPU/memory bars. |
269
- | `DatabaseNode` | `status`, `capacity` | Three-platter database cylinder with capacity bar. |
270
- | `WebDispatcherNode` | `status`, `traffic`, `activeRoutes` | Network appliance with 8 port LEDs and traffic metrics. |
271
- | `MessageServerNode` | `status`, `queueDepth`, `msgsPerSec`, `instances` | Message server with instance LEDs and queue metrics. |
272
- | `HumanNode` | `status`, `scale` | SVG wireframe person icon for user/actor nodes. |
817
+ | Component | Key props | Description |
818
+ | ------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
819
+ | `ServerNode` | `status`, `cpuLoad`, `memLoad`, `brandLabel`, `dialogMetrics`, `subComponents`, `graphSeries`, `alert` | App server tower with LEDs and CPU/memory bars. |
820
+ | `DatabaseNode` | `status`, `capacity`, `dialogMetrics`, `subComponents`, `graphSeries`, `alert` | Three-platter database cylinder with capacity bar. |
821
+ | `WebDispatcherNode` | `status`, `traffic`, `activeRoutes`, `dialogMetrics`, `subComponents`, `graphSeries`, `alert` | Network appliance with 8 port LEDs and traffic metrics. |
822
+ | `MessageServerNode` | `status`, `queueDepth`, `msgsPerSec`, `instances`, `dialogMetrics`, `subComponents`, `graphSeries`, `alert` | Message server with instance LEDs and queue metrics. |
823
+ | `HumanNode` | `status`, `scale` | SVG wireframe person icon for user/actor nodes. |
273
824
 
274
- All compound nodes share: `ex`, `ey`, `compactOffset`, `zIndex`, `name`, `subLabel`, `color`, `delay`, `visibleAtPhase`, `alert`.
825
+ All compound nodes share: `ex`, `ey`, `compactOffset`, `zIndex`, `name`, `subLabel`, `color`, `delay`, `visibleAtPhase`.
275
826
 
276
827
  ### 3D models (low-level)
277
828
 
@@ -287,7 +838,7 @@ All 3D models accept: `rotateX`, `rotateY`, `rotateZ`, `scale`, `autoRotate`.
287
838
  | --------------- | ------------------------------------ | ---------------------------------------------------------------- |
288
839
  | `SyncBridge` | `synced`, `latencyMs` | Database replication bridge between primary and standby. |
289
840
  | `NodeCallout` | `status`, `title`, `msg`, `ex`, `ey` | Alert callout with leader line (auto-rendered by `ServiceNode`). |
290
- | `HoloBase` | `size`, `color`, `widthRatio` | Neon holographic base platform (auto-rendered by `Service`). Position via `baseConfig` on `Service`. |
841
+ | `HoloBase` | `size`, `color`, `widthRatio` | Neon holographic base platform (auto-rendered by `Service`). |
291
842
  | `SvgConnection` | `x1`, `y1`, `x2`, `y2`, `show` | Animated dashed SVG connection line. |
292
843
 
293
844
  ### Dialogs
@@ -319,29 +870,7 @@ Compose compound nodes inside a `Service` container. Each node needs:
319
870
  - **`ex`, `ey`** — Position in the expanded topology (pixels from top-left of scene).
320
871
  - **`compactOffset`** — Offset from the service center in the compact carousel view.
321
872
  - **`zIndex`** — Stacking order (higher tiers get higher values).
322
- - **`visibleAtPhase`** — When the node fades in during expansion (0–6). Use `2` for top-tier nodes, `3` for middle, etc.
323
-
324
- ### HoloBase position
325
-
326
- The holographic base platform rendered by each `Service` can be repositioned via `baseConfig`:
327
-
328
- ```tsx
329
- <Service
330
- name="My Service"
331
- baseConfig={{
332
- size: 90,
333
- color: "#00e5ff",
334
- widthRatio: 3,
335
- expandedX: 330, // absolute X in expanded view (default: container centre)
336
- expandedY: 500, // absolute Y in expanded view (default: 570)
337
- compactOffsetX: 0, // horizontal nudge in compact carousel view
338
- compactOffsetY: -20, // vertical nudge in compact carousel view
339
- }}
340
- ...
341
- >
342
- ```
343
-
344
- ### Connection lines
873
+ - **`visibleAtPhase`** — When the node fades in during expansion (0–6). Use `2` for top-tier, `3` for middle, etc.
345
874
 
346
875
  Define connection lines between nodes via the `connections` prop on `Service`:
347
876
 
@@ -352,41 +881,6 @@ connections={[
352
881
  ]}
353
882
  ```
354
883
 
355
- ### Alerts
356
-
357
- Nodes automatically detect threshold breaches and render alert callouts. Default thresholds:
358
-
359
- - **Warning** at 70%
360
- - **Critical** at 85%
361
-
362
- Position the callout with the `alert` prop:
363
-
364
- ```tsx
365
- <ServerNode
366
- alert={{ offsetX: -160, offsetY: -60, align: "left" }}
367
- cpuLoad={99}
368
- ...
369
- />
370
- ```
371
-
372
- ### Service dialog
373
-
374
- Pass `ServiceMeta` objects to `AIOPsDashboard` via the `services` prop to populate the stats panel that appears when a service is expanded:
375
-
376
- ```tsx
377
- const services: ServiceMeta[] = [
378
- {
379
- name: "My Service",
380
- status: "online",
381
- metrics: [
382
- { label: "Service Health", value: "99.9%", color: "#00ff88" },
383
- { label: "Avg Response Time", value: "14ms", color: "#00e5ff" },
384
- ],
385
- alerts: [{ level: "info", message: "All Systems Nominal" }],
386
- },
387
- ];
388
- ```
389
-
390
884
  ---
391
885
 
392
886
  ## Status types
@@ -407,7 +901,7 @@ type ComponentStatus = "online" | "warning" | "critical" | "offline";
407
901
  ## Theme constants
408
902
 
409
903
  ```ts
410
- import { STATUS_CFG, HOLO_CYAN, HOLO_BLUE, HOLO_SURFACE, HOLO_GLASS, makeFaceStyles } from "dashstream";
904
+ import { STATUS_CFG, HOLO_CYAN, HOLO_BLUE, HOLO_SURFACE, HOLO_GLASS, makeFaceStyles } from "react-dashstream";
411
905
  ```
412
906
 
413
907
  - `STATUS_CFG` — status-to-color lookup table