feat: Implement server connections API and enhance GraphView with custom edge styling for improved visualization and user interaction in the topology graph.
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m23s

This commit is contained in:
2025-12-01 16:49:00 +07:00
parent 840865e074
commit 007c7514e1
4 changed files with 122 additions and 49 deletions
+53 -43
View File
@@ -77,6 +77,7 @@ function GraphView({ servers, connections, onCreateConnection }) {
source: String(c.from),
target: String(c.to),
label: ipLabel ? `${baseLabel} · ${ipLabel}` : baseLabel,
type: 'smoothstep',
style: {
stroke: style.color,
strokeWidth: style.width,
@@ -89,7 +90,9 @@ function GraphView({ servers, connections, onCreateConnection }) {
fontWeight: 500,
fill: '#0f172a',
},
markerEnd: { type: MarkerType.ArrowClosed, color: style.color, width: 20, height: 20 },
className: c.tunnelType ? `edge-tunnel-${String(c.tunnelType).toLowerCase()}` : 'edge-tunnel-default',
markerStart: { type: MarkerType.ArrowClosed, color: style.color, width: 16, height: 16 },
markerEnd: { type: MarkerType.ArrowClosed, color: style.color, width: 16, height: 16 },
animated: false,
};
});
@@ -251,8 +254,7 @@ function GraphView({ servers, connections, onCreateConnection }) {
nodeTypes={nodeTypes}
fitView
defaultEdgeOptions={{
type: 'default',
markerEnd: { type: MarkerType.ArrowClosed },
type: 'smoothstep',
}}
onInit={onInit}
style={{ width: '100%', height: '100%', background: '#f8fafc' }}
@@ -267,46 +269,54 @@ function GraphView({ servers, connections, onCreateConnection }) {
{/* Своя панель управления: -, +, Fit, Fullscreen */}
<div className="position-absolute" style={{ right: 10, top: 10, zIndex: 10, pointerEvents: 'auto' }}>
<div className="btn-group btn-group-sm">
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => instanceRef.current?.zoomOut?.()}
title="Уменьшить"
>
<IconZoomOut size={16} />
</button>
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => instanceRef.current?.zoomIn?.()}
title="Увеличить"
>
<IconZoomIn size={16} />
</button>
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => instanceRef.current?.fitView?.({ padding: 0.2 })}
title="Подогнать к окну"
>
Fit
</button>
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => {
const el = containerRef.current;
if (!el) return;
if (!document.fullscreenElement) {
el.requestFullscreen?.();
} else {
document.exitFullscreen?.();
}
}}
title="Во весь экран"
>
{isFullscreen ? <IconMinimize size={16} /> : <IconMaximize size={16} />}
</button>
<Tooltip content="Уменьшить масштаб" position="left">
<button
type="button"
className="btn btn-outline-secondary btn-icon"
onClick={() => instanceRef.current?.zoomOut?.()}
aria-label="Уменьшить масштаб"
>
<IconZoomOut size={16} />
</button>
</Tooltip>
<Tooltip content="Увеличить масштаб" position="left">
<button
type="button"
className="btn btn-outline-secondary btn-icon"
onClick={() => instanceRef.current?.zoomIn?.()}
aria-label="Увеличить масштаб"
>
<IconZoomIn size={16} />
</button>
</Tooltip>
<Tooltip content="Подогнать граф к окну" position="left">
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => instanceRef.current?.fitView?.({ padding: 0.2 })}
aria-label="Подогнать граф к окну"
>
Fit
</button>
</Tooltip>
<Tooltip content={isFullscreen ? 'Выйти из полноэкранного режима' : 'Во весь экран'} position="left">
<button
type="button"
className="btn btn-outline-secondary btn-icon"
onClick={() => {
const el = containerRef.current;
if (!el) return;
if (!document.fullscreenElement) {
el.requestFullscreen?.();
} else {
document.exitFullscreen?.();
}
}}
aria-label={isFullscreen ? 'Выйти из полноэкранного режима' : 'Во весь экран'}
>
{isFullscreen ? <IconMinimize size={16} /> : <IconMaximize size={16} />}
</button>
</Tooltip>
</div>
</div>
</div>