eaglelandsonce commited on
Commit
4cb3f71
·
verified ·
1 Parent(s): 15de190

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +101 -237
index.html CHANGED
@@ -3,251 +3,115 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Terraform 3D Icon Visualizer</title>
7
  <style>
8
- body { margin: 0; background-color: #0d1117; color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; overflow: hidden; }
9
- canvas { display: block; }
10
- #loader-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; z-index: 200; backdrop-filter: blur(5px); background-color: rgba(13, 17, 23, 0.5);}
11
- #loader-content { text-align: center; padding: 2em; background-color: #161b22; border-radius: 10px; border: 1px solid #30363d; }
12
- #loader-content p { font-size: 1.5em; color: #c9d1d9; margin-top: 0; }
13
- #loader-content span { font-size: 1em; color: #8b949e; max-width: 500px; display: inline-block; }
14
- #info { position: absolute; top: 10px; width: 100%; text-align: center; z-index: 100; color: #c9d1d9; font-size: 1.2em; pointer-events: none; }
15
- #tooltip { position: absolute; display: none; padding: 10px; background-color: rgba(22, 27, 34, 0.9); border: 1px solid #30363d; border-radius: 5px; pointer-events: none; backdrop-filter: blur(5px); }
 
 
 
 
 
 
 
 
 
16
  </style>
17
  </head>
18
  <body>
19
- <!-- This container provides status updates and error messages -->
20
- <div id="loader-container">
21
- <div id="loader-content">
22
- <p>Loading Visualizer...</p>
23
- <span id="loader-message">Initializing 3D scene...</span>
24
- </div>
25
- </div>
26
-
27
- <div id="info">Terraform Azure 3D Visualizer</div>
28
- <div id="tooltip"></div>
29
-
30
- <!-- Importmap for Three.js modules -->
31
- <script type="importmap">
32
- {
33
- "imports": {
34
- "three": "https://unpkg.com/[email protected]/build/three.module.js",
35
- "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
36
- }
37
- }
38
- </script>
39
-
40
- <!-- Main JavaScript Module -->
41
- <script type="module">
42
- // BUILT-IN CHECK: This is the critical fix.
43
- if (window.location.protocol === 'file:') {
44
- const loaderContent = document.getElementById('loader-content');
45
- loaderContent.innerHTML = `
46
- <p style="color: #f85149;">Error: Cannot Run From File</p>
47
- <span style="max-width: 500px; display: inline-block;">
48
- You must open this HTML file using a local web server due to browser security rules (CORS).
49
- <br><br>
50
- <strong>The easiest way is with the 'Live Server' extension in VS Code.</strong>
51
- </span>
52
- `;
53
- throw new Error("Execution stopped: This file must be served over HTTP/HTTPS, not from a file: URL.");
54
- }
55
 
56
- import * as THREE from 'three';
57
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
58
- import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
59
- import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
60
 
61
- // The Terraform HCL code to visualize
62
- const terraformCode = `
63
- resource "azurerm_resource_group" "tfexample" { name = "my-terraform-rg" }
64
- resource "azurerm_virtual_network" "tfexample" { resource_group_name = azurerm_resource_group.tfexample.name }
65
- resource "azurerm_subnet" "tfexample" { virtual_network_name = azurerm_virtual_network.tfexample.name }
66
- resource "azurerm_public_ip" "tfexample" { resource_group_name = azurerm_resource_group.tfexample.name }
67
- resource "azurerm_lb" "tfexample" { public_ip_address_id = azurerm_public_ip.tfexample.id }
68
- resource "azurerm_linux_virtual_machine_scale_set" "tfexample" { subnet_id = azurerm_subnet.tfexample.id }
69
- `;
70
-
71
- // Simple HCL parser for resources and their direct dependencies
72
- function parseTerraform(code) {
73
- const resources = [], dependencies = new Set(), regex = /resource "([\w_]+)" "([\w_]+)"/g;
74
- let match;
75
- while ((match = regex.exec(code)) !== null) resources.push({ type: match[1], name: match[2], id: `${match[1]}.${match[2]}` });
76
- resources.forEach(res => {
77
- const blockRegex = new RegExp(`resource "${res.type}" "${res.name}" \\{[\\s\\S]*?\\}`, 'g');
78
- const blockMatch = blockRegex.exec(code);
79
- if (blockMatch) resources.forEach(p => { if (res.id !== p.id && blockMatch[0].includes(p.id)) dependencies.add(JSON.stringify({ from: res.id, to: p.id })) });
80
- });
81
- return { nodes: resources, edges: Array.from(dependencies).map(d => JSON.parse(d)) };
82
- }
83
-
84
- // Mapping resource types to their official Azure SVG icons
85
- const iconMap = {
86
- "azurerm_resource_group": "https://raw.githubusercontent.com/microsoft/vscode-azure-icons/main/icons/svg/resourcegroup.svg",
87
- "azurerm_virtual_network": "https://raw.githubusercontent.com/microsoft/vscode-azure-icons/main/icons/svg/virtualnetwork.svg",
88
- "azurerm_subnet": "https://raw.githubusercontent.com/microsoft/vscode-azure-icons/main/icons/svg/subnets.svg",
89
- "azurerm_public_ip": "https://raw.githubusercontent.com/microsoft/vscode-azure-icons/main/icons/svg/publicipaddress.svg",
90
- "azurerm_lb": "https://raw.githubusercontent.com/microsoft/vscode-azure-icons/main/icons/svg/loadbalancer.svg",
91
- "azurerm_linux_virtual_machine_scale_set": "https://raw.githubusercontent.com/microsoft/vscode-azure-icons/main/icons/svg/virtualmachinescaleset.svg",
92
- "default": "https://raw.githubusercontent.com/microsoft/vscode-azure-icons/main/icons/svg/resource.svg"
93
- };
94
 
95
- let camera, scene, renderer, labelRenderer, controls;
96
- const nodes = {};
97
- const intersectableObjects = [];
98
-
99
- // Function to create a visual node (3D or 2D sprite), returns a Promise
100
- function createNodeVisual(node, position) {
101
- return new Promise((resolve, reject) => {
102
- const is3D = node.type === 'azurerm_resource_group' || node.type === 'azurerm_virtual_network';
103
- const iconUrl = iconMap[node.type] || iconMap.default;
104
-
105
- if (is3D) {
106
- new SVGLoader().load(iconUrl, (data) => {
107
- const iconGroup = new THREE.Group();
108
- const extrudeSettings = { depth: 0.8, bevelEnabled: true, bevelThickness: 0.2, bevelSize: 0.1, bevelSegments: 2 };
109
- data.paths.forEach(path => {
110
- const material = new THREE.MeshStandardMaterial({ color: path.color, metalness: 0.5, roughness: 0.5 });
111
- SVGLoader.createShapes(path).forEach(shape => {
112
- const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
113
- geometry.center();
114
- iconGroup.add(new THREE.Mesh(geometry, material));
115
- });
116
- });
117
- const box = new THREE.Box3().setFromObject(iconGroup);
118
- iconGroup.scale.multiplyScalar(5 / box.getSize(new THREE.Vector3()).length());
119
- iconGroup.position.copy(position);
120
- resolve(iconGroup);
121
- }, undefined, (error) => reject(`SVG Load Error for ${node.id}: ${error}`));
122
- } else {
123
- new THREE.TextureLoader().load(iconUrl, (map) => {
124
- const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map }));
125
- sprite.scale.set(6, 6, 6);
126
- sprite.position.copy(position);
127
- resolve(sprite);
128
- }, undefined, (error) => reject(`Texture Load Error for ${node.id}: ${error}`));
129
- }
130
- });
131
- }
132
 
