@backbay/glia-three 0.2.0-alpha.2

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 (172) hide show
  1. package/package.json +43 -0
  2. package/src/environment/AuroraLayer/AuroraLayer.stories.tsx +43 -0
  3. package/src/environment/AuroraLayer/AuroraLayer.tsx +200 -0
  4. package/src/environment/AuroraLayer/index.ts +2 -0
  5. package/src/environment/AuroraLayer/types.ts +30 -0
  6. package/src/environment/EnvironmentLayer/EnvironmentLayer.stories.tsx +262 -0
  7. package/src/environment/EnvironmentLayer/EnvironmentLayer.tsx +105 -0
  8. package/src/environment/EnvironmentLayer/index.ts +4 -0
  9. package/src/environment/EnvironmentLayer/presets.ts +128 -0
  10. package/src/environment/FogLayer/FogLayer.stories.tsx +83 -0
  11. package/src/environment/FogLayer/FogLayer.tsx +113 -0
  12. package/src/environment/FogLayer/index.ts +2 -0
  13. package/src/environment/FogLayer/types.ts +45 -0
  14. package/src/environment/VolumetricLight/VolumetricLight.stories.tsx +55 -0
  15. package/src/environment/VolumetricLight/VolumetricLight.tsx +188 -0
  16. package/src/environment/VolumetricLight/index.ts +2 -0
  17. package/src/environment/VolumetricLight/types.ts +33 -0
  18. package/src/environment/WeatherLayer/WeatherLayer.stories.tsx +348 -0
  19. package/src/environment/WeatherLayer/WeatherLayer.tsx +266 -0
  20. package/src/environment/WeatherLayer/cinematicCanvas.tsx +809 -0
  21. package/src/environment/WeatherLayer/colors.ts +27 -0
  22. package/src/environment/WeatherLayer/index.ts +4 -0
  23. package/src/environment/WeatherLayer/leafPresets.ts +12 -0
  24. package/src/environment/WeatherLayer/particles.ts +227 -0
  25. package/src/environment/WeatherLayer/types.ts +140 -0
  26. package/src/environment/index.ts +17 -0
  27. package/src/environment/shared/index.ts +2 -0
  28. package/src/environment/shared/noise.ts +10 -0
  29. package/src/environment/shared/performance.ts +33 -0
  30. package/src/environment/shared/types.ts +26 -0
  31. package/src/index.ts +2 -0
  32. package/src/lib/utils.ts +6 -0
  33. package/src/lib/vision-types.ts +84 -0
  34. package/src/three/AgentConsole/AgentConsole.stories.tsx +397 -0
  35. package/src/three/AgentConsole/AgentConsole.tsx +195 -0
  36. package/src/three/AgentConsole/ConsoleChat.tsx +237 -0
  37. package/src/three/AgentConsole/FocusConstellation.tsx +297 -0
  38. package/src/three/AgentConsole/GlyphAvatar.stories.tsx +110 -0
  39. package/src/three/AgentConsole/GlyphAvatar.tsx +117 -0
  40. package/src/three/AgentConsole/QuickActions.tsx +111 -0
  41. package/src/three/AgentConsole/index.ts +41 -0
  42. package/src/three/AgentConsole/types.ts +241 -0
  43. package/src/three/AmbientField/AmbientField.stories.tsx +290 -0
  44. package/src/three/AmbientField/AmbientField.tsx +307 -0
  45. package/src/three/AmbientField/BackbayFieldBus.ts +326 -0
  46. package/src/three/AmbientField/FieldProvider.tsx +83 -0
  47. package/src/three/AmbientField/index.ts +37 -0
  48. package/src/three/AmbientField/shaders/constellation.ts +384 -0
  49. package/src/three/AmbientField/types.ts +174 -0
  50. package/src/three/AttackGraph/AttackGraph.stories.tsx +144 -0
  51. package/src/three/AttackGraph/AttackGraph.tsx +325 -0
  52. package/src/three/AttackGraph/index.ts +19 -0
  53. package/src/three/AttackGraph/types.ts +97 -0
  54. package/src/three/AuditTrail/AuditTrail.stories.tsx +567 -0
  55. package/src/three/AuditTrail/AuditTrail.tsx +644 -0
  56. package/src/three/AuditTrail/index.ts +33 -0
  57. package/src/three/AuditTrail/types.ts +192 -0
  58. package/src/three/CrystallineOrganism/Breadcrumb.tsx +61 -0
  59. package/src/three/CrystallineOrganism/CrystallineOrganism.stories.tsx +509 -0
  60. package/src/three/CrystallineOrganism/CrystallineOrganism.tsx +273 -0
  61. package/src/three/CrystallineOrganism/LatticeEdge.tsx +69 -0
  62. package/src/three/CrystallineOrganism/OrganismLattice.tsx +159 -0
  63. package/src/three/CrystallineOrganism/OrganismParticles.tsx +148 -0
  64. package/src/three/CrystallineOrganism/OrganismShell.tsx +277 -0
  65. package/src/three/CrystallineOrganism/constants.ts +161 -0
  66. package/src/three/CrystallineOrganism/index.ts +17 -0
  67. package/src/three/CrystallineOrganism/layouts/hexGrid.ts +85 -0
  68. package/src/three/CrystallineOrganism/layouts/index.ts +1 -0
  69. package/src/three/CrystallineOrganism/types.ts +167 -0
  70. package/src/three/CrystallineOrganism/useOrganismEmotion.ts +84 -0
  71. package/src/three/FirewallBarrier/FirewallBarrier.stories.tsx +167 -0
  72. package/src/three/FirewallBarrier/FirewallBarrier.tsx +259 -0
  73. package/src/three/FirewallBarrier/index.ts +14 -0
  74. package/src/three/FirewallBarrier/types.ts +52 -0
  75. package/src/three/Glyph/GlyphObject.stories.tsx +577 -0
  76. package/src/three/Glyph/GlyphObject.tsx +422 -0
  77. package/src/three/Glyph/index.ts +10 -0
  78. package/src/three/Glyph/types.ts +36 -0
  79. package/src/three/Glyph/useGlyphController.ts +231 -0
  80. package/src/three/Glyph/useGlyphEmotion.ts +70 -0
  81. package/src/three/Graph3D/Graph3D.stories.tsx +269 -0
  82. package/src/three/Graph3D/Graph3D.tsx +248 -0
  83. package/src/three/Graph3D/GraphEdge.tsx +79 -0
  84. package/src/three/Graph3D/GraphNode.tsx +239 -0
  85. package/src/three/Graph3D/types.ts +66 -0
  86. package/src/three/Graph3D/utils.ts +204 -0
  87. package/src/three/IntelFeed/IntelFeed.stories.tsx +168 -0
  88. package/src/three/IntelFeed/IntelFeed.tsx +284 -0
  89. package/src/three/IntelFeed/index.ts +14 -0
  90. package/src/three/IntelFeed/types.ts +56 -0
  91. package/src/three/MetricsGalaxy/MetricsGalaxy.tsx +484 -0
  92. package/src/three/MetricsGalaxy/index.ts +6 -0
  93. package/src/three/MetricsGalaxy/types.ts +26 -0
  94. package/src/three/NetworkTopology/NetworkTopology.stories.tsx +184 -0
  95. package/src/three/NetworkTopology/NetworkTopology.tsx +421 -0
  96. package/src/three/NetworkTopology/index.ts +34 -0
  97. package/src/three/NetworkTopology/types.ts +128 -0
  98. package/src/three/ParticleField/ParticleField.stories.tsx +162 -0
  99. package/src/three/ParticleField/ParticleField.tsx +81 -0
  100. package/src/three/ParticleField/index.ts +1 -0
  101. package/src/three/PermissionMatrix/PermissionMatrix.stories.tsx +475 -0
  102. package/src/three/PermissionMatrix/PermissionMatrix.tsx +380 -0
  103. package/src/three/PermissionMatrix/index.ts +15 -0
  104. package/src/three/PermissionMatrix/types.ts +54 -0
  105. package/src/three/QuantumField/ConstellationField.tsx +238 -0
  106. package/src/three/QuantumField/FieldBus.ts +349 -0
  107. package/src/three/QuantumField/FieldLayer.tsx +430 -0
  108. package/src/three/QuantumField/FieldProvider.tsx +460 -0
  109. package/src/three/QuantumField/PcbField.tsx +406 -0
  110. package/src/three/QuantumField/QuantumField.stories.tsx +1155 -0
  111. package/src/three/QuantumField/TrailRTT.ts +212 -0
  112. package/src/three/QuantumField/WaterField.tsx +226 -0
  113. package/src/three/QuantumField/WaterSimRTT.ts +283 -0
  114. package/src/three/QuantumField/domMapping.ts +185 -0
  115. package/src/three/QuantumField/index.ts +110 -0
  116. package/src/three/QuantumField/styles/index.ts +9 -0
  117. package/src/three/QuantumField/styles/styleA.ts +526 -0
  118. package/src/three/QuantumField/styles/styleB.ts +1210 -0
  119. package/src/three/QuantumField/styles/styleC.ts +266 -0
  120. package/src/three/QuantumField/themes.ts +211 -0
  121. package/src/three/QuantumField/types.ts +380 -0
  122. package/src/three/SOCCommandCenter/SOCCommandCenter.stories.tsx +591 -0
  123. package/src/three/SOCCommandCenter/SOCCommandCenter.tsx +248 -0
  124. package/src/three/SOCCommandCenter/index.ts +26 -0
  125. package/src/three/SOCCommandCenter/types.ts +201 -0
  126. package/src/three/SecurityDashboard/SecurityDashboard.stories.tsx +508 -0
  127. package/src/three/SecurityDashboard/SecurityDashboard.tsx +507 -0
  128. package/src/three/SecurityDashboard/index.ts +37 -0
  129. package/src/three/SecurityDashboard/types.ts +143 -0
  130. package/src/three/SecurityShield/SecurityShield.stories.tsx +257 -0
  131. package/src/three/SecurityShield/SecurityShield.tsx +502 -0
  132. package/src/three/SecurityShield/index.ts +25 -0
  133. package/src/three/SecurityShield/types.ts +64 -0
  134. package/src/three/Sentinel/AvatarMode.tsx +578 -0
  135. package/src/three/Sentinel/AvatarRenderer.tsx +199 -0
  136. package/src/three/Sentinel/CameraPip.tsx +127 -0
  137. package/src/three/Sentinel/CardinalItem.tsx +83 -0
  138. package/src/three/Sentinel/CardinalMenu.tsx +370 -0
  139. package/src/three/Sentinel/DockedMiniOrb.tsx +146 -0
  140. package/src/three/Sentinel/RadialSubmenu.tsx +273 -0
  141. package/src/three/Sentinel/SentinelConversation.tsx +802 -0
  142. package/src/three/Sentinel/SentinelOrb.tsx +316 -0
  143. package/src/three/Sentinel/SentinelOverlay.tsx +146 -0
  144. package/src/three/Sentinel/SentinelProvider.tsx +145 -0
  145. package/src/three/Sentinel/SentinelTether.tsx +182 -0
  146. package/src/three/Sentinel/SigilPlaceholder.tsx +176 -0
  147. package/src/three/Sentinel/VerticalSubmenu.tsx +150 -0
  148. package/src/three/Sentinel/index.ts +145 -0
  149. package/src/three/Sentinel/sentinelStore.ts +196 -0
  150. package/src/three/Sentinel/types.ts +403 -0
  151. package/src/three/Sentinel/useCameraPermission.ts +153 -0
  152. package/src/three/Sentinel/useThrowPhysics.ts +220 -0
  153. package/src/three/SpatialWorkspace/CyntraWorkspace.tsx +84 -0
  154. package/src/three/SpatialWorkspace/JobCluster.tsx +281 -0
  155. package/src/three/SpatialWorkspace/NodeGraph.tsx +236 -0
  156. package/src/three/SpatialWorkspace/ReceiptOrbit.tsx +368 -0
  157. package/src/three/SpatialWorkspace/SpatialWorkspace.stories.tsx +547 -0
  158. package/src/three/SpatialWorkspace/SpatialWorkspace.tsx +428 -0
  159. package/src/three/SpatialWorkspace/TrustRings.tsx +228 -0
  160. package/src/three/SpatialWorkspace/adapters.ts +353 -0
  161. package/src/three/SpatialWorkspace/index.ts +85 -0
  162. package/src/three/SpatialWorkspace/nexusAdapter.ts +182 -0
  163. package/src/three/SpatialWorkspace/types.ts +389 -0
  164. package/src/three/ThreatRadar/ThreatRadar.stories.tsx +451 -0
  165. package/src/three/ThreatRadar/ThreatRadar.tsx +542 -0
  166. package/src/three/ThreatRadar/index.ts +8 -0
  167. package/src/three/ThreatRadar/types.ts +90 -0
  168. package/src/three/ThreeErrorBoundary/ThreeErrorBoundary.tsx +235 -0
  169. package/src/three/ThreeErrorBoundary/index.ts +5 -0
  170. package/src/three/index.ts +56 -0
  171. package/tsconfig.json +20 -0
  172. package/tsup.config.ts +21 -0
