Skip to content

Commit 6dc709c

Browse files
committed
feat: implement Visual DevOps Globe and update docs
- Adds 3D Earth Point Cloud visualization - Integrates Three.js ecosystem (Fiber, Drei) - Adds frontend integration methodology - Updates global dependencies
1 parent 96c9733 commit 6dc709c

8 files changed

Lines changed: 846 additions & 67 deletions

File tree

218 KB
Loading
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import { useRef, useMemo, useLayoutEffect } from 'react';
2+
import { useFrame } from '@react-three/fiber';
3+
import { Vector3, Color, QuadraticBezierCurve3 } from 'three';
4+
import { Line, QuadraticBezierLine, Instance, Instances, Html } from '@react-three/drei';
5+
import * as THREE from 'three';
6+
7+
interface DataLayersProps {
8+
radius?: number;
9+
}
10+
11+
// Helper: Lat/Lng -> Vector3
12+
function latLngToVector3(lat: number, lng: number, radius: number): Vector3 {
13+
const phi = (90 - lat) * (Math.PI / 180);
14+
const theta = (lng + 180) * (Math.PI / 180);
15+
const x = -(radius * Math.sin(phi) * Math.cos(theta));
16+
const z = radius * Math.sin(phi) * Math.sin(theta);
17+
const y = radius * Math.cos(phi);
18+
return new Vector3(x, y, z);
19+
}
20+
21+
// Data Sets
22+
const EXCHANGES = [
23+
// AMERICAS
24+
{ name: 'NYSE (New York)', lat: 40.7128, lng: -74.0060, color: '#00ccff' },
25+
{ name: 'B3 (Sao Paulo)', lat: -23.5505, lng: -46.6333, color: '#00ccff' },
26+
// EMEA
27+
{ name: 'LSE (London)', lat: 51.5074, lng: -0.1278, color: '#00ccff' },
28+
{ name: 'FRA (Frankfurt)', lat: 50.1109, lng: 8.6821, color: '#00ccff' }, // Deutsche Borse
29+
{ name: 'PAR (Paris)', lat: 48.8566, lng: 2.3522, color: '#00ccff' }, // Euronext
30+
// APAC
31+
{ name: 'JPX (Tokyo)', lat: 35.6762, lng: 139.6503, color: '#ff0055' },
32+
{ name: 'SGX (Singapore)', lat: 1.3521, lng: 103.8198, color: '#00ccff' },
33+
{ name: 'HKEX (Hong Kong)', lat: 22.3193, lng: 114.1694, color: '#00ccff' },
34+
{ name: 'SSE (Shanghai)', lat: 31.2304, lng: 121.4737, color: '#00ccff' },
35+
{ name: 'NSE (Mumbai)', lat: 19.0760, lng: 72.8777, color: '#00ccff' },
36+
{ name: 'ASX (Sydney)', lat: -33.8688, lng: 151.2093, color: '#00ccff' },
37+
];
38+
39+
const NODES = [
40+
{ name: 'AWS-Virgina', lat: 39.0438, lng: -77.4874, type: 'cloud' },
41+
{ name: 'AWS-Frankfurt', lat: 50.1109, lng: 8.6821, type: 'cloud' },
42+
{ name: 'GCP-Tokyo', lat: 35.6895, lng: 139.6917, type: 'cloud' },
43+
{ name: 'Azure-SouthCentral', lat: 29.4241, lng: -98.4936, type: 'cloud' },
44+
{ name: 'QuanuX-Edge-CHI', lat: 41.8781, lng: -87.6298, type: 'edge' }, // Chicago CME
45+
{ name: 'QuanuX-Edge-LDN', lat: 51.5074, lng: -0.1278, type: 'edge' },
46+
];
47+
48+
const CONNECTIONS = [
49+
{ from: 'NYSE (New York)', to: 'LSE (London)', latency: '29ms' },
50+
{ from: 'NYSE (New York)', to: 'QuanuX-Edge-CHI', latency: '4ms' },
51+
{ from: 'LSE (London)', to: 'FRA (Frankfurt)', latency: '8ms' },
52+
{ from: 'FRA (Frankfurt)', to: 'SGX (Singapore)', latency: '140ms' },
53+
{ from: 'JPX (Tokyo)', to: 'SGX (Singapore)', latency: '35ms' },
54+
{ from: 'JPX (Tokyo)', to: 'HKEX (Hong Kong)', latency: '20ms' },
55+
{ from: 'QuanuX-Edge-CHI', to: 'Azure-SouthCentral', latency: '12ms' },
56+
{ from: 'NYSE (New York)', to: 'B3 (Sao Paulo)', latency: '60ms' },
57+
{ from: 'SGX (Singapore)', to: 'ASX (Sydney)', latency: '90ms' },
58+
];
59+
60+
export function DataLayers({ radius = 5 }: DataLayersProps) {
61+
return (
62+
<group>
63+
<ExchangeMarkers radius={radius} />
64+
<NodeMarkers radius={radius} />
65+
<GlobalConnections radius={radius} />
66+
<SatelliteSwarm radius={radius} count={1500} />
67+
<StarlinkUplinks radius={radius} />
68+
</group>
69+
);
70+
}
71+
72+
// --- SUB COMPONENTS ---
73+
74+
function ExchangeMarkers({ radius }: { radius: number }) {
75+
return (
76+
<group>
77+
{EXCHANGES.map((ex, i) => {
78+
const pos = latLngToVector3(ex.lat, ex.lng, radius).multiplyScalar(1.0);
79+
return (
80+
<group key={i} position={pos} lookAt={new Vector3(0, 0, 0)}>
81+
{/* Vertical Pillar */}
82+
<mesh rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0.6]}>
83+
<cylinderGeometry args={[0.03, 0.03, 1.2, 6]} />
84+
<meshBasicMaterial color={ex.color} opacity={0.6} transparent />
85+
</mesh>
86+
{/* Label */}
87+
<group position={[0, 0, 1.3]} rotation={[Math.PI / 2, Math.PI, 0]}> {/* Adjusted for billboard logic implicitly via lookAt? No, Html handles it */}
88+
{/* Using simple meshes for markers, labels could be HTML but trying to keep it performance heavy 3D? Html is better */}
89+
</group>
90+
</group>
91+
);
92+
})}
93+
</group>
94+
)
95+
}
96+
97+
function NodeMarkers({ radius }: { radius: number }) {
98+
return (
99+
<group>
100+
{NODES.map((node, i) => {
101+
const pos = latLngToVector3(node.lat, node.lng, radius).multiplyScalar(1.0);
102+
const color = node.type === 'edge' ? '#00ff88' : '#aa00ff'; // Green Edge, Purple Cloud
103+
return (
104+
<mesh key={i} position={pos}>
105+
<boxGeometry args={[0.12, 0.12, 0.12]} />
106+
<meshStandardMaterial color={color} emissive={color} emissiveIntensity={3} toneMapped={false} />
107+
</mesh>
108+
);
109+
})}
110+
</group>
111+
)
112+
}
113+
114+
function GlobalConnections({ radius }: { radius: number }) {
115+
// Collect all points
116+
const pointsMap = useMemo(() => {
117+
const map = new Map<string, { lat: number, lng: number }>();
118+
[...EXCHANGES, ...NODES].forEach(p => map.set(p.name, p));
119+
return map;
120+
}, []);
121+
122+
return (
123+
<group>
124+
{CONNECTIONS.map((conn, i) => {
125+
const start = pointsMap.get(conn.from);
126+
const end = pointsMap.get(conn.to);
127+
if (!start || !end) return null;
128+
129+
const startPos = latLngToVector3(start.lat, start.lng, radius);
130+
const endPos = latLngToVector3(end.lat, end.lng, radius);
131+
132+
// Calculate control point (midpoint projected out)
133+
const mid = startPos.clone().add(endPos).multiplyScalar(0.5).normalize().multiplyScalar(radius * 1.3);
134+
135+
return (
136+
<group key={i}>
137+
<QuadraticBezierLine
138+
start={startPos}
139+
end={endPos}
140+
mid={mid}
141+
color="#ffffff"
142+
lineWidth={1}
143+
dashed
144+
dashScale={5}
145+
gapSize={3}
146+
opacity={0.2}
147+
transparent
148+
/>
149+
{conn.latency && (
150+
<Html position={mid} center>
151+
<div style={{ color: 'white', fontSize: '0.2em', whiteSpace: 'nowrap', textShadow: '0 0 2px black' }}>
152+
{conn.latency}
153+
</div>
154+
</Html>
155+
)}
156+
</group>
157+
);
158+
})}
159+
</group>
160+
);
161+
}
162+
163+
function SatelliteSwarm({ radius, count }: { radius: number, count: number }) {
164+
const meshRef = useRef<THREE.InstancedMesh>(null);
165+
const dummy = useMemo(() => new THREE.Object3D(), []);
166+
167+
// Static Random Positions for now, animated rotation of the whole group
168+
const particles = useMemo(() => {
169+
const temp = [];
170+
for (let i = 0; i < count; i++) {
171+
const r = radius * (1.15 + Math.random() * 0.2); // Altitude 1.15x - 1.35x
172+
const theta = Math.random() * Math.PI * 2;
173+
const phi = Math.acos(2 * Math.random() - 1);
174+
175+
const x = r * Math.sin(phi) * Math.cos(theta);
176+
const y = r * Math.sin(phi) * Math.sin(theta);
177+
const z = r * Math.cos(phi);
178+
temp.push({ x, y, z });
179+
}
180+
return temp;
181+
}, [count, radius]);
182+
183+
useLayoutEffect(() => {
184+
if (!meshRef.current) return;
185+
particles.forEach((p, i) => {
186+
dummy.position.set(p.x, p.y, p.z);
187+
dummy.lookAt(0, 0, 0);
188+
dummy.scale.setScalar(Math.random() > 0.9 ? 1.5 : 0.6);
189+
dummy.updateMatrix();
190+
meshRef.current?.setMatrixAt(i, dummy.matrix);
191+
});
192+
meshRef.current.instanceMatrix.needsUpdate = true;
193+
}, [particles, dummy]);
194+
195+
useFrame((state) => {
196+
if (!meshRef.current) return;
197+
meshRef.current.rotation.y -= 0.0002; // Orbit
198+
});
199+
200+
return (
201+
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
202+
<dodecahedronGeometry args={[0.02, 0]} />
203+
<meshBasicMaterial color="#ffcc00" transparent opacity={0.6} />
204+
</instancedMesh>
205+
);
206+
}
207+
208+
// Visualize "Uplinks" from QuanuX Nodes to nearby Satellites
209+
function StarlinkUplinks({ radius }: { radius: number }) {
210+
// For each QuanuX node, find a "satellite" position directly above it at orbit altitude
211+
const links = useMemo(() => {
212+
return NODES.map(node => {
213+
const start = latLngToVector3(node.lat, node.lng, radius);
214+
// Simulate a satellite being roughly above
215+
const end = start.clone().multiplyScalar(1.25);
216+
return { start, end };
217+
});
218+
}, [radius]);
219+
220+
return (
221+
<group>
222+
{links.map((link, i) => (
223+
<group key={i}>
224+
<Line
225+
points={[link.start, link.end]}
226+
color="#00ff88"
227+
opacity={0.4}
228+
transparent
229+
lineWidth={1}
230+
/>
231+
{/* Pulsing signal packet would be nice here, but simple line for now */}
232+
</group>
233+
))}
234+
</group>
235+
)
236+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useRef, useState, useEffect } from 'react';
2+
import { Canvas } from '@react-three/fiber';
3+
import { OrbitControls, Stars } from '@react-three/drei';
4+
import { Suspense } from 'react';
5+
import { EarthPointCloud } from './EarthPointCloud';
6+
import { DataLayers } from './DataLayers';
7+
8+
export function DevOpsGlobe() {
9+
const [isRotating, setIsRotating] = useState(true);
10+
11+
return (
12+
<div className="w-screen h-screen bg-black relative">
13+
<Canvas camera={{ position: [0, 0, 15], fov: 45 }}>
14+
<Suspense fallback={null}>
15+
<ambientLight intensity={0.5} />
16+
<pointLight position={[10, 10, 10]} intensity={1} />
17+
18+
<Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade speed={1} />
19+
20+
<group rotation={[0, 0, 0.4]}> {/* Tilt axis */}
21+
<EarthPointCloud radius={5} count={20000} />
22+
<DataLayers radius={5} />
23+
</group>
24+
25+
<OrbitControls
26+
enablePan={false}
27+
enableZoom={true}
28+
minDistance={8}
29+
maxDistance={25}
30+
autoRotate={isRotating}
31+
autoRotateSpeed={0.5}
32+
/>
33+
</Suspense>
34+
</Canvas>
35+
36+
{/* UI Overlay */}
37+
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-4">
38+
{/* Controls */}
39+
<div className="flex items-center gap-2 bg-black/80 backdrop-blur-md px-4 py-2 rounded-full border border-white/10">
40+
<button
41+
onClick={() => setIsRotating(!isRotating)}
42+
className={`px-3 py-1 rounded text-xs font-mono transition-colors ${isRotating ? 'bg-cyan-500/20 text-cyan-400 hover:bg-cyan-500/30' : 'bg-white/10 text-white hover:bg-white/20'
43+
}`}
44+
>
45+
{isRotating ? 'PAUSE ROTATION' : 'START ROTATION'}
46+
</button>
47+
</div>
48+
49+
{/* Status Bar */}
50+
<div className="bg-black/60 backdrop-blur-md px-6 py-3 rounded-full border border-cyan-500/30 flex items-center gap-4">
51+
<span className="text-cyan-400 font-mono text-xs animate-pulse">● LIVE DATA STREAM</span>
52+
<div className="h-4 w-[1px] bg-white/20"></div>
53+
<span className="text-white/60 text-xs font-mono">15 NODES ACTIVE</span>
54+
</div>
55+
</div>
56+
</div>
57+
);
58+
}

0 commit comments

Comments
 (0)