diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 82770e856..70a9a0dac 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#7300](https://github.com/netbox-community/netbox/issues/7300) - Fix incorrect Device LLDP interface row coloring * [#7495](https://github.com/netbox-community/netbox/issues/7495) - Fix navigation UI issue that caused improper element overlap * [#7529](https://github.com/netbox-community/netbox/issues/7529) - Restore horizontal scrolling for tables in narrow viewports * [#7534](https://github.com/netbox-community/netbox/issues/7534) - Avoid exception when utilizing "create and add another" twice in succession diff --git a/netbox/project-static/dist/lldp.js b/netbox/project-static/dist/lldp.js index 7fac1012a..2b3934742 100644 Binary files a/netbox/project-static/dist/lldp.js and b/netbox/project-static/dist/lldp.js differ diff --git a/netbox/project-static/dist/lldp.js.map b/netbox/project-static/dist/lldp.js.map index 911cd77c3..25d5fc87e 100644 Binary files a/netbox/project-static/dist/lldp.js.map and b/netbox/project-static/dist/lldp.js.map differ diff --git a/netbox/project-static/src/device/lldp.ts b/netbox/project-static/src/device/lldp.ts index 6baaa9b38..ebf71138c 100644 --- a/netbox/project-static/src/device/lldp.ts +++ b/netbox/project-static/src/device/lldp.ts @@ -1,6 +1,17 @@ import { createToast } from '../bs'; import { getNetboxData, apiGetBase, hasError, isTruthy, toggleLoader } from '../util'; +// Match an interface name that begins with a capital letter and is followed by at least one other +// alphabetic character, and ends with a forward-slash-separated numeric sequence such as 0/1/2. +const CISCO_IOS_PATTERN = new RegExp(/^([A-Z][A-Za-z]+)[^0-9]*([0-9/]+)$/); + +// Mapping of overrides to default Cisco IOS interface alias behavior (default behavior is to use +// the first two characters). +const CISCO_IOS_OVERRIDES = new Map([ + // Cisco IOS abbreviates 25G (TwentyFiveGigE) interfaces as 'Twe'. + ['TwentyFiveGigE', 'Twe'], +]); + /** * Get an attribute from a row's cell. * @@ -12,6 +23,40 @@ function getData(row: HTMLTableRowElement, query: string, attr: string): string return row.querySelector(query)?.getAttribute(attr) ?? null; } +/** + * Get preconfigured alias for given interface. Primarily for matching long-form Cisco IOS + * interface names with short-form Cisco IOS interface names. For example, `GigabitEthernet0/1/2` + * would become `Gi0/1/2`. + * + * This should probably be replaced with something in the primary application (Django), such as + * a database field attached to given interface types. However, this is a temporary measure to + * replace the functionality of this one-liner: + * + * @see https://github.com/netbox-community/netbox/blob/9cc4992fad2fe04ef0211d998c517414e8871d8c/netbox/templates/dcim/device/lldp_neighbors.html#L69 + * + * @param name Long-form/original interface name. + */ +function getInterfaceAlias(name: string | null): string | null { + if (name === null) { + return name; + } + if (name.match(CISCO_IOS_PATTERN)) { + // Extract the base name and numeric portions of the interface. For example, an input interface + // of `GigabitEthernet0/0/1` would result in an array of `['GigabitEthernet', '0/0/1']`. + const [base, numeric] = (name.match(CISCO_IOS_PATTERN) ?? []).slice(1, 3); + + if (isTruthy(base) && isTruthy(numeric)) { + // Check the override map and use its value if the base name is present in the map. + // Otherwise, use the first two characters of the base name. For example, + // `GigabitEthernet0/0/1` would become `Gi0/0/1`, but `TwentyFiveGigE0/0/1` would become + // `Twe0/0/1`. + const aliasBase = CISCO_IOS_OVERRIDES.get(base) || base.slice(0, 2); + return `${aliasBase}${numeric}`; + } + } + return name; +} + /** * Update row styles based on LLDP neighbor data. */ @@ -23,38 +68,41 @@ function updateRowStyle(data: LLDPNeighborDetail) { if (row !== null) { for (const neighbor of neighbors) { - const cellDevice = row.querySelector('td.device'); - const cellInterface = row.querySelector('td.interface'); - const cDevice = getData(row, 'td.configured_device', 'data'); - const cChassis = getData(row, 'td.configured_chassis', 'data-chassis'); - const cInterface = getData(row, 'td.configured_interface', 'data'); + const deviceCell = row.querySelector('td.device'); + const interfaceCell = row.querySelector('td.interface'); + const configuredDevice = getData(row, 'td.configured_device', 'data'); + const configuredChassis = getData(row, 'td.configured_chassis', 'data-chassis'); + const configuredIface = getData(row, 'td.configured_interface', 'data'); - let cInterfaceShort = null; - if (isTruthy(cInterface)) { - cInterfaceShort = cInterface.replace(/^([A-Z][a-z])[^0-9]*([0-9/]+)$/, '$1$2'); + const interfaceAlias = getInterfaceAlias(configuredIface); + + const remoteName = neighbor.remote_system_name ?? ''; + const remotePort = neighbor.remote_port ?? ''; + const [neighborDevice] = remoteName.split('.'); + const [neighborIface] = remotePort.split('.'); + + if (deviceCell !== null) { + deviceCell.innerText = neighborDevice; } - const nHost = neighbor.remote_system_name ?? ''; - const nPort = neighbor.remote_port ?? ''; - const [nDevice] = nHost.split('.'); - const [nInterface] = nPort.split('.'); - - if (cellDevice !== null) { - cellDevice.innerText = nDevice; + if (interfaceCell !== null) { + interfaceCell.innerText = neighborIface; } - if (cellInterface !== null) { - cellInterface.innerText = nInterface; - } + // Interface has an LLDP neighbor, but the neighbor is not configured in NetBox. + const nonConfiguredDevice = !isTruthy(configuredDevice) && isTruthy(neighborDevice); - if (!isTruthy(cDevice) && isTruthy(nDevice)) { + // NetBox device or chassis matches LLDP neighbor. + const validNode = + configuredDevice === neighborDevice || configuredChassis === neighborDevice; + + // NetBox configured interface matches LLDP neighbor interface. + const validInterface = + configuredIface === neighborIface || interfaceAlias === neighborIface; + + if (nonConfiguredDevice) { row.classList.add('info'); - } else if ( - (cDevice === nDevice || cChassis === nDevice) && - cInterfaceShort === nInterface - ) { - row.classList.add('success'); - } else if (cDevice === nDevice || cChassis === nDevice) { + } else if (validNode && validInterface) { row.classList.add('success'); } else { row.classList.add('danger');