feat: Enhance GraphView and ServerManager components by adding node highlighting and a modal for quick connection creation, improving user interaction and visual feedback in the graph interface.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m37s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m37s
This commit is contained in:
@@ -12,12 +12,14 @@ import {
|
||||
} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { IconZoomIn, IconZoomOut, IconMaximize, IconMinimize } from '@tabler/icons-react';
|
||||
import Tooltip from './components/Tooltip.jsx';
|
||||
|
||||
function GraphView({ servers, connections, onCreateConnection }) {
|
||||
const flowRef = useRef(null);
|
||||
const instanceRef = useRef(null);
|
||||
const containerRef = useRef(null);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [highlightedNodeId, setHighlightedNodeId] = useState(null);
|
||||
|
||||
const getTunnelStyle = useCallback((tunnelType) => {
|
||||
const map = {
|
||||
@@ -68,12 +70,25 @@ function GraphView({ servers, connections, onCreateConnection }) {
|
||||
const initialEdgesData = useMemo(() => {
|
||||
return (connections || []).map((c, idx) => {
|
||||
const style = getTunnelStyle(c.tunnelType);
|
||||
const baseLabel = c.tunnelType || 'TUNNEL';
|
||||
const ipLabel = c.ipA && c.ipB ? `${c.ipA} ⇄ ${c.ipB}` : '';
|
||||
return {
|
||||
id: `${c.from}-${c.to}-${idx}`,
|
||||
source: String(c.from),
|
||||
target: String(c.to),
|
||||
label: c.ipA && c.ipB ? `${c.tunnelType} ${c.ipA} ⇄ ${c.ipB}` : c.tunnelType,
|
||||
style: { stroke: style.color, strokeWidth: style.width, strokeDasharray: style.dash },
|
||||
label: ipLabel ? `${baseLabel} · ${ipLabel}` : baseLabel,
|
||||
style: {
|
||||
stroke: style.color,
|
||||
strokeWidth: style.width,
|
||||
strokeDasharray: style.dash,
|
||||
},
|
||||
labelBgPadding: [6, 4],
|
||||
labelBgBorderRadius: 999,
|
||||
labelStyle: {
|
||||
fontSize: 11,
|
||||
fontWeight: 500,
|
||||
fill: '#0f172a',
|
||||
},
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: style.color, width: 20, height: 20 },
|
||||
animated: false,
|
||||
};
|
||||
@@ -99,7 +114,7 @@ function GraphView({ servers, connections, onCreateConnection }) {
|
||||
|
||||
const nodeTypes = useMemo(
|
||||
() => ({
|
||||
server: ({ data }) => {
|
||||
server: ({ id, data }) => {
|
||||
const s = data.server || {};
|
||||
const countryColor = getCountryColor(s.country);
|
||||
const getFlagEmoji = (code) => {
|
||||
@@ -111,16 +126,24 @@ function GraphView({ servers, connections, onCreateConnection }) {
|
||||
return cc.replace(/./g, (ch) => String.fromCodePoint(127397 + ch.charCodeAt()));
|
||||
};
|
||||
const flag = getFlagEmoji(s.country);
|
||||
const isHighlighted = highlightedNodeId === id;
|
||||
return (
|
||||
<div
|
||||
className="card shadow-sm"
|
||||
className={`card shadow-sm${isHighlighted ? ' border-primary' : ''}`}
|
||||
style={{
|
||||
width: 200,
|
||||
borderRadius: 12,
|
||||
border: `2px solid ${countryColor}`,
|
||||
border: `2px solid ${isHighlighted ? '#206bc4' : countryColor}`,
|
||||
overflow: 'hidden',
|
||||
background: '#fff',
|
||||
boxShadow: isHighlighted
|
||||
? '0 0 0 1px rgba(32,107,196,0.15), 0 10px 24px rgba(15,23,42,0.25)'
|
||||
: '0 4px 12px rgba(15,23,42,0.12)',
|
||||
transform: isHighlighted ? 'translateY(-2px)' : 'translateY(0)',
|
||||
transition: 'box-shadow 120ms ease-out, transform 120ms ease-out, border-color 120ms ease-out',
|
||||
}}
|
||||
onMouseEnter={() => setHighlightedNodeId(id)}
|
||||
onMouseLeave={() => setHighlightedNodeId((prev) => (prev === id ? null : prev))}
|
||||
>
|
||||
{/* Точка входа соединений */}
|
||||
<Handle type="target" position={Position.Left} style={{ background: countryColor }} />
|
||||
|
||||
Reference in New Issue
Block a user