Skip to content

Commit

Permalink
Fixes #7300: Fix incorrect Device LLDP interface row coloring & impro…
Browse files Browse the repository at this point in the history
…ve related JS
  • Loading branch information
thatmattlove committed Oct 16, 2021
1 parent f1f0d9c commit 84c14aa
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 28 deletions.
1 change: 1 addition & 0 deletions docs/release-notes/version-3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions netbox/project-static/dist/lldp.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion netbox/project-static/dist/lldp.js.map

Large diffs are not rendered by default.

98 changes: 73 additions & 25 deletions netbox/project-static/src/device/lldp.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>([
// Cisco IOS abbreviates 25G (TwentyFiveGigE) interfaces as 'Twe'.
['TwentyFiveGigE', 'Twe'],
]);

/**
* Get an attribute from a row's cell.
*
Expand All @@ -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.
*/
Expand All @@ -23,38 +68,41 @@ function updateRowStyle(data: LLDPNeighborDetail) {

if (row !== null) {
for (const neighbor of neighbors) {
const cellDevice = row.querySelector<HTMLTableCellElement>('td.device');
const cellInterface = row.querySelector<HTMLTableCellElement>('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');

let cInterfaceShort = null;
if (isTruthy(cInterface)) {
cInterfaceShort = cInterface.replace(/^([A-Z][a-z])[^0-9]*([0-9/]+)$/, '$1$2');
}
const deviceCell = row.querySelector<HTMLTableCellElement>('td.device');
const interfaceCell = row.querySelector<HTMLTableCellElement>('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');

const interfaceAlias = getInterfaceAlias(configuredIface);

const nHost = neighbor.remote_system_name ?? '';
const nPort = neighbor.remote_port ?? '';
const [nDevice] = nHost.split('.');
const [nInterface] = nPort.split('.');
const remoteName = neighbor.remote_system_name ?? '';
const remotePort = neighbor.remote_port ?? '';
const [neighborDevice] = remoteName.split('.');
const [neighborIface] = remotePort.split('.');

if (cellDevice !== null) {
cellDevice.innerText = nDevice;
if (deviceCell !== null) {
deviceCell.innerText = neighborDevice;
}

if (cellInterface !== null) {
cellInterface.innerText = nInterface;
if (interfaceCell !== null) {
interfaceCell.innerText = neighborIface;
}

if (!isTruthy(cDevice) && isTruthy(nDevice)) {
// Interface has an LLDP neighbor, but the neighbor is not configured in NetBox.
const nonConfiguredDevice = !isTruthy(configuredDevice) && isTruthy(neighborDevice);

// 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');
Expand Down

0 comments on commit 84c14aa

Please sign in to comment.