react-dashstream 0.0.7 → 0.0.9

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.
Files changed (3) hide show
  1. package/README.md +637 -320
  2. package/dashstream-skill.md +842 -0
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -71,376 +71,728 @@ 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:**
76
134
 
77
135
  ```tsx
78
136
  import "react-dashstream/dist/index.css";
79
- import {
80
- AIOPsDashboard,
81
- Service,
82
- HumanNode,
83
- WebDispatcherNode,
84
- ServerNode,
85
- DatabaseNode,
86
- } from "react-dashstream";
87
- import type { ServiceMeta } from "react-dashstream";
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"}',
88
159
 
89
- // ── Service metadata (drives the stats dialog) ──────────────────────────
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
+ }
90
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
91
263
  const services: ServiceMeta[] = [
92
264
  {
93
- name: "Payment Gateway",
94
- status: "critical",
95
- metrics: [
96
- { label: "Service Health", value: "76.3%", color: "#ff8c00" },
97
- { label: "Avg Response Time", value: "243ms", color: "#ff8c00" },
98
- { label: "Active Connections", value: "1,847", color: "#bb55ff" },
99
- { label: "Request Throughput", value: "3.2k req/s", color: "#00e5ff" },
100
- { label: "Error Rate", value: "3.81%", color: "#ff2255" },
101
- ],
102
- alerts: [{ level: "critical", message: "SRV-01 CPU at 99% — immediate action required" }],
103
- },
104
- {
105
- name: "Auth Service",
265
+ name: "My Service",
106
266
  status: "online",
107
267
  metrics: [
108
- { label: "Service Health", value: "99.9%", color: "#00ff88" },
109
- { label: "Avg Response Time", value: "11ms", color: "#00e5ff" },
110
- { label: "Active Sessions", value: "8,421", color: "#bb55ff" },
111
- { label: "Auth Rate", value: "920 req/s", color: "#00e5ff" },
112
- { 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" },
113
271
  ],
114
272
  alerts: [{ level: "info", message: "All Systems Nominal" }],
115
273
  },
116
274
  ];
275
+ ```
117
276
 
118
- // ── Payment Gateway 4-layer topology ──────────────────────────────────
277
+ **Live way** use `serviceDataBindings` to fetch values from your endpoint:
119
278
 
120
- function PaymentGateway({ name }: { name: string }) {
121
- return (
122
- <Service
123
- name={name}
124
- status="critical"
125
- connections={[
126
- { from: [330, 100], to: [330, 230], visibleAtPhase: 2, color: "#00e5ff" },
127
- { from: [330, 230], to: [200, 380], visibleAtPhase: 3 },
128
- { from: [330, 230], to: [460, 380], visibleAtPhase: 3 },
129
- { from: [200, 380], to: [330, 520], visibleAtPhase: 4, color: "#ff8c00" },
130
- { from: [460, 380], to: [330, 520], visibleAtPhase: 4, color: "#ff8c00" },
131
- ]}
132
- >
133
- <HumanNode
134
- ex={330}
135
- ey={100}
136
- compactOffset={{ x: 0, y: -50 }}
137
- zIndex={10}
138
- visibleAtPhase={2}
139
- color="#00e5ff"
140
- scale={1.5}
141
- />
142
- <WebDispatcherNode
143
- ex={330}
144
- ey={230}
145
- compactOffset={{ x: 0, y: -25 }}
146
- zIndex={9}
147
- name="GATEWAY"
148
- subLabel="HTTP LAYER"
149
- status="online"
150
- traffic={82}
151
- activeRoutes={6}
152
- visibleAtPhase={2}
153
- />
154
- <ServerNode
155
- ex={200}
156
- ey={380}
157
- compactOffset={{ x: -30, y: 10 }}
158
- zIndex={8}
159
- name="SRV-01"
160
- subLabel="PROCESSOR"
161
- status="critical"
162
- cpuLoad={99}
163
- memLoad={64}
164
- alert={{ offsetX: -160, offsetY: -60, align: "left" }}
165
- />
166
- <ServerNode
167
- ex={460}
168
- ey={380}
169
- compactOffset={{ x: 30, y: 10 }}
170
- zIndex={8}
171
- name="SRV-02"
172
- subLabel="PROCESSOR"
173
- delay="0.4s"
174
- status="online"
175
- cpuLoad={38}
176
- memLoad={51}
177
- />
178
- <DatabaseNode
179
- ex={330}
180
- ey={520}
181
- compactOffset={{ x: 0, y: 40 }}
182
- zIndex={7}
183
- name="PG-DB"
184
- subLabel="PRIMARY"
185
- color="#ff8c00"
186
- status="online"
187
- capacity={61}
188
- />
189
- </Service>
190
- );
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)
191
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:
192
365
 
