fix: handle multi-IP and add tests for clickable IP link
Follow-up to #78: - Use primaryIp() so href targets the first IP when data.ip is comma-separated (e.g. "192.168.1.1, 2001:db8::1") - Add primaryIp() helper to maskIp.ts - Add 4 tests covering single IP link, absent IP, multi-IP href, multi-IP display text
This commit is contained in:
@@ -5,6 +5,7 @@ import { Input } from '@/components/ui/input'
|
|||||||
import { useCanvasStore } from '@/stores/canvasStore'
|
import { useCanvasStore } from '@/stores/canvasStore'
|
||||||
import { NODE_TYPE_LABELS, STATUS_COLORS, type ServiceInfo, type NodeData, type NodeProperty } from '@/types'
|
import { NODE_TYPE_LABELS, STATUS_COLORS, type ServiceInfo, type NodeData, type NodeProperty } from '@/types'
|
||||||
import { getServiceUrl } from '@/utils/serviceUrl'
|
import { getServiceUrl } from '@/utils/serviceUrl'
|
||||||
|
import { primaryIp } from '@/utils/maskIp'
|
||||||
import { PROPERTY_ICONS, PROPERTY_ICON_NAMES, resolvePropertyIcon } from '@/utils/propertyIcons'
|
import { PROPERTY_ICONS, PROPERTY_ICON_NAMES, resolvePropertyIcon } from '@/utils/propertyIcons'
|
||||||
import type { Node } from '@xyflow/react'
|
import type { Node } from '@xyflow/react'
|
||||||
|
|
||||||
@@ -205,7 +206,7 @@ export function DetailPanel({ onEdit }: DetailPanelProps) {
|
|||||||
{data.ip && (
|
{data.ip && (
|
||||||
<div className="flex justify-between gap-2 items-baseline">
|
<div className="flex justify-between gap-2 items-baseline">
|
||||||
<span className="text-muted-foreground text-xs shrink-0">IP Address</span>
|
<span className="text-muted-foreground text-xs shrink-0">IP Address</span>
|
||||||
<a href={`http://${data.ip}`} target="_blank" rel="noopener noreferrer" className="text-xs font-mono text-[#00d4ff] hover:underline truncate flex items-center gap-1" title={data.ip}>
|
<a href={`http://${primaryIp(data.ip)}`} target="_blank" rel="noopener noreferrer" className="text-xs font-mono text-[#00d4ff] hover:underline truncate flex items-center gap-1" title={data.ip}>
|
||||||
{data.ip}<ExternalLink size={10} className="shrink-0" />
|
{data.ip}<ExternalLink size={10} className="shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -385,4 +385,34 @@ describe('DetailPanel', () => {
|
|||||||
expect(screen.getByText('nginx')).toBeDefined()
|
expect(screen.getByText('nginx')).toBeDefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('IP Address — clickable link', () => {
|
||||||
|
it('renders a link for a single IP', () => {
|
||||||
|
setupStore({ ip: '192.168.1.10' })
|
||||||
|
render(<DetailPanel onEdit={vi.fn()} />)
|
||||||
|
const link = screen.getByRole('link', { name: /192\.168\.1\.10/ })
|
||||||
|
expect(link).toBeDefined()
|
||||||
|
expect(link.getAttribute('href')).toBe('http://192.168.1.10')
|
||||||
|
expect(link.getAttribute('target')).toBe('_blank')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders no IP link when ip is absent', () => {
|
||||||
|
setupStore({ ip: undefined })
|
||||||
|
render(<DetailPanel onEdit={vi.fn()} />)
|
||||||
|
expect(screen.queryByText('IP Address')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses primary IP as href for comma-separated IPs', () => {
|
||||||
|
setupStore({ ip: '192.168.1.10, 192.168.1.11' })
|
||||||
|
render(<DetailPanel onEdit={vi.fn()} />)
|
||||||
|
const link = screen.getByRole('link', { name: /192\.168\.1\.10/ })
|
||||||
|
expect(link.getAttribute('href')).toBe('http://192.168.1.10')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays full comma-separated IP string as link text', () => {
|
||||||
|
setupStore({ ip: '192.168.1.10, 192.168.1.11' })
|
||||||
|
render(<DetailPanel onEdit={vi.fn()} />)
|
||||||
|
expect(screen.getByText(/192\.168\.1\.10, 192\.168\.1\.11/)).toBeDefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,3 +8,8 @@ export function maskIp(ip: string): string {
|
|||||||
if (parts.length === 4) return `${parts[0]}.${parts[1]}.XX.XX`
|
if (parts.length === 4) return `${parts[0]}.${parts[1]}.XX.XX`
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function primaryIp(ip: string): string {
|
||||||
|
if (!ip?.trim()) return ''
|
||||||
|
return ip.split(',')[0].trim()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user