Initial commit: VortexGPU rent-a-GPU platform + Linux node agent (nightmare 4080 SUPER)
This commit is contained in:
145
src/components/Cyber3DCanvas.tsx
Normal file
145
src/components/Cyber3DCanvas.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import * as THREE from 'three';
|
||||
|
||||
interface CyberCanvasProps {
|
||||
vmState: 'off' | 'booting' | 'running' | 'stopping' | 'suspended';
|
||||
gpuLoad: number;
|
||||
}
|
||||
|
||||
export const Cyber3DCanvas: React.FC<CyberCanvasProps> = ({ vmState, gpuLoad }) => {
|
||||
const mountRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const container = mountRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const width = container.clientWidth || 300;
|
||||
const height = container.clientHeight || 300;
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
|
||||
camera.position.set(0, 3, 6);
|
||||
camera.lookAt(0, 0, 0);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||||
renderer.setSize(width, height);
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
// Cyber grid
|
||||
const gridHelper = new THREE.GridHelper(10, 20, 0x00ffcc, 0x112233);
|
||||
gridHelper.position.y = -1;
|
||||
scene.add(gridHelper);
|
||||
|
||||
// Cyber GPU Core Cube
|
||||
const geometry = new THREE.BoxGeometry(1.6, 1.6, 1.6);
|
||||
const wireframeGeo = new THREE.WireframeGeometry(geometry);
|
||||
|
||||
// Custom material
|
||||
let activeColor = 0x00ffcc; // default cyan
|
||||
if (vmState === 'running') activeColor = 0x00ff66; // green
|
||||
else if (vmState === 'booting') activeColor = 0xffaa00; // amber
|
||||
else if (vmState === 'off') activeColor = 0x445566; // dim dark
|
||||
|
||||
const lineMaterial = new THREE.LineBasicMaterial({
|
||||
color: activeColor,
|
||||
linewidth: 2,
|
||||
});
|
||||
const cubeWireframe = new THREE.LineSegments(wireframeGeo, lineMaterial);
|
||||
scene.add(cubeWireframe);
|
||||
|
||||
// Inner glowing core
|
||||
const coreGeo = new THREE.IcosahedronGeometry(0.7, 2);
|
||||
const coreMat = new THREE.MeshBasicMaterial({
|
||||
color: activeColor,
|
||||
wireframe: true,
|
||||
transparent: true,
|
||||
opacity: vmState === 'off' ? 0.2 : 0.8,
|
||||
});
|
||||
const coreMesh = new THREE.Mesh(coreGeo, coreMat);
|
||||
scene.add(coreMesh);
|
||||
|
||||
// Particle ring
|
||||
const particleCount = 60;
|
||||
const particlesGeo = new THREE.BufferGeometry();
|
||||
const positions = new Float32Array(particleCount * 3);
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const angle = (i / particleCount) * Math.PI * 2;
|
||||
const radius = 2.2;
|
||||
positions[i * 3] = Math.cos(angle) * radius;
|
||||
positions[i * 3 + 1] = (Math.random() - 0.5) * 0.4;
|
||||
positions[i * 3 + 2] = Math.sin(angle) * radius;
|
||||
}
|
||||
particlesGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
const particleMat = new THREE.PointsMaterial({
|
||||
color: activeColor,
|
||||
size: 0.08,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
});
|
||||
const particleSystem = new THREE.Points(particlesGeo, particleMat);
|
||||
scene.add(particleSystem);
|
||||
|
||||
let animationFrameId: number;
|
||||
let clock = new THREE.Clock();
|
||||
|
||||
const animate = () => {
|
||||
animationFrameId = requestAnimationFrame(animate);
|
||||
const delta = clock.getDelta();
|
||||
const speed = vmState === 'running' ? 0.8 + (gpuLoad / 100) * 2 : 0.2;
|
||||
|
||||
cubeWireframe.rotation.x += delta * speed * 0.5;
|
||||
cubeWireframe.rotation.y += delta * speed;
|
||||
|
||||
coreMesh.rotation.y -= delta * speed * 0.8;
|
||||
particleSystem.rotation.y += delta * speed * 0.3;
|
||||
|
||||
renderer.render(scene, camera);
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
const handleResize = () => {
|
||||
if (!container) return;
|
||||
const w = container.clientWidth;
|
||||
const h = container.clientHeight;
|
||||
camera.aspect = w / h;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(w, h);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (container.contains(renderer.domElement)) {
|
||||
container.removeChild(renderer.domElement);
|
||||
}
|
||||
geometry.dispose();
|
||||
wireframeGeo.dispose();
|
||||
lineMaterial.dispose();
|
||||
coreGeo.dispose();
|
||||
coreMat.dispose();
|
||||
particlesGeo.dispose();
|
||||
particleMat.dispose();
|
||||
renderer.dispose();
|
||||
};
|
||||
}, [vmState, gpuLoad]);
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full min-h-[220px] flex items-center justify-center bg-zinc-950/80 rounded-xl border border-cyan-500/20 overflow-hidden shadow-inner">
|
||||
<div ref={mountRef} className="w-full h-full absolute inset-0 pointer-events-none" />
|
||||
<div className="absolute top-3 left-3 z-10 flex items-center gap-2 bg-black/60 backdrop-blur-md px-2.5 py-1 rounded border border-cyan-500/30 text-[10px] font-mono text-cyan-400">
|
||||
<span className={`w-2 h-2 rounded-full ${
|
||||
vmState === 'running' ? 'bg-emerald-400 animate-ping' :
|
||||
vmState === 'booting' ? 'bg-amber-400 animate-pulse' : 'bg-zinc-600'
|
||||
}`} />
|
||||
<span className="uppercase tracking-widest font-bold">{vmState}</span>
|
||||
</div>
|
||||
<div className="absolute bottom-3 right-3 z-10 text-[10px] font-mono text-cyan-300/80 bg-black/60 px-2 py-1 rounded border border-cyan-500/20">
|
||||
GPU LOAD: <span className="text-emerald-400 font-bold">{gpuLoad}%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user