| <!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> |