diff --git a/frontend/src/GraphView.jsx b/frontend/src/GraphView.jsx index ac60227..1194563 100644 --- a/frontend/src/GraphView.jsx +++ b/frontend/src/GraphView.jsx @@ -1,4 +1,4 @@ -import React, { useMemo, useCallback, useEffect, useRef } from 'react'; +import React, { useMemo, useCallback, useEffect, useRef, useState } from 'react'; import { ReactFlow, Background, @@ -7,14 +7,17 @@ import { useNodesState, addEdge, MarkerType, + Handle, + Position, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { IconZoomIn, IconZoomOut, IconMaximize, IconMinimize } from '@tabler/icons-react'; -function GraphView({ servers, connections }) { +function GraphView({ servers, connections, onCreateConnection }) { const flowRef = useRef(null); const instanceRef = useRef(null); const containerRef = useRef(null); + const [isFullscreen, setIsFullscreen] = useState(false); const getTunnelStyle = useCallback((tunnelType) => { const map = { @@ -83,7 +86,16 @@ function GraphView({ servers, connections }) { setEdges(initialEdgesData); }, [initialEdgesData, setEdges]); - const onConnect = useCallback((params) => setEdges((eds) => addEdge({ ...params, type: 'default' }, eds)), [setEdges]); + const onConnect = useCallback( + (params) => { + // Делегируем создание связи в родителя, чтобы сохранить единую логику хранения + if (!params?.source || !params?.target || params.source === params.target) return; + if (onCreateConnection) { + onCreateConnection({ from: params.source, to: params.target }); + } + }, + [onCreateConnection] + ); const nodeTypes = useMemo( () => ({ @@ -110,6 +122,8 @@ function GraphView({ servers, connections }) { background: '#fff', }} > + {/* Точка входа соединений */} +
{flag} @@ -132,6 +146,8 @@ function GraphView({ servers, connections }) {
{s.ip}
{s.provider}
+ {/* Точка исхода соединений */} +
); }, @@ -161,6 +177,23 @@ function GraphView({ servers, connections }) { return () => clearTimeout(t); }, [servers, connections]); + // Трек полноэкранного режима и авто-fit после входа/выхода + useEffect(() => { + const handler = () => { + const fs = Boolean(document.fullscreenElement); + setIsFullscreen(fs); + if (instanceRef.current) { + setTimeout(() => { + try { + instanceRef.current.fitView({ padding: 0.2 }); + } catch {} + }, 60); + } + }; + document.addEventListener('fullscreenchange', handler); + return () => document.removeEventListener('fullscreenchange', handler); + }, []); + if (!servers || servers.length === 0) { return (
@@ -173,7 +206,18 @@ function GraphView({ servers, connections }) { } return ( -
+
- {document.fullscreenElement ? : } + {isFullscreen ? : }
diff --git a/frontend/src/ServerManager.jsx b/frontend/src/ServerManager.jsx index 066b9bf..9b8e999 100644 --- a/frontend/src/ServerManager.jsx +++ b/frontend/src/ServerManager.jsx @@ -765,7 +765,13 @@ function ServerManager() {
(!graphCountryFilter || s.country === graphCountryFilter) && (!graphProviderFilter || s.provider === graphProviderFilter))} - connections={connections.filter(c => graphTunnelFilters[c.tunnelType])} + connections={connections.filter(c => graphTunnelFilters[c.tunnelType])} + onCreateConnection={({ from, to }) => { + // Открываем модал добавления связи и проставляем выбранные узлы + setNewConnection(prev => ({ ...prev, from, to })); + const el = document.querySelector('#add-connection-anchor'); + if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }} />
@@ -777,7 +783,7 @@ function ServerManager() {
-
+

Добавить связь между серверами