From 7d5194d6fc58bc417c00b5be290bed7ea6ab22af Mon Sep 17 00:00:00 2001 From: Tour Date: Mon, 8 Dec 2025 21:28:18 +0100 Subject: [PATCH] fgdf --- public/d2.html | 300 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 253 insertions(+), 47 deletions(-) diff --git a/public/d2.html b/public/d2.html index 014e5ca..423bb4e 100644 --- a/public/d2.html +++ b/public/d2.html @@ -269,6 +269,19 @@ filter: drop-shadow(0 4px 12px rgba(96, 165, 250, 0.6)); transform: scale(1.1); } + /* Generic shape styling for all node shapes (rects, polygons, paths, etc.) */ + .node-shape { + filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3)); + transition: all 0.3s ease; + } + .node-group:hover .node-shape { + filter: drop-shadow(0 4px 12px rgba(96, 165, 250, 0.6)); + transform: scale(1.08); + } + .node-group.selected .node-shape { + stroke-width: 4px !important; + filter: drop-shadow(0 0 20px currentColor); + } .node-label { pointer-events: none; user-select: none; @@ -309,19 +322,42 @@ /* Network Zones */ .network-zone { - fill: rgba(30, 41, 59, 0.3); - stroke: #475569; - stroke-width: 2; + stroke-width: 3; stroke-dasharray: 10,5; rx: 20; ry: 20; pointer-events: none; + opacity: 0.8; + } + .zone-isp { + fill: rgba(14, 165, 233, 0.08); + stroke: #0ea5e9; + } + .zone-lan { + fill: rgba(16, 185, 129, 0.08); + stroke: #10b981; + } + .zone-apps { + fill: rgba(239, 68, 68, 0.08); + stroke: #ef4444; + } + .zone-tether { + fill: rgba(139, 92, 246, 0.08); + stroke: #8b5cf6; } .zone-label { + fill: #e2e8f0; + font-size: 13px; + font-weight: 700; + text-anchor: start; + pointer-events: none; + text-shadow: 0 2px 4px rgba(0,0,0,0.8); + } + .zone-sublabel { fill: #94a3b8; - font-size: 14px; - font-weight: 600; - text-anchor: middle; + font-size: 10px; + font-weight: 500; + text-anchor: start; pointer-events: none; } @@ -516,21 +552,45 @@ // Add network zone backgrounds (will be positioned after simulation) const zonesGroup = g.append('g').attr('class', 'zones'); + // ISP/Internet Zone + const ispZone = zonesGroup.append('rect') + .attr('class', 'network-zone zone-isp'); + const ispLabel = zonesGroup.append('text') + .attr('class', 'zone-label') + .text('☁️ ISP / Internet'); + const ispSublabel = zonesGroup.append('text') + .attr('class', 'zone-sublabel') + .text('External Services'); + + // LAN Zone const lanZone = zonesGroup.append('rect') - .attr('class', 'network-zone lan-zone') - .attr('fill', 'rgba(16, 185, 129, 0.1)'); - - const tetherZone = zonesGroup.append('rect') - .attr('class', 'network-zone tether-zone') - .attr('fill', 'rgba(139, 92, 246, 0.1)'); - + .attr('class', 'network-zone zone-lan'); const lanLabel = zonesGroup.append('text') .attr('class', 'zone-label') - .text('LAN 192.168.1.0/24'); + .text('🏠 Home LAN'); + const lanSublabel = zonesGroup.append('text') + .attr('class', 'zone-sublabel') + .text('192.168.1.0/24'); + // Applications Zone (inside LAN) + const appsZone = zonesGroup.append('rect') + .attr('class', 'network-zone zone-apps'); + const appsLabel = zonesGroup.append('text') + .attr('class', 'zone-label') + .text('⚡ Application Stack'); + const appsSublabel = zonesGroup.append('text') + .attr('class', 'zone-sublabel') + .text('Traefik + Services (192.168.1.159)'); + + // Tether Zone + const tetherZone = zonesGroup.append('rect') + .attr('class', 'network-zone zone-tether'); const tetherLabel = zonesGroup.append('text') .attr('class', 'zone-label') - .text('Tether 192.168.137.0/24'); + .text('📱 Tether Network'); + const tetherSublabel = zonesGroup.append('text') + .attr('class', 'zone-sublabel') + .text('192.168.137.0/24 (USB Bridge)'); // Create arrow markers for directed edges svg.append('defs').selectAll('marker') @@ -567,15 +627,12 @@ .on('drag', dragged) .on('end', dragended)); - // Add circles to nodes - node.append('circle') - .attr('class', 'node-circle') - .attr('r', d => nodeTypes[d.type]?.radius || 30) - .attr('fill', d => nodeTypes[d.type]?.fill || '#6b7280') - .attr('stroke', d => nodeTypes[d.type]?.stroke || '#9ca3af') - .attr('stroke-width', 3); + // Draw meaningful shapes per node instead of circles + node.each(function(d) { + drawNodeShape(d3.select(this), d); + }); - // Add icon/emoji to nodes + // Add icon/emoji to nodes (centered) node.append('text') .attr('class', 'node-icon') .attr('text-anchor', 'middle') @@ -624,16 +681,23 @@ } }); - // Create force simulation + // Create force simulation with better positioning simulation = d3.forceSimulation(graphData.nodes) .force('link', d3.forceLink(graphData.links) .id(d => d.id) - .distance(d => d.distance || 150)) + .distance(d => d.distance || 150) + .strength(0.5)) .force('charge', d3.forceManyBody() - .strength(-800)) + .strength(-1000) + .distanceMax(400)) .force('center', d3.forceCenter(width / 2, height / 2)) .force('collision', d3.forceCollide() - .radius(d => (nodeTypes[d.type]?.radius || 30) + 10)) + .radius(d => (nodeTypes[d.type]?.radius || 30) + 15) + .strength(0.7)) + .force('x', d3.forceX(width / 2).strength(0.05)) + .force('y', d3.forceY(height / 2).strength(0.05)) + .alphaDecay(0.02) // Slower decay = more settling time + .velocityDecay(0.4) // More damping = smoother movement .on('tick', ticked); function ticked() { @@ -659,6 +723,140 @@ d.fy = d.y; } + // Helper: compute polygon points (e.g., hexagon) centered at 0,0 + function polygonPoints(sides, radius) { + const points = []; + for (let i = 0; i < sides; i++) { + const angle = (Math.PI * 2 * i) / sides - Math.PI / 2; + const x = Math.cos(angle) * radius; + const y = Math.sin(angle) * radius; + points.push(`${x},${y}`); + } + return points.join(' '); + } + + // Draw a shape for a node based on d.shape + function drawNodeShape(group, d) { + const r = (nodeTypes[d.type]?.radius || 30); + const fill = nodeTypes[d.type]?.fill || '#6b7280'; + const stroke = nodeTypes[d.type]?.stroke || '#9ca3af'; + + switch (d.shape) { + case 'cloud': { + // Cloud using three ellipses + group.append('ellipse') + .attr('class', 'node-shape') + .attr('cx', 0).attr('cy', -r * 0.2) + .attr('rx', r * 0.9).attr('ry', r * 0.6) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + group.append('ellipse') + .attr('class', 'node-shape') + .attr('cx', -r * 0.7).attr('cy', -r * 0.15) + .attr('rx', r * 0.5).attr('ry', r * 0.35) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + group.append('ellipse') + .attr('class', 'node-shape') + .attr('cx', r * 0.7).attr('cy', -r * 0.1) + .attr('rx', r * 0.5).attr('ry', r * 0.35) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + break; + } + case 'hex': { + group.append('polygon') + .attr('class', 'node-shape') + .attr('points', polygonPoints(6, r)) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + break; + } + case 'router': { + const w = r * 2.1, h = r * 1.2; + group.append('rect') + .attr('class', 'node-shape') + .attr('x', -w / 2).attr('y', -h / 2) + .attr('width', w).attr('height', h) + .attr('rx', 14).attr('ry', 14) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + // Simple antenna + group.append('line') + .attr('x1', 0).attr('y1', -h / 2) + .attr('x2', 0).attr('y2', -h / 2 - r * 0.6) + .attr('stroke', stroke).attr('stroke-width', 3); + break; + } + case 'server': { + const w = r * 2.0, h = r * 1.6; + group.append('rect') + .attr('class', 'node-shape') + .attr('x', -w / 2).attr('y', -h / 2) + .attr('width', w).attr('height', h) + .attr('rx', 10).attr('ry', 10) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + // Status lights/lines + const y1 = -h / 4, y2 = 0, y3 = h / 4; + [y1, y2, y3].forEach((yy, i) => { + group.append('line') + .attr('x1', -w/2 + 10).attr('y1', yy) + .attr('x2', w/2 - 10).attr('y2', yy) + .attr('stroke', i === 0 ? '#93c5fd' : '#94a3b8') + .attr('stroke-width', 2).attr('opacity', 0.8); + }); + break; + } + case 'chip': { + const w = r * 2.0, h = r * 1.6; + group.append('rect') + .attr('class', 'node-shape') + .attr('x', -w / 2).attr('y', -h / 2) + .attr('width', w).attr('height', h) + .attr('rx', 8).attr('ry', 8) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + // Pins + const pinCount = 5, pinLen = 6; + for (let i = 0; i < pinCount; i++) { + const t = ((i + 1) / (pinCount + 1)); + const y = -h/2 + t * h; + // left pins + group.append('line').attr('x1', -w/2).attr('y1', y).attr('x2', -w/2 - pinLen).attr('y2', y).attr('stroke', stroke).attr('stroke-width', 2); + // right pins + group.append('line').attr('x1', w/2).attr('y1', y).attr('x2', w/2 + pinLen).attr('y2', y).attr('stroke', stroke).attr('stroke-width', 2); + } + break; + } + case 'monitor': { + const w = r * 2.0, h = r * 1.3; + group.append('rect') + .attr('class', 'node-shape') + .attr('x', -w / 2).attr('y', -h / 2) + .attr('width', w).attr('height', h) + .attr('rx', 6).attr('ry', 6) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + group.append('rect') + .attr('x', -w * 0.15).attr('y', h / 2 - 4) + .attr('width', w * 0.3).attr('height', 8) + .attr('fill', stroke).attr('rx', 3).attr('ry', 3); + break; + } + default: { + // Fallback circle + group.append('circle') + .attr('class', 'node-shape') + .attr('r', r) + .attr('fill', fill).attr('stroke', stroke).attr('stroke-width', 3); + } + } + + // Extra overlay: red cross for AdGuard + if (d.id === 'AdGuard') { + const s = r * 0.9; + group.append('line') + .attr('x1', -s/2).attr('y1', -s/2).attr('x2', s/2).attr('y2', s/2) + .attr('stroke', '#ef4444').attr('stroke-width', 4).attr('opacity', 0.9); + group.append('line') + .attr('x1', s/2).attr('y1', -s/2).attr('x2', -s/2).attr('y2', s/2) + .attr('stroke', '#ef4444').attr('stroke-width', 4).attr('opacity', 0.9); + } + } + function dragged(event, d) { d.fx = event.x; d.fy = event.y; @@ -688,26 +886,34 @@ // Create graph data structure from network data function createGraphData() { const nodes = [ - { id: 'Internet', label: 'Internet', sublabel: '5.132.33.195', icon: '☁️', type: 'dns' }, - { id: 'Router', label: 'Zyxel Router', sublabel: '192.168.1.1', icon: '🛜', type: 'router' }, - { id: 'Traefik', label: 'Traefik', sublabel: '192.168.1.159', icon: '⚡', type: 'core' }, - { id: 'Dokku', label: 'Dokku', sublabel: '192.168.1.159', icon: '⚡', type: 'core' }, - { id: 'Gitea', label: 'Gitea', sublabel: 'git.appmodel.nl', icon: '⚡', type: 'core' }, - { id: 'Auction', label: 'Auction', sublabel: 'auction.appmodel.nl', icon: '⚡', type: 'core' }, - { id: 'MI50', label: 'MI50/Ollama', sublabel: 'ollama.lan', icon: '⚡', type: 'core' }, - { id: 'AdGuard', label: 'AdGuard', sublabel: 'DNS Filter', icon: '🔧', type: 'infra' }, - { id: 'XU4', label: 'XU4 DNS', sublabel: '192.168.1.163', icon: '🔧', type: 'infra' }, - { id: 'C2', label: 'C2 DNS', sublabel: '192.168.1.227', icon: '🔧', type: 'infra' }, - { id: 'HA', label: 'Home Assistant', sublabel: '192.168.1.193', icon: '🔧', type: 'infra' }, - { id: 'Atlas', label: 'Atlas', sublabel: '192.168.1.100', icon: '💻', type: 'worker' }, - { id: 'TV', label: 'Kamer-TV', sublabel: '192.168.1.240', icon: '📺', type: 'iot' }, - { id: 'Hue', label: 'Philips Hue', sublabel: '192.168.1.49', icon: '💡', type: 'iot' }, - { id: 'Eufy', label: 'Eufy S380HB', sublabel: '192.168.1.59', icon: '📹', type: 'iot' }, - { id: 'IoT', label: 'IoT Devices', sublabel: 'Various', icon: '🔌', type: 'iot' }, - { id: 'MIKE', label: 'MIKE PC', sublabel: '192.168.1.100', icon: '💻', type: 'client' }, - { id: 'Lotte', label: 'Lotte', sublabel: '192.168.1.133', icon: '📱', type: 'client' }, - { id: 'Hermes', label: 'Hermes', sublabel: '192.168.137.239', icon: '💻', type: 'worker' }, - { id: 'Plato', label: 'Plato', sublabel: 'llm.plato.lan', icon: '💻', type: 'worker' } + { id: 'Internet', label: 'Internet', sublabel: '5.132.33.195', icon: '☁️', type: 'dns', shape: 'cloud' }, + { id: 'Router', label: 'Zyxel Router', sublabel: '192.168.1.1', icon: '🛜', type: 'router', shape: 'router' }, + + // Application stack (Traefik with nginx-like hexagon) + { id: 'Traefik', label: 'Traefik', sublabel: '192.168.1.159', icon: 'N', type: 'core', shape: 'hex' }, + { id: 'Dokku', label: 'Dokku', sublabel: '192.168.1.159', icon: '⚡', type: 'core', shape: 'server' }, + { id: 'Gitea', label: 'Gitea', sublabel: 'git.appmodel.nl', icon: '⚡', type: 'core', shape: 'server' }, + { id: 'Auction', label: 'Auction', sublabel: 'auction.appmodel.nl', icon: '⚡', type: 'core', shape: 'server' }, + { id: 'MI50', label: 'MI50/Ollama', sublabel: 'ollama.lan', icon: '🧠', type: 'core', shape: 'server' }, + + // Infra + { id: 'AdGuard', label: 'AdGuard', sublabel: 'DNS Filter', icon: '🛡️', type: 'infra', shape: 'server' }, + { id: 'XU4', label: 'XU4 DNS', sublabel: '192.168.1.163', icon: '🔧', type: 'infra', shape: 'chip' }, + { id: 'C2', label: 'C2 DNS', sublabel: '192.168.1.227', icon: '🔧', type: 'infra', shape: 'chip' }, + { id: 'HA', label: 'Home Assistant', sublabel: '192.168.1.193', icon: '🏠', type: 'infra', shape: 'server' }, + + // Workers / devices + { id: 'Atlas', label: 'Atlas', sublabel: '192.168.1.100', icon: '💻', type: 'worker', shape: 'chip' }, + { id: 'Hermes', label: 'Hermes', sublabel: '192.168.137.239', icon: '💻', type: 'worker', shape: 'chip' }, + { id: 'Plato', label: 'Plato', sublabel: 'llm.plato.lan', icon: '💻', type: 'worker', shape: 'chip' }, + + // IoT and clients + { id: 'TV', label: 'Kamer-TV', sublabel: '192.168.1.240', icon: '📺', type: 'iot', shape: 'monitor' }, + { id: 'Hue', label: 'Philips Hue', sublabel: '192.168.1.49', icon: '💡', type: 'iot', shape: 'server' }, + { id: 'Eufy', label: 'Eufy S380HB', sublabel: '192.168.1.59', icon: '📹', type: 'iot', shape: 'server' }, + { id: 'IoT', label: 'IoT Devices', sublabel: 'Various', icon: '🔌', type: 'iot', shape: 'server' }, + { id: 'MIKE', label: 'MIKE PC', sublabel: '192.168.1.100', icon: '🖥️', type: 'client', shape: 'monitor' }, + { id: 'Lotte', label: 'Lotte', sublabel: '192.168.1.133', icon: '📱', type: 'client', shape: 'monitor' } ]; const links = [