193
- // ── Auth Service — 2-layer topology ─────────────────────────────────────
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
+ }
194
379
 
195
- function AuthService({ name }: { name: string }) {
380
+ function MyService({
381
+ name,
382
+ status = "online",
383
+ cpuLoad = 42,
384
+ memLoad = 60,
385
+ iops = 20,
386
+ threadCount = 45,
387
+ }: MyServiceProps) {
196
388
  return (
197
389
  <Service
198
390
  name={name}
199
- status="online"
200
- connections={[
201
- { from: [200, 200], to: [200, 380], visibleAtPhase: 3 },
202
- { from: [460, 200], to: [460, 380], visibleAtPhase: 3 },
203
- { from: [200, 380], to: [330, 520], visibleAtPhase: 4, color: "#ff8c00" },
204
- { from: [460, 380], to: [330, 520], visibleAtPhase: 4, color: "#ff8c00" },
205
- ]}
391
+ status={status}
392
+ connections={
393
+ [
394
+ /* ... */
395
+ ]
396
+ }
206
397
  >
207
398
  <ServerNode
208
399
  ex={200}
209
400
  ey={380}
210
401
  compactOffset={{ x: -30, y: -20 }}
211
402
  zIndex={8}
212
- name="AUTH-01"
213
- subLabel="IDENTITY"
214
- status="online"
215
- cpuLoad={34}
216
- memLoad={48}
217
- />
218
- <ServerNode
219
- ex={460}
220
- ey={380}
221
- compactOffset={{ x: 30, y: -20 }}
222
- zIndex={8}
223
- name="AUTH-02"
224
- subLabel="SESSION"
225
- delay="0.4s"
226
- status="online"
227
- cpuLoad={29}
228
- memLoad={42}
229
- />
230
- <DatabaseNode
231
- ex={330}
232
- ey={520}
233
- compactOffset={{ x: 0, y: 20 }}
234
- zIndex={7}
235
- name="AUTH-DB"
236
- subLabel="PRIMARY"
237
- color="#ff8c00"
238
- status="online"
239
- capacity={37}
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" }}
240
431
  />
241
432
  </Service>
242
433
  );
243
434
  }
244
435
 
245
- // ── Dashboard ───────────────────────────────────────────────────────────
246
-
247
- export default function App() {
248
- return (
249
- <AIOPsDashboard brandName="DASHSTREAM" brandTag="EXAMPLE" services={services}>
250
- <PaymentGateway name="Payment Gateway" />
251
- <AuthService name="Auth Service" />
252
- </AIOPsDashboard>
253
- );
254
- }
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>;
255
453
  ```
256
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
+
257
457
  ---
258
458
 
259
- ## External data source monitoring
459
+ ### 4. Alerts automatic threshold detection
260
460
 
261
- DashStream supports live data from any HTTP-accessible monitoring backend (Prometheus, Grafana, custom APIs, etc.) via built-in interval polling. When enabled, the dashboard polls your endpoint with PromQL-style queries, authenticates with header-based credentials, and automatically injects resolved values as props into your service components.
461
+ Nodes **automatically** show alert callouts when metrics breach thresholds:
262
462
 
263
- ### How it works
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:
264
467
 
265
- 1. Set `liveData={true}` on `AIOPsDashboard` along with `dataEndpoint` and `dataBindings`.
266
- 2. On first load, a **credentials modal** prompts for an `access-key` and `access-secret-key` (stored in memory only — never persisted).
267
- 3. The dashboard polls `GET <dataEndpoint>?query=<encodedQuery>` for each unique query, sending credentials as HTTP headers.
268
- 4. Resolved values are automatically injected as props into child service components matched by `name`.
269
- 5. The header shows a **"DATA REFRESH FAILED"** indicator if any queries fail.
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
+ ```
270
482
 
271
- ### Enabling live data
483
+ **Position the callout:**
272
484
 
273
485
  ```tsx
274
- import "react-dashstream/dist/index.css";
275
- import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
486
+ <ServerNode
487
+ cpuLoad={92}
488
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
489
+ ...
490
+ />
491
+ ```
276
492
 
277
- export default function App() {
278
- return (
279
- <AIOPsDashboard
280
- brandName="LIVE MONITOR"
281
- services={services}
282
- liveData={true}
283
- dataEndpoint="https://prometheus.example.com/api/v1/query"
284
- dataRefreshInterval={30000}
285
- dataBindings={{
286
- "My Service": {
287
- cpuLoad: 'cpu_usage{instance="srv-01"}',
288
- memLoad: 'memory_usage{instance="srv-01"}',
289
- status: {
290
- query: 'up{instance="srv-01"}',
291
- transform: (v) => (Number(v) > 0 ? "online" : "offline"),
292
- },
293
- },
294
- }}
295
- dataTransform={(raw) => {
296
- const n = Number(raw);
297
- return isNaN(n) ? raw : n;
298
- }}
299
- serviceDataBindings={{
300
- "My Service": [
301
- { label: "Uptime", query: 'uptime{svc="my-service"}', unit: "%", color: "#00ff88" },
302
- { label: "Latency", query: 'latency{svc="my-service"}', unit: "ms", color: "#00e5ff" },
303
- {
304
- label: "Error Rate",
305
- query: 'error_rate{svc="my-service"}',
306
- unit: "%",
307
- color: "#ff2255",
308
- },
309
- ],
310
- }}
311
- >
312
- <Service name="My Service" status="online" connections={[]}>
313
- <ServerNode
314
- ex={200}
315
- ey={380}
316
- compactOffset={{ x: -30, y: -20 }}
317
- zIndex={8}
318
- name="SRV-01"
319
- subLabel="APP SERVER"
320
- status="online"
321
- cpuLoad={0}
322
- memLoad={0}
323
- />
324
- <DatabaseNode
325
- ex={460}
326
- ey={380}
327
- compactOffset={{ x: 30, y: -20 }}
328
- zIndex={7}
329
- name="DB-01"
330
- subLabel="PRIMARY"
331
- status="online"
332
- capacity={55}
333
- />
334
- </Service>
335
- </AIOPsDashboard>
336
- );
337
- }
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
+ />
338
514
  ```
339
515
 
340
- ### Live data props reference
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:
341
525
 
342
- | Prop | Type | Description |
343
- | --------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------- |
344
- | `liveData` | `boolean` | Enable the live data pipeline. Default `false`. |
345
- | `dataEndpoint` | `string` | Base URL of the monitoring endpoint. Queries are sent as `GET <endpoint>?query=<encodedQuery>`. |
346
- | `dataBindings` | `DataBindings` | Maps service name → prop name → PromQL query. Resolved values are injected as component props. |
347
- | `dataTransform` | `(raw: unknown) => unknown` | Global transform applied to every raw response. Defaults to numeric parsing. |
348
- | `dataRefreshInterval` | `number` | Polling interval in milliseconds. Default `60000` (1 minute). |
349
- | `serviceDataBindings` | `Record<string, ServiceMetricBinding[]>` | Maps service name → metric rows for the service stats dialog. |
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
+ ];
350
562
 
351
- ### Data bindings
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
+ ```
352
576
 
353
- Each binding maps a prop name on a child service component to a query. Bindings can be a bare query string (uses the global `dataTransform`) or an object with a per-binding `transform`:
577
+ Each `SubComponentConfig`:
354
578
 
355
579
  ```ts
356
- type DataBinding = string | { query: string; transform?: (raw: unknown) => unknown };
357
- type DataBindings = Record<string, Record<string, DataBinding>>;
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
+ }
358
587
  ```
359
588
 
360
- The service name key must match the `name` prop on the corresponding child component. When the polled response arrives, the transform is applied and the result is injected as the named prop, overriding any static default.
589
+ Available internal 3D components: `CPU3D`, `Memory3D`, `DriveBay3D`, `NetworkBlock3D`, `ThreadPool3D`, `Platter3D`, `Port3D`.
361
590
 
362
- ### Service metric bindings
591
+ ---
363
592
 
364
- These bind KPI rows in the **ServiceDialog** (the stats panel shown when a service is expanded) to live queries:
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`:
365
623
 
366
624
  ```ts
367
- interface ServiceMetricBinding {
368
- label: string; // Row label (e.g. "Uptime")
369
- query: string; // PromQL query string
370
- unit?: string; // Unit suffix (e.g. "%", "ms")
371
- color?: string; // Accent color for the row
372
- transform?: (raw: unknown) => string; // Custom formatter
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)
373
631
  }
