|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<title>CI/CD Trigger Simulation</title> |
|
|
<style> |
|
|
body { margin: 0; overflow: hidden; font-family: 'Segoe UI', sans-serif; background-color: #1a1a2e; color: white; } |
|
|
#controls { |
|
|
position: absolute; |
|
|
top: 20px; |
|
|
left: 20px; |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
padding: 20px; |
|
|
border-radius: 8px; |
|
|
backdrop-filter: blur(5px); |
|
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
|
} |
|
|
h1 { margin-top: 0; font-size: 1.2rem; color: #e94560; } |
|
|
p { font-size: 0.9rem; color: #ccc; margin-bottom: 15px; } |
|
|
button { |
|
|
display: block; |
|
|
width: 100%; |
|
|
padding: 10px; |
|
|
margin-bottom: 10px; |
|
|
border: none; |
|
|
border-radius: 4px; |
|
|
cursor: pointer; |
|
|
font-weight: bold; |
|
|
transition: transform 0.1s; |
|
|
} |
|
|
button:active { transform: scale(0.98); } |
|
|
.btn-commit { background-color: #4cc9f0; color: #000; } |
|
|
.btn-schedule { background-color: #f72585; color: white; } |
|
|
.btn-manual { background-color: #ffd60a; color: #000; } |
|
|
|
|
|
|
|
|
.label { |
|
|
position: absolute; |
|
|
color: white; |
|
|
font-size: 12px; |
|
|
background: rgba(0,0,0,0.6); |
|
|
padding: 4px 8px; |
|
|
border-radius: 4px; |
|
|
pointer-events: none; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<div id="controls"> |
|
|
<h1>CI/CD Trigger Control</h1> |
|
|
<p>Activate a trigger to start the pipeline:</p> |
|
|
<button class="btn-commit" onclick="triggerPipeline('commit')">simulate Code Commit </></button> |
|
|
<button class="btn-schedule" onclick="triggerPipeline('schedule')">simulate Schedule (Timer)</button> |
|
|
<button class="btn-manual" onclick="triggerPipeline('manual')">simulate Manual Approval</button> |
|
|
</div> |
|
|
|
|
|
<div id="canvas-container"></div> |
|
|
|
|
|
<script type="module"> |
|
|
import * as THREE from 'https://unpkg.com/three@0.160.0/build/three.module.js'; |
|
|
|
|
|
|
|
|
const scene = new THREE.Scene(); |
|
|
scene.background = new THREE.Color(0x1a1a2e); |
|
|
|
|
|
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
|
camera.position.set(0, 10, 20); |
|
|
camera.lookAt(0, 0, 0); |
|
|
|
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true }); |
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
document.getElementById('canvas-container').appendChild(renderer.domElement); |
|
|
|
|
|
|
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); |
|
|
scene.add(ambientLight); |
|
|
const dirLight = new THREE.DirectionalLight(0xffffff, 1); |
|
|
dirLight.position.set(5, 10, 7); |
|
|
scene.add(dirLight); |
|
|
|
|
|
|
|
|
const matTrigger = new THREE.MeshStandardMaterial({ color: 0x4cc9f0 }); |
|
|
const matSchedule = new THREE.MeshStandardMaterial({ color: 0xf72585 }); |
|
|
const matManual = new THREE.MeshStandardMaterial({ color: 0xffd60a }); |
|
|
const matPipeline = new THREE.MeshStandardMaterial({ color: 0xb5179e }); |
|
|
const matOutput = new THREE.MeshStandardMaterial({ color: 0x2ec4b6 }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const commitNode = createBox(-6, 0, -3, matTrigger, "Commit"); |
|
|
const scheduleNode = createBox(-6, 0, 0, matSchedule, "Schedule"); |
|
|
const manualNode = createBox(-6, 0, 3, matManual, "Manual"); |
|
|
|
|
|
|
|
|
const pipelineGeo = new THREE.CylinderGeometry(1.5, 1.5, 6, 32); |
|
|
pipelineGeo.rotateZ(Math.PI / 2); |
|
|
const pipeline = new THREE.Mesh(pipelineGeo, matPipeline); |
|
|
scene.add(pipeline); |
|
|
|
|
|
|
|
|
const buildNode = createBox(6, 0, -2, matOutput, "Build & Test"); |
|
|
const deployNode = createBox(6, 0, 2, matOutput, "Deploy"); |
|
|
|
|
|
|
|
|
drawPath(commitNode.position, new THREE.Vector3(-3, 0, 0)); |
|
|
drawPath(scheduleNode.position, new THREE.Vector3(-3, 0, 0)); |
|
|
drawPath(manualNode.position, new THREE.Vector3(-3, 0, 0)); |
|
|
drawPath(new THREE.Vector3(3, 0, 0), buildNode.position); |
|
|
drawPath(new THREE.Vector3(3, 0, 0), deployNode.position); |
|
|
|
|
|
|
|
|
const particles = []; |
|
|
|
|
|
function createBox(x, y, z, material, name) { |
|
|
const geo = new THREE.BoxGeometry(1.5, 1.5, 1.5); |
|
|
const mesh = new THREE.Mesh(geo, material); |
|
|
mesh.position.set(x, y, z); |
|
|
scene.add(mesh); |
|
|
|
|
|
|
|
|
addLabel(name, x, y + 1.2, z); |
|
|
return mesh; |
|
|
} |
|
|
|
|
|
function drawPath(start, end) { |
|
|
const points = [start, end]; |
|
|
const geometry = new THREE.BufferGeometry().setFromPoints(points); |
|
|
const material = new THREE.LineBasicMaterial({ color: 0xffffff, opacity: 0.3, transparent: true }); |
|
|
const line = new THREE.Line(geometry, material); |
|
|
scene.add(line); |
|
|
} |
|
|
|
|
|
function addLabel(text, x, y, z) { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'label'; |
|
|
div.textContent = text; |
|
|
document.body.appendChild(div); |
|
|
|
|
|
|
|
|
div.dataset.x = x; |
|
|
div.dataset.y = y; |
|
|
div.dataset.z = z; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
window.triggerPipeline = (type) => { |
|
|
let startPos, color, destination; |
|
|
|
|
|
if (type === 'commit') { |
|
|
startPos = commitNode.position.clone(); |
|
|
color = 0x4cc9f0; |
|
|
destination = buildNode.position.clone(); |
|
|
} else if (type === 'schedule') { |
|
|
startPos = scheduleNode.position.clone(); |
|
|
color = 0xf72585; |
|
|
destination = buildNode.position.clone(); |
|
|
} else if (type === 'manual') { |
|
|
startPos = manualNode.position.clone(); |
|
|
color = 0xffd60a; |
|
|
destination = deployNode.position.clone(); |
|
|
} |
|
|
|
|
|
|
|
|
const geo = new THREE.SphereGeometry(0.4, 16, 16); |
|
|
const mat = new THREE.MeshBasicMaterial({ color: color }); |
|
|
const packet = new THREE.Mesh(geo, mat); |
|
|
packet.position.copy(startPos); |
|
|
scene.add(packet); |
|
|
|
|
|
|
|
|
particles.push({ |
|
|
mesh: packet, |
|
|
progress: 0, |
|
|
path: [ |
|
|
startPos, |
|
|
new THREE.Vector3(-3, 0, 0), |
|
|
new THREE.Vector3(3, 0, 0), |
|
|
destination |
|
|
] |
|
|
}); |
|
|
}; |
|
|
|
|
|
function updateParticles() { |
|
|
for (let i = particles.length - 1; i >= 0; i--) { |
|
|
const p = particles[i]; |
|
|
p.progress += 0.015; |
|
|
|
|
|
if (p.progress >= 3) { |
|
|
|
|
|
scene.remove(p.mesh); |
|
|
particles.splice(i, 1); |
|
|
|
|
|
|
|
|
continue; |
|
|
} |
|
|
|
|
|
|
|
|
let segmentIndex = Math.floor(p.progress); |
|
|
let segmentProgress = p.progress - segmentIndex; |
|
|
|
|
|
if (segmentIndex < 3) { |
|
|
const start = p.path[segmentIndex]; |
|
|
const end = p.path[segmentIndex + 1]; |
|
|
p.mesh.position.lerpVectors(start, end, segmentProgress); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function updateLabels() { |
|
|
const labels = document.querySelectorAll('.label'); |
|
|
labels.forEach(label => { |
|
|
const pos = new THREE.Vector3( |
|
|
parseFloat(label.dataset.x), |
|
|
parseFloat(label.dataset.y), |
|
|
parseFloat(label.dataset.z) |
|
|
); |
|
|
pos.project(camera); |
|
|
|
|
|
const x = (pos.x * .5 + .5) * window.innerWidth; |
|
|
const y = (pos.y * -.5 + .5) * window.innerHeight; |
|
|
|
|
|
label.style.transform = `translate(-50%, -50%) translate(${x}px, ${y}px)`; |
|
|
}); |
|
|
} |
|
|
|
|
|
function animate() { |
|
|
requestAnimationFrame(animate); |
|
|
|
|
|
|
|
|
pipeline.rotation.x += 0.01; |
|
|
|
|
|
updateParticles(); |
|
|
updateLabels(); |
|
|
renderer.render(scene, camera); |
|
|
} |
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => { |
|
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
|
camera.updateProjectionMatrix(); |
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
}); |
|
|
|
|
|
animate(); |
|
|
</script> |
|
|
</body> |
|
|
</html> |