From 04766bd0056d00820003c8b734528d5feb1578d3 Mon Sep 17 00:00:00 2001 From: Denis Shatskiy Date: Tue, 15 Jul 2025 23:46:14 +0700 Subject: [PATCH] feat: Enhance GraphView component with draggable nodes, improved positioning, and country color coding for better visualization of server connections --- frontend/src/GraphView.jsx | 227 +++++++++++++++++++++++++++++++------ 1 file changed, 190 insertions(+), 37 deletions(-) diff --git a/frontend/src/GraphView.jsx b/frontend/src/GraphView.jsx index 5aef783..ac3e8fb 100644 --- a/frontend/src/GraphView.jsx +++ b/frontend/src/GraphView.jsx @@ -1,6 +1,11 @@ -import React from 'react'; +import React, { useState, useRef } from 'react'; function GraphView({ servers, connections }) { + const [nodePositions, setNodePositions] = useState({}); + const [draggedNode, setDraggedNode] = useState(null); + const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); + const svgRef = useRef(null); + if (servers.length === 0) { return (
@@ -12,21 +17,99 @@ function GraphView({ servers, connections }) { ); } - // Простое позиционирование узлов в сетке - const getNodePosition = (index, total) => { + // Инициализация позиций узлов при первом рендере + const getInitialNodePosition = (index, total) => { + if (nodePositions[servers[index]?.ip]) { + return nodePositions[servers[index].ip]; + } + const cols = Math.ceil(Math.sqrt(total)); const row = Math.floor(index / cols); const col = index % cols; - const spacing = 150; + const spacing = 180; return { - x: 100 + col * spacing, - y: 100 + row * spacing + x: 120 + col * spacing, + y: 120 + row * spacing }; }; + // Обработчик начала перетаскивания + const handleMouseDown = (e, serverIp) => { + const svg = svgRef.current; + const pt = svg.createSVGPoint(); + pt.x = e.clientX; + pt.y = e.clientY; + const svgP = pt.matrixTransform(svg.getScreenCTM().inverse()); + + const currentPos = nodePositions[serverIp] || getInitialNodePosition( + servers.findIndex(s => s.ip === serverIp), + servers.length + ); + + setDragOffset({ + x: svgP.x - currentPos.x, + y: svgP.y - currentPos.y + }); + setDraggedNode(serverIp); + }; + + // Обработчик перетаскивания + const handleMouseMove = (e) => { + if (!draggedNode) return; + + const svg = svgRef.current; + const pt = svg.createSVGPoint(); + pt.x = e.clientX; + pt.y = e.clientY; + const svgP = pt.matrixTransform(svg.getScreenCTM().inverse()); + + setNodePositions(prev => ({ + ...prev, + [draggedNode]: { + x: svgP.x - dragOffset.x, + y: svgP.y - dragOffset.y + } + })); + }; + + // Обработчик окончания перетаскивания + const handleMouseUp = () => { + setDraggedNode(null); + }; + + // Получение позиции узла + const getNodePosition = (serverIp) => { + return nodePositions[serverIp] || getInitialNodePosition( + servers.findIndex(s => s.ip === serverIp), + servers.length + ); + }; + + // Цвета для разных стран + const getCountryColor = (country) => { + const colors = { + 'RU': '#dc2626', // красный + 'US': '#2563eb', // синий + 'DE': '#059669', // зеленый + 'SE': '#7c3aed', // фиолетовый + 'NL': '#ea580c', // оранжевый + 'SG': '#0891b2' // голубой + }; + return colors[country] || '#6b7280'; + }; + return (
- + {/* Связи */} {connections.map((connection, index) => { const fromServer = servers.find(s => s.ip === connection.from); @@ -34,10 +117,8 @@ function GraphView({ servers, connections }) { if (!fromServer || !toServer) return null; - const fromIndex = servers.indexOf(fromServer); - const toIndex = servers.indexOf(toServer); - const fromPos = getNodePosition(fromIndex, servers.length); - const toPos = getNodePosition(toIndex, servers.length); + const fromPos = getNodePosition(connection.from); + const toPos = getNodePosition(connection.to); return ( @@ -47,17 +128,28 @@ function GraphView({ servers, connections }) { y1={fromPos.y} x2={toPos.x} y2={toPos.y} - stroke="#64748b" - strokeWidth="2" + stroke="#94a3b8" + strokeWidth="3" + strokeDasharray="5,5" markerEnd="url(#arrowhead)" /> {/* Подпись связи */} + @@ -65,9 +157,9 @@ function GraphView({ servers, connections }) { {connection.ipA} ⇄ {connection.ipB} @@ -77,29 +169,65 @@ function GraphView({ servers, connections }) { })} {/* Узлы (серверы) */} - {servers.map((server, index) => { - const pos = getNodePosition(index, servers.length); + {servers.map((server) => { + const pos = getNodePosition(server.ip); + const countryColor = getCountryColor(server.country); + const isDragging = draggedNode === server.ip; return ( - {/* Круг узла */} + {/* Тень */} + + + {/* Основной круг */} handleMouseDown(e, server.ip)} /> - {/* DNS имя */} + {/* Внутренний круг с цветом страны */} + + + {/* Иконка сервера */} + 🖥️ + + + {/* DNS имя */} + {server.dns} @@ -107,21 +235,46 @@ function GraphView({ servers, connections }) { {/* IP адрес */} {server.ip} - {/* Страна */} + {/* Провайдер */} + {server.provider} + + + {/* Бейдж страны */} + + {server.country} @@ -133,15 +286,15 @@ function GraphView({ servers, connections }) {