react-dashstream 0.1.3 → 0.3.0

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