react-dashstream 0.0.10 → 0.1.1

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
@@ -1,916 +1,1057 @@
1
- # React DashStream
2
-
3
- Holographic 3D infrastructure monitoring dashboard for React. Pure CSS-3D — no WebGL, no canvas, no dependencies beyond React.
4
-
5
- ```
6
- npm install react-dashstream
7
- ```
8
-
9
- ---
10
-
11
- ## Quick start
12
-
13
- ```tsx
14
- import "react-dashstream/dist/index.css";
15
- import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
16
- import type { ServiceMeta } from "react-dashstream";
17
-
18
- const services: ServiceMeta[] = [
19
- {
20
- name: "My Service",
21
- status: "online",
22
- metrics: [
23
- { label: "Service Health", value: "99.9%", color: "#00ff88" },
24
- { label: "Avg Response Time", value: "14ms", color: "#00e5ff" },
25
- ],
26
- alerts: [{ level: "info", message: "All Systems Nominal" }],
27
- },
28
- ];
29
-
30
- export default function App() {
31
- return (
32
- <AIOPsDashboard brandName="MY DASHBOARD" services={services}>
33
- <Service
34
- name="My Service"
35
- status="online"
36
- connections={[
37
- { from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
38
- { from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
39
- ]}
40
- >
41
- <ServerNode
42
- ex={200}
43
- ey={380}
44
- compactOffset={{ x: -30, y: -20 }}
45
- zIndex={8}
46
- name="SRV-01"
47
- subLabel="APP SERVER"
48
- status="online"
49
- cpuLoad={42}
50
- memLoad={60}
51
- />
52
- <DatabaseNode
53
- ex={460}
54
- ey={380}
55
- compactOffset={{ x: 30, y: -20 }}
56
- zIndex={7}
57
- name="DB-01"
58
- subLabel="PRIMARY"
59
- status="online"
60
- capacity={55}
61
- />
62
- </Service>
63
- </AIOPsDashboard>
64
- );
65
- }
66
- ```
67
-
68
- Click a service to expand its topology. Click a component to drill into its internals.
69
-
70
- ---
71
-
72
- ## Full example — multi-service, multi-layer
73
-
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.
186
-
187
- ```tsx
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
- ---
255
-
256
- ### 2. Service dialog — live KPI metrics
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
263
- const services: ServiceMeta[] = [
264
- {
265
- name: "My Service",
266
- status: "online",
267
- metrics: [
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" },
271
- ],
272
- alerts: [{ level: "info", message: "All Systems Nominal" }],
273
- },
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
- ```
306
-
307
- When `serviceDataBindings` is provided for a service, live values **replace** the static `ServiceMeta.metrics`.
308
-
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) {
388
- return (
389
- <Service
390
- name={name}
391
- status={status}
392
- connections={
393
- [
394
- /* ... */
395
- ]
396
- }
397
- >
398
- <ServerNode
399
- ex={200}
400
- ey={380}
401
- compactOffset={{ x: -30, y: -20 }}
402
- zIndex={8}
403
- name="SRV-01"
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
- ]}
430
- alert={{ offsetX: -160, offsetY: -60, align: "left" }}
431
- />
432
- </Service>
433
- );
434
- }
435
-
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
- };
678
-
679
- function MyService({ name, status, cpuLoad, memLoad, dbCapacity, dbStatus }: any) {
680
- return (
681
- <Service
682
- name={name}
683
- status={status ?? "online"}
684
- connections={[
685
- { from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
686
- { from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
687
- ]}
688
- >
689
- <ServerNode
690
- ex={200}
691
- ey={380}
692
- compactOffset={{ x: -30, y: -20 }}
693
- zIndex={8}
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
- ]}
717
- />
718
- <DatabaseNode
719
- ex={460}
720
- ey={380}
721
- compactOffset={{ x: 30, y: -20 }}
722
- zIndex={7}
723
- name="DB-01"
724
- subLabel="PRIMARY"
725
- status={dbStatus ?? "online"}
726
- capacity={dbCapacity ?? 55}
727
- alert={{ offsetX: 160, offsetY: -60, align: "right" }}
728
- />
729
- </Service>
730
- );
731
- }
732
-
733
- export default function App() {
734
- return (
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" />
745
- </AIOPsDashboard>
746
- );
747
- }
748
- ```
749
-
750
- ---
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
-
803
- ## Components
804
-
805
- ### Layout
806
-
807
- | Component | Description |
808
- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
809
- | `AIOPsDashboard` | Full dashboard shell — header, carousel, and state management. Drop services in as children. |
810
- | `Service` | Service container — positions child nodes on a 3D orbit and draws connection lines between them. |
811
- | `ServiceNode` | Low-level positioned wrapper with floating animation, scan line, and labels. Used by the compound nodes below. |
812
-
813
- ### Compound nodes (recommended)
814
-
815
- These combine `ServiceNode` + 3D model + `componentInfo` into a single element:
816
-
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. |
824
-
825
- All compound nodes share: `ex`, `ey`, `compactOffset`, `zIndex`, `name`, `subLabel`, `color`, `delay`, `visibleAtPhase`.
826
-
827
- ### 3D models (low-level)
828
-
829
- If you need full control, use the raw 3D models inside a `ServiceNode`:
830
-
831
- `Server3D`, `Database3D`, `WebDispatcher3D`, `MessageServer3D`, `Human3D`
832
-
833
- All 3D models accept: `rotateX`, `rotateY`, `rotateZ`, `scale`, `autoRotate`.
834
-
835
- ### Status indicators
836
-
837
- | Component | Props | Description |
838
- | --------------- | ------------------------------------ | ---------------------------------------------------------------- |
839
- | `SyncBridge` | `synced`, `latencyMs` | Database replication bridge between primary and standby. |
840
- | `NodeCallout` | `status`, `title`, `msg`, `ex`, `ey` | Alert callout with leader line (auto-rendered by `ServiceNode`). |
841
- | `HoloBase` | `size`, `color`, `widthRatio` | Neon holographic base platform (auto-rendered by `Service`). |
842
- | `SvgConnection` | `x1`, `y1`, `x2`, `y2`, `show` | Animated dashed SVG connection line. |
843
-
844
- ### Dialogs
845
-
846
- | Component | Description |
847
- | ----------------- | ----------------------------------------------------------------------------------------------- |
848
- | `ServiceDialog` | Service-level stats panel — shows metrics and alerts. Auto-rendered when a service is expanded. |
849
- | `ComponentDialog` | Component drill-down with sub-component internals and sparkline graphs. |
850
-
851
- ### Drill-down internals
852
-
853
- Rendered inside `ComponentDialog` when a component is inspected:
854
-
855
- `CPU3D`, `Memory3D`, `DriveBay3D`, `NetworkBlock3D`, `ThreadPool3D`, `Platter3D`, `Port3D`, `HistoricalGraphPanel`, `ComponentDrillView`
856
-
857
- ### Pre-built services
858
-
859
- | Component | Topology |
860
- | ----------------- | --------------------------------------------------------------------------------- |
861
- | `SAPService` | Users → Web Dispatcher + Message Server → 3 App Servers → Primary DB + Standby DB |
862
- | `ExchangeService` | Users Dispatcher → 3 App Servers → Primary DB + Standby DB |
863
-
864
- ---
865
-
866
- ## Building a custom service
867
-
868
- Compose compound nodes inside a `Service` container. Each node needs:
869
-
870
- - **`ex`, `ey`** — Position in the expanded topology (pixels from top-left of scene).
871
- - **`compactOffset`** — Offset from the service center in the compact carousel view.
872
- - **`zIndex`** — Stacking order (higher tiers get higher values).
873
- - **`visibleAtPhase`** — When the node fades in during expansion (0–6). Use `2` for top-tier, `3` for middle, etc.
874
-
875
- Define connection lines between nodes via the `connections` prop on `Service`:
876
-
877
- ```tsx
878
- connections={[
879
- { from: [x1, y1], to: [x2, y2], visibleAtPhase: 3 },
880
- { from: [x1, y1], to: [x2, y2], visibleAtPhase: 4, color: "#ff8c00" },
881
- ]}
882
- ```
883
-
884
- ---
885
-
886
- ## Status types
887
-
888
- ```ts
889
- type ComponentStatus = "online" | "warning" | "critical" | "offline";
890
- ```
891
-
892
- | Status | Color | Glow |
893
- | ---------- | --------- | -------- |
894
- | `online` | `#00e5ff` | cyan |
895
- | `warning` | `#ff8c00` | orange |
896
- | `critical` | `#ff2255` | red |
897
- | `offline` | `#1e3a5a` | dim blue |
898
-
899
- ---
900
-
901
- ## Theme constants
902
-
903
- ```ts
904
- import { STATUS_CFG, HOLO_CYAN, HOLO_BLUE, HOLO_SURFACE, HOLO_GLASS, makeFaceStyles } from "react-dashstream";
905
- ```
906
-
907
- - `STATUS_CFG` — status-to-color lookup table
908
- - `HOLO_CYAN` / `HOLO_BLUE` — accent colors
909
- - `HOLO_SURFACE` / `HOLO_GLASS` — CSS gradient backgrounds for 3D faces
910
- - `makeFaceStyles(W, H, D)` — generates CSS transforms for the 6 faces of a 3D box
911
-
912
- ---
913
-
914
- ## License
915
-
916
- MIT
1
+ # React DashStream
2
+
3
+ Holographic 3D infrastructure monitoring dashboard for React. Pure CSS-3D — no WebGL, no canvas, no dependencies beyond React.
4
+
5
+ ```
6
+ npm install react-dashstream
7
+ ```
8
+
9
+ ---
10
+
11
+ ## Quick start
12
+
13
+ ```tsx
14
+ import "react-dashstream/dist/index.css";
15
+ import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
16
+ import type { ServiceMeta } from "react-dashstream";
17
+
18
+ const services: ServiceMeta[] = [
19
+ {
20
+ name: "My Service",
21
+ status: "online",
22
+ metrics: [
23
+ { label: "Service Health", value: "99.9%", color: "#00ff88" },
24
+ { label: "Avg Response Time", value: "14ms", color: "#00e5ff" },
25
+ ],
26
+ alerts: [{ level: "info", message: "All Systems Nominal" }],
27
+ },
28
+ ];
29
+
30
+ export default function App() {
31
+ return (
32
+ <AIOPsDashboard brandName="MY DASHBOARD" services={services}>
33
+ <Service
34
+ name="My Service"
35
+ status="online"
36
+ connections={[
37
+ { from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
38
+ { from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
39
+ ]}
40
+ >
41
+ <ServerNode
42
+ ex={200}
43
+ ey={380}
44
+ compactOffset={{ x: -30, y: -20 }}
45
+ zIndex={8}
46
+ name="SRV-01"
47
+ subLabel="APP SERVER"
48
+ status="online"
49
+ cpuLoad={42}
50
+ memLoad={60}
51
+ />
52
+ <DatabaseNode
53
+ ex={460}
54
+ ey={380}
55
+ compactOffset={{ x: 30, y: -20 }}
56
+ zIndex={7}
57
+ name="DB-01"
58
+ subLabel="PRIMARY"
59
+ status="online"
60
+ capacity={55}
61
+ />
62
+ </Service>
63
+ </AIOPsDashboard>
64
+ );
65
+ }
66
+ ```
67
+
68
+ Click a service to expand its topology. Click a component to drill into its internals.
69
+
70
+ ---
71
+
72
+ ## Full example — multi-service, multi-layer
73
+
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
+ ## Event Console
79
+
80
+ 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.
81
+
82
+ ### Quick start — API mode
83
+
84
+ ```tsx
85
+ import "react-dashstream/dist/index.css";
86
+ import { EventView } from "react-dashstream";
87
+ import type { EventApiConfig } from "react-dashstream";
88
+
89
+ const eventApiConfig: EventApiConfig = {
90
+ baseUrl: "https://your-monitoring-server.example.com",
91
+ // endpoint defaults to "/tsws/monitoring/api/v1.0/events/search"
92
+ payload: {
93
+ filter: {},
94
+ sortBy: "date_reception",
95
+ sortOrder: "DESC",
96
+ },
97
+ fieldMapping: {
98
+ id: "mc_ueid",
99
+ occurrence: "date_reception",
100
+ severityLastModified: "severity_last_modified",
101
+ severity: "severity",
102
+ owner: "owner",
103
+ class: "class",
104
+ host: "mc_host",
105
+ message: "msg",
106
+ remedySupportGroup: "ara_remedy_support_group",
107
+ incidentId: "ara_incident_id",
108
+ smsStatus: "ara_sms_status",
109
+ onCallNumber: "ara_on_call_number",
110
+ hostedApplication: "ara_hosted_application",
111
+ monitoringCategory: "ara_hosted_app_monitoring",
112
+ applicationSupportUnit: "ara_application_support_unit",
113
+ },
114
+ };
115
+
116
+ export default function Events() {
117
+ return <EventView apiConfig={eventApiConfig} />;
118
+ }
119
+ ```
120
+
121
+ 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.
122
+
123
+ ### How it works
124
+
125
+ 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.
126
+ 2. **POST request** sends `apiConfig.payload` as the JSON body.
127
+ 3. **Response format** — expects:
128
+ ```json
129
+ {
130
+ "eventSeverityCount": { "MAJOR": 1, "CRITICAL": 2, "MINOR": 3 },
131
+ "totalCount": 6,
132
+ "eventList": [{ "mc_ueid": "...", "msg": "...", ... }]
133
+ }
134
+ ```
135
+ 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.
136
+ 5. **Severity mapping** — raw severity strings (e.g. `"CRITICAL"`) are mapped to `EventSeverity` via `apiConfig.severityMap` (defaults to `{ CRITICAL: "Critical", MAJOR: "Major", MINOR: "Minor" }`).
137
+
138
+ ### Pre-fetched events mode
139
+
140
+ If you already have events data, pass them directly — no API call is made:
141
+
142
+ ```tsx
143
+ <EventView events={myEvents} title="My Event Console" />
144
+ ```
145
+
146
+ ### EventView props
147
+
148
+ | Prop | Type | Default | Description |
149
+ | ------------- | ---------------- | ----------------- | --------------------------------------------------------------------------- |
150
+ | `apiConfig` | `EventApiConfig` | — | API configuration (base URL, payload, field mapping). Required for API mode |
151
+ | `events` | `AIOpsEvent[]` | — | Pre-fetched events. When provided, `apiConfig` is not used |
152
+ | `credentials` | `Credentials` | — | Explicit credentials. Falls back to DataProvider context, then modal |
153
+ | `pageSize` | `number` | `15` | Number of rows per page |
154
+ | `title` | `string` | `"Event Console"` | Title displayed in the component header |
155
+
156
+ ### EventApiConfig
157
+
158
+ | Field | Type | Default | Description |
159
+ | ----------------- | ------------------------------- | ---------------------------------------------------------- | ------------------------------------------------- |
160
+ | `baseUrl` | `string` | (required) | Protocol + host, e.g. `"https://mon.example.com"` |
161
+ | `endpoint` | `string` | `"/tsws/monitoring/api/v1.0/events/search"` | Path appended to `baseUrl` |
162
+ | `payload` | `Record<string, unknown>` | (required) | POST body sent with every request |
163
+ | `fieldMapping` | `EventFieldMapping` | (required) | Maps API field names → `AIOpsEvent` fields |
164
+ | `severityMap` | `Record<string, EventSeverity>` | `{ CRITICAL: "Critical", MAJOR: "Major", MINOR: "Minor" }` | Maps raw severity → `EventSeverity` |
165
+ | `refreshInterval` | `number` | `60000` | Polling interval in milliseconds |
166
+
167
+ ### Field mapping
168
+
169
+ The `fieldMapping` object maps every `AIOpsEvent` property to the key name the API returns. All fields are required:
170
+
171
+ ```ts
172
+ const fieldMapping: EventFieldMapping = {
173
+ id: "mc_ueid", // unique event identifier
174
+ occurrence: "date_reception", // datetime
175
+ severityLastModified: "severity_last_modified",
176
+ severity: "severity", // mapped through severityMap
177
+ owner: "owner",
178
+ class: "class", // Self-monitoring, SCOM, etc.
179
+ host: "mc_host",
180
+ message: "msg",
181
+ remedySupportGroup: "ara_remedy_support_group",
182
+ incidentId: "ara_incident_id",
183
+ smsStatus: "ara_sms_status", // "", "SUCCESS", "FAILED"
184
+ onCallNumber: "ara_on_call_number",
185
+ hostedApplication: "ara_hosted_application",
186
+ monitoringCategory: "ara_hosted_app_monitoring", // "24/7" or "Office Hours"
187
+ applicationSupportUnit: "ara_application_support_unit",
188
+ };
189
+ ```
190
+
191
+ ### Credential resolution order
192
+
193
+ 1. `credentials` prop (if provided)
194
+ 2. `DataProvider` context credentials (when inside `AIOPsDashboard` with `liveData`)
195
+ 3. Built-in credentials modal (same UI as the 3D dashboard)
196
+
197
+ ### Severity colors
198
+
199
+ | Severity | Color | Usage |
200
+ | -------- | --------- | ------------------ |
201
+ | Minor | `#ffb800` | Amber, low urgency |
202
+ | Major | `#ff6600` | Orange, attention |
203
+ | Critical | `#ff2255` | Red, immediate |
204
+
205
+ ### Features
206
+
207
+ - **Live API polling** POST to configurable endpoint with auto-refresh
208
+ - **Field mapping** maps arbitrary API response keys to typed event columns
209
+ - **Shared authentication** reuses credentials from `DataProvider` context when available
210
+ - **Severity filter chips** — toggle Critical, Major, Minor with live counts
211
+ - **Free-text search** — filters across message, host, owner, and incident ID
212
+ - **Sortable columns** — click any header to cycle ascending / descending / none
213
+ - **Pagination** configurable page size (10, 15, 25, 50)
214
+ - **Loading / error states** — spinner, error badge, last-refresh timestamp, manual refresh button
215
+ - **Holographic theme** — dark translucent panels, glowing severity indicators, scan-line header animation
216
+
217
+ ---
218
+
219
+ ## External data source monitoring
220
+
221
+ 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.
222
+
223
+ ### How it works
224
+
225
+ 1. Set `liveData={true}` on `AIOPsDashboard` with `dataEndpoint` and `dataBindings`.
226
+ 2. A **credentials modal** appears on first load asking for `access-key` and `access-secret-key` (stored in memory only).
227
+ 3. The dashboard polls `GET <endpoint>?query=<encodedQuery>` for each unique query with credentials as HTTP headers.
228
+ 4. Resolved values are injected as **props** into child service components matched by `name`.
229
+ 5. The header shows **"DATA REFRESH FAILED"** if any queries fail.
230
+
231
+ ### Live data props
232
+
233
+ | Prop | Type | Default | Description |
234
+ | --------------------- | ---------------------------------------- | ------------- | ------------------------------------------------------ |
235
+ | `liveData` | `boolean` | `false` | Enable the live data pipeline. |
236
+ | `dataEndpoint` | `string` | — | Base URL. Queries sent as `GET <url>?query=<encoded>`. |
237
+ | `dataBindings` | `DataBindings` | — | Maps service → prop → query. See below. |
238
+ | `dataTransform` | `(raw) => unknown` | Numeric parse | Global transform for raw responses. |
239
+ | `dataRefreshInterval` | `number` | `60000` | Polling interval in ms. |
240
+ | `serviceDataBindings` | `Record<string, ServiceMetricBinding[]>` | — | Live metrics for the service stats dialog. |
241
+
242
+ ### Endpoint contract
243
+
244
+ | Aspect | Requirement |
245
+ | ------------ | --------------------------------------------------------------- |
246
+ | **Method** | `GET` |
247
+ | **URL** | `<dataEndpoint>?query=<urlEncodedQuery>` |
248
+ | **Headers** | `access-key` and `access-secret-key` |
249
+ | **Response** | Plain text body (trimmed). Default transform parses as number. |
250
+ | **Errors** | Non-2xx → counted as failure. Partial failures shown in header. |
251
+
252
+ For JSON responses (e.g. Prometheus), provide a custom `dataTransform`:
253
+
254
+ ```tsx
255
+ dataTransform={(raw) => {
256
+ const parsed = JSON.parse(String(raw));
257
+ return parsed?.data?.result?.[0]?.value?.[1] ?? raw;
258
+ }}
259
+ ```
260
+
261
+ ---
262
+
263
+ ### 1. Data bindings — injecting live props into nodes
264
+
265
+ `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.
266
+
267
+ A binding can be a bare query string or an object with a custom transform:
268
+
269
+ ```ts
270
+ type DataBinding = string | { query: string; transform?: (raw: unknown) => unknown };
271
+ type DataBindings = Record<string, Record<string, DataBinding>>;
272
+ ```
273
+
274
+ **Example — one service with a server and database:**
275
+
276
+ ```tsx
277
+ import "react-dashstream/dist/index.css";
278
+ import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
279
+
280
+ function statusFromValue(raw: unknown) {
281
+ const n = Number(raw);
282
+ if (n >= 85) return "critical";
283
+ if (n >= 70) return "warning";
284
+ return "online";
285
+ }
286
+
287
+ export default function App() {
288
+ return (
289
+ <AIOPsDashboard
290
+ brandName="LIVE DASHBOARD"
291
+ services={[{ name: "My Service", status: "online" }]}
292
+ liveData={true}
293
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
294
+ dataRefreshInterval={10000}
295
+ dataBindings={{
296
+ "My Service": {
297
+ // bare string → parsed as number automatically
298
+ cpuLoad: 'cpu_usage{instance="srv-01"}',
299
+ memLoad: 'memory_usage{instance="srv-01"}',
300
+
301
+ // object with transform convert number to status string
302
+ status: {
303
+ query: 'up{instance="srv-01"}',
304
+ transform: statusFromValue,
305
+ },
306
+
307
+ // bind database props too
308
+ dbCapacity: 'disk_capacity{instance="db-01"}',
309
+ dbStatus: {
310
+ query: 'disk_capacity{instance="db-01"}',
311
+ transform: statusFromValue,
312
+ },
313
+ },
314
+ }}
315
+ >
316
+ <MyService name="My Service" />
317
+ </AIOPsDashboard>
318
+ );
319
+ }
320
+ ```
321
+
322
+ The component receives `cpuLoad`, `memLoad`, `status`, `dbCapacity`, and `dbStatus` as live props, overriding their defaults.
323
+
324
+ #### Multi-component services
325
+
326
+ 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.
327
+
328
+ ```tsx
329
+ // Service component accepts prefixed props for each server
330
+ interface ServiceXProps {
331
+ name: string;
332
+ status?: ComponentStatus;
333
+ srv1CpuLoad?: number;
334
+ srv1MemLoad?: number;
335
+ srv1Status?: ComponentStatus;
336
+ srv2CpuLoad?: number;
337
+ srv2MemLoad?: number;
338
+ srv2Status?: ComponentStatus;
339
+ srv3CpuLoad?: number;
340
+ srv3MemLoad?: number;
341
+ srv3Status?: ComponentStatus;
342
+ dbCapacity?: number;
343
+ dbStatus?: ComponentStatus;
344
+ }
345
+
346
+ function ServiceX({
347
+ name, status = "online",
348
+ srv1CpuLoad = 54, srv1MemLoad = 58, srv1Status = "online",
349
+ srv2CpuLoad = 63, srv2MemLoad = 66, srv2Status = "online",
350
+ srv3CpuLoad = 78, srv3MemLoad = 71, srv3Status = "online",
351
+ dbCapacity = 68, dbStatus = "online",
352
+ }: ServiceXProps) {
353
+ return (
354
+ <Service name={name} status={status} connections={[/* ... */]}>
355
+ <ServerNode name="SRV-X1" status={srv1Status}
356
+ cpuLoad={srv1CpuLoad} memLoad={srv1MemLoad} ... />
357
+ <ServerNode name="SRV-X2" status={srv2Status}
358
+ cpuLoad={srv2CpuLoad} memLoad={srv2MemLoad} ... />
359
+ <ServerNode name="SRV-X3" status={srv3Status}
360
+ cpuLoad={srv3CpuLoad} memLoad={srv3MemLoad} ... />
361
+ <DatabaseNode name="DB-X1" status={dbStatus}
362
+ capacity={dbCapacity} ... />
363
+ </Service>
364
+ );
365
+ }
366
+
367
+ // Dashboard bind each prefixed prop to its query
368
+ <AIOPsDashboard
369
+ liveData={true}
370
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
371
+ dataBindings={{
372
+ ServiceX: {
373
+ status: { query: 'status{instance="svcx"}', transform: statusFromValue },
374
+ srv1CpuLoad: 'cpu_usage{instance="srvx-01"}',
375
+ srv1MemLoad: 'memory_usage{instance="srvx-01"}',
376
+ srv1Status: { query: 'status{instance="srvx-01"}', transform: statusFromValue },
377
+ srv2CpuLoad: 'cpu_usage{instance="srvx-02"}',
378
+ srv2MemLoad: 'memory_usage{instance="srvx-02"}',
379
+ srv2Status: { query: 'status{instance="srvx-02"}', transform: statusFromValue },
380
+ srv3CpuLoad: 'cpu_usage{instance="srvx-03"}',
381
+ srv3MemLoad: 'memory_usage{instance="srvx-03"}',
382
+ srv3Status: { query: 'status{instance="srvx-03"}', transform: statusFromValue },
383
+ dbCapacity: 'disk_capacity{instance="dbx-01"}',
384
+ dbStatus: { query: 'status{instance="dbx-01"}', transform: statusFromValue },
385
+ },
386
+ }}
387
+ services={[{ name: "ServiceX", status: "online" }]}
388
+ >
389
+ <ServiceX name="ServiceX" />
390
+ </AIOPsDashboard>
391
+ ```
392
+
393
+ 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.
394
+
395
+ ---
396
+
397
+ ### 2. Service dialog — live KPI metrics
398
+
399
+ The **ServiceDialog** is the stats panel that appears when you click a service. It shows KPI rows and alerts.
400
+
401
+ **Static way** pass `ServiceMeta` with hardcoded values:
402
+
403
+ ```tsx
404
+ const services: ServiceMeta[] = [
405
+ {
406
+ name: "My Service",
407
+ status: "online",
408
+ metrics: [
409
+ { label: "Uptime", value: "99.99%", color: "#00ff88" },
410
+ { label: "Avg Latency", value: "8ms", color: "#00e5ff" },
411
+ { label: "Error Rate", value: "0.02%", color: "#00ff88" },
412
+ ],
413
+ alerts: [{ level: "info", message: "All Systems Nominal" }],
414
+ },
415
+ ];
416
+ ```
417
+
418
+ **Live way** — use `serviceDataBindings` to fetch values from your endpoint:
419
+
420
+ ```tsx
421
+ <AIOPsDashboard
422
+ services={[{ name: "My Service", status: "online" }]}
423
+ liveData={true}
424
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
425
+ dataBindings={{ /* ... */ }}
426
+ serviceDataBindings={{
427
+ "My Service": [
428
+ { label: "CPU Load", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" },
429
+ { label: "Memory", query: 'memory_usage{instance="srv-01"}', unit: "%", color: "#bb55ff" },
430
+ { label: "Disk", query: 'disk_capacity{instance="db-01"}', unit: "%", color: "#ff8c00" },
431
+ ],
432
+ }}
433
+ >
434
+ ```
435
+
436
+ Each `ServiceMetricBinding`:
437
+
438
+ ```ts
439
+ interface ServiceMetricBinding {
440
+ label: string; // Row label
441
+ query: string; // PromQL query
442
+ unit?: string; // Suffix (e.g. "%", "ms")
443
+ color?: string; // Accent color
444
+ transform?: (raw: unknown) => string; // Custom formatter
445
+ }
446
+ ```
447
+
448
+ When `serviceDataBindings` is provided for a service, live values **replace** the static `ServiceMeta.metrics`.
449
+
450
+ ---
451
+
452
+ ### 3. Component dialog — custom gauges
453
+
454
+ 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.
455
+
456
+ **Override these gauges** with the `dialogMetrics` prop on any compound node:
457
+
458
+ ```tsx
459
+ <ServerNode
460
+ ex={200}
461
+ ey={380}
462
+ compactOffset={{ x: -30, y: -20 }}
463
+ zIndex={8}
464
+ name="SRV-01"
465
+ subLabel="APP SERVER"
466
+ status="online"
467
+ cpuLoad={67}
468
+ memLoad={72}
469
+ dialogMetrics={[
470
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67, unit: "%" },
471
+ { id: "mem", label: "MEMORY", sublabel: "HEAP USAGE", value: 72, unit: "%" },
472
+ { id: "iops", label: "IOPS", sublabel: "DISK OPS", value: 45, unit: "k/s", icon: "disk" },
473
+ {
474
+ id: "threads",
475
+ label: "THREADS",
476
+ sublabel: "ACTIVE",
477
+ value: 82,
478
+ unit: "%",
479
+ warnAt: 60,
480
+ critAt: 80,
481
+ icon: "cpu",
482
+ },
483
+ ]}
484
+ />
485
+ ```
486
+
487
+ Each `ComponentDialogMetric`:
488
+
489
+ ```ts
490
+ interface ComponentDialogMetric {
491
+ id: string; // Unique key
492
+ label: string; // Upper label (e.g. "CPU")
493
+ sublabel: string; // Lower label (e.g. "PROCESSOR")
494
+ value: number; // 0–100 gauge value
495
+ unit?: string; // Suffix (default "%")
496
+ icon?: "cpu" | "mem" | "disk"; // Gauge icon (default "cpu")
497
+ warnAt?: number; // Orange threshold (default 70)
498
+ critAt?: number; // Red threshold (default 85)
499
+ color?: string; // Override bar color (bypasses thresholds)
500
+ }
501
+ ```
502
+
503
+ All compound nodes (`ServerNode`, `DatabaseNode`, `WebDispatcherNode`, `MessageServerNode`) accept `dialogMetrics`.
504
+
505
+ **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:
506
+
507
+ ```tsx
508
+ // 1. Define your service component with props for each custom metric
509
+ import { Service, ServerNode } from "react-dashstream";
510
+ import type { ComponentStatus } from "react-dashstream";
511
+
512
+ interface MyServiceProps {
513
+ name: string;
514
+ status?: ComponentStatus;
515
+ cpuLoad?: number;
516
+ memLoad?: number;
517
+ iops?: number;
518
+ threadCount?: number;
519
+ }
520
+
521
+ function MyService({
522
+ name,
523
+ status = "online",
524
+ cpuLoad = 42,
525
+ memLoad = 60,
526
+ iops = 20,
527
+ threadCount = 45,
528
+ }: MyServiceProps) {
529
+ return (
530
+ <Service
531
+ name={name}
532
+ status={status}
533
+ connections={
534
+ [
535
+ /* ... */
536
+ ]
537
+ }
538
+ >
539
+ <ServerNode
540
+ ex={200}
541
+ ey={380}
542
+ compactOffset={{ x: -30, y: -20 }}
543
+ zIndex={8}
544
+ name="SRV-01"
545
+ subLabel="APP SERVER"
546
+ status={status}
547
+ cpuLoad={cpuLoad}
548
+ memLoad={memLoad}
549
+ dialogMetrics={[
550
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: cpuLoad },
551
+ { id: "mem", label: "MEMORY", sublabel: "HEAP", value: memLoad },
552
+ {
553
+ id: "iops",
554
+ label: "IOPS",
555
+ sublabel: "DISK OPS",
556
+ value: iops,
557
+ icon: "disk",
558
+ warnAt: 50,
559
+ critAt: 80,
560
+ },
561
+ {
562
+ id: "threads",
563
+ label: "THREADS",
564
+ sublabel: "ACTIVE",
565
+ value: threadCount,
566
+ icon: "cpu",
567
+ warnAt: 60,
568
+ critAt: 85,
569
+ },
570
+ ]}
571
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
572
+ />
573
+ </Service>
574
+ );
575
+ }
576
+
577
+ // 2. Bind each prop to a live query
578
+ <AIOPsDashboard
579
+ liveData={true}
580
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
581
+ dataBindings={{
582
+ "My Service": {
583
+ cpuLoad: 'cpu_usage{instance="srv-01"}',
584
+ memLoad: 'memory_usage{instance="srv-01"}',
585
+ iops: 'disk_iops{instance="srv-01"}',
586
+ threadCount: 'active_threads{instance="srv-01"}',
587
+ status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
588
+ },
589
+ }}
590
+ services={[{ name: "My Service", status: "online" }]}
591
+ >
592
+ <MyService name="My Service" />
593
+ </AIOPsDashboard>;
594
+ ```
595
+
596
+ 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.
597
+
598
+ ---
599
+
600
+ ### 4. Alerts automatic threshold detection
601
+
602
+ Nodes **automatically** show alert callouts when metrics breach thresholds:
603
+
604
+ - **Warning** at 70% (orange callout)
605
+ - **Critical** at 85% (red callout)
606
+
607
+ This works from `cpuLoad`, `memLoad`, `traffic`, `queueDepth`, or `capacity` — no extra code needed:
608
+
609
+ ```tsx
610
+ // This server shows a critical alert automatically because cpuLoad > 85
611
+ <ServerNode
612
+ ex={200}
613
+ ey={380}
614
+ compactOffset={{ x: -30, y: -20 }}
615
+ zIndex={8}
616
+ name="SRV-01"
617
+ subLabel="PROCESSOR"
618
+ status="online"
619
+ cpuLoad={92}
620
+ memLoad={64}
621
+ />
622
+ ```
623
+
624
+ **Position the callout:**
625
+
626
+ ```tsx
627
+ <ServerNode
628
+ cpuLoad={92}
629
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
630
+ ...
631
+ />
632
+ ```
633
+
634
+ **Custom alert message:**
635
+
636
+ ```tsx
637
+ <ServerNode
638
+ cpuLoad={92}
639
+ alert={{ msg: "CPU overload — scale out", offsetX: -160, offsetY: -60, align: "left" }}
640
+ ...
641
+ />
642
+ ```
643
+
644
+ **Custom thresholds** via `dialogMetrics`:
645
+
646
+ ```tsx
647
+ <ServerNode
648
+ dialogMetrics={[
649
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67,
650
+ warnAt: 50, critAt: 75 }, // Alert triggers at 67% because critAt is 75
651
+ ]}
652
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
653
+ ...
654
+ />
655
+ ```
656
+
657
+ Alert `align` options: `"left"`, `"right"`, `"top"`, `"bottom"`.
658
+
659
+ ---
660
+
661
+ ### 5. Drill-down internals — custom sub-components
662
+
663
+ 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.
664
+
665
+ Provide **custom sub-components** via the `subComponents` prop:
666
+
667
+ ```tsx
668
+ import { ServerNode, CPU3D, Memory3D, DriveBay3D, ThreadPool3D } from "react-dashstream";
669
+ import type { SubComponentConfig } from "react-dashstream";
670
+
671
+ const internals: SubComponentConfig[] = [
672
+ {
673
+ id: "cpu-0",
674
+ label: "CPU-0",
675
+ status: "online",
676
+ element: <CPU3D label="CPU-0" load={67} color="#00e5ff" />,
677
+ },
678
+ {
679
+ id: "cpu-1",
680
+ label: "CPU-1",
681
+ status: "warning",
682
+ element: <CPU3D label="CPU-1" load={88} color="#ff8c00" status="warning" />,
683
+ },
684
+ {
685
+ id: "heap",
686
+ label: "HEAP",
687
+ status: "online",
688
+ element: <Memory3D label="HEAP" usedPercent={72} color="#8855ee" />,
689
+ },
690
+ {
691
+ id: "drive",
692
+ label: "DRIVE-0",
693
+ status: "online",
694
+ element: <DriveBay3D label="DRIVE-0" color="#00e5ff" activity={true} />,
695
+ },
696
+ {
697
+ id: "threads",
698
+ label: "THREADS",
699
+ status: "online",
700
+ element: <ThreadPool3D label="THREADS" color="#00e5ff" />,
701
+ },
702
+ ];
703
+
704
+ <ServerNode
705
+ ex={200}
706
+ ey={380}
707
+ compactOffset={{ x: -30, y: -20 }}
708
+ zIndex={8}
709
+ name="SRV-01"
710
+ subLabel="APP SERVER"
711
+ status="online"
712
+ cpuLoad={67}
713
+ memLoad={72}
714
+ subComponents={internals}
715
+ />;
716
+ ```
717
+
718
+ Each `SubComponentConfig`:
719
+
720
+ ```ts
721
+ interface SubComponentConfig {
722
+ id: string; // Unique key
723
+ label: string; // Display name
724
+ status: ComponentStatus; // Drives LED color
725
+ detail?: string; // Shown on fault
726
+ element: ReactNode; // The 3D element to render
727
+ }
728
+ ```
729
+
730
+ Available internal 3D components: `CPU3D`, `Memory3D`, `DriveBay3D`, `NetworkBlock3D`, `ThreadPool3D`, `Platter3D`, `Port3D`.
731
+
732
+ ---
733
+
734
+ ### 6. Graph series — custom sparklines
735
+
736
+ The drill-down also shows a **historical graph panel** with sparklines. By default, graphs are auto-generated from mock data.
737
+
738
+ Provide **custom graph data** via the `graphSeries` prop:
739
+
740
+ ```tsx
741
+ import type { GraphSeries } from "react-dashstream";
742
+
743
+ const graphs: GraphSeries[] = [
744
+ { id: "cpu", label: "CPU-0", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68, 55, 62] },
745
+ { id: "mem", label: "HEAP", unit: "%", color: "#8855ee", data: [60, 65, 72, 78, 82, 75, 70] },
746
+ { id: "iops", label: "DISK", unit: "k/s", color: "#ff8c00", data: [12, 15, 22, 18, 25, 20, 16] },
747
+ ];
748
+
749
+ <ServerNode
750
+ ex={200}
751
+ ey={380}
752
+ compactOffset={{ x: -30, y: -20 }}
753
+ zIndex={8}
754
+ name="SRV-01"
755
+ subLabel="APP SERVER"
756
+ status="online"
757
+ cpuLoad={67}
758
+ memLoad={72}
759
+ graphSeries={graphs}
760
+ />;
761
+ ```
762
+
763
+ Each `GraphSeries`:
764
+
765
+ ```ts
766
+ interface GraphSeries {
767
+ id: string; // Unique key
768
+ label: string; // Label above the sparkline
769
+ unit: string; // Suffix (e.g. "%", "kbps")
770
+ color: string; // Sparkline color
771
+ data: number[]; // Data points (most recent last)
772
+ }
773
+ ```
774
+
775
+ ---
776
+
777
+ ### 7. Putting it all together
778
+
779
+ A complete example with live data, service dialog metrics, component dialog gauges, sub-components, graphs, and alert positioning:
780
+
781
+ ```tsx
782
+ import "react-dashstream/dist/index.css";
783
+ import { AIOPsDashboard, Service, ServerNode, DatabaseNode, CPU3D, Memory3D } from "react-dashstream";
784
+ import type { ServiceMeta, DataBindings, ServiceMetricBinding } from "react-dashstream";
785
+
786
+ const services: ServiceMeta[] = [
787
+ {
788
+ name: "My Service",
789
+ status: "online",
790
+ metrics: [{ label: "Uptime", value: "99.99%", color: "#00ff88" }],
791
+ alerts: [{ level: "info", message: "All Systems Nominal" }],
792
+ },
793
+ ];
794
+
795
+ function statusFromValue(raw: unknown) {
796
+ const n = Number(raw);
797
+ if (n >= 85) return "critical";
798
+ if (n >= 70) return "warning";
799
+ return "online";
800
+ }
801
+
802
+ const dataBindings: DataBindings = {
803
+ "My Service": {
804
+ cpuLoad: 'cpu_usage{instance="srv-01"}',
805
+ memLoad: 'memory_usage{instance="srv-01"}',
806
+ status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
807
+ dbCapacity: 'disk_capacity{instance="db-01"}',
808
+ dbStatus: { query: 'disk_capacity{instance="db-01"}', transform: statusFromValue },
809
+ },
810
+ };
811
+
812
+ const serviceDataBindings: Record<string, ServiceMetricBinding[]> = {
813
+ "My Service": [
814
+ { label: "CPU Load", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" },
815
+ { label: "Memory", query: 'memory_usage{instance="srv-01"}', unit: "%", color: "#bb55ff" },
816
+ { label: "Disk", query: 'disk_capacity{instance="db-01"}', unit: "%", color: "#ff8c00" },
817
+ ],
818
+ };
819
+
820
+ function MyService({ name, status, cpuLoad, memLoad, dbCapacity, dbStatus }: any) {
821
+ return (
822
+ <Service
823
+ name={name}
824
+ status={status ?? "online"}
825
+ connections={[
826
+ { from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
827
+ { from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
828
+ ]}
829
+ >
830
+ <ServerNode
831
+ ex={200}
832
+ ey={380}
833
+ compactOffset={{ x: -30, y: -20 }}
834
+ zIndex={8}
835
+ name="SRV-01"
836
+ subLabel="APP SERVER"
837
+ status={status ?? "online"}
838
+ cpuLoad={cpuLoad ?? 42}
839
+ memLoad={memLoad ?? 60}
840
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
841
+ subComponents={[
842
+ {
843
+ id: "cpu",
844
+ label: "CPU-0",
845
+ status: "online",
846
+ element: <CPU3D label="CPU-0" load={cpuLoad ?? 42} />,
847
+ },
848
+ {
849
+ id: "mem",
850
+ label: "HEAP",
851
+ status: "online",
852
+ element: <Memory3D label="HEAP" usedPercent={memLoad ?? 60} />,
853
+ },
854
+ ]}
855
+ graphSeries={[
856
+ { id: "cpu", label: "CPU", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68] },
857
+ ]}
858
+ />
859
+ <DatabaseNode
860
+ ex={460}
861
+ ey={380}
862
+ compactOffset={{ x: 30, y: -20 }}
863
+ zIndex={7}
864
+ name="DB-01"
865
+ subLabel="PRIMARY"
866
+ status={dbStatus ?? "online"}
867
+ capacity={dbCapacity ?? 55}
868
+ alert={{ offsetX: 160, offsetY: -60, align: "right" }}
869
+ />
870
+ </Service>
871
+ );
872
+ }
873
+
874
+ export default function App() {
875
+ return (
876
+ <AIOPsDashboard
877
+ brandName="MY DASHBOARD"
878
+ services={services}
879
+ liveData={true}
880
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
881
+ dataRefreshInterval={10000}
882
+ dataBindings={dataBindings}
883
+ serviceDataBindings={serviceDataBindings}
884
+ >
885
+ <MyService name="My Service" />
886
+ </AIOPsDashboard>
887
+ );
888
+ }
889
+ ```
890
+
891
+ ---
892
+
893
+ ### 8. Data hooks
894
+
895
+ Access live data anywhere inside the dashboard tree:
896
+
897
+ ```tsx
898
+ import { useAIOpsData, useAIOpsDataOptional, useQueryResult } from "react-dashstream";
899
+
900
+ function CustomWidget() {
901
+ const { data, isRefreshing, lastRefreshError } = useAIOpsData();
902
+ const cpu = useQueryResult('cpu_usage{instance="srv-01"}');
903
+
904
+ return (
905
+ <div>
906
+ {isRefreshing && <span>Refreshing...</span>}
907
+ {lastRefreshError && <span>{lastRefreshError}</span>}
908
+ <span>CPU: {String(cpu)}</span>
909
+ </div>
910
+ );
911
+ }
912
+ ```
913
+
914
+ | Hook | Returns | Throws? |
915
+ | ------------------------ | -------------------------- | ---------------------------- |
916
+ | `useAIOpsData()` | Full `DataContextValue` | Yes — outside `DataProvider` |
917
+ | `useAIOpsDataOptional()` | `DataContextValue \| null` | No |
918
+ | `useQueryResult(query)` | Raw response or `null` | Yes — outside `DataProvider` |
919
+
920
+ ### 9. Standalone DataProvider
921
+
922
+ Use the data layer without the full dashboard shell:
923
+
924
+ ```tsx
925
+ import { DataProvider, useAIOpsData } from "react-dashstream";
926
+
927
+ function MyApp() {
928
+ return (
929
+ <DataProvider
930
+ config={{
931
+ endpoint: "https://prometheus.example.com/api/v1/query",
932
+ queries: ['cpu_usage{instance="srv-01"}'],
933
+ refreshInterval: 15000,
934
+ }}
935
+ >
936
+ <MyContent />
937
+ </DataProvider>
938
+ );
939
+ }
940
+ ```
941
+
942
+ ---
943
+
944
+ ## Components
945
+
946
+ ### Layout
947
+
948
+ | Component | Description |
949
+ | ---------------- | -------------------------------------------------------------------------------------------------------------- |
950
+ | `AIOPsDashboard` | Full dashboard shell — header, carousel, and state management. Drop services in as children. |
951
+ | `Service` | Service container — positions child nodes on a 3D orbit and draws connection lines between them. |
952
+ | `ServiceNode` | Low-level positioned wrapper with floating animation, scan line, and labels. Used by the compound nodes below. |
953
+
954
+ ### Compound nodes (recommended)
955
+
956
+ These combine `ServiceNode` + 3D model + `componentInfo` into a single element:
957
+
958
+ | Component | Key props | Description |
959
+ | ------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
960
+ | `ServerNode` | `status`, `cpuLoad`, `memLoad`, `brandLabel`, `dialogMetrics`, `subComponents`, `graphSeries`, `alert` | App server tower with LEDs and CPU/memory bars. |
961
+ | `DatabaseNode` | `status`, `capacity`, `dialogMetrics`, `subComponents`, `graphSeries`, `alert` | Three-platter database cylinder with capacity bar. |
962
+ | `WebDispatcherNode` | `status`, `traffic`, `activeRoutes`, `dialogMetrics`, `subComponents`, `graphSeries`, `alert` | Network appliance with 8 port LEDs and traffic metrics. |
963
+ | `MessageServerNode` | `status`, `queueDepth`, `msgsPerSec`, `instances`, `dialogMetrics`, `subComponents`, `graphSeries`, `alert` | Message server with instance LEDs and queue metrics. |
964
+ | `HumanNode` | `status`, `scale` | SVG wireframe person icon for user/actor nodes. |
965
+
966
+ All compound nodes share: `ex`, `ey`, `compactOffset`, `zIndex`, `name`, `subLabel`, `color`, `delay`, `visibleAtPhase`.
967
+
968
+ ### 3D models (low-level)
969
+
970
+ If you need full control, use the raw 3D models inside a `ServiceNode`:
971
+
972
+ `Server3D`, `Database3D`, `WebDispatcher3D`, `MessageServer3D`, `Human3D`
973
+
974
+ All 3D models accept: `rotateX`, `rotateY`, `rotateZ`, `scale`, `autoRotate`.
975
+
976
+ ### Status indicators
977
+
978
+ | Component | Props | Description |
979
+ | --------------- | ------------------------------------ | ---------------------------------------------------------------- |
980
+ | `SyncBridge` | `synced`, `latencyMs` | Database replication bridge between primary and standby. |
981
+ | `NodeCallout` | `status`, `title`, `msg`, `ex`, `ey` | Alert callout with leader line (auto-rendered by `ServiceNode`). |
982
+ | `HoloBase` | `size`, `color`, `widthRatio` | Neon holographic base platform (auto-rendered by `Service`). |
983
+ | `SvgConnection` | `x1`, `y1`, `x2`, `y2`, `show` | Animated dashed SVG connection line. |
984
+
985
+ ### Dialogs
986
+
987
+ | Component | Description |
988
+ | ----------------- | ----------------------------------------------------------------------------------------------- |
989
+ | `ServiceDialog` | Service-level stats panel — shows metrics and alerts. Auto-rendered when a service is expanded. |
990
+ | `ComponentDialog` | Component drill-down with sub-component internals and sparkline graphs. |
991
+
992
+ ### Drill-down internals
993
+
994
+ Rendered inside `ComponentDialog` when a component is inspected:
995
+
996
+ `CPU3D`, `Memory3D`, `DriveBay3D`, `NetworkBlock3D`, `ThreadPool3D`, `Platter3D`, `Port3D`, `HistoricalGraphPanel`, `ComponentDrillView`
997
+
998
+ ### Pre-built services
999
+
1000
+ | Component | Topology |
1001
+ | ----------------- | --------------------------------------------------------------------------------- |
1002
+ | `SAPService` | Users → Web Dispatcher + Message Server → 3 App Servers → Primary DB + Standby DB |
1003
+ | `ExchangeService` | Users → Dispatcher → 3 App Servers → Primary DB + Standby DB |
1004
+
1005
+ ---
1006
+
1007
+ ## Building a custom service
1008
+
1009
+ Compose compound nodes inside a `Service` container. Each node needs:
1010
+
1011
+ - **`ex`, `ey`** — Position in the expanded topology (pixels from top-left of scene).
1012
+ - **`compactOffset`** — Offset from the service center in the compact carousel view.
1013
+ - **`zIndex`** — Stacking order (higher tiers get higher values).
1014
+ - **`visibleAtPhase`** — When the node fades in during expansion (0–6). Use `2` for top-tier, `3` for middle, etc.
1015
+
1016
+ Define connection lines between nodes via the `connections` prop on `Service`:
1017
+
1018
+ ```tsx
1019
+ connections={[
1020
+ { from: [x1, y1], to: [x2, y2], visibleAtPhase: 3 },
1021
+ { from: [x1, y1], to: [x2, y2], visibleAtPhase: 4, color: "#ff8c00" },
1022
+ ]}
1023
+ ```
1024
+
1025
+ ---
1026
+
1027
+ ## Status types
1028
+
1029
+ ```ts
1030
+ type ComponentStatus = "online" | "warning" | "critical" | "offline";
1031
+ ```
1032
+
1033
+ | Status | Color | Glow |
1034
+ | ---------- | --------- | -------- |
1035
+ | `online` | `#00e5ff` | cyan |
1036
+ | `warning` | `#ff8c00` | orange |
1037
+ | `critical` | `#ff2255` | red |
1038
+ | `offline` | `#1e3a5a` | dim blue |
1039
+
1040
+ ---
1041
+
1042
+ ## Theme constants
1043
+
1044
+ ```ts
1045
+ import { STATUS_CFG, HOLO_CYAN, HOLO_BLUE, HOLO_SURFACE, HOLO_GLASS, makeFaceStyles } from "react-dashstream";
1046
+ ```
1047
+
1048
+ - `STATUS_CFG` — status-to-color lookup table
1049
+ - `HOLO_CYAN` / `HOLO_BLUE` — accent colors
1050
+ - `HOLO_SURFACE` / `HOLO_GLASS` — CSS gradient backgrounds for 3D faces
1051
+ - `makeFaceStyles(W, H, D)` — generates CSS transforms for the 6 faces of a 3D box
1052
+
1053
+ ---
1054
+
1055
+ ## License
1056
+
1057
+ MIT