133
- // Main function to initialize the scene and run the visualizer
134
- async function init() {
135
- const loaderMessage = document.getElementById('loader-message');
136
-
137
- // Setup basic Three.js scene
138
- scene = new THREE.Scene();
139
- scene.background = new THREE.Color(0x0d1117);
140
- camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
141
- camera.position.set(0, 15, 45);
142
- renderer = new THREE.WebGLRenderer({ antialias: true });
143
- renderer.setSize(window.innerWidth, window.innerHeight);
144
- document.body.appendChild(renderer.domElement);
145
- labelRenderer = new CSS2DRenderer();
146
- labelRenderer.setSize(window.innerWidth, window.innerHeight);
147
- labelRenderer.domElement.style.position = 'absolute';
148
- labelRenderer.domElement.style.top = '0px';
149
- document.body.appendChild(labelRenderer.domElement);
150
- controls = new OrbitControls(camera, labelRenderer.domElement);
151
- controls.enableDamping = true;
152
- scene.add(new THREE.AmbientLight(0xffffff, 0.7));
153
- const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
154
- dirLight.position.set(8, 15, 10);
155
- scene.add(dirLight);
156
-
157
- // Parse Terraform and create node creation promises
158
- const graph = parseTerraform(terraformCode);
159
- loaderMessage.innerText = `Found ${graph.nodes.length} resources. Loading models...`;
160
- const nodePromises = graph.nodes.map((node, index) => {
161
- const angle = (index / graph.nodes.length) * Math.PI * 2;
162
- const position = new THREE.Vector3(30 * Math.cos(angle), (Math.random() - 0.5) * 15, 30 * Math.sin(angle));
163
- return createNodeVisual(node, position);
164
- });
165
-
166
- // Wait for all models to load before proceeding
167
- const loadedNodes = await Promise.all(nodePromises);
168
- document.getElementById('loader-container').style.display = 'none';
169
-
170
- // Add loaded nodes to the scene
171
- loadedNodes.forEach((visualNode, index) => {
172
- const nodeInfo = graph.nodes[index];
173
- visualNode.userData = { id: nodeInfo.id, type: nodeInfo.type };
174
- nodes[nodeInfo.id] = visualNode;
175
- intersectableObjects.push(visualNode);
176
- scene.add(visualNode);
177
- const labelDiv = document.createElement('div');
178
- labelDiv.textContent = nodeInfo.id;
179
- labelDiv.style.color = '#c9d1d9';
180
- labelDiv.style.fontSize = '12px';
181
- labelDiv.style.marginTop = '-1em';
182
- const label = new CSS2DObject(labelDiv);
183
- label.position.set(0, -5, 0);
184
- visualNode.add(label);
185
- });
186
-
187
- // Draw dependency lines now that nodes have positions
188
- graph.edges.forEach(edge => {
189
- const startNode = nodes[edge.from];
190
- const endNode = nodes[edge.to];
191
- if (startNode && endNode) {
192
- const geometry = new THREE.BufferGeometry().setFromPoints([startNode.position, endNode.position]);
193
- scene.add(new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0x3081f8, transparent: true, opacity: 0.7 })));
194
- }
195
- });
196
-
197
- // Start the animation loop
198
- animate();
199
- }
200
-
201
- const raycaster = new THREE.Raycaster();
202
- const mouse = new THREE.Vector2();
203
- const tooltip = document.getElementById('tooltip');
204
-
205
- function animate() {
206
- requestAnimationFrame(animate);
207
- controls.update();
208
-
209
- // Handle hover/tooltip logic
210
- raycaster.setFromCamera(mouse, camera);
211
- const intersects = raycaster.intersectObjects(intersectableObjects, true);
212
- if (intersects.length > 0) {
213
- let obj = intersects[0].object;
214
- while(obj.parent && !obj.userData.id) obj = obj.parent;
215
- if (obj.userData.id) {
216
- tooltip.style.display = 'block';
217
- tooltip.innerHTML = `<b>Type:</b> ${obj.userData.type}<br><b>Name:</b> ${obj.userData.id}`;
218
- }
219
- } else {
220
- tooltip.style.display = 'none';
221
- }
222
-
223
- renderer.render(scene, camera);
224
- labelRenderer.render(scene, camera);
225
- }
226
-
227
- window.addEventListener('mousemove', (event) => {
228
- mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
229
- mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
230
- if (tooltip.style.display === 'block') {
231
- tooltip.style.left = `${event.clientX + 15}px`;
232
- tooltip.style.top = `${event.clientY}px`;
233
- }
234
- });
235
-
236
- window.addEventListener('resize', () => {
237
- if(camera) {
238
- camera.aspect = window.innerWidth / window.innerHeight;
239
- camera.updateProjectionMatrix();
240
- renderer.setSize(window.innerWidth, window.innerHeight);
241
- labelRenderer.setSize(window.innerWidth, window.innerHeight);
242
- }
243
- });
244
 
