feat: Enhance GraphView component with connection creation delegation and fullscreen mode handling, and update ServerManager to support new connection logic
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m25s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m25s
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useCallback, useEffect, useRef } from 'react';
|
import React, { useMemo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
Background,
|
Background,
|
||||||
@@ -7,14 +7,17 @@ import {
|
|||||||
useNodesState,
|
useNodesState,
|
||||||
addEdge,
|
addEdge,
|
||||||
MarkerType,
|
MarkerType,
|
||||||
|
Handle,
|
||||||
|
Position,
|
||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import '@xyflow/react/dist/style.css';
|
import '@xyflow/react/dist/style.css';
|
||||||
import { IconZoomIn, IconZoomOut, IconMaximize, IconMinimize } from '@tabler/icons-react';
|
import { IconZoomIn, IconZoomOut, IconMaximize, IconMinimize } from '@tabler/icons-react';
|
||||||
|
|
||||||
function GraphView({ servers, connections }) {
|
function GraphView({ servers, connections, onCreateConnection }) {
|
||||||
const flowRef = useRef(null);
|
const flowRef = useRef(null);
|
||||||
const instanceRef = useRef(null);
|
const instanceRef = useRef(null);
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
|
||||||
const getTunnelStyle = useCallback((tunnelType) => {
|
const getTunnelStyle = useCallback((tunnelType) => {
|
||||||
const map = {
|
const map = {
|
||||||
@@ -83,7 +86,16 @@ function GraphView({ servers, connections }) {
|
|||||||
setEdges(initialEdgesData);
|
setEdges(initialEdgesData);
|
||||||
}, [initialEdgesData, setEdges]);
|
}, [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(
|
const nodeTypes = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -110,6 +122,8 @@ function GraphView({ servers, connections }) {
|
|||||||
background: '#fff',
|
background: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Точка входа соединений */}
|
||||||
|
<Handle type="target" position={Position.Left} style={{ background: countryColor }} />
|
||||||
<div className="px-2 py-1 d-flex align-items-center" style={{ background: '#f8fafc', borderBottom: '1px solid #eef2f7' }}>
|
<div className="px-2 py-1 d-flex align-items-center" style={{ background: '#f8fafc', borderBottom: '1px solid #eef2f7' }}>
|
||||||
<div className="d-flex align-items-center" style={{ gap: 6 }}>
|
<div className="d-flex align-items-center" style={{ gap: 6 }}>
|
||||||
<span style={{ fontSize: 16, lineHeight: '1' }}>{flag}</span>
|
<span style={{ fontSize: 16, lineHeight: '1' }}>{flag}</span>
|
||||||
@@ -132,6 +146,8 @@ function GraphView({ servers, connections }) {
|
|||||||
<div style={{ fontSize: 12, color: '#334155' }}>{s.ip}</div>
|
<div style={{ fontSize: 12, color: '#334155' }}>{s.ip}</div>
|
||||||
<div style={{ fontSize: 10, color: '#6b7280' }}>{s.provider}</div>
|
<div style={{ fontSize: 10, color: '#6b7280' }}>{s.provider}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Точка исхода соединений */}
|
||||||
|
<Handle type="source" position={Position.Right} style={{ background: countryColor }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -161,6 +177,23 @@ function GraphView({ servers, connections }) {
|
|||||||
return () => clearTimeout(t);
|
return () => clearTimeout(t);
|
||||||
}, [servers, connections]);
|
}, [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) {
|
if (!servers || servers.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-5">
|
<div className="text-center py-5">
|
||||||
@@ -173,7 +206,18 @@ function GraphView({ servers, connections }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} style={{ width: '100%', height: 500, border: '1px solid #e5e7eb', borderRadius: 8, overflow: 'hidden', position: 'relative' }}>
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: isFullscreen ? '100vh' : 500,
|
||||||
|
border: '1px solid #e5e7eb',
|
||||||
|
borderRadius: 8,
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
|
background: '#f8fafc',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
ref={flowRef}
|
ref={flowRef}
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
@@ -188,6 +232,7 @@ function GraphView({ servers, connections }) {
|
|||||||
markerEnd: { type: MarkerType.ArrowClosed },
|
markerEnd: { type: MarkerType.ArrowClosed },
|
||||||
}}
|
}}
|
||||||
onInit={onInit}
|
onInit={onInit}
|
||||||
|
style={{ width: '100%', height: '100%', background: '#f8fafc' }}
|
||||||
>
|
>
|
||||||
<Background variant="dots" gap={20} size={1} color="#e5e7eb" />
|
<Background variant="dots" gap={20} size={1} color="#e5e7eb" />
|
||||||
<MiniMap
|
<MiniMap
|
||||||
@@ -237,7 +282,7 @@ function GraphView({ servers, connections }) {
|
|||||||
}}
|
}}
|
||||||
title="Во весь экран"
|
title="Во весь экран"
|
||||||
>
|
>
|
||||||
{document.fullscreenElement ? <IconMinimize size={16} /> : <IconMaximize size={16} />}
|
{isFullscreen ? <IconMinimize size={16} /> : <IconMaximize size={16} />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -766,6 +766,12 @@ function ServerManager() {
|
|||||||
<GraphView
|
<GraphView
|
||||||
servers={servers.filter(s => (!graphCountryFilter || s.country === graphCountryFilter) && (!graphProviderFilter || s.provider === graphProviderFilter))}
|
servers={servers.filter(s => (!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' });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<div className="d-flex align-items-center gap-3 flex-wrap">
|
<div className="d-flex align-items-center gap-3 flex-wrap">
|
||||||
@@ -777,7 +783,7 @@ function ServerManager() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card w-100 mb-4">
|
<div id="add-connection-anchor" className="card w-100 mb-4">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<h3 className="card-title mb-0">Добавить связь между серверами</h3>
|
<h3 className="card-title mb-0">Добавить связь между серверами</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user