react-dashstream 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1062 +1,1208 @@
1
- # React DashStream — AI Coding Assistant Skill
2
-
3
- > **Package**: `react-dashstream` (npm)
4
- > **Version**: 0.2.0
5
- > **Peer deps**: `react` ^18 || ^19, `react-dom` ^18 || ^19
6
- > **License**: MIT
7
-
8
- Use this skill when the user is working with the `react-dashstream` package — a holographic 3D infrastructure monitoring dashboard built entirely with CSS 3D transforms (no WebGL, no canvas).
9
-
10
- ---
11
-
12
- ## What this package does
13
-
14
- React DashStream renders a rotating 3D carousel of infrastructure services. Each service can be clicked to expand into a multi-tiered topology showing servers, databases, dispatchers, message servers, and user nodes connected by animated SVG lines. Components can be drilled into to reveal internal sub-components with sparkline graphs and gauges.
15
-
16
- Two modes:
17
-
18
- 1. **Static mode** — pass props directly for demo or snapshot views.
19
- 2. **Live data mode** — poll an HTTP endpoint with PromQL queries and auto-inject resolved values as component props.
20
-
21
- ---
22
-
23
- ## Installation and CSS
24
-
25
- ```bash
26
- npm install react-dashstream
27
- ```
28
-
29
- ```tsx
30
- import "react-dashstream/dist/index.css";
31
- import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
32
- ```
33
-
34
- **Always import the CSS file** — without it, no styles render.
35
-
36
- ---
37
-
38
- ## Light / dark theme
39
-
40
- The dashboard supports **`"light"`** and **`"dark"`** modes.
41
-
42
- - **`AIOPsDashboard`** defaults to **light** and exposes a header toggle. Use **`backgroundImage`** (dark) and **`lightBackgroundImage`** (light when set).
43
- - **Controlled mode:** pass **`theme`** and **`onThemeChange`** (`DashboardTheme`) so parent state owns the mode (e.g. sync app chrome + `EventView`).
44
- - **`ThemeProvider`** (export) + **`useTheme()`** — wrap subtrees so **`EventView`**, **`CredentialsModal`**, **`ServiceDialog`**, and **`ComponentDialog`** match the dashboard. Without a provider, `useTheme()` defaults to **`"dark"`**; prefer wrapping when using `EventView` or the lock screen next to a light dashboard.
45
- - **`EventView`** accepts optional **`theme`** to override context.
46
-
47
- When generating multi-view apps (3D + Event Console + credentials), wrap the root in **`ThemeProvider`** with the same value passed to **`AIOPsDashboard`**’s controlled props.
48
-
49
- ---
50
-
51
- ## Architecture
52
-
53
- ```
54
- AIOPsDashboard ← Shell: header, state, optional DataProvider
55
- └─ Carousel ← 3D orbit, rotation, hosts dialogs
56
- └─ Service ← Per-service container, connections, holo base
57
- ├─ ServerNode ← Compound: ServiceNode + Server3D
58
- ├─ DatabaseNode ← Compound: ServiceNode + Database3D
59
- ├─ WebDispatcherNode ← Compound: ServiceNode + WebDispatcher3D
60
- ├─ MessageServerNode ← Compound: ServiceNode + MessageServer3D
61
- ├─ HumanNode ← Compound: ServiceNode + Human3D (SVG)
62
- ├─ SvgConnection ← Animated dashed topology line
63
- ├─ SyncBridge ← DB replication indicator
64
- ├─ NodeCallout ← Alert callout (auto from thresholds)
65
- └─ HoloBase ← Neon platform (auto-rendered)
66
-
67
- ServiceDialog ← Service-level KPI panel (auto on expand)
68
- ComponentDialog ← Drill-down with internals + graphs
69
- ├─ HistoricalGraphPanel ← Sparkline charts
70
- └─ CPU3D / Memory3D / DriveBay3D / NetworkBlock3D / ThreadPool3D / Platter3D / Port3D
71
-
72
- DataProvider ← Polling engine + credentials modal
73
-
74
- EventView ← Standalone event console (table view)
75
- ```
76
-
77
- ### Source layout
78
-
79
- ```
80
- src/
81
- ├── index.ts # Public API barrel
82
- ├── AIOPsDashboard.tsx # Dashboard shell + live-data wiring
83
- ├── types.ts # Shared topology/drill-down types
84
- ├── theme.ts # Status tokens, holo palette, 3D face helpers
85
- ├── ThemeContext.ts # DashboardTheme, ThemeProvider, useTheme
86
- ├── styles.css / index.css # Library + dashboard styles
87
- ├── data/
88
- ├── DataProvider.tsx # Polling engine, context, hooks
89
- ├── CredentialsModal.tsx # Auth prompt UI
90
- └── index.ts
91
- ├── components/ # ~30 UI components
92
- ├── Carousel.tsx, Service.tsx, ServiceNode.tsx
93
- ├── ServerNode.tsx, DatabaseNode.tsx, WebDispatcherNode.tsx, etc.
94
- │ ├── Server3D.tsx, Database3D.tsx, etc.
95
- │ ├── ServiceDialog.tsx, ComponentDialog.tsx
96
- │ ├── Internal3DComponents.tsx, HistoricalGraphPanel.tsx
97
- │ ├── ComponentDrillView.tsx, SvgConnection.tsx, SyncBridge.tsx
98
- │ ├── NodeCallout.tsx, HoloBase.tsx, CarouselContext.ts
99
- │ ├── EventView/ # Operations event console
100
- ├── EventView.tsx # Table component (filter, sort, search, infinite scroll)
101
- ├── EventView.css # Holographic table styles
102
- │ │ ├── EventAlertsContext.tsx # EventAlertsProvider + per-host alert map
103
- │ │ ├── fetchEvents.ts # Shared fetch/mapping logic (used by both EventView & provider)
104
- │ │ ├── types.ts # AIOpsEvent, EventSeverity, EventApiConfig, etc.
105
- │ │ ├── mockData.ts # 28 realistic mock events
106
- │ │ └── index.ts
107
- └── index.ts
108
- ├── services/
109
- │ ├── SAPService.tsx, ExchangeService.tsx
110
- │ ├── sapSubComponents.tsx
111
- └── index.ts
112
- example/
113
- ├── Dashboard.tsx
114
- └── services/
115
- ```
116
-
117
- ---
118
-
119
- ## Complete data flow reference
120
-
121
- There are **10 distinct data paths** in this package. Understanding them is critical for building live dashboards.
122
-
123
- ### 1. Data bindings — live props injected into service components
124
-
125
- `dataBindings` maps **service name → prop name → PromQL query**. The dashboard polls each query, applies a transform, and injects the result as a prop on the matched child component.
126
-
127
- ```ts
128
- type DataBinding = string | { query: string; transform?: (raw: unknown) => unknown };
129
- type DataBindings = Record<string, Record<string, DataBinding>>;
130
- ```
131
-
132
- - Bare string uses global `dataTransform` (defaults to numeric parsing)
133
- - Object `{ query, transform }` → uses per-binding transform
134
- - Service name key must match child's `name` prop **exactly**
135
-
136
- ```tsx
137
- const dataBindings: DataBindings = {
138
- "My Service": {
139
- cpuLoad: 'cpu_usage{instance="srv-01"}', // bare string → number
140
- memLoad: 'memory_usage{instance="srv-01"}', // bare string → number
141
- status: {
142
- // objectcustom transform
143
- query: 'cpu_usage{instance="srv-01"}',
144
- transform: (raw) => {
145
- const n = Number(raw);
146
- if (n >= 85) return "critical";
147
- if (n >= 70) return "warning";
148
- return "online";
149
- },
150
- },
151
- dbCapacity: 'disk_capacity{instance="db-01"}',
152
- dbStatus: { query: 'disk_capacity{instance="db-01"}', transform: statusFromValue },
153
- },
154
- };
155
- ```
156
-
157
- #### Multi-component services
158
-
159
- When a service has multiple nodes of the same type (e.g. three servers), use a flat naming convention with prefixes. Each `DataBinding` maps **one prop → one query** — there is no array or object binding type.
160
-
161
- ```tsx
162
- interface ServiceXProps {
163
- name: string;
164
- status?: ComponentStatus;
165
- srv1CpuLoad?: number;
166
- srv1MemLoad?: number;
167
- srv1Status?: ComponentStatus;
168
- srv2CpuLoad?: number;
169
- srv2MemLoad?: number;
170
- srv2Status?: ComponentStatus;
171
- srv3CpuLoad?: number;
172
- srv3MemLoad?: number;
173
- srv3Status?: ComponentStatus;
174
- dbCapacity?: number;
175
- dbStatus?: ComponentStatus;
176
- }
177
-
178
- function ServiceX({
179
- name, status = "online",
180
- srv1CpuLoad = 54, srv1MemLoad = 58, srv1Status = "online",
181
- srv2CpuLoad = 63, srv2MemLoad = 66, srv2Status = "online",
182
- srv3CpuLoad = 78, srv3MemLoad = 71, srv3Status = "online",
183
- dbCapacity = 68, dbStatus = "online",
184
- }: ServiceXProps) {
185
- return (
186
- <Service name={name} status={status} connections={[/* ... */]}>
187
- <ServerNode name="SRV-X1" status={srv1Status}
188
- cpuLoad={srv1CpuLoad} memLoad={srv1MemLoad} ... />
189
- <ServerNode name="SRV-X2" status={srv2Status}
190
- cpuLoad={srv2CpuLoad} memLoad={srv2MemLoad} ... />
191
- <ServerNode name="SRV-X3" status={srv3Status}
192
- cpuLoad={srv3CpuLoad} memLoad={srv3MemLoad} ... />
193
- <DatabaseNode name="DB-X1" status={dbStatus}
194
- capacity={dbCapacity} ... />
195
- </Service>
196
- );
197
- }
198
- ```
199
-
200
- Bind each prefixed prop individually:
201
-
202
- ```tsx
203
- dataBindings={{
204
- ServiceX: {
205
- status: { query: 'status{instance="svcx"}', transform: statusFromValue },
206
- srv1CpuLoad: 'cpu_usage{instance="srvx-01"}',
207
- srv1MemLoad: 'memory_usage{instance="srvx-01"}',
208
- srv1Status: { query: 'status{instance="srvx-01"}', transform: statusFromValue },
209
- srv2CpuLoad: 'cpu_usage{instance="srvx-02"}',
210
- srv2MemLoad: 'memory_usage{instance="srvx-02"}',
211
- srv2Status: { query: 'status{instance="srvx-02"}', transform: statusFromValue },
212
- srv3CpuLoad: 'cpu_usage{instance="srvx-03"}',
213
- srv3MemLoad: 'memory_usage{instance="srvx-03"}',
214
- srv3Status: { query: 'status{instance="srvx-03"}', transform: statusFromValue },
215
- dbCapacity: 'disk_capacity{instance="dbx-01"}',
216
- dbStatus: { query: 'status{instance="dbx-01"}', transform: statusFromValue },
217
- },
218
- }}
219
- ```
220
-
221
- Use a consistent convention like `srv1CpuLoad`, `srv2CpuLoad`. The service component maps each prefixed prop to the correct node.
222
-
223
- ### 2. Service dialog static ServiceMeta metrics
224
-
225
- The **ServiceDialog** panel shows KPIs when a service is expanded. Static metrics are passed via `ServiceMeta`:
226
-
227
- ```tsx
228
- const services: ServiceMeta[] = [
229
- {
230
- name: "My Service",
231
- status: "online",
232
- metrics: [
233
- { label: "Uptime", value: "99.99%", color: "#00ff88" },
234
- { label: "Avg Latency", value: "8ms", color: "#00e5ff" },
235
- ],
236
- alerts: [
237
- { level: "info", message: "All Systems Nominal" },
238
- { level: "warning", message: "High CPU on SRV-01" },
239
- { level: "critical", message: "DB connection pool exhausted" },
240
- ],
241
- },
242
- ];
243
- ```
244
-
245
- ### 3. Service dialog — live serviceDataBindings
246
-
247
- Replace static metrics with live-fetched values using `serviceDataBindings`:
248
-
249
- ```tsx
250
- serviceDataBindings={{
251
- "My Service": [
252
- { label: "CPU Load", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" },
253
- { label: "Memory", query: 'memory_usage{instance="srv-01"}', unit: "%", color: "#bb55ff" },
254
- { label: "Disk", query: 'disk_capacity{instance="db-01"}', unit: "%", color: "#ff8c00" },
255
- ],
256
- }}
257
- ```
258
-
259
- ```ts
260
- interface ServiceMetricBinding {
261
- label: string; // Row label
262
- query: string; // PromQL query
263
- unit?: string; // Suffix (e.g. "%", "ms")
264
- color?: string; // Accent color
265
- transform?: (raw: unknown) => string; // Custom formatter
266
- }
267
- ```
268
-
269
- When provided, live values **replace** static `ServiceMeta.metrics` for that service.
270
-
271
- ### 4. Component dialog — default gauges
272
-
273
- The **ComponentDialog** appears when clicking a node. Default gauges are derived from node type:
274
-
275
- - **Server**: CPU (from `cpuLoad`), Memory (from `memLoad`), Storage
276
- - **Database**: Capacity (from `capacity`), Memory, Storage
277
- - **Dispatcher**: Traffic (from `traffic`), Memory, Storage
278
- - **Message Server**: Queue (from `queueDepth`), Memory, Storage
279
-
280
- Default thresholds: warn at 70, critical at 85.
281
-
282
- ### 5. Component dialog custom dialogMetrics
283
-
284
- Override default gauges with `dialogMetrics` on any compound node:
285
-
286
- ```tsx
287
- <ServerNode
288
- name="SRV-01"
289
- status="online"
290
- cpuLoad={67}
291
- memLoad={72}
292
- ex={200}
293
- ey={380}
294
- compactOffset={{ x: -30, y: -20 }}
295
- zIndex={8}
296
- dialogMetrics={[
297
- { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67, unit: "%" },
298
- { id: "mem", label: "MEMORY", sublabel: "HEAP USAGE", value: 72, unit: "%" },
299
- { id: "iops", label: "IOPS", sublabel: "DISK OPS", value: 45, unit: "k/s", icon: "disk" },
300
- {
301
- id: "threads",
302
- label: "THREADS",
303
- sublabel: "ACTIVE",
304
- value: 82,
305
- unit: "%",
306
- warnAt: 60,
307
- critAt: 80,
308
- icon: "cpu",
309
- },
310
- ]}
311
- />
312
- ```
313
-
314
- ```ts
315
- interface ComponentDialogMetric {
316
- id: string; // Unique key
317
- label: string; // Upper label
318
- sublabel: string; // Lower label
319
- value: number; // 0–100
320
- unit?: string; // Default "%"
321
- icon?: "cpu" | "mem" | "disk"; // Default "cpu"
322
- warnAt?: number; // Default 70
323
- critAt?: number; // Default 85
324
- color?: string; // Override bar color (bypasses threshold coloring)
325
- }
326
- ```
327
-
328
- All compound nodes accept `dialogMetrics`: `ServerNode`, `DatabaseNode`, `WebDispatcherNode`, `MessageServerNode`.
329
-
330
- **Live component dialog metrics pattern**: To feed live query values into custom gauges, expose each metric as a prop on your service component. Use `dataBindings` to inject them. Then build the `dialogMetrics` array from those props:
331
-
332
- ```tsx
333
- // Service component accepts individual metric props, builds dialogMetrics from them
334
- function MyService({ name, status = "online", cpuLoad = 42, memLoad = 60, iops = 20 }: any) {
335
- return (
336
- <Service
337
- name={name}
338
- status={status}
339
- connections={
340
- [
341
- /* ... */
342
- ]
343
- }
344
- >
345
- <ServerNode
346
- name="SRV-01"
347
- status={status}
348
- cpuLoad={cpuLoad}
349
- memLoad={memLoad}
350
- ex={200}
351
- ey={380}
352
- compactOffset={{ x: -30, y: -20 }}
353
- zIndex={8}
354
- dialogMetrics={[
355
- { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: cpuLoad },
356
- { id: "mem", label: "MEMORY", sublabel: "HEAP", value: memLoad },
357
- {
358
- id: "iops",
359
- label: "IOPS",
360
- sublabel: "DISK OPS",
361
- value: iops,
362
- icon: "disk",
363
- warnAt: 50,
364
- critAt: 80,
365
- },
366
- ]}
367
- />
368
- </Service>
369
- );
370
- }
371
-
372
- // Dashboard — bind each prop to a query
373
- <AIOPsDashboard
374
- liveData
375
- dataEndpoint="..."
376
- dataBindings={{
377
- "My Service": {
378
- cpuLoad: 'cpu_usage{instance="srv-01"}',
379
- memLoad: 'memory_usage{instance="srv-01"}',
380
- iops: 'disk_iops{instance="srv-01"}',
381
- status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
382
- },
383
- }}
384
- >
385
- <MyService name="My Service" />
386
- </AIOPsDashboard>;
387
- ```
388
-
389
- The dashboard injects `cpuLoad`, `memLoad`, `iops` as live props → they flow into `dialogMetrics` → gauges update automatically on every poll. Works for any number of custom metrics.
390
-
391
- ### 6. Alerts — automatic threshold detection
392
-
393
- Nodes auto-render `NodeCallout` alerts when metrics breach thresholds:
394
-
395
- - **Warning** at 70% → orange callout
396
- - **Critical** at 85% → red callout
397
-
398
- Sources (checked in priority order):
399
-
400
- 1. `dialogMetrics` values vs their `warnAt`/`critAt`
401
- 2. Context values (`cpuLoad`, `memLoad`, `traffic`, `queueDepth`, `capacity`) vs default thresholds
402
-
403
- ```tsx
404
- // Auto-alert from cpuLoad > 85
405
- <ServerNode cpuLoad={92} memLoad={64}
406
- alert={{ offsetX: -160, offsetY: -60, align: "left" }}
407
- ... />
408
-
409
- // Custom message
410
- <ServerNode cpuLoad={92}
411
- alert={{ msg: "CPU overload — scale out", offsetX: -160, offsetY: -60, align: "left" }}
412
- ... />
413
-
414
- // Custom thresholds via dialogMetrics
415
- <ServerNode
416
- dialogMetrics={[
417
- { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67,
418
- warnAt: 50, critAt: 75 }, // Alert at 67 because critAt=75
419
- ]}
420
- alert={{ offsetX: -160, offsetY: -60, align: "left" }}
421
- ... />
422
- ```
423
-
424
- Alert `align` options: `"left"`, `"right"`, `"top"`, `"bottom"`.
425
-
426
- Alert prop shape:
427
-
428
- ```ts
429
- alert?: {
430
- msg?: string; // Custom message (auto-generated if omitted)
431
- offsetX?: number; // X offset from node (default 110)
432
- offsetY?: number; // Y offset from node (default -30)
433
- align?: "left" | "right" | "top" | "bottom"; // Default "right"
434
- internalRef?: string; // Sub-component ID
435
- }
436
- ```
437
-
438
- ### 7. Event-to-dashboard bridge — automatic node alerts from events API
439
-
440
- When `eventApiConfig` is passed to `AIOPsDashboard`, events are fetched in the background and nodes whose `name` (case-insensitive) matches an event `host` automatically show alert callouts.
441
-
442
- ```tsx
443
- <AIOPsDashboard
444
- eventApiConfig={eventApiConfig} // same EventApiConfig used for EventView
445
- liveData
446
- dataEndpoint="..."
447
- dataBindings={dataBindings}
448
- services={services}
449
- >
450
- <MyService name="My Service" />
451
- </AIOPsDashboard>
452
- ```
453
-
454
- **How it works internally:**
455
-
456
- 1. `AIOPsDashboard` wraps children in `EventAlertsProvider` (inside `DataProvider` when `liveData` is set — shares credentials automatically)
457
- 2. `EventAlertsProvider` fetches events using the same `fetchEvents` + `mapRawEvent` logic as `EventView`
458
- 3. Events are grouped by **lower-cased hostname** into a `Map<string, EventHostAlert>`
459
- 4. `ServiceNode` reads from `EventAlertsContext` via `useEventAlertsOptional()`
460
- 5. If `componentInfo.name.toLowerCase()` matches an entry, event severity is factored into the existing `resolveNodeSeverity` pipeline as a **third severity source** (alongside node status and metric breaches)
461
- 6. Highest severity among all three sources wins
462
- 7. `NodeCallout` appears automatically with the event message; multi-event hosts show `"N events – message"`
463
-
464
- **Severity mapping:**
465
-
466
- | Event severity | ComponentStatus |
467
- | -------------- | ----------------- |
468
- | Critical | `"critical"` |
469
- | Major | `"warning"` |
470
- | Minor | `"warning"` |
471
-
472
- **Key requirement:** Node `name` props must match event `host` values from the API (case-insensitive). Example: if the API returns `host: "SAP-PRD-APP01"`, the node must be `<ServerNode name="SAP-PRD-APP01" ... />`.
473
-
474
- **Credential resolution order** (for EventAlertsProvider inside AIOPsDashboard):
475
-
476
- 1. `DataProvider` context credentials (when `liveData` is enabled — same credentials, one login)
477
- 2. Built-in `CredentialsModal` (when `liveData` is not enabled)
478
-
479
- **Opt-in:** Without `eventApiConfig`, no events are fetched — zero overhead.
480
-
481
- **When generating an `AIOPsDashboard` with event bridge:** Reuse the same `EventApiConfig` object that the user already has for their `EventView`. Just add it as `eventApiConfig` on `AIOPsDashboard`.
482
-
483
- ### 8. Drill-down internals custom subComponents
484
-
485
- When clicking a node, the drill-down shows internal 3D sub-components. Default sub-components are auto-generated per type. Override with `subComponents`:
486
-
487
- ```tsx
488
- import { CPU3D, Memory3D, DriveBay3D, ThreadPool3D, NetworkBlock3D, Platter3D, Port3D } from "react-dashstream";
489
- import type { SubComponentConfig } from "react-dashstream";
490
-
491
- const internals: SubComponentConfig[] = [
492
- { id: "cpu-0", label: "CPU-0", status: "online",
493
- element: <CPU3D label="CPU-0" load={67} color="#00e5ff" /> },
494
- { id: "heap", label: "HEAP", status: "warning",
495
- element: <Memory3D label="HEAP" usedPercent={92} color="#8855ee" status="warning" /> },
496
- { id: "drive", label: "DRIVE-0", status: "online",
497
- element: <DriveBay3D label="DRIVE-0" color="#00e5ff" activity={true} /> },
498
- { id: "threads", label: "THREADS", status: "online",
499
- element: <ThreadPool3D label="THREADS" color="#00e5ff" /> },
500
- { id: "net", label: "NET", status: "online",
501
- element: <NetworkBlock3D label="NET" color="#00e5ff" /> },
502
- ];
503
-
504
- <ServerNode subComponents={internals} ... />
505
- ```
506
-
507
- ```ts
508
- interface SubComponentConfig {
509
- id: string; // Unique key
510
- label: string; // Display name
511
- status: ComponentStatus; // LED color
512
- detail?: string; // Fault description
513
- element: ReactNode; // 3D element to render
514
- }
515
- ```
516
-
517
- All compound nodes accept `subComponents`.
518
-
519
- ### 9. Graph series — custom sparklines
520
-
521
- The drill-down's historical panel shows sparkline charts. Default graphs are auto-generated. Override with `graphSeries`:
522
-
523
- ```tsx
524
- import type { GraphSeries } from "react-dashstream";
525
-
526
- const graphs: GraphSeries[] = [
527
- { id: "cpu", label: "CPU-0", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68, 55] },
528
- { id: "mem", label: "HEAP", unit: "%", color: "#8855ee", data: [60, 65, 72, 78, 82, 75] },
529
- { id: "iops", label: "DISK", unit: "k/s", color: "#ff8c00", data: [12, 15, 22, 18, 25, 20] },
530
- ];
531
-
532
- <ServerNode graphSeries={graphs} ... />
533
- ```
534
-
535
- ```ts
536
- interface GraphSeries {
537
- id: string; // Unique key
538
- label: string; // Label above sparkline
539
- unit: string; // Suffix
540
- color: string; // Line color
541
- data: number[]; // Points (most recent last)
542
- }
543
- ```
544
-
545
- All compound nodes accept `graphSeries`.
546
-
547
- ### 10. Data hooks programmatic access
548
-
549
- Access live data context anywhere inside the dashboard:
550
-
551
- ```tsx
552
- import { useAIOpsData, useAIOpsDataOptional, useQueryResult } from "react-dashstream";
553
-
554
- function CustomWidget() {
555
- const { data, isRefreshing, lastRefreshError, lastRefreshTime, credentialsSet } = useAIOpsData();
556
- const cpu = useQueryResult('cpu_usage{instance="srv-01"}');
557
- return <div>CPU: {String(cpu)}</div>;
558
- }
559
- ```
560
-
561
- | Hook | Returns | Throws? |
562
- | ------------------------ | -------------------------- | ---------------------------- |
563
- | `useAIOpsData()` | `DataContextValue` | Yes — outside `DataProvider` |
564
- | `useAIOpsDataOptional()` | `DataContextValue \| null` | No |
565
- | `useQueryResult(query)` | `unknown \| null` | Yes — outside `DataProvider` |
566
-
567
- ```ts
568
- interface DataContextValue {
569
- data: Record<string, unknown>; // Raw responses keyed by query
570
- isRefreshing: boolean;
571
- lastRefreshError: string | null;
572
- lastRefreshTime: Date | null;
573
- credentialsSet: boolean;
574
- setCredentials: (creds: Credentials) => void;
575
- }
576
- ```
577
-
578
- ---
579
-
580
- ## Exports
581
-
582
- Everything below is exported from `react-dashstream`.
583
-
584
- ### Default export
585
-
586
- `AIOPsDashboard` — main dashboard shell component.
587
-
588
- ### Compound nodes
589
-
590
- `ServerNode`, `DatabaseNode`, `WebDispatcherNode`, `MessageServerNode`, `HumanNode`
591
-
592
- ### Layout
593
-
594
- `Carousel`, `Service`, `ServiceNode`
595
-
596
- ### 3D models
597
-
598
- `Server3D`, `Database3D`, `WebDispatcher3D`, `MessageServer3D`, `Human3D`
599
-
600
- ### Internal 3D components (for drill-down)
601
-
602
- `CPU3D`, `Memory3D`, `DriveBay3D`, `NetworkBlock3D`, `ThreadPool3D`, `Platter3D`, `Port3D`
603
-
604
- ### Indicators and connections
605
-
606
- `SvgConnection`, `SyncBridge`, `NodeCallout`, `HoloBase`
607
-
608
- ### Dialogs
609
-
610
- `ServiceDialog`, `ComponentDialog`, `HistoricalGraphPanel`, `ComponentDrillView`
611
-
612
- ### Pre-built services
613
-
614
- `SAPService`, `ExchangeService`
615
-
616
- SAP helpers: `computeSAPServiceStatus`, `computeSAPDialogMetrics`, `computeSAPDialogAlerts`, `SAP_CONNECTIONS`
617
-
618
- Exchange helpers: `computeExchangeServiceStatus`, `computeExchangeDialogMetrics`, `computeExchangeDialogAlerts`, `EXCHANGE_CONNECTIONS`
619
-
620
- SAP sub-component helpers: `getServerSubComponents`, `getServerGraphSeries`, `getDispatcherSubComponents`, `getDispatcherGraphSeries`, `getMessageServerSubComponents`, `getMessageServerGraphSeries`, `getDatabaseSubComponents`, `getDatabaseGraphSeries`
621
-
622
- ### Data layer
623
-
624
- `DataProvider`, `useAIOpsData`, `useAIOpsDataOptional`, `useQueryResult`, `defaultDataTransform`
625
-
626
- ### Event alerts
627
-
628
- `EventAlertsProvider`, `useEventAlerts`, `useEventAlertsOptional`
629
-
630
- ### Theme
631
-
632
- `STATUS_CFG`, `HOLO_CYAN` (`"#00e5ff"`), `HOLO_BLUE` (`"#0055cc"`), `HOLO_SURFACE`, `HOLO_GLASS`, `makeFaceStyles(W, H, D)`
633
-
634
- `ThemeProvider`, `useTheme`, type `DashboardTheme` (`"light" | "dark"`)
635
-
636
- ### Context hooks
637
-
638
- `CarouselContext`, `CarouselItemContext`, `useCarouselContext`, `useCarouselItemContext`, `ServiceContext`
639
-
640
- ### Type exports
641
-
642
- `AIOPsDashboardProps`, `ServiceMeta`, `ServiceMetricBinding`, `DashboardTheme`, `ComponentStatus`, `StatusCfg`, `FaceName`, `Base3DProps`, `ComponentType`, `ComponentContext`, `ComponentDialogMetric`, `SubComponentConfig`, `GraphSeries`, `SelectedComponent`, `ConnectionConfig`, `ViewState`, `DataBinding`, `DataBindings`, `DataProviderConfig`, `Credentials`, `DataContextValue`, `ServiceProps`, `ServiceContextValue`, `ServiceNodeProps`, `CarouselProps`, `ServerNodeProps`, `DatabaseNodeProps`, `HumanNodeProps`, `WebDispatcherNodeProps`, `MessageServerNodeProps`, `SvgConnectionProps`, `SyncBridgeProps`, `NodeCalloutProps`, `ServiceDialogProps`, `ServiceDialogMetric`, `ServiceDialogAlert`, `ComponentDialogProps`, `ComponentDrillViewProps`, `CarouselContextValue`, `CarouselItemContextValue`, `SAPServiceProps`, `SAPServiceOwnProps`, `SAPServiceConfig`, `ExchangeServiceProps`, `ExchangeServiceOwnProps`, `ExchangeServiceConfig`, `EventHostAlert`, `EventAlertsContextValue`, `EventAlertsProviderProps`
643
-
644
- ---
645
-
646
- ## Key types quick reference
647
-
648
- ### AIOPsDashboardProps
649
-
650
- `DashboardTheme` is `"light" | "dark"` (exported).
651
-
652
- ```ts
653
- interface AIOPsDashboardProps {
654
- title?: string;
655
- brandName?: string; // Default: "BUSAUD AIOps"
656
- brandTag?: string; // Default: "3D MONITOR"
657
- services?: ServiceMeta[];
658
- backgroundImage?: string;
659
- lightBackgroundImage?: string; // Used in light mode when set
660
- theme?: DashboardTheme; // Controlled theme (pair with onThemeChange)
661
- onThemeChange?: (theme: DashboardTheme) => void;
662
- logoUrl?: string;
663
- carouselSpeed?: number; // Default: 0.006
664
- fontFamily?: string;
665
- liveData?: boolean; // Default: false
666
- dataEndpoint?: string;
667
- dataBindings?: DataBindings;
668
- dataTransform?: (raw: unknown) => unknown;
669
- dataRefreshInterval?: number; // Default: 60000
670
- serviceDataBindings?: Record<string, ServiceMetricBinding[]>;
671
- eventApiConfig?: EventApiConfig; // Enable event-to-dashboard bridge
672
- children: React.ReactNode;
673
- }
674
- ```
675
-
676
- ### ServiceMeta
677
-
678
- ```ts
679
- interface ServiceMeta {
680
- name: string; // Must match child component name
681
- status: ComponentStatus;
682
- dbSync?: boolean; // Default: true
683
- metrics?: ServiceDialogMetric[];
684
- alerts?: ServiceDialogAlert[];
685
- }
686
-
687
- interface ServiceDialogMetric {
688
- label: string;
689
- value: string;
690
- color: string;
691
- }
692
-
693
- interface ServiceDialogAlert {
694
- level: "info" | "warning" | "critical";
695
- message: string;
696
- }
697
- ```
698
-
699
- ### ConnectionConfig
700
-
701
- ```ts
702
- interface ConnectionConfig {
703
- from: [number, number];
704
- to: [number, number];
705
- visibleAtPhase?: number; // Default: 0
706
- color?: string; // Default: "#00e5ff"
707
- duration?: string; // Default: "1.2s"
708
- }
709
- ```
710
-
711
- ### ComponentStatus
712
-
713
- ```ts
714
- type ComponentStatus = "online" | "warning" | "critical" | "offline";
715
- ```
716
-
717
- | Status | Color | Glow |
718
- | ---------- | --------- | -------- |
719
- | `online` | `#00e5ff` | cyan |
720
- | `warning` | `#ff8c00` | orange |
721
- | `critical` | `#ff2255` | red |
722
- | `offline` | `#1e3a5a` | dim blue |
723
-
724
- ---
725
-
726
- ## Endpoint contract
727
-
728
- | Aspect | Requirement |
729
- | ------------ | -------------------------------------------------------------- |
730
- | **Method** | `GET` |
731
- | **URL** | `<dataEndpoint>?query=<urlEncodedQuery>` |
732
- | **Headers** | `access-key` and `access-secret-key` |
733
- | **Response** | Plain text body (trimmed). Default transform parses as number. |
734
- | **Errors** | Non-2xx counted as failure per query |
735
-
736
- For JSON responses, provide a custom `dataTransform`:
737
-
738
- ```tsx
739
- dataTransform={(raw) => {
740
- const parsed = JSON.parse(String(raw));
741
- return parsed?.data?.result?.[0]?.value?.[1] ?? raw;
742
- }}
743
- ```
744
-
745
- ---
746
-
747
- ## Standalone DataProvider
748
-
749
- Use the data layer without `AIOPsDashboard`:
750
-
751
- ```tsx
752
- import { DataProvider, useAIOpsData } from "react-dashstream";
753
-
754
- <DataProvider
755
- config={{
756
- endpoint: "https://prometheus.example.com/api/v1/query",
757
- queries: ['cpu_usage{instance="srv-01"}'],
758
- refreshInterval: 15000,
759
- }}
760
- >
761
- <MyContent />
762
- </DataProvider>;
763
- ```
764
-
765
- ---
766
-
767
- ## Node positioning guide
768
-
769
- The topology scene is ~660×640 pixels.
770
-
771
- | Layer | Typical `ey` | `visibleAtPhase` | Component |
772
- | ----------- | ------------ | ---------------- | ------------------- |
773
- | Users (top) | 100 | 2 | `HumanNode` |
774
- | Dispatcher | 230 | 2 | `WebDispatcherNode` |
775
- | App servers | 350–380 | 3 | `ServerNode` |
776
- | Databases | 500–520 | 4 | `DatabaseNode` |
777
-
778
- Center X = `330`. Left/right columns: `200` / `460`. Three-column: `165` / `330` / `495`.
779
-
780
- `compactOffset` controls node position in the compact carousel view relative to center.
781
-
782
- ---
783
-
784
- ## Usage patterns
785
-
786
- ### Minimal static dashboard
787
-
788
- ```tsx
789
- import "react-dashstream/dist/index.css";
790
- import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
791
-
792
- <AIOPsDashboard brandName="MY DASHBOARD" services={[{ name: "Svc", status: "online" }]}>
793
- <Service
794
- name="Svc"
795
- status="online"
796
- connections={[{ from: [330, 200], to: [330, 380], visibleAtPhase: 3 }]}
797
- >
798
- <ServerNode
799
- ex={330}
800
- ey={380}
801
- compactOffset={{ x: 0, y: 0 }}
802
- zIndex={8}
803
- name="SRV-01"
804
- status="online"
805
- cpuLoad={42}
806
- memLoad={60}
807
- />
808
- </Service>
809
- </AIOPsDashboard>;
810
- ```
811
-
812
- ### Full live-data dashboard
813
-
814
- ```tsx
815
- <AIOPsDashboard
816
- brandName="LIVE MONITOR"
817
- services={services}
818
- liveData={true}
819
- dataEndpoint="https://prometheus.example.com/api/v1/query"
820
- dataRefreshInterval={10000}
821
- dataBindings={{
822
- "My Service": {
823
- cpuLoad: 'cpu_usage{instance="srv-01"}',
824
- status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
825
- },
826
- }}
827
- serviceDataBindings={{
828
- "My Service": [{ label: "CPU", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" }],
829
- }}
830
- >
831
- <MyService name="My Service" />
832
- </AIOPsDashboard>
833
- ```
834
-
835
- ### Custom service with all data features
836
-
837
- ```tsx
838
- function MyService({ name, status, cpuLoad, memLoad, dbCapacity, dbStatus }: any) {
839
- return (
840
- <Service
841
- name={name}
842
- status={status ?? "online"}
843
- connections={[
844
- { from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
845
- { from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
846
- ]}
847
- >
848
- <ServerNode
849
- ex={200}
850
- ey={380}
851
- compactOffset={{ x: -30, y: -20 }}
852
- zIndex={8}
853
- name="SRV-01"
854
- subLabel="APP SERVER"
855
- status={status ?? "online"}
856
- cpuLoad={cpuLoad ?? 42}
857
- memLoad={memLoad ?? 60}
858
- alert={{ offsetX: -160, offsetY: -60, align: "left" }}
859
- dialogMetrics={[
860
- { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: cpuLoad ?? 42 },
861
- { id: "mem", label: "MEMORY", sublabel: "HEAP", value: memLoad ?? 60 },
862
- ]}
863
- subComponents={[
864
- {
865
- id: "cpu",
866
- label: "CPU-0",
867
- status: "online",
868
- element: <CPU3D label="CPU-0" load={cpuLoad ?? 42} />,
869
- },
870
- {
871
- id: "mem",
872
- label: "HEAP",
873
- status: "online",
874
- element: <Memory3D label="HEAP" usedPercent={memLoad ?? 60} />,
875
- },
876
- ]}
877
- graphSeries={[
878
- { id: "cpu", label: "CPU", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68] },
879
- ]}
880
- />
881
- <DatabaseNode
882
- ex={460}
883
- ey={380}
884
- compactOffset={{ x: 30, y: -20 }}
885
- zIndex={7}
886
- name="DB-01"
887
- subLabel="PRIMARY"
888
- status={dbStatus ?? "online"}
889
- capacity={dbCapacity ?? 55}
890
- alert={{ offsetX: 160, offsetY: -60, align: "right" }}
891
- />
892
- </Service>
893
- );
894
- }
895
- ```
896
-
897
- ---
898
-
899
- ## EventView — Operations Event Console
900
-
901
- Table component for displaying infrastructure events from a live API. Uses the same `access-key` / `access-secret-key` authentication and holographic theme as the 3D dashboard. No built-in default data — requires either `apiConfig` (to fetch from an API) or `events` (pre-fetched array).
902
-
903
- ### API mode (primary usage)
904
-
905
- ```tsx
906
- import "react-dashstream/dist/index.css";
907
- import { EventView } from "react-dashstream";
908
- import type { EventApiConfig } from "react-dashstream";
909
-
910
- const apiConfig: EventApiConfig = {
911
- baseUrl: "https://your-monitoring-server.example.com",
912
- payload: { filter: {}, sortBy: "date_reception", sortOrder: "DESC" },
913
- fieldMapping: {
914
- id: "mc_ueid",
915
- occurrence: "date_reception",
916
- severityLastModified: "severity_last_modified",
917
- severity: "severity",
918
- owner: "owner",
919
- class: "class",
920
- host: "mc_host",
921
- message: "msg",
922
- remedySupportGroup: "ara_remedy_support_group",
923
- incidentId: "ara_incident_id",
924
- smsStatus: "ara_sms_status",
925
- onCallNumber: "ara_on_call_number",
926
- hostedApplication: "ara_hosted_application",
927
- monitoringCategory: "ara_hosted_app_monitoring",
928
- applicationSupportUnit: "ara_application_support_unit",
929
- },
930
- };
931
-
932
- <EventView apiConfig={apiConfig} />;
933
- ```
934
-
935
- ### How it works
936
-
937
- 1. Shows credentials modal (or reuses DataProvider credentials if inside `AIOPsDashboard`)
938
- 2. Sends `POST {baseUrl}/tsws/monitoring/api/v1.0/events/search` with the payload as JSON body
939
- 3. Auth via headers: `access-key` and `access-secret-key`
940
- 4. Expects response: `{ eventSeverityCount: {...}, totalCount: N, eventList: [{...}] }`
941
- 5. Maps each object in `eventList` to `AIOpsEvent` using `fieldMapping`
942
- 6. Maps severity strings via `severityMap` (default: `CRITICAL→Critical`, `MAJOR→Major`, `MINOR→Minor`)
943
- 7. Polls on `refreshInterval` (default 60s)
944
-
945
- ### Props
946
-
947
- | Prop | Type | Default | Notes |
948
- | -------------------- | ---------------- | ----------------- | -------------------------------------------------- |
949
- | `apiConfig` | `EventApiConfig` | | Required for API mode |
950
- | `events` | `AIOpsEvent[]` | — | Pre-fetched events (skips API calls) |
951
- | `credentials` | `Credentials` | — | Explicit creds; falls back to DataProvider modal |
952
- | `columnWidthsCookie` | `string` | `"ev_col_widths"` | Cookie name for persisting column widths |
953
- | `title` | `string` | `"Event Console"` | Header title |
954
- | `theme` | `DashboardTheme` | — | Optional override; else `ThemeProvider` / default |
955
-
956
- ### EventApiConfig
957
-
958
- ```ts
959
- interface EventApiConfig {
960
- baseUrl: string; // "https://monitoring.example.com"
961
- endpoint?: string; // default: "/tsws/monitoring/api/v1.0/events/search"
962
- payload: Record<string, unknown>; // POST body
963
- fieldMapping: EventFieldMapping; // maps API keys AIOpsEvent keys
964
- severityMap?: Record<string, EventSeverity>; // default: { CRITICAL, MAJOR, MINOR }
965
- refreshInterval?: number; // ms, default 60000
966
- }
967
- ```
968
-
969
- ### EventFieldMapping
970
-
971
- Maps every `AIOpsEvent` property to the API response field name. All 15 keys required:
972
-
973
- ```ts
974
- type EventFieldMapping = Record<keyof AIOpsEvent, string>;
975
- ```
976
-
977
- ### AIOpsEvent interface
978
-
979
- ```ts
980
- interface AIOpsEvent {
981
- id: string;
982
- occurrence: string;
983
- severityLastModified: string;
984
- severity: "Minor" | "Major" | "Critical";
985
- owner: string;
986
- class: "Self-monitoring" | "SCOM" | "Situation" | "Alarm" | "Event";
987
- host: string;
988
- message: string;
989
- remedySupportGroup: string;
990
- incidentId: string;
991
- smsStatus: "" | "SUCCESS" | "FAILED";
992
- onCallNumber: string;
993
- hostedApplication: string;
994
- monitoringCategory: "24/7" | "Office Hours";
995
- applicationSupportUnit: string;
996
- }
997
- ```
998
-
999
- ### Credential resolution order
1000
-
1001
- 1. `credentials` prop (if provided)
1002
- 2. `DataProvider` context credentials (when inside `AIOPsDashboard` with `liveData`)
1003
- 3. Built-in credentials modal (same UI as the 3D dashboard)
1004
-
1005
- ### Severity colors
1006
-
1007
- - **Minor** → `#ffb800` (amber)
1008
- - **Major** → `#ff6600` (orange)
1009
- - **Critical** → `#ff2255` (red)
1010
-
1011
- ### Features
1012
-
1013
- - Live API polling with configurable interval and manual refresh button
1014
- - Configurable field mapping — any API response shape can be mapped
1015
- - Shared auth — reuses DataProvider credentials when available
1016
- - Severity filter chips with live counts
1017
- - Free-text search across message, host, owner, incident ID
1018
- - Sortable columns (click header: asc → desc → none)
1019
- - Infinite scroll — all matching events in a single scrollable table
1020
- - Resizable columns drag column edges; widths persist in cookies
1021
- - Loading spinner, error badge, last-refresh timestamp
1022
- - Light / dark table chrome (via `ThemeProvider` or `theme` prop), scan-line header, severity badges
1023
-
1024
- ### Generating an EventApiConfig from the AI
1025
-
1026
- When asked to set up EventView for a new API, generate the config like this:
1027
-
1028
- 1. Set `baseUrl` to the server's protocol + host + port
1029
- 2. Set `endpoint` only if it differs from the default `/tsws/monitoring/api/v1.0/events/search`
1030
- 3. Build `payload` based on the API's expected POST body schema
1031
- 4. Build `fieldMapping` by asking the user which API field maps to each `AIOpsEvent` property
1032
- 5. Set `severityMap` only if the API uses non-standard severity values
1033
-
1034
- ---
1035
-
1036
- ## Build and development
1037
-
1038
- ```bash
1039
- npm run dev # Vite dev server (loads example/Dashboard.tsx)
1040
- npm run build # TypeScript check + Vite library build
1041
- npm run build:lib # Vite library build only
1042
- npm run preview # Preview production build
1043
- ```
1044
-
1045
- Output: `dist/index.js` (ESM), `dist/index.d.ts`, `dist/index.css`.
1046
-
1047
- ---
1048
-
1049
- ## Common pitfalls
1050
-
1051
- 1. **Missing CSS import** — Always import `react-dashstream/dist/index.css`.
1052
- 2. **Name mismatch** — `name` on child components must exactly match keys in `dataBindings`, `serviceDataBindings`, and `ServiceMeta.name`.
1053
- 3. **Credentials are in-memory only** — Page refresh requires re-entering credentials.
1054
- 4. **Default transform is plain-text** For JSON endpoints, provide a custom `dataTransform`.
1055
- 5. **Polling only** — No WebSocket/SSE support; uses `setInterval`.
1056
- 6. **`visibleAtPhase` matters** Nodes without proper values won't animate during expansion.
1057
- 7. **Connection coordinates** — `from`/`to` arrays use `[x, y]` matching `ex`/`ey` of connected nodes.
1058
- 8. **`dialogMetrics` replaces defaults** When provided, all default gauges (CPU/Memory/Storage) are removed.
1059
- 9. **`subComponents` replaces defaults** — When provided, all auto-generated sub-components are removed.
1060
- 10. **`graphSeries` replaces defaults** — When provided, all auto-generated sparklines are removed.
1061
- 11. **Event bridge matching** — Node `name` must match event `host` (case-insensitive) for `eventApiConfig` to highlight nodes automatically.
1062
- 12. **Theme context** — `EventView` and `CredentialsModal` use `useTheme()`. Without `ThemeProvider`, context defaults to `"dark"`. Wrap the app (or use `EventView`’s `theme` prop) so light dashboards are not paired with a dark event console or lock screen.
1
+ # React DashStream — AI Coding Assistant Skill
2
+
3
+ > **Package**: `react-dashstream` (npm)
4
+ > **Version**: 0.2.0
5
+ > **Peer deps**: `react` ^18 || ^19, `react-dom` ^18 || ^19
6
+ > **License**: MIT
7
+
8
+ Use this skill when the user is working with the `react-dashstream` package — a holographic 3D infrastructure monitoring dashboard built entirely with CSS 3D transforms (no WebGL, no canvas).
9
+
10
+ ---
11
+
12
+ ## What this package does
13
+
14
+ React DashStream renders a rotating 3D carousel of infrastructure services. Each service can be clicked to expand into a multi-tiered topology showing servers, databases, dispatchers, message servers, and user nodes connected by animated SVG lines. Components can be drilled into to reveal internal sub-components with sparkline graphs and gauges.
15
+
16
+ Two modes:
17
+
18
+ 1. **Static mode** — pass props directly for demo or snapshot views.
19
+ 2. **Live data mode** — poll an HTTP endpoint with PromQL queries and auto-inject resolved values as component props.
20
+
21
+ ---
22
+
23
+ ## Installation and CSS
24
+
25
+ ```bash
26
+ npm install react-dashstream
27
+ ```
28
+
29
+ ```tsx
30
+ import "react-dashstream/dist/index.css";
31
+ import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
32
+ ```
33
+
34
+ **Always import the CSS file** — without it, no styles render.
35
+
36
+ ---
37
+
38
+ ## Light / dark theme
39
+
40
+ The dashboard supports **`"light"`** and **`"dark"`** modes.
41
+
42
+ - **`AIOPsDashboard`** defaults to **light** and exposes a header toggle. Use **`backgroundImage`** (dark) and **`lightBackgroundImage`** (light when set).
43
+ - **Controlled mode:** pass **`theme`** and **`onThemeChange`** (`DashboardTheme`) so parent state owns the mode (e.g. sync app chrome + `EventView`).
44
+ - **`ThemeProvider`** (export) + **`useTheme()`** — wrap subtrees so **`EventView`**, **`CredentialsModal`**, **`ServiceDialog`**, and **`ComponentDialog`** match the dashboard. Without a provider, `useTheme()` defaults to **`"dark"`**; prefer wrapping when using `EventView` or the lock screen next to a light dashboard.
45
+ - **`EventView`** accepts optional **`theme`** to override context.
46
+
47
+ When generating multi-view apps (3D + Event Console + credentials), wrap the root in **`ThemeProvider`** with the same value passed to **`AIOPsDashboard`**’s controlled props.
48
+
49
+ ---
50
+
51
+ ## Architecture
52
+
53
+ ```
54
+ AIOPsDashboard ← Shell: header, state, optional DataProvider
55
+ └─ Carousel ← 3D orbit, rotation, hosts dialogs
56
+ └─ Service ← Per-service container, connections, holo base
57
+ ├─ ServerNode ← Compound: ServiceNode + Server3D
58
+ ├─ DatabaseNode ← Compound: ServiceNode + Database3D
59
+ ├─ WebDispatcherNode ← Compound: ServiceNode + WebDispatcher3D
60
+ ├─ MessageServerNode ← Compound: ServiceNode + MessageServer3D
61
+ ├─ HumanNode ← Compound: ServiceNode + Human3D (SVG)
62
+ ├─ SvgConnection ← Animated dashed topology line
63
+ ├─ SyncBridge ← DB replication indicator
64
+ ├─ NodeCallout ← Alert callout (auto from thresholds)
65
+ └─ HoloBase ← Neon platform (auto-rendered)
66
+
67
+ ServiceDialog ← Service-level KPI panel (auto on expand)
68
+ ComponentDialog ← Drill-down with internals + graphs
69
+ ├─ HistoricalGraphPanel ← Sparkline charts
70
+ └─ CPU3D / Memory3D / DriveBay3D / NetworkBlock3D / ThreadPool3D / Platter3D / Port3D
71
+
72
+ DataProvider ← Polling engine + credentials modal
73
+
74
+ EventView ← Standalone event console (table view)
75
+
76
+ DatacenterView ← SVG topology / optional geography map + drill-down dashboard
77
+ ```
78
+
79
+ ### Source layout
80
+
81
+ ```
82
+ src/
83
+ ├── index.ts # Public API barrel
84
+ ├── AIOPsDashboard.tsx # Dashboard shell + live-data wiring
85
+ ├── types.ts # Shared topology/drill-down types
86
+ ├── theme.ts # Status tokens, holo palette, 3D face helpers
87
+ ├── ThemeContext.ts # DashboardTheme, ThemeProvider, useTheme
88
+ ├── styles.css / index.css # Library + dashboard styles
89
+ ├── data/
90
+ ├── DataProvider.tsx # Polling engine, context, hooks
91
+ ├── CredentialsModal.tsx # Auth prompt UI
92
+ └── index.ts
93
+ ├── components/ # ~30 UI components
94
+ │ ├── Carousel.tsx, Service.tsx, ServiceNode.tsx
95
+ │ ├── ServerNode.tsx, DatabaseNode.tsx, WebDispatcherNode.tsx, etc.
96
+ │ ├── Server3D.tsx, Database3D.tsx, etc.
97
+ │ ├── ServiceDialog.tsx, ComponentDialog.tsx
98
+ │ ├── Internal3DComponents.tsx, HistoricalGraphPanel.tsx
99
+ │ ├── ComponentDrillView.tsx, SvgConnection.tsx, SyncBridge.tsx
100
+ │ ├── NodeCallout.tsx, HoloBase.tsx, CarouselContext.ts
101
+ │ ├── EventView/ # Operations event console
102
+ │ │ ├── EventView.tsx # Table component (filter, sort, search, infinite scroll)
103
+ │ │ ├── EventView.css # Holographic table styles
104
+ │ │ ├── EventAlertsContext.tsx # EventAlertsProvider + per-host alert map
105
+ │ │ ├── fetchEvents.ts # Shared fetch/mapping logic (used by both EventView & provider)
106
+ │ │ ├── types.ts # AIOpsEvent, EventSeverity, EventApiConfig, etc.
107
+ │ ├── mockData.ts # 28 realistic mock events
108
+ │ │ └── index.ts
109
+ │ ├── DatacenterView/ # Topology map + CSS 3D building markers
110
+ ├── DatacenterView.tsx
111
+ │ ├── DatacenterView.css
112
+ │ │ ├── buildings.tsx # DatacenterSite, TowerDC, FlatDC, … (declarative JSX)
113
+ │ │ ├── collectDatacenterTree.ts
114
+ │ │ ├── types.ts
115
+ │ │ └── index.ts
116
+ │ └── index.ts
117
+ ├── services/
118
+ │ ├── SAPService.tsx, ExchangeService.tsx
119
+ │ ├── sapSubComponents.tsx
120
+ │ └── index.ts
121
+ example/
122
+ ├── Dashboard.tsx
123
+ └── services/
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Complete data flow reference
129
+
130
+ There are **10 distinct data paths** in this package. Understanding them is critical for building live dashboards.
131
+
132
+ ### 1. Data bindings live props injected into service components
133
+
134
+ `dataBindings` maps **service name prop name PromQL query**. The dashboard polls each query, applies a transform, and injects the result as a prop on the matched child component.
135
+
136
+ ```ts
137
+ type DataBinding = string | { query: string; transform?: (raw: unknown) => unknown };
138
+ type DataBindings = Record<string, Record<string, DataBinding>>;
139
+ ```
140
+
141
+ - Bare string → uses global `dataTransform` (defaults to numeric parsing)
142
+ - Object `{ query, transform }` uses per-binding transform
143
+ - Service name key must match child's `name` prop **exactly**
144
+
145
+ ```tsx
146
+ const dataBindings: DataBindings = {
147
+ "My Service": {
148
+ cpuLoad: 'cpu_usage{instance="srv-01"}', // bare string → number
149
+ memLoad: 'memory_usage{instance="srv-01"}', // bare string → number
150
+ status: {
151
+ // object → custom transform
152
+ query: 'cpu_usage{instance="srv-01"}',
153
+ transform: (raw) => {
154
+ const n = Number(raw);
155
+ if (n >= 85) return "critical";
156
+ if (n >= 70) return "warning";
157
+ return "online";
158
+ },
159
+ },
160
+ dbCapacity: 'disk_capacity{instance="db-01"}',
161
+ dbStatus: { query: 'disk_capacity{instance="db-01"}', transform: statusFromValue },
162
+ },
163
+ };
164
+ ```
165
+
166
+ #### Multi-component services
167
+
168
+ When a service has multiple nodes of the same type (e.g. three servers), use a flat naming convention with prefixes. Each `DataBinding` maps **one prop → one query** — there is no array or object binding type.
169
+
170
+ ```tsx
171
+ interface ServiceXProps {
172
+ name: string;
173
+ status?: ComponentStatus;
174
+ srv1CpuLoad?: number;
175
+ srv1MemLoad?: number;
176
+ srv1Status?: ComponentStatus;
177
+ srv2CpuLoad?: number;
178
+ srv2MemLoad?: number;
179
+ srv2Status?: ComponentStatus;
180
+ srv3CpuLoad?: number;
181
+ srv3MemLoad?: number;
182
+ srv3Status?: ComponentStatus;
183
+ dbCapacity?: number;
184
+ dbStatus?: ComponentStatus;
185
+ }
186
+
187
+ function ServiceX({
188
+ name, status = "online",
189
+ srv1CpuLoad = 54, srv1MemLoad = 58, srv1Status = "online",
190
+ srv2CpuLoad = 63, srv2MemLoad = 66, srv2Status = "online",
191
+ srv3CpuLoad = 78, srv3MemLoad = 71, srv3Status = "online",
192
+ dbCapacity = 68, dbStatus = "online",
193
+ }: ServiceXProps) {
194
+ return (
195
+ <Service name={name} status={status} connections={[/* ... */]}>
196
+ <ServerNode name="SRV-X1" status={srv1Status}
197
+ cpuLoad={srv1CpuLoad} memLoad={srv1MemLoad} ... />
198
+ <ServerNode name="SRV-X2" status={srv2Status}
199
+ cpuLoad={srv2CpuLoad} memLoad={srv2MemLoad} ... />
200
+ <ServerNode name="SRV-X3" status={srv3Status}
201
+ cpuLoad={srv3CpuLoad} memLoad={srv3MemLoad} ... />
202
+ <DatabaseNode name="DB-X1" status={dbStatus}
203
+ capacity={dbCapacity} ... />
204
+ </Service>
205
+ );
206
+ }
207
+ ```
208
+
209
+ Bind each prefixed prop individually:
210
+
211
+ ```tsx
212
+ dataBindings={{
213
+ ServiceX: {
214
+ status: { query: 'status{instance="svcx"}', transform: statusFromValue },
215
+ srv1CpuLoad: 'cpu_usage{instance="srvx-01"}',
216
+ srv1MemLoad: 'memory_usage{instance="srvx-01"}',
217
+ srv1Status: { query: 'status{instance="srvx-01"}', transform: statusFromValue },
218
+ srv2CpuLoad: 'cpu_usage{instance="srvx-02"}',
219
+ srv2MemLoad: 'memory_usage{instance="srvx-02"}',
220
+ srv2Status: { query: 'status{instance="srvx-02"}', transform: statusFromValue },
221
+ srv3CpuLoad: 'cpu_usage{instance="srvx-03"}',
222
+ srv3MemLoad: 'memory_usage{instance="srvx-03"}',
223
+ srv3Status: { query: 'status{instance="srvx-03"}', transform: statusFromValue },
224
+ dbCapacity: 'disk_capacity{instance="dbx-01"}',
225
+ dbStatus: { query: 'status{instance="dbx-01"}', transform: statusFromValue },
226
+ },
227
+ }}
228
+ ```
229
+
230
+ Use a consistent convention like `srv1CpuLoad`, `srv2CpuLoad`. The service component maps each prefixed prop to the correct node.
231
+
232
+ ### 2. Service dialog — static ServiceMeta metrics
233
+
234
+ The **ServiceDialog** panel shows KPIs when a service is expanded. Static metrics are passed via `ServiceMeta`:
235
+
236
+ ```tsx
237
+ const services: ServiceMeta[] = [
238
+ {
239
+ name: "My Service",
240
+ status: "online",
241
+ metrics: [
242
+ { label: "Uptime", value: "99.99%", color: "#00ff88" },
243
+ { label: "Avg Latency", value: "8ms", color: "#00e5ff" },
244
+ ],
245
+ alerts: [
246
+ { level: "info", message: "All Systems Nominal" },
247
+ { level: "warning", message: "High CPU on SRV-01" },
248
+ { level: "critical", message: "DB connection pool exhausted" },
249
+ ],
250
+ },
251
+ ];
252
+ ```
253
+
254
+ ### 3. Service dialog live serviceDataBindings
255
+
256
+ Replace static metrics with live-fetched values using `serviceDataBindings`:
257
+
258
+ ```tsx
259
+ serviceDataBindings={{
260
+ "My Service": [
261
+ { label: "CPU Load", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" },
262
+ { label: "Memory", query: 'memory_usage{instance="srv-01"}', unit: "%", color: "#bb55ff" },
263
+ { label: "Disk", query: 'disk_capacity{instance="db-01"}', unit: "%", color: "#ff8c00" },
264
+ ],
265
+ }}
266
+ ```
267
+
268
+ ```ts
269
+ interface ServiceMetricBinding {
270
+ label: string; // Row label
271
+ query: string; // PromQL query
272
+ unit?: string; // Suffix (e.g. "%", "ms")
273
+ color?: string; // Accent color
274
+ transform?: (raw: unknown) => string; // Custom formatter
275
+ }
276
+ ```
277
+
278
+ When provided, live values **replace** static `ServiceMeta.metrics` for that service.
279
+
280
+ ### 4. Component dialog default gauges
281
+
282
+ The **ComponentDialog** appears when clicking a node. Default gauges are derived from node type:
283
+
284
+ - **Server**: CPU (from `cpuLoad`), Memory (from `memLoad`), Storage
285
+ - **Database**: Capacity (from `capacity`), Memory, Storage
286
+ - **Dispatcher**: Traffic (from `traffic`), Memory, Storage
287
+ - **Message Server**: Queue (from `queueDepth`), Memory, Storage
288
+
289
+ Default thresholds: warn at 70, critical at 85.
290
+
291
+ ### 5. Component dialog — custom dialogMetrics
292
+
293
+ Override default gauges with `dialogMetrics` on any compound node:
294
+
295
+ ```tsx
296
+ <ServerNode
297
+ name="SRV-01"
298
+ status="online"
299
+ cpuLoad={67}
300
+ memLoad={72}
301
+ ex={200}
302
+ ey={380}
303
+ compactOffset={{ x: -30, y: -20 }}
304
+ zIndex={8}
305
+ dialogMetrics={[
306
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67, unit: "%" },
307
+ { id: "mem", label: "MEMORY", sublabel: "HEAP USAGE", value: 72, unit: "%" },
308
+ { id: "iops", label: "IOPS", sublabel: "DISK OPS", value: 45, unit: "k/s", icon: "disk" },
309
+ {
310
+ id: "threads",
311
+ label: "THREADS",
312
+ sublabel: "ACTIVE",
313
+ value: 82,
314
+ unit: "%",
315
+ warnAt: 60,
316
+ critAt: 80,
317
+ icon: "cpu",
318
+ },
319
+ ]}
320
+ />
321
+ ```
322
+
323
+ ```ts
324
+ interface ComponentDialogMetric {
325
+ id: string; // Unique key
326
+ label: string; // Upper label
327
+ sublabel: string; // Lower label
328
+ value: number; // 0–100
329
+ unit?: string; // Default "%"
330
+ icon?: "cpu" | "mem" | "disk"; // Default "cpu"
331
+ warnAt?: number; // Default 70
332
+ critAt?: number; // Default 85
333
+ color?: string; // Override bar color (bypasses threshold coloring)
334
+ }
335
+ ```
336
+
337
+ All compound nodes accept `dialogMetrics`: `ServerNode`, `DatabaseNode`, `WebDispatcherNode`, `MessageServerNode`.
338
+
339
+ **Live component dialog metrics pattern**: To feed live query values into custom gauges, expose each metric as a prop on your service component. Use `dataBindings` to inject them. Then build the `dialogMetrics` array from those props:
340
+
341
+ ```tsx
342
+ // Service component — accepts individual metric props, builds dialogMetrics from them
343
+ function MyService({ name, status = "online", cpuLoad = 42, memLoad = 60, iops = 20 }: any) {
344
+ return (
345
+ <Service
346
+ name={name}
347
+ status={status}
348
+ connections={
349
+ [
350
+ /* ... */
351
+ ]
352
+ }
353
+ >
354
+ <ServerNode
355
+ name="SRV-01"
356
+ status={status}
357
+ cpuLoad={cpuLoad}
358
+ memLoad={memLoad}
359
+ ex={200}
360
+ ey={380}
361
+ compactOffset={{ x: -30, y: -20 }}
362
+ zIndex={8}
363
+ dialogMetrics={[
364
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: cpuLoad },
365
+ { id: "mem", label: "MEMORY", sublabel: "HEAP", value: memLoad },
366
+ {
367
+ id: "iops",
368
+ label: "IOPS",
369
+ sublabel: "DISK OPS",
370
+ value: iops,
371
+ icon: "disk",
372
+ warnAt: 50,
373
+ critAt: 80,
374
+ },
375
+ ]}
376
+ />
377
+ </Service>
378
+ );
379
+ }
380
+
381
+ // Dashboard bind each prop to a query
382
+ <AIOPsDashboard
383
+ liveData
384
+ dataEndpoint="..."
385
+ dataBindings={{
386
+ "My Service": {
387
+ cpuLoad: 'cpu_usage{instance="srv-01"}',
388
+ memLoad: 'memory_usage{instance="srv-01"}',
389
+ iops: 'disk_iops{instance="srv-01"}',
390
+ status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
391
+ },
392
+ }}
393
+ >
394
+ <MyService name="My Service" />
395
+ </AIOPsDashboard>;
396
+ ```
397
+
398
+ The dashboard injects `cpuLoad`, `memLoad`, `iops` as live props → they flow into `dialogMetrics` → gauges update automatically on every poll. Works for any number of custom metrics.
399
+
400
+ ### 6. Alerts automatic threshold detection
401
+
402
+ Nodes auto-render `NodeCallout` alerts when metrics breach thresholds:
403
+
404
+ - **Warning** at 70% → orange callout
405
+ - **Critical** at 85% → red callout
406
+
407
+ Sources (checked in priority order):
408
+
409
+ 1. `dialogMetrics` values vs their `warnAt`/`critAt`
410
+ 2. Context values (`cpuLoad`, `memLoad`, `traffic`, `queueDepth`, `capacity`) vs default thresholds
411
+
412
+ ```tsx
413
+ // Auto-alert from cpuLoad > 85
414
+ <ServerNode cpuLoad={92} memLoad={64}
415
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
416
+ ... />
417
+
418
+ // Custom message
419
+ <ServerNode cpuLoad={92}
420
+ alert={{ msg: "CPU overload — scale out", offsetX: -160, offsetY: -60, align: "left" }}
421
+ ... />
422
+
423
+ // Custom thresholds via dialogMetrics
424
+ <ServerNode
425
+ dialogMetrics={[
426
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: 67,
427
+ warnAt: 50, critAt: 75 }, // Alert at 67 because critAt=75
428
+ ]}
429
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
430
+ ... />
431
+ ```
432
+
433
+ Alert `align` options: `"left"`, `"right"`, `"top"`, `"bottom"`.
434
+
435
+ Alert prop shape:
436
+
437
+ ```ts
438
+ alert?: {
439
+ msg?: string; // Custom message (auto-generated if omitted)
440
+ offsetX?: number; // X offset from node (default 110)
441
+ offsetY?: number; // Y offset from node (default -30)
442
+ align?: "left" | "right" | "top" | "bottom"; // Default "right"
443
+ internalRef?: string; // Sub-component ID
444
+ }
445
+ ```
446
+
447
+ ### 7. Event-to-dashboard bridge — automatic node alerts from events API
448
+
449
+ When `eventApiConfig` is passed to `AIOPsDashboard`, events are fetched in the background and nodes whose `name` (case-insensitive) matches an event `host` automatically show alert callouts.
450
+
451
+ ```tsx
452
+ <AIOPsDashboard
453
+ eventApiConfig={eventApiConfig} // same EventApiConfig used for EventView
454
+ liveData
455
+ dataEndpoint="..."
456
+ dataBindings={dataBindings}
457
+ services={services}
458
+ >
459
+ <MyService name="My Service" />
460
+ </AIOPsDashboard>
461
+ ```
462
+
463
+ **How it works internally:**
464
+
465
+ 1. `AIOPsDashboard` wraps children in `EventAlertsProvider` (inside `DataProvider` when `liveData` is set — shares credentials automatically)
466
+ 2. `EventAlertsProvider` fetches events using the same `fetchEvents` + `mapRawEvent` logic as `EventView`
467
+ 3. Events are grouped by **lower-cased hostname** into a `Map<string, EventHostAlert>`
468
+ 4. `ServiceNode` reads from `EventAlertsContext` via `useEventAlertsOptional()`
469
+ 5. If `componentInfo.name.toLowerCase()` matches an entry, event severity is factored into the existing `resolveNodeSeverity` pipeline as a **third severity source** (alongside node status and metric breaches)
470
+ 6. Highest severity among all three sources wins
471
+ 7. `NodeCallout` appears automatically with the event message; multi-event hosts show `"N events – message"`
472
+
473
+ **Severity mapping:**
474
+
475
+ | Event severity | → ComponentStatus |
476
+ | -------------- | ----------------- |
477
+ | Critical | `"critical"` |
478
+ | Major | `"warning"` |
479
+ | Minor | `"warning"` |
480
+
481
+ **Key requirement:** Node `name` props must match event `host` values from the API (case-insensitive). Example: if the API returns `host: "SAP-PRD-APP01"`, the node must be `<ServerNode name="SAP-PRD-APP01" ... />`.
482
+
483
+ **Credential resolution order** (for EventAlertsProvider inside AIOPsDashboard):
484
+
485
+ 1. `DataProvider` context credentials (when `liveData` is enabled same credentials, one login)
486
+ 2. Built-in `CredentialsModal` (when `liveData` is not enabled)
487
+
488
+ **Opt-in:** Without `eventApiConfig`, no events are fetched zero overhead.
489
+
490
+ **When generating an `AIOPsDashboard` with event bridge:** Reuse the same `EventApiConfig` object that the user already has for their `EventView`. Just add it as `eventApiConfig` on `AIOPsDashboard`.
491
+
492
+ ### 8. Drill-down internals custom subComponents
493
+
494
+ When clicking a node, the drill-down shows internal 3D sub-components. Default sub-components are auto-generated per type. Override with `subComponents`:
495
+
496
+ ```tsx
497
+ import { CPU3D, Memory3D, DriveBay3D, ThreadPool3D, NetworkBlock3D, Platter3D, Port3D } from "react-dashstream";
498
+ import type { SubComponentConfig } from "react-dashstream";
499
+
500
+ const internals: SubComponentConfig[] = [
501
+ { id: "cpu-0", label: "CPU-0", status: "online",
502
+ element: <CPU3D label="CPU-0" load={67} color="#00e5ff" /> },
503
+ { id: "heap", label: "HEAP", status: "warning",
504
+ element: <Memory3D label="HEAP" usedPercent={92} color="#8855ee" status="warning" /> },
505
+ { id: "drive", label: "DRIVE-0", status: "online",
506
+ element: <DriveBay3D label="DRIVE-0" color="#00e5ff" activity={true} /> },
507
+ { id: "threads", label: "THREADS", status: "online",
508
+ element: <ThreadPool3D label="THREADS" color="#00e5ff" /> },
509
+ { id: "net", label: "NET", status: "online",
510
+ element: <NetworkBlock3D label="NET" color="#00e5ff" /> },
511
+ ];
512
+
513
+ <ServerNode subComponents={internals} ... />
514
+ ```
515
+
516
+ ```ts
517
+ interface SubComponentConfig {
518
+ id: string; // Unique key
519
+ label: string; // Display name
520
+ status: ComponentStatus; // LED color
521
+ detail?: string; // Fault description
522
+ element: ReactNode; // 3D element to render
523
+ }
524
+ ```
525
+
526
+ All compound nodes accept `subComponents`.
527
+
528
+ ### 9. Graph series custom sparklines
529
+
530
+ The drill-down's historical panel shows sparkline charts. Default graphs are auto-generated. Override with `graphSeries`:
531
+
532
+ ```tsx
533
+ import type { GraphSeries } from "react-dashstream";
534
+
535
+ const graphs: GraphSeries[] = [
536
+ { id: "cpu", label: "CPU-0", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68, 55] },
537
+ { id: "mem", label: "HEAP", unit: "%", color: "#8855ee", data: [60, 65, 72, 78, 82, 75] },
538
+ { id: "iops", label: "DISK", unit: "k/s", color: "#ff8c00", data: [12, 15, 22, 18, 25, 20] },
539
+ ];
540
+
541
+ <ServerNode graphSeries={graphs} ... />
542
+ ```
543
+
544
+ ```ts
545
+ interface GraphSeries {
546
+ id: string; // Unique key
547
+ label: string; // Label above sparkline
548
+ unit: string; // Suffix
549
+ color: string; // Line color
550
+ data: number[]; // Points (most recent last)
551
+ }
552
+ ```
553
+
554
+ All compound nodes accept `graphSeries`.
555
+
556
+ ### 10. Data hooks — programmatic access
557
+
558
+ Access live data context anywhere inside the dashboard:
559
+
560
+ ```tsx
561
+ import { useAIOpsData, useAIOpsDataOptional, useQueryResult } from "react-dashstream";
562
+
563
+ function CustomWidget() {
564
+ const { data, isRefreshing, lastRefreshError, lastRefreshTime, credentialsSet } = useAIOpsData();
565
+ const cpu = useQueryResult('cpu_usage{instance="srv-01"}');
566
+ return <div>CPU: {String(cpu)}</div>;
567
+ }
568
+ ```
569
+
570
+ | Hook | Returns | Throws? |
571
+ | ------------------------ | -------------------------- | ---------------------------- |
572
+ | `useAIOpsData()` | `DataContextValue` | Yes — outside `DataProvider` |
573
+ | `useAIOpsDataOptional()` | `DataContextValue \| null` | No |
574
+ | `useQueryResult(query)` | `unknown \| null` | Yes — outside `DataProvider` |
575
+
576
+ ```ts
577
+ interface DataContextValue {
578
+ data: Record<string, unknown>; // Raw responses keyed by query
579
+ isRefreshing: boolean;
580
+ lastRefreshError: string | null;
581
+ lastRefreshTime: Date | null;
582
+ credentialsSet: boolean;
583
+ setCredentials: (creds: Credentials) => void;
584
+ }
585
+ ```
586
+
587
+ ---
588
+
589
+ ## Exports
590
+
591
+ Everything below is exported from `react-dashstream`.
592
+
593
+ ### Default export
594
+
595
+ `AIOPsDashboard` — main dashboard shell component.
596
+
597
+ ### Compound nodes
598
+
599
+ `ServerNode`, `DatabaseNode`, `WebDispatcherNode`, `MessageServerNode`, `HumanNode`
600
+
601
+ ### Layout
602
+
603
+ `Carousel`, `Service`, `ServiceNode`
604
+
605
+ ### 3D models
606
+
607
+ `Server3D`, `Database3D`, `WebDispatcher3D`, `MessageServer3D`, `Human3D`
608
+
609
+ ### Internal 3D components (for drill-down)
610
+
611
+ `CPU3D`, `Memory3D`, `DriveBay3D`, `NetworkBlock3D`, `ThreadPool3D`, `Platter3D`, `Port3D`
612
+
613
+ ### Indicators and connections
614
+
615
+ `SvgConnection`, `SyncBridge`, `NodeCallout`, `HoloBase`
616
+
617
+ ### Dialogs
618
+
619
+ `ServiceDialog`, `ComponentDialog`, `HistoricalGraphPanel`, `ComponentDrillView`
620
+
621
+ ### Pre-built services
622
+
623
+ `SAPService`, `ExchangeService`
624
+
625
+ SAP helpers: `computeSAPServiceStatus`, `computeSAPDialogMetrics`, `computeSAPDialogAlerts`, `SAP_CONNECTIONS`
626
+
627
+ Exchange helpers: `computeExchangeServiceStatus`, `computeExchangeDialogMetrics`, `computeExchangeDialogAlerts`, `EXCHANGE_CONNECTIONS`
628
+
629
+ SAP sub-component helpers: `getServerSubComponents`, `getServerGraphSeries`, `getDispatcherSubComponents`, `getDispatcherGraphSeries`, `getMessageServerSubComponents`, `getMessageServerGraphSeries`, `getDatabaseSubComponents`, `getDatabaseGraphSeries`
630
+
631
+ ### Data layer
632
+
633
+ `DataProvider`, `useAIOpsData`, `useAIOpsDataOptional`, `useQueryResult`, `extractUniqueQueries`, `extractDatacenterMetricQueries`, `defaultDataTransform`
634
+
635
+ ### Datacenter topology map
636
+
637
+ `DatacenterView` — plus `DatacenterSite`, shape components (`StandardDC`, `TowerDC`, `FlatDC`, `StorageDC`, `GoogleDC`, `MicroDC`, `OfficeDC`), generic `Datacenter`, `collectDatacenterTree`, `getBuildingVariant`, and types `DatacenterViewProps`, `DatacenterBuildingConfig`, `DatacenterBuildingNodeProps`, `DatacenterBuildingVariant`, `DatacenterConnection`, `DatacenterGeography`, `DatacenterMetrics`, `DatacenterProps`, `DatacenterSiteLabels`, `DatacenterSiteProps`, `DatacenterViewPhase`, `CollectedTopology`
638
+
639
+ ### Event alerts
640
+
641
+ `EventAlertsProvider`, `useEventAlerts`, `useEventAlertsOptional`
642
+
643
+ ### Theme
644
+
645
+ `STATUS_CFG`, `HOLO_CYAN` (`"#00e5ff"`), `HOLO_BLUE` (`"#0055cc"`), `HOLO_SURFACE`, `HOLO_GLASS`, `makeFaceStyles(W, H, D)`
646
+
647
+ `ThemeProvider`, `useTheme`, type `DashboardTheme` (`"light" | "dark"`)
648
+
649
+ ### Context hooks
650
+
651
+ `CarouselContext`, `CarouselItemContext`, `useCarouselContext`, `useCarouselItemContext`, `ServiceContext`
652
+
653
+ ### Type exports
654
+
655
+ `AIOPsDashboardProps`, `ServiceMeta`, `ServiceMetricBinding`, `DashboardTheme`, `ComponentStatus`, `StatusCfg`, `FaceName`, `Base3DProps`, `ComponentType`, `ComponentContext`, `ComponentDialogMetric`, `SubComponentConfig`, `GraphSeries`, `SelectedComponent`, `ConnectionConfig`, `ViewState`, `DataBinding`, `DataBindings`, `DatacenterMetricBindings`, `DataProviderConfig`, `Credentials`, `DataContextValue`, `ServiceProps`, `ServiceContextValue`, `ServiceNodeProps`, `CarouselProps`, `ServerNodeProps`, `DatabaseNodeProps`, `HumanNodeProps`, `WebDispatcherNodeProps`, `MessageServerNodeProps`, `SvgConnectionProps`, `SyncBridgeProps`, `NodeCalloutProps`, `ServiceDialogProps`, `ServiceDialogMetric`, `ServiceDialogAlert`, `ComponentDialogProps`, `ComponentDrillViewProps`, `CarouselContextValue`, `CarouselItemContextValue`, `SAPServiceProps`, `SAPServiceOwnProps`, `SAPServiceConfig`, `ExchangeServiceProps`, `ExchangeServiceOwnProps`, `ExchangeServiceConfig`, `EventHostAlert`, `EventAlertsContextValue`, `EventAlertsProviderProps`, `DatacenterViewProps`, `DatacenterBuildingConfig`, `DatacenterBuildingNodeProps`, `DatacenterBuildingVariant`, `DatacenterConnection`, `DatacenterGeography`, `DatacenterMetrics`, `DatacenterProps`, `DatacenterSiteLabels`, `DatacenterSiteProps`, `DatacenterViewPhase`, `CollectedTopology`
656
+
657
+ ---
658
+
659
+ ## Key types quick reference
660
+
661
+ ### AIOPsDashboardProps
662
+
663
+ `DashboardTheme` is `"light" | "dark"` (exported).
664
+
665
+ ```ts
666
+ interface AIOPsDashboardProps {
667
+ title?: string;
668
+ brandName?: string; // Default: "BUSAUD AIOps"
669
+ brandTag?: string; // Default: "3D MONITOR"
670
+ services?: ServiceMeta[];
671
+ backgroundImage?: string;
672
+ lightBackgroundImage?: string; // Used in light mode when set
673
+ theme?: DashboardTheme; // Controlled theme (pair with onThemeChange)
674
+ onThemeChange?: (theme: DashboardTheme) => void;
675
+ logoUrl?: string;
676
+ carouselSpeed?: number; // Default: 0.006
677
+ fontFamily?: string;
678
+ liveData?: boolean; // Default: false
679
+ dataEndpoint?: string;
680
+ dataBindings?: DataBindings;
681
+ dataTransform?: (raw: unknown) => unknown;
682
+ dataRefreshInterval?: number; // Default: 60000
683
+ serviceDataBindings?: Record<string, ServiceMetricBinding[]>;
684
+ eventApiConfig?: EventApiConfig; // Enable event-to-dashboard bridge
685
+ children: React.ReactNode;
686
+ }
687
+ ```
688
+
689
+ ### ServiceMeta
690
+
691
+ ```ts
692
+ interface ServiceMeta {
693
+ name: string; // Must match child component name
694
+ status: ComponentStatus;
695
+ dbSync?: boolean; // Default: true
696
+ metrics?: ServiceDialogMetric[];
697
+ alerts?: ServiceDialogAlert[];
698
+ }
699
+
700
+ interface ServiceDialogMetric {
701
+ label: string;
702
+ value: string;
703
+ color: string;
704
+ }
705
+
706
+ interface ServiceDialogAlert {
707
+ level: "info" | "warning" | "critical";
708
+ message: string;
709
+ }
710
+ ```
711
+
712
+ ### ConnectionConfig
713
+
714
+ ```ts
715
+ interface ConnectionConfig {
716
+ from: [number, number];
717
+ to: [number, number];
718
+ visibleAtPhase?: number; // Default: 0
719
+ color?: string; // Default: "#00e5ff"
720
+ duration?: string; // Default: "1.2s"
721
+ }
722
+ ```
723
+
724
+ ### ComponentStatus
725
+
726
+ ```ts
727
+ type ComponentStatus = "online" | "warning" | "critical" | "offline";
728
+ ```
729
+
730
+ | Status | Color | Glow |
731
+ | ---------- | --------- | -------- |
732
+ | `online` | `#00e5ff` | cyan |
733
+ | `warning` | `#ff8c00` | orange |
734
+ | `critical` | `#ff2255` | red |
735
+ | `offline` | `#1e3a5a` | dim blue |
736
+
737
+ ---
738
+
739
+ ## Endpoint contract
740
+
741
+ | Aspect | Requirement |
742
+ | ------------ | -------------------------------------------------------------- |
743
+ | **Method** | `GET` |
744
+ | **URL** | `<dataEndpoint>?query=<urlEncodedQuery>` |
745
+ | **Headers** | `access-key` and `access-secret-key` |
746
+ | **Response** | Plain text body (trimmed). Default transform parses as number. |
747
+ | **Errors** | Non-2xx → counted as failure per query |
748
+
749
+ For JSON responses, provide a custom `dataTransform`:
750
+
751
+ ```tsx
752
+ dataTransform={(raw) => {
753
+ const parsed = JSON.parse(String(raw));
754
+ return parsed?.data?.result?.[0]?.value?.[1] ?? raw;
755
+ }}
756
+ ```
757
+
758
+ ---
759
+
760
+ ## Standalone DataProvider
761
+
762
+ Use the data layer without `AIOPsDashboard`:
763
+
764
+ ```tsx
765
+ import { DataProvider, useAIOpsData } from "react-dashstream";
766
+
767
+ <DataProvider
768
+ config={{
769
+ endpoint: "https://prometheus.example.com/api/v1/query",
770
+ queries: ['cpu_usage{instance="srv-01"}'],
771
+ refreshInterval: 15000,
772
+ }}
773
+ >
774
+ <MyContent />
775
+ </DataProvider>;
776
+ ```
777
+
778
+ ---
779
+
780
+ ## Node positioning guide
781
+
782
+ The topology scene is ~660×640 pixels.
783
+
784
+ | Layer | Typical `ey` | `visibleAtPhase` | Component |
785
+ | ----------- | ------------ | ---------------- | ------------------- |
786
+ | Users (top) | 100 | 2 | `HumanNode` |
787
+ | Dispatcher | 230 | 2 | `WebDispatcherNode` |
788
+ | App servers | 350–380 | 3 | `ServerNode` |
789
+ | Databases | 500–520 | 4 | `DatabaseNode` |
790
+
791
+ Center X = `330`. Left/right columns: `200` / `460`. Three-column: `165` / `330` / `495`.
792
+
793
+ `compactOffset` controls node position in the compact carousel view relative to center.
794
+
795
+ ---
796
+
797
+ ## Usage patterns
798
+
799
+ ### Minimal static dashboard
800
+
801
+ ```tsx
802
+ import "react-dashstream/dist/index.css";
803
+ import { AIOPsDashboard, Service, ServerNode, DatabaseNode } from "react-dashstream";
804
+
805
+ <AIOPsDashboard brandName="MY DASHBOARD" services={[{ name: "Svc", status: "online" }]}>
806
+ <Service
807
+ name="Svc"
808
+ status="online"
809
+ connections={[{ from: [330, 200], to: [330, 380], visibleAtPhase: 3 }]}
810
+ >
811
+ <ServerNode
812
+ ex={330}
813
+ ey={380}
814
+ compactOffset={{ x: 0, y: 0 }}
815
+ zIndex={8}
816
+ name="SRV-01"
817
+ status="online"
818
+ cpuLoad={42}
819
+ memLoad={60}
820
+ />
821
+ </Service>
822
+ </AIOPsDashboard>;
823
+ ```
824
+
825
+ ### Full live-data dashboard
826
+
827
+ ```tsx
828
+ <AIOPsDashboard
829
+ brandName="LIVE MONITOR"
830
+ services={services}
831
+ liveData={true}
832
+ dataEndpoint="https://prometheus.example.com/api/v1/query"
833
+ dataRefreshInterval={10000}
834
+ dataBindings={{
835
+ "My Service": {
836
+ cpuLoad: 'cpu_usage{instance="srv-01"}',
837
+ status: { query: 'cpu_usage{instance="srv-01"}', transform: statusFromValue },
838
+ },
839
+ }}
840
+ serviceDataBindings={{
841
+ "My Service": [{ label: "CPU", query: 'cpu_usage{instance="srv-01"}', unit: "%", color: "#00e5ff" }],
842
+ }}
843
+ >
844
+ <MyService name="My Service" />
845
+ </AIOPsDashboard>
846
+ ```
847
+
848
+ ### Custom service with all data features
849
+
850
+ ```tsx
851
+ function MyService({ name, status, cpuLoad, memLoad, dbCapacity, dbStatus }: any) {
852
+ return (
853
+ <Service
854
+ name={name}
855
+ status={status ?? "online"}
856
+ connections={[
857
+ { from: [330, 200], to: [200, 380], visibleAtPhase: 3 },
858
+ { from: [330, 200], to: [460, 380], visibleAtPhase: 3 },
859
+ ]}
860
+ >
861
+ <ServerNode
862
+ ex={200}
863
+ ey={380}
864
+ compactOffset={{ x: -30, y: -20 }}
865
+ zIndex={8}
866
+ name="SRV-01"
867
+ subLabel="APP SERVER"
868
+ status={status ?? "online"}
869
+ cpuLoad={cpuLoad ?? 42}
870
+ memLoad={memLoad ?? 60}
871
+ alert={{ offsetX: -160, offsetY: -60, align: "left" }}
872
+ dialogMetrics={[
873
+ { id: "cpu", label: "CPU", sublabel: "PROCESSOR", value: cpuLoad ?? 42 },
874
+ { id: "mem", label: "MEMORY", sublabel: "HEAP", value: memLoad ?? 60 },
875
+ ]}
876
+ subComponents={[
877
+ {
878
+ id: "cpu",
879
+ label: "CPU-0",
880
+ status: "online",
881
+ element: <CPU3D label="CPU-0" load={cpuLoad ?? 42} />,
882
+ },
883
+ {
884
+ id: "mem",
885
+ label: "HEAP",
886
+ status: "online",
887
+ element: <Memory3D label="HEAP" usedPercent={memLoad ?? 60} />,
888
+ },
889
+ ]}
890
+ graphSeries={[
891
+ { id: "cpu", label: "CPU", unit: "%", color: "#00e5ff", data: [45, 52, 67, 71, 68] },
892
+ ]}
893
+ />
894
+ <DatabaseNode
895
+ ex={460}
896
+ ey={380}
897
+ compactOffset={{ x: 30, y: -20 }}
898
+ zIndex={7}
899
+ name="DB-01"
900
+ subLabel="PRIMARY"
901
+ status={dbStatus ?? "online"}
902
+ capacity={dbCapacity ?? 55}
903
+ alert={{ offsetX: 160, offsetY: -60, align: "right" }}
904
+ />
905
+ </Service>
906
+ );
907
+ }
908
+ ```
909
+
910
+ ---
911
+
912
+ ## DatacenterView topology / geography map
913
+
914
+ SVG-based **network topology** (grid) or optional **geographic outline** (consumer-supplied SVG path `d`), CSS **3D building** markers, animated links, and **drill-down** into a nested `AIOPsDashboard` when the user selects a site or building.
915
+
916
+ ### Data model
917
+
918
+ - **`DatacenterBuildingConfig`** — one marker on the map (`id`, `name`, `subtitle`, `x` / `y` as **percent** of the 800×600 scene, `status`, `metrics`, `services`, `renderServices`, `variant`, optional `brand`, `isExternal`).
919
+ - **`siteId`** — optional; multiple buildings with the same `siteId` share **one marker**, **combined metrics** (sums / averages for PUE, temperature, uptime), and a **multi-building cluster** visualization (each building’s `variant` is shown). Optional **`siteLabels`** (`Record<string, { label: string; subtitle?: string }>`) overrides auto-generated titles. **`DatacenterSite`** can supply `label` / `subtitle` per `siteId`; the **`siteLabels`** prop merges on top (prop wins on conflict).
920
+
921
+ ### Declarative JSX topology (optional)
922
+
923
+ Instead of a **`dataCenters`** array, pass **`children`** using **`DatacenterSite`** (coordinates + optional site titles) and one shape component per building: **`StandardDC`**, **`TowerDC`**, **`FlatDC`**, **`StorageDC`**, **`GoogleDC`**, **`MicroDC`**, or **`OfficeDC`** (alias of `MicroDC`). Use **`Datacenter`** with **`variant`** when you want a single generic component. Building components render **`null`** on the map; **`children`** on each building are the drill-down **`Service`** tree (same as `renderServices()` in the array API). **`collectDatacenterTree(children)`** is available if you need the resolved **`DatacenterBuildingConfig[]`** outside the view.
924
+ - **`DatacenterConnection`** — `from` / `to` endpoint ids (building `id` or `siteId`), `throughput` label, optional `isExternal`.
925
+ - **`DatacenterMetrics`** — numeric KPIs used on markers and for aggregation.
926
+
927
+ ### Mock metrics
928
+
929
+ Pass **`dataCenters`** with embedded **`metrics`**. No `DataProvider` required.
930
+
931
+ ### PromQL (live) metrics on markers
932
+
933
+ 1. Wrap the tree in **`DataProvider`** with `config.queries` including every query used by **`metricBindings`**, collected via **`extractDatacenterMetricQueries(metricBindings)`** (and merge with queries for any nested `AIOPsDashboard` if you use `aiOpsDashboardProps`).
934
+ 2. Pass **`metricBindings`**: `Record<buildingId, Partial<Record<keyof DatacenterMetrics, DataBinding>>>` — same **`DataBinding`** shape as service-level bindings (`string` or `{ query, transform? }`).
935
+ 3. **`DatacenterView`** calls **`useAIOpsDataOptional()`** and merges transformed numbers over each building’s static `metrics` (site-level rows aggregate the merged per-building values).
936
+
937
+ This path is **independent** of `AIOPsDashboard`’s **`dataBindings`** (which key off **service `name`** on carousel children).
938
+
939
+ ### Drill-down dashboard
940
+
941
+ **`aiOpsDashboardProps`** is spread onto the inner **`AIOPsDashboard`**; **`services`**, **`theme`**, **`children`** (from `renderServices()`), and branding are forced from the selected building so the map stays consistent. Use **`aiOpsDashboardProps`** for **`liveData`**, **`dataEndpoint`**, **`dataBindings`**, **`serviceDataBindings`**, etc., for **service-level** PromQL inside the opened datacenter.
942
+
943
+ ### Geography (optional)
944
+
945
+ **`showGeography`** and **`geography: { outlinePathD, referencePoints? }`** — the package does **not** ship country assets; the app parses or provides an SVG path `d` string (see `example/SaudiMapView.tsx`).
946
+
947
+ ### DatacenterViewProps (reference)
948
+
949
+ | Prop | Type | Default | Notes |
950
+ |------|------|---------|--------|
951
+ | `children` | `ReactNode` | — | Declarative topology (`DatacenterSite`, `TowerDC`, …). When set, **`dataCenters` is ignored** (dev warning if both are passed) |
952
+ | `dataCenters` | `DatacenterBuildingConfig[]` | `[]` | Flat / JSON-friendly config; required for topology unless **`children`** is used |
953
+ | `connections` | `DatacenterConnection[]` | `[]` | Link lines between building or site ids |
954
+ | `metricBindings` | `DatacenterMetricBindings` | — | Building **id** metric field → PromQL `DataBinding`; needs `DataProvider` + `extractDatacenterMetricQueries` |
955
+ | `dataTransform` | `(raw: unknown) => unknown` | `defaultDataTransform` | Applied to each binding unless overridden per binding |
956
+ | `geography` | `DatacenterGeography` | — | `outlinePathD` + optional `referencePoints` (SVG coords) |
957
+ | `showGeography` | `boolean` | `false` | When true, draws outline + reference points |
958
+ | `mapTitle` | `string` | `"Geography — Operations"` | Header when `showGeography` |
959
+ | `topologyTitle` | `string` | `"Network topology"` | Header when not showing geography |
960
+ | `topologyAffiliatesLabel` | `string` | `"AFFILIATES"` | Label in topology-only mode (affiliates zone) |
961
+ | `brandText` | `string` | `"AIOps"` | Header brand |
962
+ | `headerTag` | `string` | `"COMMAND CENTER"` | Corner tag |
963
+ | `initialBuildingId` | `string` | | Building `id` to open when `openDashboardOnMount` is true |
964
+ | `openDashboardOnMount` | `boolean` | `false` | Auto-zoom into `initialBuildingId` |
965
+ | `onNavigateEvents` | `() => void` | — | e.g. switch app view to event console |
966
+ | `theme` | `DashboardTheme` | — | Passed through to drill-down dashboard |
967
+ | `onThemeChange` | `(t) => void` | — | |
968
+ | `backgroundImage` / `lightBackgroundImage` | `string` | — | Drill-down `AIOPsDashboard` backgrounds |
969
+ | `aiOpsDashboardProps` | `Partial<AIOPsDashboardProps>` | — | Spread first; `services`, children, theme, branding overridden from selection |
970
+ | `defaultBuildingBrand` | `string` | `"AIOps"` | Fallback label on 3D building faces |
971
+ | `siteLabels` | `Record<string, DatacenterSiteLabels>` | | Per-`siteId` titles for multi-building markers |
972
+ | `showMapScaleControls` | `boolean` | `true` | Size / hover sliders in header (map mode) |
973
+ | `mapFooter` | `ReactNode` | — | Rendered below the map when drill-down is **not** active |
974
+
975
+ ### Minimal usage (array config)
976
+
977
+ ```tsx
978
+ import "react-dashstream/dist/index.css";
979
+ import { DatacenterView } from "react-dashstream";
980
+ import type { DatacenterBuildingConfig, DatacenterConnection } from "react-dashstream";
981
+
982
+ const dataCenters: DatacenterBuildingConfig[] = [
983
+ /* id, x, y (%), metrics, services, renderServices, variant, siteId? … */
984
+ ];
985
+ const connections: DatacenterConnection[] = [];
986
+
987
+ <DatacenterView dataCenters={dataCenters} connections={connections} />;
988
+ ```
989
+
990
+ ### JSX composition (same runtime behavior)
991
+
992
+ ```tsx
993
+ import "react-dashstream/dist/index.css";
994
+ import {
995
+ DatacenterView,
996
+ DatacenterSite,
997
+ TowerDC,
998
+ FlatDC,
999
+ } from "react-dashstream";
1000
+
1001
+ <DatacenterView connections={[]}>
1002
+ <DatacenterSite siteId="riyadh-hub" x={42} y={38} label="Riyadh hub" subtitle="Primary region">
1003
+ <TowerDC
1004
+ id="dc-east-1"
1005
+ name="East tower"
1006
+ subtitle="Tier III"
1007
+ status="online"
1008
+ metrics={{
1009
+ carbonEmissions: 0,
1010
+ powerUtilization: 0,
1011
+ cooling: 0,
1012
+ pue: 1.4,
1013
+ uptime: 99.9,
1014
+ activeServers: 0,
1015
+ temperature: 22,
1016
+ networkThroughput: 0,
1017
+ }}
1018
+ services={[]}
1019
+ >
1020
+ {/* Service / ServerNode / drill-down tree */}
1021
+ </TowerDC>
1022
+ <FlatDC
1023
+ id="dc-flat-2"
1024
+ name="Flat block"
1025
+ subtitle="Storage"
1026
+ status="online"
1027
+ metrics={{
1028
+ carbonEmissions: 0,
1029
+ powerUtilization: 0,
1030
+ cooling: 0,
1031
+ pue: 1.3,
1032
+ uptime: 99.5,
1033
+ activeServers: 0,
1034
+ temperature: 21,
1035
+ networkThroughput: 0,
1036
+ }}
1037
+ services={[]}
1038
+ />
1039
+ </DatacenterSite>
1040
+ </DatacenterView>;
1041
+ ```
1042
+
1043
+ ---
1044
+
1045
+ ## EventView Operations Event Console
1046
+
1047
+ Table component for displaying infrastructure events from a live API. Uses the same `access-key` / `access-secret-key` authentication and holographic theme as the 3D dashboard. No built-in default data — requires either `apiConfig` (to fetch from an API) or `events` (pre-fetched array).
1048
+
1049
+ ### API mode (primary usage)
1050
+
1051
+ ```tsx
1052
+ import "react-dashstream/dist/index.css";
1053
+ import { EventView } from "react-dashstream";
1054
+ import type { EventApiConfig } from "react-dashstream";
1055
+
1056
+ const apiConfig: EventApiConfig = {
1057
+ baseUrl: "https://your-monitoring-server.example.com",
1058
+ payload: { filter: {}, sortBy: "date_reception", sortOrder: "DESC" },
1059
+ fieldMapping: {
1060
+ id: "mc_ueid",
1061
+ occurrence: "date_reception",
1062
+ severityLastModified: "severity_last_modified",
1063
+ severity: "severity",
1064
+ owner: "owner",
1065
+ class: "class",
1066
+ host: "mc_host",
1067
+ message: "msg",
1068
+ remedySupportGroup: "ara_remedy_support_group",
1069
+ incidentId: "ara_incident_id",
1070
+ smsStatus: "ara_sms_status",
1071
+ onCallNumber: "ara_on_call_number",
1072
+ hostedApplication: "ara_hosted_application",
1073
+ monitoringCategory: "ara_hosted_app_monitoring",
1074
+ applicationSupportUnit: "ara_application_support_unit",
1075
+ },
1076
+ };
1077
+
1078
+ <EventView apiConfig={apiConfig} />;
1079
+ ```
1080
+
1081
+ ### How it works
1082
+
1083
+ 1. Shows credentials modal (or reuses DataProvider credentials if inside `AIOPsDashboard`)
1084
+ 2. Sends `POST {baseUrl}/tsws/monitoring/api/v1.0/events/search` with the payload as JSON body
1085
+ 3. Auth via headers: `access-key` and `access-secret-key`
1086
+ 4. Expects response: `{ eventSeverityCount: {...}, totalCount: N, eventList: [{...}] }`
1087
+ 5. Maps each object in `eventList` to `AIOpsEvent` using `fieldMapping`
1088
+ 6. Maps severity strings via `severityMap` (default: `CRITICAL→Critical`, `MAJOR→Major`, `MINOR→Minor`)
1089
+ 7. Polls on `refreshInterval` (default 60s)
1090
+
1091
+ ### Props
1092
+
1093
+ | Prop | Type | Default | Notes |
1094
+ | -------------------- | ---------------- | ----------------- | -------------------------------------------------- |
1095
+ | `apiConfig` | `EventApiConfig` | — | Required for API mode |
1096
+ | `events` | `AIOpsEvent[]` | — | Pre-fetched events (skips API calls) |
1097
+ | `credentials` | `Credentials` | — | Explicit creds; falls back to DataProvider → modal |
1098
+ | `columnWidthsCookie` | `string` | `"ev_col_widths"` | Cookie name for persisting column widths |
1099
+ | `title` | `string` | `"Event Console"` | Header title |
1100
+ | `theme` | `DashboardTheme` | — | Optional override; else `ThemeProvider` / default |
1101
+
1102
+ ### EventApiConfig
1103
+
1104
+ ```ts
1105
+ interface EventApiConfig {
1106
+ baseUrl: string; // "https://monitoring.example.com"
1107
+ endpoint?: string; // default: "/tsws/monitoring/api/v1.0/events/search"
1108
+ payload: Record<string, unknown>; // POST body
1109
+ fieldMapping: EventFieldMapping; // maps API keys → AIOpsEvent keys
1110
+ severityMap?: Record<string, EventSeverity>; // default: { CRITICAL, MAJOR, MINOR }
1111
+ refreshInterval?: number; // ms, default 60000
1112
+ }
1113
+ ```
1114
+
1115
+ ### EventFieldMapping
1116
+
1117
+ Maps every `AIOpsEvent` property to the API response field name. All 15 keys required:
1118
+
1119
+ ```ts
1120
+ type EventFieldMapping = Record<keyof AIOpsEvent, string>;
1121
+ ```
1122
+
1123
+ ### AIOpsEvent interface
1124
+
1125
+ ```ts
1126
+ interface AIOpsEvent {
1127
+ id: string;
1128
+ occurrence: string;
1129
+ severityLastModified: string;
1130
+ severity: "Minor" | "Major" | "Critical";
1131
+ owner: string;
1132
+ class: "Self-monitoring" | "SCOM" | "Situation" | "Alarm" | "Event";
1133
+ host: string;
1134
+ message: string;
1135
+ remedySupportGroup: string;
1136
+ incidentId: string;
1137
+ smsStatus: "" | "SUCCESS" | "FAILED";
1138
+ onCallNumber: string;
1139
+ hostedApplication: string;
1140
+ monitoringCategory: "24/7" | "Office Hours";
1141
+ applicationSupportUnit: string;
1142
+ }
1143
+ ```
1144
+
1145
+ ### Credential resolution order
1146
+
1147
+ 1. `credentials` prop (if provided)
1148
+ 2. `DataProvider` context credentials (when inside `AIOPsDashboard` with `liveData`)
1149
+ 3. Built-in credentials modal (same UI as the 3D dashboard)
1150
+
1151
+ ### Severity colors
1152
+
1153
+ - **Minor** → `#ffb800` (amber)
1154
+ - **Major** → `#ff6600` (orange)
1155
+ - **Critical** → `#ff2255` (red)
1156
+
1157
+ ### Features
1158
+
1159
+ - Live API polling with configurable interval and manual refresh button
1160
+ - Configurable field mapping — any API response shape can be mapped
1161
+ - Shared auth — reuses DataProvider credentials when available
1162
+ - Severity filter chips with live counts
1163
+ - Free-text search across message, host, owner, incident ID
1164
+ - Sortable columns (click header: asc → desc → none)
1165
+ - Infinite scroll — all matching events in a single scrollable table
1166
+ - Resizable columns — drag column edges; widths persist in cookies
1167
+ - Loading spinner, error badge, last-refresh timestamp
1168
+ - Light / dark table chrome (via `ThemeProvider` or `theme` prop), scan-line header, severity badges
1169
+
1170
+ ### Generating an EventApiConfig from the AI
1171
+
1172
+ When asked to set up EventView for a new API, generate the config like this:
1173
+
1174
+ 1. Set `baseUrl` to the server's protocol + host + port
1175
+ 2. Set `endpoint` only if it differs from the default `/tsws/monitoring/api/v1.0/events/search`
1176
+ 3. Build `payload` based on the API's expected POST body schema
1177
+ 4. Build `fieldMapping` by asking the user which API field maps to each `AIOpsEvent` property
1178
+ 5. Set `severityMap` only if the API uses non-standard severity values
1179
+
1180
+ ---
1181
+
1182
+ ## Build and development
1183
+
1184
+ ```bash
1185
+ npm run dev # Vite dev server (loads example/Dashboard.tsx)
1186
+ npm run build # TypeScript check + Vite library build
1187
+ npm run build:lib # Vite library build only
1188
+ npm run preview # Preview production build
1189
+ ```
1190
+
1191
+ Output: `dist/index.js` (ESM), `dist/index.d.ts`, `dist/index.css`.
1192
+
1193
+ ---
1194
+
1195
+ ## Common pitfalls
1196
+
1197
+ 1. **Missing CSS import** — Always import `react-dashstream/dist/index.css`.
1198
+ 2. **Name mismatch** — `name` on child components must exactly match keys in `dataBindings`, `serviceDataBindings`, and `ServiceMeta.name`.
1199
+ 3. **Credentials are in-memory only** — Page refresh requires re-entering credentials.
1200
+ 4. **Default transform is plain-text** — For JSON endpoints, provide a custom `dataTransform`.
1201
+ 5. **Polling only** — No WebSocket/SSE support; uses `setInterval`.
1202
+ 6. **`visibleAtPhase` matters** — Nodes without proper values won't animate during expansion.
1203
+ 7. **Connection coordinates** — `from`/`to` arrays use `[x, y]` matching `ex`/`ey` of connected nodes.
1204
+ 8. **`dialogMetrics` replaces defaults** — When provided, all default gauges (CPU/Memory/Storage) are removed.
1205
+ 9. **`subComponents` replaces defaults** — When provided, all auto-generated sub-components are removed.
1206
+ 10. **`graphSeries` replaces defaults** — When provided, all auto-generated sparklines are removed.
1207
+ 11. **Event bridge matching** — Node `name` must match event `host` (case-insensitive) for `eventApiConfig` to highlight nodes automatically.
1208
+ 12. **Theme context** — `EventView` and `CredentialsModal` use `useTheme()`. Without `ThemeProvider`, context defaults to `"dark"`. Wrap the app (or use `EventView`’s `theme` prop) so light dashboards are not paired with a dark event console or lock screen.