374
632
  ```
375
633
 
376
- When `serviceDataBindings` is provided, live metric values replace the static `ServiceMeta.metrics` for that service.
634
+ ---
377
635
 
378
- ### Endpoint contract
636
+ ### 7. Putting it all together
379
637
 
380
- Your monitoring endpoint must satisfy this contract:
638
+ A complete example with live data, service dialog metrics, component dialog gauges, sub-components, graphs, and alert positioning:
381
639
 
382
- | Aspect | Requirement |
383
- | ------------------ | -------------------------------------------------------------------------------------------------------------------------- |
384
- | **Method** | `GET` |
385
- | **URL format** | `<dataEndpoint>?query=<urlEncodedQuery>` |
386
- | **Auth headers** | `access-key` and `access-secret-key` |
387
- | **Response body** | Plain text or JSON. The default transform parses the trimmed body as a number; non-numeric values are returned as strings. |
388
- | **Error handling** | Non-2xx responses are counted as failures. Partial failures are reported in the header. |
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";
389
644
 
390
- If your backend returns structured JSON (e.g. Prometheus instant-query format), provide a custom `dataTransform` to extract the scalar value:
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
+ ];
391
653
 
392
- ```tsx
393
- dataTransform={(raw) => {
394
- const parsed = JSON.parse(String(raw));
395
- return parsed?.data?.result?.[0]?.value?.[1] ?? raw;
396
- }}
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
+ }
397
748
  ```
398
749
 
399
- ### Using the data hooks directly
750
+ ---
400
751
 
401
- For advanced use cases, you can access the live data context anywhere inside the dashboard tree:
752
+ ### 8. Data hooks
753
+
754
+ Access live data anywhere inside the dashboard tree:
402
755
 
403
756
  ```tsx
