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

This commit is contained in:
2025-12-01 16:36:32 +07:00
parent e73d5e2721
commit 840865e074
2 changed files with 142 additions and 18 deletions
+28 -5
View File
@@ -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 }} />