diff --git a/src/lib/components/NetworkDiagram.svelte b/src/lib/components/NetworkDiagram.svelte index fd46bf5..0f9998f 100644 --- a/src/lib/components/NetworkDiagram.svelte +++ b/src/lib/components/NetworkDiagram.svelte @@ -15,6 +15,7 @@ deletePort, renameOwner, renamePort, + setPortCable, setPortConnection, type OwnerKind, withReconciledConnections @@ -25,7 +26,7 @@ import { validateNetworkData, type ValidationIssue } from '$lib/data/networkSchema'; import transformNetworkDataToGraph from '$lib/graph/transformNetworkData'; import { computeSearchMatches } from '$lib/graph/searchHighlight'; - import type { GraphNodeData, GraphTransformResult } from '$lib/graph/types'; + import type { GraphEdgeData, GraphNodeData, GraphTransformResult } from '$lib/graph/types'; import { themeMode, type ThemeMode } from '$lib/stores/theme'; import type { NetworkData, Port } from '$lib/types'; import Modal from './Modal.svelte'; @@ -571,6 +572,12 @@ color: theme === 'dark' ? '#94a3b8' : '#64748b' } }, + { + selector: 'edge[cableColor]', + style: { + 'line-color': 'data(cableColor)' + } + }, { selector: 'node[vlanColor]', style: { @@ -1417,6 +1424,31 @@ updateDevicePortConnection(deviceIndex, portIndex, next); } + function updatePortCableField( + kind: OwnerKind, + ownerName: string, + port: Port, + field: 'type' | 'color' | 'lengthM', + value: string + ) { + const current: NonNullable['cable']> = { + ...(port.connectedTo?.cable ?? {}) + }; + if (field === 'lengthM') { + const parsed = Number(value); + if (value.trim() && Number.isFinite(parsed) && parsed >= 0) { + current.lengthM = parsed; + } else { + delete current.lengthM; + } + } else if (value.trim()) { + current[field] = value.trim(); + } else { + delete current[field]; + } + applyDraft(setPortCable(networkData, kind, ownerName, port.portName, current)); + } + function deleteMachine(machineIndex: number) { const machine = networkData.machines[machineIndex]; if (!machine) { @@ -1734,6 +1766,33 @@ tooltip.visible = false; }); + cy.on('mouseover', 'edge[kind = "physical"]', (event) => { + const edge = event.target; + const data = edge.data() as GraphEdgeData; + const parts = [ + `${data.sourcePort ?? '?'} ↔ ${data.targetPort ?? '?'}`, + ...(data.speedGbps ? [`${data.speedGbps}GbE`] : []), + ...(data.cableType ? [data.cableType] : []), + ...(data.cableColorName ? [data.cableColorName] : []), + ...(typeof data.cableLengthM === 'number' ? [`${data.cableLengthM}m`] : []) + ]; + const midpoint = edge.renderedMidpoint(); + const clampedPosition = clampTooltipPosition( + midpoint.x + tooltipOffset.x, + midpoint.y + tooltipOffset.y + ); + tooltip = { + visible: true, + text: parts.join(' · '), + x: clampedPosition.x, + y: clampedPosition.y + }; + }); + + cy.on('mouseout', 'edge', () => { + tooltip.visible = false; + }); + cy.on('pan zoom', () => { tooltip.visible = false; }); @@ -2150,6 +2209,27 @@

No icons match your search.

{/if} + + + + + + + + + + + + + + + + + + + + + {#if selectedTarget?.type === 'machine' && selectedMachine}
{#if selectedMachineHostId && selectedMachineVmCount > 0} @@ -2452,6 +2532,57 @@ )} /> + {#if port.connectedTo} + + + + {/if}