diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 730309156..686382a8c 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -588,6 +588,7 @@ class DeviceInterfaceTable(InterfaceTable): 'class': get_interface_row_class, 'data-name': lambda record: record.name, 'data-enabled': get_interface_state_attribute, + 'data-type': lambda record: record.type, } diff --git a/netbox/project-static/src/tables/interfaceTable.ts b/netbox/project-static/src/tables/interfaceTable.ts index d2b20f322..b5d82a322 100644 --- a/netbox/project-static/src/tables/interfaceTable.ts +++ b/netbox/project-static/src/tables/interfaceTable.ts @@ -1,6 +1,5 @@ import { getElements, replaceAll, findFirstAdjacent } from '../util'; -type InterfaceState = 'enabled' | 'disabled'; type ShowHide = 'show' | 'hide'; function isShowHide(value: unknown): value is ShowHide { @@ -27,54 +26,23 @@ class ButtonState { * Underlying Button DOM Element */ public button: HTMLButtonElement; - /** - * Table rows with `data-enabled` set to `"enabled"` - */ - private enabledRows: NodeListOf; - /** - * Table rows with `data-enabled` set to `"disabled"` - */ - private disabledRows: NodeListOf; - constructor(button: HTMLButtonElement, table: HTMLTableElement) { + /** + * Table rows provided in constructor + */ + private rows: NodeListOf; + + constructor(button: HTMLButtonElement, rows: NodeListOf) { this.button = button; - this.enabledRows = table.querySelectorAll('tr[data-enabled="enabled"]'); - this.disabledRows = table.querySelectorAll('tr[data-enabled="disabled"]'); + this.rows = rows; } /** - * This button's controlled type. For example, a button with the class `toggle-disabled` has - * directive 'disabled' because it controls the visibility of rows with - * `data-enabled="disabled"`. Likewise, `toggle-enabled` controls rows with - * `data-enabled="enabled"`. + * Remove visibility of button state rows. */ - private get directive(): InterfaceState { - if (this.button.classList.contains('toggle-disabled')) { - return 'disabled'; - } else if (this.button.classList.contains('toggle-enabled')) { - return 'enabled'; - } - // If this class has been instantiated but doesn't contain these classes, it's probably because - // the classes are missing in the HTML template. - console.warn(this.button); - throw new Error('Toggle button does not contain expected class'); - } - - /** - * Toggle visibility of rows with `data-enabled="enabled"`. - */ - private toggleEnabledRows(): void { - for (const row of this.enabledRows) { - row.classList.toggle('d-none'); - } - } - - /** - * Toggle visibility of rows with `data-enabled="disabled"`. - */ - private toggleDisabledRows(): void { - for (const row of this.disabledRows) { - row.classList.toggle('d-none'); + private hideRows(): void { + for (const row of this.rows) { + row.classList.add('d-none'); } } @@ -111,17 +79,6 @@ class ButtonState { } } - /** - * Toggle visibility for the rows this element controls. - */ - private toggleRows(): void { - if (this.directive === 'enabled') { - this.toggleEnabledRows(); - } else if (this.directive === 'disabled') { - this.toggleDisabledRows(); - } - } - /** * Toggle the DOM element's `data-state` attribute. */ @@ -139,17 +96,20 @@ class ButtonState { private toggle(): void { this.toggleState(); this.toggleButton(); - this.toggleRows(); } /** - * When the button is clicked, toggle all controlled elements. + * When the button is clicked, toggle all controlled elements and hide rows based on + * buttonstate. */ public handleClick(event: Event): void { const button = event.currentTarget as HTMLButtonElement; if (button.isEqualNode(this.button)) { this.toggle(); } + if (this.buttonState === 'hide') { + this.hideRows(); + } } } @@ -174,14 +134,25 @@ class TableState { // @ts-expect-error null handling is performed in the constructor private disabledButton: ButtonState; + /** + * Instance of ButtonState for the 'show/hide virtual rows' button. + */ + // @ts-expect-error null handling is performed in the constructor + private virtualButton: ButtonState; + /** * Underlying DOM Table Caption Element. */ private caption: Nullable = null; + /** + * All table rows in table + */ + private rows: NodeListOf; + constructor(table: HTMLTableElement) { this.table = table; - + this.rows = this.table.querySelectorAll('tr'); try { const toggleEnabledButton = findFirstAdjacent( this.table, @@ -191,6 +162,10 @@ class TableState { this.table, 'button.toggle-disabled', ); + const toggleVirtualButton = findFirstAdjacent( + this.table, + 'button.toggle-virtual', + ); const caption = this.table.querySelector('caption'); this.caption = caption; @@ -203,13 +178,28 @@ class TableState { throw new TableStateError("Table is missing a 'toggle-disabled' button.", table); } + if (toggleVirtualButton === null) { + throw new TableStateError("Table is missing a 'toggle-virtual' button.", table); + } + // Attach event listeners to the buttons elements. toggleEnabledButton.addEventListener('click', event => this.handleClick(event, this)); toggleDisabledButton.addEventListener('click', event => this.handleClick(event, this)); + toggleVirtualButton.addEventListener('click', event => this.handleClick(event, this)); // Instantiate ButtonState for each button for state management. - this.enabledButton = new ButtonState(toggleEnabledButton, this.table); - this.disabledButton = new ButtonState(toggleDisabledButton, this.table); + this.enabledButton = new ButtonState( + toggleEnabledButton, + table.querySelectorAll('tr[data-enabled="enabled"]'), + ); + this.disabledButton = new ButtonState( + toggleDisabledButton, + table.querySelectorAll('tr[data-enabled="disabled"]'), + ); + this.virtualButton = new ButtonState( + toggleVirtualButton, + table.querySelectorAll('tr[data-type="virtual"]'), + ); } catch (err) { if (err instanceof TableStateError) { // This class is useless for tables that don't have toggle buttons. @@ -261,22 +251,20 @@ class TableState { } /** - * When toggle buttons are clicked, pass the event to the relevant button's handler and update - * this instance's state. + * When toggle buttons are clicked, reapply visability all rows and + * pass the event to all button handlers * * @param event onClick event for toggle buttons. * @param instance Instance of TableState (`this` cannot be used since that's context-specific). */ public handleClick(event: Event, instance: TableState): void { - const button = event.currentTarget as HTMLButtonElement; - const enabled = button.isEqualNode(instance.enabledButton.button); - const disabled = button.isEqualNode(instance.disabledButton.button); - - if (enabled) { - instance.enabledButton.handleClick(event); - } else if (disabled) { - instance.disabledButton.handleClick(event); + for (const row of this.rows) { + row.classList.remove('d-none'); } + + instance.enabledButton.handleClick(event); + instance.disabledButton.handleClick(event); + instance.virtualButton.handleClick(event); instance.toggleCaption(); } } diff --git a/netbox/templates/dcim/device/inc/interface_table_controls.html b/netbox/templates/dcim/device/inc/interface_table_controls.html index 14e552439..2b082cfe6 100644 --- a/netbox/templates/dcim/device/inc/interface_table_controls.html +++ b/netbox/templates/dcim/device/inc/interface_table_controls.html @@ -7,5 +7,6 @@ {% endblock extra_table_controls %}