245
- // Run the main initialization function
246
- init().catch(error => {
247
- console.error("Visualizer initialization failed:", error);
248
- const loaderContent = document.getElementById('loader-content');
249
- loaderContent.innerHTML = `<p style="color: #f85149;">A critical error occurred.</p><span>Check the browser's developer console (F12) for details.</span>`;
250
- });
251
- </script>
252
  </body>
253
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Azure Terraform Visualization</title>
7
  <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ background-color: #f0f2f5;
11
+ display: flex;
12
+ justify-content: center;
13
+ align-items: center;
14
+ flex-direction: column;
15
+ padding: 20px;
16
+ }
17
+ h1 {
18
+ color: #333;
19
+ }
20
+ .svg-container {
21
+ border: 1px solid #ccc;
22
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
23
+ background-color: #fff;
24
+ }
25
  </style>
26
  </head>
27
  <body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ <h1>Visualization of Azure Terraform Layout</h1>
 
 
 
30
 
31
+ <div class="svg-container">
32
+ <svg width="800" height="600" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="font-family: Arial, sans-serif; background-color: #f9f9f9;">
33
+ <title>Azure Terraform Layout</title>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ <!-- Definitions for icons and markers -->
36
+ <defs>
37
+ <marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
38
+ <path d="M 0 0 L 10 5 L 0 10 z" fill="#444" />
39
+ </marker>
40
+ <!-- Azure Icons (simplified representations) -->
41
+ <g id="icon-public-ip">
42
+ <path d="M2,2 h20 v20 h-20z" fill="#0078D4"/>
43
+ <path d="M12,5 l-5,5 h3 v6 h4 v-6 h3 z" fill="white"/>
44
+ </g>
45
+ <g id="icon-lb">
46
+ <path d="M2,2 h20 v20 h-20z" fill="#0078D4"/>
47
+ <path d="M4,12 h16 m-16,-4 h16 m-12,8 l-3,-4 h14 l-3,4" fill="none" stroke="white" stroke-width="2"/>
48
+ </g>
49
+ <g id="icon-vmss">
50
+ <path d="M2,2 h20 v20 h-20z" fill="#0078D4"/>
51
+ <rect x="5" y="5" width="14" height="14" fill="white" />
52
+ <rect x="7" y="7" width="10" height="10" fill="#0078D4" />
53
+ <rect x="9" y="9" width="6" height="6" fill="white" />
54
+ </g>
55
+ <g id="icon-internet">
56
+ <path fill="#A0A0A0" d="M35.6,20.8c-0.1-2-0.8-3.8-1.9-5.5c-1.2-1.6-2.8-2.9-4.7-3.6c-1.9-0.7-4-0.8-6-0.3c-2,0.5-3.8,1.5-5.2,3 c-1.4,1.5-2.3,3.4-2.6,5.5c-0.3,2.1,0.1,4.2,0.9,6.2c0.8,2,2.1,3.7,3.8,5c1.7,1.3,3.7,2.1,5.9,2.2c2.2,0.1,4.3-0.4,6.2-1.4 c1.9-1,3.5-2.5,4.6-4.3C35.2,25.9,35.7,23.4,35.6,20.8z M33.2,26.5c-1,1.6-2.4,2.9-4.1,3.7c-1.7,0.8-3.6,1.1-5.5,0.9 c-1.9-0.2-3.7-0.9-5.2-2.1c-1.5-1.2-2.6-2.7-3.2-4.5c-0.6-1.8-0.7-3.7-0.3-5.5c0.4-1.8,1.3-3.5,2.6-4.8c1.3-1.3,3-2.2,4.8-2.6 c1.8-0.4,3.7-0.3,5.5,0.3c1.8,0.6,3.4,1.7,4.5,3.2c1.1,1.5,1.7,3.3,1.9,5.1c0.1,1.8-0.2,3.6-0.9,5.2 C34.4,24.3,33.9,25.5,33.2,26.5z"/>
57
+ </g>
58
+ </defs>
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ <!-- Main Drawing -->
61
+
62
+ <!-- Resource Group -->
63
+ <rect x="10" y="10" width="780" height="580" rx="10" ry="10" fill="#E6F2FF" stroke="#0078D4" stroke-width="2"/>
64
+ <text x="30" y="40" font-size="16" font-weight="bold">Resource Group: my-terraform-rg</text>
65
+
66
+ <!-- VNet -->
67
+ <rect x="30" y="70" width="740" height="400" rx="5" ry="5" fill="#D4E8FF" stroke="#0078D4" stroke-width="1.5" stroke-dasharray="4"/>
68
+ <text x="50" y="100" font-size="14" font-weight="bold">Virtual Network: my-terraform-vnet</text>
69
+ <text x="50" y="120" font-size="12">Address Space: 10.0.0.0/16</text>
70
+
71
+ <!-- Subnet -->
72
+ <rect x="50" y="150" width="700" height="300" rx="5" ry="5" fill="#C2DEFF" stroke="#0078D4" stroke-width="1" stroke-dasharray="2"/>
73
+ <text x="70" y="180" font-size="14" font-weight="bold">Subnet: my-terraform-subnet</text>
74
+ <text x="70" y="200" font-size="12">Address Prefixes: 10.0.2.0/24</text>
75
+
76
+ <!-- Internet -->
77
+ <use xlink:href="#icon-internet" x="50" y="250" width="50" height="50"/>
78
+ <text x="70" y="320" font-size="14">Internet</text>
79
+
80
+ <!-- Public IP -->
81
+ <use xlink:href="#icon-public-ip" x="180" y="280" width="30" height="30"/>
82
+ <text x="170" y="325" font-size="12" font-weight="bold">Public IP</text>
83
+ <text x="145" y="340" font-size="10">my-terraform-public-ip</text>
84
+
85
+ <!-- Load Balancer -->
86
+ <use xlink:href="#icon-lb" x="300" y="280" width="30" height="30"/>
87
+ <text x="275" y="325" font-size="12" font-weight="bold">Load Balancer</text>
88
+ <text x="278" y="340" font-size="10">my-terraform-lb</text>
89
+ <text x="275" y="365" font-size="10" fill="#333">Rule: 80 -> ${var.server_port}</text>
90
+ <text x="280" y="380" font-size="10" fill="#333">Probe: Port ${var.server_port}</text>
91
+
92
+ <!-- VM Scale Set -->
93
+ <g transform="translate(450, 250)">
94
+ <use xlink:href="#icon-vmss" width="40" height="40"/>
95
+ <text x="-5" y="55" font-size="12" font-weight="bold">VM Scale Set</text>
96
+ <text x="-35" y="70" font-size="10">my-terraform-vm-scale-set</text>
97
+ </g>
98
+ <g transform="translate(550, 250)">
99
+ <use xlink:href="#icon-vmss" width="40" height="40"/>
100
+ <text x="-5" y="55" font-size="12" font-weight="bold">VM Scale Set</text>
101
+ <text x="-35" y="70" font-size="10">my-terraform-vm-scale-set</text>
102
+ </g>
103
+
104
+ <rect x="430" y="230" width="240" height="110" fill="none" stroke="#4CAF50" stroke-width="1.5" stroke-dasharray="3"/>
105
+ <text x="460" y="360" font-size="10" font-weight="bold" fill="#4CAF50">Backend Address Pool</text>
106
+ <text x="462" y="375" font-size="10" fill="#4CAF50">my-terraform-lb-backend-pool</text>
107
+
108
+ <!-- Connection Lines -->
109
+ <path d="M 120 295 C 150 295, 150 295, 180 295" stroke="#444" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
110
+ <path d="M 210 295 C 250 295, 270 295, 300 295" stroke="#444" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
111
+ <path d="M 330 295 C 380 295, 400 295, 430 295" stroke="#444" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
112
+
113
+ </svg>
114
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
 
 
 
 
 
 
 
116
  </body>
117
  </html>