From f9cf1293f015502d06bf9a7e029de52ea4cdadeb Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 3 Oct 2025 19:48:11 +0000 Subject: [PATCH] fix: default node display to canonical name --- frontend-modern/src/utils/nodes.ts | 53 +++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/frontend-modern/src/utils/nodes.ts b/frontend-modern/src/utils/nodes.ts index 116329e59..c902a979d 100644 --- a/frontend-modern/src/utils/nodes.ts +++ b/frontend-modern/src/utils/nodes.ts @@ -1,19 +1,62 @@ import type { Node } from '@/types/api'; -type DisplayableNode = Pick & Partial>; +type DisplayableNode = Pick & + Partial>; + +const sanitize = (value: string): string => value.trim().toLowerCase().replace(/[^a-z0-9]/g, ''); + +const escapeRegExp = (value: string): string => value.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\$&'); + +const extractHostname = (value: string): string => { + if (!value) return ''; + const trimmed = value.trim(); + const withoutProtocol = trimmed.replace(/^[a-z]+:\/\//i, ''); + const [hostPart] = withoutProtocol.split('/'); + return hostPart.replace(/:\d+$/, ''); +}; export function getNodeDisplayName(node: T): string { + const nameRaw = typeof node.name === 'string' ? node.name.trim() : ''; + if (nameRaw) return nameRaw; + const display = typeof node.displayName === 'string' ? node.displayName.trim() : ''; if (display) return display; + const hostRaw = typeof node.host === 'string' ? node.host.trim() : ''; + const hostname = extractHostname(hostRaw); + if (hostname) return hostname; + const instance = typeof node.instance === 'string' ? node.instance.trim() : ''; if (instance) return instance; - return node.name; + return ''; } export function hasAlternateDisplayName(node: T): boolean { - const display = typeof node.displayName === 'string' ? node.displayName.trim() : ''; - if (!display) return false; - return display.toLowerCase() !== node.name.trim().toLowerCase(); + const displayRaw = typeof node.displayName === 'string' ? node.displayName.trim() : ''; + if (!displayRaw) return false; + + const nameRaw = typeof node.name === 'string' ? node.name.trim() : ''; + if (!nameRaw) return false; + + const displayLower = displayRaw.toLowerCase(); + const nameLower = nameRaw.toLowerCase(); + + if (displayLower === nameLower) return false; + + // Normalize values so cosmetic punctuation/domains do not trigger duplicates in the UI + const sanitizedDisplay = sanitize(displayRaw); + const sanitizedName = sanitize(nameRaw); + + if (sanitizedDisplay === sanitizedName) return false; + + // Catch cases where the raw display text already embeds the node name (e.g. "Friendly (node)") + const namePattern = new RegExp(`\\b${escapeRegExp(nameLower)}\\b`, 'i'); + if (namePattern.test(displayLower)) return false; + + const [firstLabel = ''] = displayLower.split('.'); + const sanitizedFirstLabel = sanitize(firstLabel); + if (sanitizedFirstLabel === sanitizedName) return false; + + return true; }