refactor(ip-parsing): centralize multi-delim splitting in splitIps

Extend splitIps to accept comma/semicolon/newline delimiters with dedupe,
and reuse it in DetailPanel for both the IP list and the service-URL host.
Fixes ServiceBadge links pointing at concatenated string when IPs were
entered with ';' or newline separators. Adds tests for new delimiters.
This commit is contained in:
Pouzor
2026-05-15 13:36:15 +02:00
parent 9cd93ef294
commit ea451885af
4 changed files with 43 additions and 13 deletions
@@ -6,7 +6,7 @@ import { Input } from '@/components/ui/input'
import { useCanvasStore } from '@/stores/canvasStore'
import { NODE_TYPE_LABELS, STATUS_COLORS, type ServiceInfo, type NodeData, type NodeProperty } from '@/types'
import { getServiceUrl } from '@/utils/serviceUrl'
import { primaryIp } from '@/utils/maskIp'
import { splitIps } from '@/utils/maskIp'
import { PROPERTY_ICONS, PROPERTY_ICON_NAMES, resolvePropertyIcon } from '@/utils/propertyIcons'
import type { Node } from '@xyflow/react'
@@ -85,14 +85,8 @@ export function DetailPanel({ onEdit }: DetailPanelProps) {
const { data } = node
const services = data.services ?? []
const statusColor = STATUS_COLORS[data.status]
const host = data.ip ? primaryIp(data.ip) : data.hostname
const ipAddresses = data.ip
? data.ip
.split(/[\n,;]+/)
.map((ip) => primaryIp(ip.trim()))
.filter(Boolean)
.filter((ip, index, all) => all.indexOf(ip) === index)
: []
const ipAddresses = data.ip ? splitIps(data.ip) : []
const host = ipAddresses[0] ?? data.hostname
const handleDelete = () => {
if (confirm(`Delete "${data.label}"?`)) {
@@ -446,6 +446,20 @@ describe('DetailPanel', () => {
expect(screen.getByRole('link', { name: /192\.168\.1\.11/ })).toBeDefined()
expect(screen.queryByText(',')).toBeNull()
})
it('renders separate links for semicolon-separated IPs', () => {
setupStore({ ip: '192.168.1.10; 192.168.1.11' })
render(<DetailPanel onEdit={vi.fn()} />)
expect(screen.getByRole('link', { name: /192\.168\.1\.10/ }).getAttribute('href')).toBe('http://192.168.1.10')
expect(screen.getByRole('link', { name: /192\.168\.1\.11/ }).getAttribute('href')).toBe('http://192.168.1.11')
})
it('renders separate links for newline-separated IPs', () => {
setupStore({ ip: '192.168.1.10\n192.168.1.11' })
render(<DetailPanel onEdit={vi.fn()} />)
expect(screen.getByRole('link', { name: /192\.168\.1\.10/ }).getAttribute('href')).toBe('http://192.168.1.10')
expect(screen.getByRole('link', { name: /192\.168\.1\.11/ }).getAttribute('href')).toBe('http://192.168.1.11')
})
})
describe('ServiceBadge rendering', () => {