diff --git a/frontend/assets/js/app.js b/frontend/assets/js/app.js index d2c65ff..a39b703 100644 --- a/frontend/assets/js/app.js +++ b/frontend/assets/js/app.js @@ -211,6 +211,7 @@ async function loadNetworkData() { renderNodeList(); renderShapeList(); if (shapes.length) nextShapeZ = Math.max(...shapes.map(x => x.z_index)) + 1; + buildCanvasGraph(); renderNetwork(); } @@ -376,14 +377,14 @@ function getNodeColorVal(type) { // ==================== CANVAS RENDERING ==================== const NODE_FA_ICONS = { - host: { path: 'M7,4 L17,4 L20,9 L20,18 L4,18 L4,9 Z', color: '#3b82f6' }, - server: { path: 'M6,3 L18,3 L20,6 L20,20 L4,20 L4,6 Z M8,10 L16,10 M8,14 L16,14 M8,17 L12,17', color: '#8b5cf6' }, - router: { path: 'M12,3 L21,10 L18,20 L6,20 L3,10 Z M12,6 L12,17 M8,10 L16,10', color: '#f59e0b' }, - firewall: { path: 'M6,3 L18,3 L21,8 L21,16 L18,21 L6,21 L3,16 L3,8 Z M9,10 L15,10 L15,14 L9,14 Z', color: '#ef4444' }, - switch: { path: 'M4,8 L20,8 L20,16 L4,16 Z M7,11 L7,13 M10,11 L10,13 M13,11 L13,13 M16,11 L16,13', color: '#06b6d4' }, - cloud: { path: 'M10,4 C6,4 4,7 5,10 C3,11 2,14 4,16 L8,16 C10,18 14,18 16,16 L20,16 C22,14 21,10 19,9 C20,6 17,4 15,5 C14,4 12,4 10,4 Z', color: '#22c55e' }, - endpoint: { path: 'M8,4 L16,4 L18,10 L18,16 L16,20 L8,20 L6,16 L6,10 Z M10,12 L14,12 M12,10 L12,14', color: '#ec4899' }, - other: { path: 'M8,6 L16,6 L18,12 L16,18 L8,18 L6,12 Z', color: '#6b7280' } + host: { path: 'M-7,-7h14v10h-14z M-5,3h3v4h-3z M2,3h3v4h-3z M0,-10v-3', color: '#3b82f6' }, + server: { path: 'M-8,-8h16v4h-16z M-8,-2h16v4h-16z M-8,4h16v4h-16z M-8,10h16v4h-16z', color: '#8b5cf6' }, + router: { path: 'M-10,0l10,-8v5h6v6h-6v5z', color: '#f59e0b' }, + firewall: { path: 'M-9,-4l3,-4h12l3,4v8l-3,4h-12l-3,-4z M-4,-2h8v4h-8z', color: '#ef4444' }, + switch: { path: 'M-10,-4h20v2h-20z M-10,0h20v2h-20z M-10,4h20v2h-20z', color: '#06b6d4' }, + cloud: { path: 'M-2,-9c-5,0-8,3-7,7c-2,2-3,5-1,8h5c2,3,7,3,10,2h5c3-2,3-7,1-9c1-4-2-8-5-8c-1,0-3,1-4,2c-1-1-2-2-4-2z', color: '#22c55e' }, + endpoint: { path: 'M-9,-7c0-2,2-4,4-4h10c2,0,4,2,4,4v14c0,2-2,4-4,4h-10c-2,0-4-2-4-4z M-5,7h10v2h-10z M-5,3h10v2h-10z M0,-2c2,0,3,1,3,3c0,2-1,3-3,3c-2,0-3-1-3-3c0-2,1-3,3-3z', color: '#ec4899' }, + other: { path: 'M-6,-6h12v12h-12z', color: '#6b7280' } }; function buildCanvasGraph() { @@ -413,11 +414,11 @@ function buildCanvasGraph() { } function renderNetwork() { - buildCanvasGraph(); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.translate(panX, panY); + // Use canvasShapes / canvasNodes / canvasLinks arrays directly (no rebuild) canvasShapes.sort((a, b) => a.z - b.z).forEach(drawShape); canvasLinks.forEach(l => { @@ -498,11 +499,18 @@ function drawCanvasNode(n) { ctx.stroke(); ctx.shadowBlur = 0; - // Icon path scaled - const scaled = n.iconPath.replace(/([\d.]+)/g, m => ((parseFloat(m) - 12) * 0.85 + 0).toFixed(1)); - const path = new Path2D(scaled); + // Draw icon + ctx.save(); + ctx.translate(n.x, n.y); + const s = 1.2; + ctx.scale(s, s); + const path = new Path2D(n.iconPath); ctx.fillStyle = n.color; + ctx.lineWidth = 1.8; + ctx.strokeStyle = n.color; ctx.fill(path); + ctx.stroke(path); + ctx.restore(); // Status dot ctx.beginPath(); @@ -528,10 +536,10 @@ function drawCanvasNode(n) { function getShapeHandles(s) { return [ - { x: s.x, y: s.y }, - { x: s.x + s.w, y: s.y }, - { x: s.x + s.w, y: s.y + s.h }, - { x: s.x, y: s.y + s.h } + { x: s.x, y: s.y, cx: 0, cy: 0 }, + { x: s.x + s.w, y: s.y, cx: 1, cy: 0 }, + { x: s.x + s.w, y: s.y + s.h, cx: 1, cy: 1 }, + { x: s.x, y: s.y + s.h, cx: 0, cy: 1 } ]; } @@ -552,7 +560,7 @@ function getShapeResizeHandleAt(mx, my) { for (const s of canvasShapes) { if (selectedShapeId != s.id) continue; for (const h of getShapeHandles(s)) { - if (Math.hypot(mx - h.x, my - h.y) < 8) return { shape: s, hx: h.x, hy: h.y }; + if (Math.hypot(mx - h.x, my - h.y) < 8) return { shape: s, cx: h.cx, cy: h.cy, sx: h.x, sy: h.y }; } } return null; @@ -563,12 +571,14 @@ function onMouseDown(e) { const mx = e.clientX - rect.left - panX; const my = e.clientY - rect.top - panY; + // Always build graph fresh before interacting + buildCanvasGraph(); + const resize = getShapeResizeHandleAt(mx, my); if (resize) { - const s = resize.shape; - dragType = 'resize'; dragTarget = s; + dragType = 'resize'; dragTarget = resize.shape; dragOffX = mx; dragOffY = my; - dragOrig = { x: s.x, y: s.y, w: s.w, h: s.h }; + dragOrig = { x: resize.shape.x, y: resize.shape.y, w: resize.shape.w, h: resize.shape.h, cx: resize.cx, cy: resize.cy }; return; } @@ -585,7 +595,7 @@ function onMouseDown(e) { selectedNodeId = null; selectedShapeId = shape.id; dragType = 'shape'; dragTarget = shape; dragOffX = mx - shape.x; dragOffY = my - shape.y; - renderNodeList(); renderShapeList(); renderNetwork(); + renderNodeList(); renderShapeList(); return; } @@ -611,13 +621,14 @@ function onMouseMove(e) { const dx = e.clientX - rect.left - panX - dragOffX; const dy = e.clientY - rect.top - panY - dragOffY; const s = dragTarget; - let nx = dragOrig.x, ny = dragOrig.y, nw = dragOrig.w, nh = dragOrig.h; - if (dragOffX < dragOrig.x + dragOrig.w / 2) { nx = dragOrig.x + dx; nw = dragOrig.w - dx; } - else { nw = dragOrig.w + dx; } - if (dragOffY < dragOrig.y + dragOrig.h / 2) { ny = dragOrig.y + dy; nh = dragOrig.h - dy; } - else { nh = dragOrig.h + dy; } - if (nw < 50) nw = 50; - if (nh < 50) nh = 50; + const o = dragOrig; + let nx = o.x, ny = o.y, nw = o.w, nh = o.h; + if (o.cx === 0) { nx = o.x + dx; nw = o.w - dx; } + else { nw = o.w + dx; } + if (o.cy === 0) { ny = o.y + dy; nh = o.h - dy; } + else { nh = o.h + dy; } + if (nw < 50) { if (o.cx === 0) nx = o.x + o.w - 50; nw = 50; } + if (nh < 50) { if (o.cy === 0) ny = o.y + o.h - 50; nh = 50; } s.x = nx; s.y = ny; s.w = nw; s.h = nh; renderNetwork(); } else if (isPanning) {