404
757
  import { useAIOpsData, useAIOpsDataOptional, useQueryResult } from "react-dashstream";
405
758
 
406
759
  function CustomWidget() {
407
- const { data, isRefreshing, lastRefreshError, lastRefreshTime } = useAIOpsData();
408
-
409
- const cpuValue = useQueryResult('cpu_usage{instance="srv-01"}');
760
+ const { data, isRefreshing, lastRefreshError } = useAIOpsData();
761
+ const cpu = useQueryResult('cpu_usage{instance="srv-01"}');
410
762
 
411
763
  return (
412
764
  <div>
413
765
  {isRefreshing && <span>Refreshing...</span>}
414
- {lastRefreshError && <span>Error: {lastRefreshError}</span>}
415
- <span>CPU: {String(cpuValue)}</span>
766
+ {lastRefreshError && <span>{lastRefreshError}</span>}
767
+ <span>CPU: {String(cpu)}</span>
416
768
  </div>
417
769
  );
418
770
  }
419
771
  ```
420
772
 
421
- | Hook | Description |
422
- | ------------------------ | --------------------------------------------------------------------------------------------------- |
423
- | `useAIOpsData()` | Returns the full `DataContextValue`. Throws if called outside a live-data dashboard. |
424
- | `useAIOpsDataOptional()` | Returns `DataContextValue` or `null` if no `DataProvider` is present. Safe in dual-mode components. |
425
- | `useQueryResult(query)` | Returns the raw response for a specific query string, or `null` if not yet fetched. |
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` |
426
778
 