@@ -0,0 +1,644 @@
1
+ "use client";
2
+
3
+ /**
4
+ * AuditTrail - 3D scrolling audit event timeline visualization
5
+ *
6
+ * Displays security audit events as a vertical or horizontal timeline
7
+ * with color-coded severity and interactive event cards.
8
+ */
9
+
10
+ import * as React from "react";
11
+ import { useFrame, ThreeEvent } from "@react-three/fiber";
12
+ import { Html, Line } from "@react-three/drei";
13
+ import * as THREE from "three";
14
+ import type {
15
+ AuditEvent,
16
+ AuditTrailProps,
17
+ EventNodeProps,
18
+ FlowParticlesProps,
19
+ } from "./types";
20
+ import {
21
+ SEVERITY_COLORS,
22
+ EVENT_GEOMETRIES,
23
+ EVENT_TYPE_LABELS,
24
+ THEME_COLORS,
25
+ } from "./types";
26
+
27
+ // -----------------------------------------------------------------------------
28
+ // Flow Particles Sub-component
29
+ // -----------------------------------------------------------------------------
30
+
31
+ function FlowParticles({
32
+ length,
33
+ orientation,
34
+ particleCount = 30,
35
+ speed = 0.5,
36
+ color = "#00ccff",
37
+ }: FlowParticlesProps) {
38
+ const particlesRef = React.useRef<THREE.Points>(null);
39
+
40
+ const particles = React.useMemo(() => {
41
+ const positions = new Float32Array(particleCount * 3);
42
+ const speeds = new Float32Array(particleCount);
43
+
44
+ for (let i = 0; i < particleCount; i++) {
45
+ const progress = Math.random();
46
+ if (orientation === "horizontal") {
47
+ positions[i * 3] = -length / 2 + progress * length;
48
+ positions[i * 3 + 1] = (Math.random() - 0.5) * 0.2;
49
+ positions[i * 3 + 2] = (Math.random() - 0.5) * 0.2;
50
+ } else {
51
+ positions[i * 3] = (Math.random() - 0.5) * 0.2;
52
+ positions[i * 3 + 1] = length / 2 - progress * length;
53
+ positions[i * 3 + 2] = (Math.random() - 0.5) * 0.2;
54
+ }
55
+ speeds[i] = 0.3 + Math.random() * 0.7;
56
+ }
57
+
58
+ return { positions, speeds };
59
+ }, [length, orientation, particleCount]);
60
+
61
+ useFrame((_, delta) => {
62
+ if (!particlesRef.current) return;
63
+ const posAttr = particlesRef.current.geometry.attributes.position;
64
+ const positions = posAttr.array as Float32Array;
65
+
66
+ for (let i = 0; i < particleCount; i++) {
67
+ const idx = orientation === "horizontal" ? i * 3 : i * 3 + 1;
68
+ const direction = orientation === "horizontal" ? 1 : -1;
69
+ positions[idx] += delta * speed * particles.speeds[i] * direction;
70
+
71
+ // Wrap around
72
+ if (orientation === "horizontal") {
73
+ if (positions[idx] > length / 2) positions[idx] = -length / 2;
74
+ } else {
75
+ if (positions[idx] < -length / 2) positions[idx] = length / 2;
76
+ }
77
+ }
78
+ posAttr.needsUpdate = true;
79
+ });
80
+
81
+ return (
82
+ <points ref={particlesRef}>
83
+ <bufferGeometry>
84
+ <bufferAttribute
85
+ attach="attributes-position"
86
+ args={[particles.positions, 3]}
87
+ />
88
+ </bufferGeometry>
89
+ <pointsMaterial
90
+ color={color}
91
+ size={0.03}
92
+ transparent
93
+ opacity={0.6}
94
+ sizeAttenuation
95
+ blending={THREE.AdditiveBlending}
96
+ depthWrite={false}
97
+ />
98
+ </points>
99
+ );
100
+ }
101
+
102
+ // -----------------------------------------------------------------------------
103
+ // Event Node Sub-component
104
+ // -----------------------------------------------------------------------------
105
+
106
+ function EventNode({
107
+ event,
108
+ position,
109
+ onEventClick,
110
+ onEventHover,
111
+ showDetails,
112
+ theme = "cyber",
113
+ }: EventNodeProps) {
114
+ const meshRef = React.useRef<THREE.Mesh>(null);
115
+ const [isHovered, setIsHovered] = React.useState(false);
116
+
117
+ const themeColors = THEME_COLORS[theme];
118
+ const severityColor = new THREE.Color(SEVERITY_COLORS[event.severity]);
119
+ const geometryType = EVENT_GEOMETRIES[event.type];
120
+
121
+ useFrame((state) => {
122
+ if (!meshRef.current) return;
123
+ const t = state.clock.elapsedTime;
124
+
125
+ // Rotation animation
126
+ meshRef.current.rotation.y = t * 0.5;
127
+ meshRef.current.rotation.z = Math.sin(t * 0.3) * 0.1;
128
+
129
+ // Pulse for critical/error events
130
+ if (event.severity === "critical" || event.severity === "error") {
131
+ const pulse = 1 + Math.sin(t * 5) * 0.15;
132
+ meshRef.current.scale.setScalar(pulse * (isHovered ? 1.3 : 1));
133
+ } else {
134
+ meshRef.current.scale.setScalar(isHovered ? 1.3 : 1);
135
+ }
136
+
137
+ // Update emissive
138
+ const material = meshRef.current.material as THREE.MeshStandardMaterial;
139
+ if (material) {
140
+ material.emissiveIntensity = isHovered ? 1.2 : 0.6;
141
+ }
142
+ });
143
+
144
+ const handlePointerEnter = React.useCallback(
145
+ (e: ThreeEvent<PointerEvent>) => {
146
+ e.stopPropagation();
147
+ setIsHovered(true);
148
+ onEventHover?.(event);
149
+ document.body.style.cursor = "pointer";
150
+ },
151
+ [event, onEventHover]
152
+ );
153
+
154
+ const handlePointerLeave = React.useCallback(() => {
155
+ setIsHovered(false);
156
+ onEventHover?.(null);
157
+ document.body.style.cursor = "auto";
158
+ }, [onEventHover]);
159
+
160
+ const handleClick = React.useCallback(
161
+ (e: ThreeEvent<PointerEvent>) => {
162
+ e.stopPropagation();
163
+ onEventClick?.(event);
164
+ },
165
+ [event, onEventClick]
166
+ );
167
+
168
+ const formatTime = (date: Date) => {
169
+ return date.toLocaleTimeString("en-US", {
170
+ hour: "2-digit",
171
+ minute: "2-digit",
172
+ second: "2-digit",
173
+ hour12: false,
174
+ });
175
+ };
176
+
177
+ // Create geometry based on event type
178
+ const renderGeometry = () => {
179
+ const size = 0.08;
180
+ switch (geometryType) {
181
+ case "torus":
182
+ return <torusGeometry args={[size, size * 0.4, 8, 16]} />;
183
+ case "box":
184
+ return <boxGeometry args={[size * 1.5, size * 1.5, size * 1.5]} />;
185
+ case "octahedron":
186
+ return <octahedronGeometry args={[size * 1.2]} />;
187
+ case "icosahedron":
188
+ return <icosahedronGeometry args={[size * 1.1]} />;
189
+ case "cone":
190
+ return <coneGeometry args={[size, size * 2, 8]} />;
191
+ default:
192
+ return <sphereGeometry args={[size, 16, 16]} />;
193
+ }
194
+ };
195
+
196
+ return (
197
+ <group position={position}>
198
+ {/* Main event geometry */}
199
+ <mesh
200
+ ref={meshRef}
201
+ onPointerEnter={handlePointerEnter}
202
+ onPointerLeave={handlePointerLeave}
203
+ onClick={handleClick}
204
+ >
205
+ {renderGeometry()}
206
+ <meshStandardMaterial
207
+ color={severityColor}
208
+ emissive={severityColor}
209
+ emissiveIntensity={0.6}
210
+ transparent
211
+ opacity={event.success ? 0.9 : 0.6}
212
+ metalness={0.3}
213
+ roughness={0.4}
214
+ />
215
+ </mesh>
216
+
217
+ {/* Outer glow */}
218
+ <mesh scale={2.5}>
219
+ <sphereGeometry args={[0.08, 12, 12]} />
220
+ <meshBasicMaterial
221
+ color={severityColor}
222
+ transparent
223
+ opacity={0.12}
224
+ blending={THREE.AdditiveBlending}
225
+ depthWrite={false}
226
+ />
227
+ </mesh>
228
+
229
+ {/* Failed indicator X */}
230
+ {!event.success && (
231
+ <group scale={0.15}>
232
+ <Line
233
+ points={[
234
+ [-1, -1, 0],
235
+ [1, 1, 0],
236
+ ]}
237
+ color="#ff0000"
238
+ lineWidth={3}
239
+ />
240
+ <Line
241
+ points={[
242
+ [-1, 1, 0],
243
+ [1, -1, 0],
244
+ ]}
245
+ color="#ff0000"
246
+ lineWidth={3}
247
+ />
248
+ </group>
249
+ )}
250
+
251
+ {/* Event label / tooltip */}
252
+ {(showDetails || isHovered) && (
253
+ <Html position={[0.25, 0.15, 0]} center={false}>
254
+ <div
255
+ style={{
256
+ background: "rgba(0, 0, 0, 0.92)",
257
+ border: `1px solid ${isHovered ? severityColor.getStyle() : themeColors.primary}`,
258
+ borderRadius: "4px",
259
+ padding: isHovered ? "10px 14px" : "4px 8px",
260
+ fontFamily: "'JetBrains Mono', 'SF Mono', monospace",
261
+ fontSize: isHovered ? "11px" : "9px",
262
+ color: "#ffffff",
263
+ minWidth: isHovered ? "200px" : "auto",
264
+ boxShadow: `0 0 20px ${severityColor.getStyle()}30`,
265
+ transition: "all 0.15s ease",
266
+ }}
267
+ >
268
+ {/* Header row */}
269
+ <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
270
+ <span
271
+ style={{
272
+ color: severityColor.getStyle(),
273
+ fontWeight: 700,
274
+ fontSize: "8px",
275
+ padding: "2px 5px",
276
+ background: `${severityColor.getStyle()}25`,
277
+ borderRadius: "3px",
278
+ }}
279
+ >
280
+ {EVENT_TYPE_LABELS[event.type]}
281
+ </span>
282
+ <span
283
+ style={{
284
+ fontSize: "8px",
285
+ opacity: 0.45,
286
+ textTransform: "uppercase",
287
+ }}
288
+ >
289
+ {event.severity}
290
+ </span>
291
+ {!event.success && (
292
+ <span
293
+ style={{
294
+ color: "#ff4444",
295
+ fontSize: "7px",
296
+ fontWeight: 700,
297
+ marginLeft: "auto",
298
+ opacity: 0.7,
299
+ }}
300
+ >
301
+ FAILED
302
+ </span>
303
+ )}
304
+ </div>
305
+
306
+ {/* Expanded details */}
307
+ {isHovered && (
308
+ <>
309
+ <div
310
+ style={{
311
+ marginTop: "8px",
312
+ paddingTop: "8px",
313
+ borderTop: "1px solid #333",
314
+ }}
315
+ >
316
+ <div style={{ marginBottom: "6px" }}>
317
+ <span style={{ color: "#666", fontSize: "9px" }}>Actor</span>
318
+ <div style={{ color: themeColors.primary }}>{event.actor}</div>
319
+ </div>
320
+ <div style={{ marginBottom: "6px" }}>
321
+ <span style={{ color: "#666", fontSize: "9px" }}>Resource</span>
322
+ <div
323
+ style={{
324
+ wordBreak: "break-all",
325
+ opacity: 0.9,
326
+ fontSize: "10px",
327
+ }}
328
+ >
329
+ {event.resource}
330
+ </div>
331
+ </div>
332
+ <div style={{ marginBottom: "6px" }}>
333
+ <span style={{ color: "#666", fontSize: "9px" }}>Action</span>
334
+ <div style={{ opacity: 0.9, fontSize: "10px" }}>{event.action}</div>
335
+ </div>
336
+ {event.details && (
337
+ <div>
338
+ <span style={{ color: "#666", fontSize: "9px" }}>Details</span>
339
+ <div style={{ opacity: 0.7, fontSize: "10px" }}>
340
+ {event.details}
341
+ </div>
342
+ </div>
343
+ )}
344
+ </div>
345
+ <div
346
+ style={{
347
+ marginTop: "8px",
348
+ paddingTop: "6px",
349
+ borderTop: "1px solid #222",
350
+ fontSize: "9px",
351
+ opacity: 0.5,
352
+ }}
353
+ >
354
+ {formatTime(event.timestamp)}
355
+ </div>
356
+ </>
357
+ )}
358
+ </div>
359
+ </Html>
360
+ )}
361
+ </group>
362
+ );
363
+ }
364
+
365
+ // -----------------------------------------------------------------------------
366
+ // Main AuditTrail Component
367
+ // -----------------------------------------------------------------------------
368
+
369
+ export function AuditTrail({
370
+ events,
371
+ maxEvents = 20,
372
+ length = 10,
373
+ position = [0, 0, 0],
374
+ orientation = "horizontal",
375
+ showDetails = false,
376
+ onEventClick,
377
+ onEventHover,
378
+ autoScroll = true,
379
+ theme = "cyber",
380
+ showSummary = true,
381
+ enableParticles = true,
382
+ rotation = [0, 0, 0],
383
+ }: AuditTrailProps) {
384
+ const groupRef = React.useRef<THREE.Group>(null);
385
+
386
+ // Limit displayed events
387
+ const displayedEvents = React.useMemo(() => {
388
+ return events.slice(-maxEvents);
389
+ }, [events, maxEvents]);
390
+
391
+ // Theme colors
392
+ const themeColors = THEME_COLORS[theme];
393
+
394
+ // Calculate positions for events along the timeline
395
+ const eventPositions = React.useMemo(() => {
396
+ if (displayedEvents.length === 0) return [];
397
+ const spacing = length / Math.max(1, displayedEvents.length);
398
+ return displayedEvents.map((_, i) => {
399
+ if (orientation === "horizontal") {
400
+ const x = -length / 2 + spacing / 2 + i * spacing;
401
+ return [x, 0, 0] as [number, number, number];
402
+ } else {
403
+ const y = length / 2 - spacing / 2 - i * spacing;
404
+ return [0, y, 0] as [number, number, number];
405
+ }
406
+ });
407
+ }, [displayedEvents.length, length, orientation]);
408
+
409
+ // Timeline spine points
410
+ const spinePoints = React.useMemo(() => {
411
+ const padding = 0.5;
412
+ if (orientation === "horizontal") {
413
+ return [
414
+ [-length / 2 - padding, 0, 0] as [number, number, number],
415
+ [length / 2 + padding, 0, 0] as [number, number, number],
416
+ ];
417
+ } else {
418
+ return [
419
+ [0, length / 2 + padding, 0] as [number, number, number],
420
+ [0, -length / 2 - padding, 0] as [number, number, number],
421
+ ];
422
+ }
423
+ }, [length, orientation]);
424
+
425
+ // Count severities
426
+ const severityCounts = React.useMemo(() => {
427
+ return displayedEvents.reduce(
428
+ (acc, e) => {
429
+ acc[e.severity]++;
430
+ if (!e.success) acc.failed++;
431
+ return acc;
432
+ },
433
+ { info: 0, warning: 0, error: 0, critical: 0, failed: 0 }
434
+ );
435
+ }, [displayedEvents]);
436
+
437
+ return (
438
+ <group ref={groupRef} position={position} rotation={rotation}>
439
+ {/* Main timeline spine */}
440
+ <Line
441
+ points={spinePoints}
442
+ color={themeColors.spine}
443
+ lineWidth={3}
444
+ transparent
445
+ opacity={0.8}
446
+ />
447
+
448
+ {/* Glow layer */}
449
+ <Line
450
+ points={spinePoints}
451
+ color={themeColors.primary}
452
+ lineWidth={8}
453
+ transparent
454
+ opacity={0.15}
455
+ />
456
+
457
+ {/* Flow particles */}
458
+ {enableParticles && (
459
+ <FlowParticles
460
+ length={length}
461
+ orientation={orientation}
462
+ color={themeColors.particle}
463
+ particleCount={40}
464
+ speed={0.8}
465
+ />
466
+ )}
467
+
468
+ {/* Event nodes */}
469
+ {displayedEvents.map((event, index) => (
470
+ <EventNode
471
+ key={event.id}
472
+ event={event}
473
+ position={eventPositions[index]}
474
+ onEventClick={onEventClick}
475
+ onEventHover={onEventHover}
476
+ showDetails={showDetails}
477
+ theme={theme}
478
+ />
479
+ ))}
480
+
481
+ {/* Connection lines from events to spine */}
482
+ {displayedEvents.map((event, index) => {
483
+ const pos = eventPositions[index];
484
+ const lineEnd =
485
+ orientation === "horizontal"
486
+ ? ([pos[0], -0.15, 0] as [number, number, number])
487
+ : ([-0.15, pos[1], 0] as [number, number, number]);
488
+ return (
489
+ <Line
490
+ key={`line-${event.id}`}
491
+ points={[pos, lineEnd]}
492
+ color={SEVERITY_COLORS[event.severity]}
493
+ lineWidth={1}
494
+ transparent
495
+ opacity={0.3}
496
+ />
497
+ );
498
+ })}
499
+
500
+ {/* Direction arrow */}
501
+ <group
502
+ position={
503
+ orientation === "horizontal"
504
+ ? [length / 2 + 0.7, 0, 0]
505
+ : [0, -length / 2 - 0.7, 0]
506
+ }
507
+ >
508
+ <mesh
509
+ rotation={
510
+ orientation === "horizontal"
511
+ ? [0, 0, -Math.PI / 2]
512
+ : [0, 0, Math.PI]
513
+ }
514
+ >
515
+ <coneGeometry args={[0.08, 0.2, 6]} />
516
+ <meshBasicMaterial
517
+ color={themeColors.primary}
518
+ transparent
519
+ opacity={0.6}
520
+ />
521
+ </mesh>
522
+ </group>
523
+
524
+ {/* Summary HUD */}
525
+ {showSummary && (
526
+ <Html
527
+ position={
528
+ orientation === "horizontal"
529
+ ? [-length / 2 - 1.5, 0.5, 0]
530
+ : [0.8, length / 2 + 0.3, 0]
531
+ }
532
+ center
533
+ >
534
+ <div
535
+ style={{
536
+ background: "rgba(0, 8, 12, 0.95)",
537
+ border: `1px solid ${themeColors.primary}50`,
538
+ borderRadius: "5px",
539
+ padding: "10px 14px",
540
+ fontFamily: "'JetBrains Mono', 'SF Mono', monospace",
541
+ fontSize: "10px",
542
+ color: themeColors.primary,
543
+ boxShadow: `0 0 20px ${themeColors.glow}, inset 0 0 15px ${themeColors.glow}`,
544
+ minWidth: "110px",
545
+ }}
546
+ >
547
+ <div
548
+ style={{
549
+ fontWeight: 700,
550
+ fontSize: "11px",
551
+ marginBottom: "8px",
552
+ letterSpacing: "1.5px",
553
+ borderBottom: `1px solid ${themeColors.primary}40`,
554
+ paddingBottom: "6px",
555
+ textAlign: "center",
556
+ }}
557
+ >
558
+ AUDIT LOG
559
+ </div>
560
+ <div
561
+ style={{
562
+ display: "flex",
563
+ justifyContent: "space-between",
564
+ marginBottom: "2px",
565
+ }}
566
+ >
567
+ <span style={{ opacity: 0.6 }}>Events:</span>
568
+ <span style={{ fontWeight: 600 }}>{displayedEvents.length}</span>
569
+ </div>
570
+ <div
571
+ style={{
572
+ display: "flex",
573
+ justifyContent: "space-between",
574
+ marginBottom: "2px",
575
+ color: SEVERITY_COLORS.warning,
576
+ }}
577
+ >
578
+ <span style={{ opacity: 0.7 }}>Warn:</span>
579
+ <span style={{ fontWeight: 600 }}>{severityCounts.warning}</span>
580
+ </div>
581
+ <div
582
+ style={{
583
+ display: "flex",
584
+ justifyContent: "space-between",
585
+ marginBottom: "2px",
586
+ color: SEVERITY_COLORS.error,
587
+ }}
588
+ >
589
+ <span style={{ opacity: 0.7 }}>Error:</span>
590
+ <span style={{ fontWeight: 600 }}>{severityCounts.error}</span>
591
+ </div>
592
+ <div
593
+ style={{
594
+ display: "flex",
595
+ justifyContent: "space-between",
596
+ marginBottom: "2px",
597
+ color: SEVERITY_COLORS.critical,
598
+ }}
599
+ >
600
+ <span style={{ opacity: 0.7 }}>Critical:</span>
601
+ <span style={{ fontWeight: 600 }}>{severityCounts.critical}</span>
602
+ </div>
603
+ {severityCounts.failed > 0 && (
604
+ <div
605
+ style={{
606
+ display: "flex",
607
+ justifyContent: "space-between",
608
+ marginTop: "4px",
609
+ paddingTop: "4px",
610
+ borderTop: "1px solid #333",
611
+ color: "#ff4444",
612
+ }}
613
+ >
614
+ <span style={{ opacity: 0.7 }}>Failed:</span>
615
+ <span style={{ fontWeight: 600 }}>{severityCounts.failed}</span>
616
+ </div>
617
+ )}
618
+ </div>
619
+ </Html>
620
+ )}
621
+
622
+ {/* Background frame glow */}
623
+ <mesh position={[0, 0, -0.1]}>
624
+ <planeGeometry
625
+ args={
626
+ orientation === "horizontal"
627
+ ? [length + 2, 1.5]
628
+ : [1.5, length + 2]
629
+ }
630
+ />
631
+ <meshBasicMaterial
632
+ color={themeColors.primary}
633
+ transparent
634
+ opacity={0.02}
635
+ side={THREE.DoubleSide}
636
+ blending={THREE.AdditiveBlending}
637
+ depthWrite={false}
638
+ />
639
+ </mesh>
640
+ </group>
641
+ );
642
+ }
643
+
644
+ AuditTrail.displayName = "AuditTrail";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * AuditTrail - 3D audit event timeline visualization
3
+ *
4
+ * @example
5
+ * ```tsx
6
+ * import { AuditTrail } from "@backbay/glia/primitives/three/AuditTrail";
7
+ *
8
+ * <AuditTrail
9
+ * events={auditEvents}
10
+ * maxEvents={20}
11
+ * length={10}
12
+ * orientation="horizontal"
13
+ * theme="cyber"
14
+ * onEventClick={(event) => console.log('Clicked:', event)}
15
+ * />
16
+ * ```
17
+ */
18
+
19
+ export { AuditTrail } from "./AuditTrail";
20
+ export type {
21
+ AuditEvent,
22
+ AuditEventType,
23
+ AuditSeverity,
24
+ AuditTrailProps,
25
+ EventNodeProps,
26
+ FlowParticlesProps,
27
+ } from "./types";
28
+ export {
29
+ SEVERITY_COLORS,
30
+ EVENT_GEOMETRIES,
31
+ EVENT_TYPE_LABELS,
32
+ THEME_COLORS,
33
+ } from "./types";