427
- ### Standalone DataProvider
779
+ ### 9. Standalone DataProvider
428
780
 
429
- You can use `DataProvider` directly for custom layouts that don't use the full `AIOPsDashboard` shell:
781
+ Use the data layer without the full dashboard shell:
430
782
 
431
783
  ```tsx
432
784
  import { DataProvider, useAIOpsData } from "react-dashstream";
433
785
 
434
- function MyCustomDashboard() {
786
+ function MyApp() {
435
787
  return (
436
788
  <DataProvider
437
789
  config={{
438
790
  endpoint: "https://prometheus.example.com/api/v1/query",
439
- queries: ['cpu_usage{instance="srv-01"}', 'memory_usage{instance="srv-01"}'],
791
+ queries: ['cpu_usage{instance="srv-01"}'],
440
792
  refreshInterval: 15000,
441
793
  }}
442
794
  >
443
- <MyCustomContent />
795
+ <MyContent />
444
796
  </DataProvider>
445
797
  );
446
798
  }
@@ -462,15 +814,15 @@ function MyCustomDashboard() {
462
814
 
463
815
  These combine `ServiceNode` + 3D model + `componentInfo` into a single element:
464
816
 
465
- | Component | Key props | Description |
466
- | ------------------- | ------------------------------------------------- | ------------------------------------------------------- |
467
- | `ServerNode` | `status`, `cpuLoad`, `memLoad`, `brandLabel` | Application server tower with LEDs and CPU/memory bars. |
468
- | `DatabaseNode` | `status`, `capacity` | Three-platter database cylinder with capacity bar. |
469
- | `WebDispatcherNode` | `status`, `traffic`, `activeRoutes` | Network appliance with 8 port LEDs and traffic metrics. |
470
- | `MessageServerNode` | `status`, `queueDepth`, `msgsPerSec`, `instances` | Message server with instance LEDs and queue metrics. |
471
- | `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. |
472
824
 
473
- 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`.
474
826
 
475
827
  ### 3D models (low-level)
476
828
 
@@ -518,7 +870,7 @@ Compose compound nodes inside a `Service` container. Each node needs:
518
870
  - **`ex`, `ey`** — Position in the expanded topology (pixels from top-left of scene).
519
871
  - **`compactOffset`** — Offset from the service center in the compact carousel view.
520
872
  - **`zIndex`** — Stacking order (higher tiers get higher values).
521
- - **`visibleAtPhase`** — When the node fades in during expansion (0–6). Use `2` for top-tier nodes, `3` for middle, etc.
873
+ - **`visibleAtPhase`** — When the node fades in during expansion (0–6). Use `2` for top-tier, `3` for middle, etc.
522
874
 
523
875
  Define connection lines between nodes via the `connections` prop on `Service`:
524
876
 
@@ -529,41 +881,6 @@ connections={[
529
881
  ]}
530
882
  ```
531
883
 
532
- ### Alerts
533
-
534
- Nodes automatically detect threshold breaches and render alert callouts. Default thresholds:
535
-
536
- - **Warning** at 70%
537
- - **Critical** at 85%
538
-
539
- Position the callout with the `alert` prop:
540
-
541
- ```tsx
542
- <ServerNode
543
- alert={{ offsetX: -160, offsetY: -60, align: "left" }}
544
- cpuLoad={99}
545
- ...
546
- />
547
- ```
548
-
549
- ### Service dialog
550
-
551
- Pass `ServiceMeta` objects to `AIOPsDashboard` via the `services` prop to populate the stats panel that appears when a service is expanded:
552
-
553
- ```tsx
554
- const services: ServiceMeta[] = [
555
- {
556
- name: "My Service",
557
- status: "online",
558
- metrics: [
559
- { label: "Service Health", value: "99.9%", color: "#00ff88" },
560
- { label: "Avg Response Time", value: "14ms", color: "#00e5ff" },
561
- ],
562
- alerts: [{ level: "info", message: "All Systems Nominal" }],
563
- },
564
- ];
565
- ```
566
-
567
884
  ---
568
885
 
569
886
  ## Status types