diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 4a6dba734..b041c7ff4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.0.11 + placeholder: v3.0.12 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 4c3ab0277..0852b4f9b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.0.11 + placeholder: v3.0.12 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index f76869f6e..51ad02395 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -1,5 +1,22 @@ # NetBox v3.0 +## v3.0.12 (2021-12-06) + +### Enhancements + +* [#7751](https://github.com/netbox-community/netbox/issues/7751) - Get API user from LDAP only when `FIND_GROUP_PERMS` is enabled +* [#7885](https://github.com/netbox-community/netbox/issues/7885) - Linkify VLAN name in VLANs table +* [#7892](https://github.com/netbox-community/netbox/issues/7892) - Add L22-30 power port & outlet types +* [#7932](https://github.com/netbox-community/netbox/issues/7932) - Improve performance of the "quick find" function +* [#7941](https://github.com/netbox-community/netbox/issues/7941) - Add multi-standard ITA power outlet type + +### Bug Fixes + +* [#7823](https://github.com/netbox-community/netbox/issues/7823) - Fix issue where `return_url` is not honored when 'Save & Continue' button is present +* [#7981](https://github.com/netbox-community/netbox/issues/7981) - Fix Markdown sanitization regex + +--- + ## v3.0.11 (2021-11-24) ### Enhancements diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 36eb24c96..d77a51c05 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -312,6 +312,7 @@ class PowerPortTypeChoices(ChoiceSet): TYPE_NEMA_L1560P = 'nema-l15-60p' TYPE_NEMA_L2120P = 'nema-l21-20p' TYPE_NEMA_L2130P = 'nema-l21-30p' + TYPE_NEMA_L2230P = 'nema-l22-30p' # California style TYPE_CS6361C = 'cs6361c' TYPE_CS6365C = 'cs6365c' @@ -417,6 +418,7 @@ class PowerPortTypeChoices(ChoiceSet): (TYPE_NEMA_L1560P, 'NEMA L15-60P'), (TYPE_NEMA_L2120P, 'NEMA L21-20P'), (TYPE_NEMA_L2130P, 'NEMA L21-30P'), + (TYPE_NEMA_L2230P, 'NEMA L22-30P'), )), ('California Style', ( (TYPE_CS6361C, 'CS6361C'), @@ -533,6 +535,7 @@ class PowerOutletTypeChoices(ChoiceSet): TYPE_NEMA_L1560R = 'nema-l15-60r' TYPE_NEMA_L2120R = 'nema-l21-20r' TYPE_NEMA_L2130R = 'nema-l21-30r' + TYPE_NEMA_L2230R = 'nema-l22-30r' # California style TYPE_CS6360C = 'CS6360C' TYPE_CS6364C = 'CS6364C' @@ -552,6 +555,7 @@ class PowerOutletTypeChoices(ChoiceSet): TYPE_ITA_M = 'ita-m' TYPE_ITA_N = 'ita-n' TYPE_ITA_O = 'ita-o' + TYPE_ITA_MULTISTANDARD = 'ita-multistandard' # USB TYPE_USB_A = 'usb-a' TYPE_USB_MICROB = 'usb-micro-b' @@ -630,6 +634,7 @@ class PowerOutletTypeChoices(ChoiceSet): (TYPE_NEMA_L1560R, 'NEMA L15-60R'), (TYPE_NEMA_L2120R, 'NEMA L21-20R'), (TYPE_NEMA_L2130R, 'NEMA L21-30R'), + (TYPE_NEMA_L2230R, 'NEMA L22-30R'), )), ('California Style', ( (TYPE_CS6360C, 'CS6360C'), @@ -651,6 +656,7 @@ class PowerOutletTypeChoices(ChoiceSet): (TYPE_ITA_M, 'ITA Type M (BS 546)'), (TYPE_ITA_N, 'ITA Type N'), (TYPE_ITA_O, 'ITA Type O'), + (TYPE_ITA_MULTISTANDARD, 'ITA Multistandard'), )), ('USB', ( (TYPE_USB_A, 'USB Type A'), diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index ffa6c5f40..a9e3dd48b 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -95,6 +95,9 @@ class VLANTable(BaseTable): template_code=VLAN_LINK, verbose_name='VID' ) + name = tables.Column( + linkify=True + ) site = tables.Column( linkify=True ) diff --git a/netbox/netbox/api/authentication.py b/netbox/netbox/api/authentication.py index 7f8bee318..5e177bfcb 100644 --- a/netbox/netbox/api/authentication.py +++ b/netbox/netbox/api/authentication.py @@ -29,10 +29,13 @@ class TokenAuthentication(authentication.TokenAuthentication): if settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend': from netbox.authentication import LDAPBackend ldap_backend = LDAPBackend() - user = ldap_backend.populate_user(token.user.username) - # If the user is found in the LDAP directory use it, if not fallback to the local user - if user: - return user, token + + # Load from LDAP if FIND_GROUP_PERMS is active + if ldap_backend.settings.FIND_GROUP_PERMS: + user = ldap_backend.populate_user(token.user.username) + # If the user is found in the LDAP directory use it, if not fallback to the local user + if user: + return user, token return token.user, token diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 8538c1d36..64869fab0 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -17,7 +17,7 @@ from django.core.validators import URLValidator # Environment setup # -VERSION = '3.0.11' +VERSION = '3.0.12' # Hostname HOSTNAME = platform.node() diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 6a60ff56d..c4a8e802d 100644 Binary files a/netbox/project-static/dist/netbox.js and b/netbox/project-static/dist/netbox.js differ diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index ba7d8cd2f..b95e6ba02 100644 Binary files a/netbox/project-static/dist/netbox.js.map and b/netbox/project-static/dist/netbox.js.map differ diff --git a/netbox/project-static/src/forms/elements.ts b/netbox/project-static/src/forms/elements.ts index 978c25e10..5cb17f5c7 100644 --- a/netbox/project-static/src/forms/elements.ts +++ b/netbox/project-static/src/forms/elements.ts @@ -1,4 +1,32 @@ -import { getElements, scrollTo } from '../util'; +import { getElements, scrollTo, isTruthy } from '../util'; + +/** + * When editing an object, it is sometimes desirable to customize the form action *without* + * overriding the form's `submit` event. For example, the 'Save & Continue' button. We don't want + * to use the `formaction` attribute on that element because it will be included on the form even + * if the button isn't clicked. + * + * @example + * ```html + * + * ``` + * + * @param event Click event. + */ +function handleSubmitWithReturnUrl(event: MouseEvent): void { + const element = event.target as HTMLElement; + if (element.tagName === 'BUTTON') { + const button = element as HTMLButtonElement; + const action = button.getAttribute('return-url'); + const form = button.form; + if (form !== null && isTruthy(action)) { + form.action = action; + form.submit(); + } + } +} function handleFormSubmit(event: Event, form: HTMLFormElement): void { // Track the names of each invalid field. @@ -38,6 +66,15 @@ function handleFormSubmit(event: Event, form: HTMLFormElement): void { } } +/** + * Attach event listeners to form buttons with the `return-url` attribute present. + */ +function initReturnUrlSubmitButtons(): void { + for (const button of getElements('button[return-url]')) { + button.addEventListener('click', handleSubmitWithReturnUrl); + } +} + /** * Attach an event listener to each form's submitter (button[type=submit]). When called, the * callback checks the validity of each form field and adds the appropriate Bootstrap CSS class @@ -54,4 +91,5 @@ export function initFormElements(): void { submitter.addEventListener('click', (event: Event) => handleFormSubmit(event, form)); } } + initReturnUrlSubmitButtons(); } diff --git a/netbox/project-static/src/search.ts b/netbox/project-static/src/search.ts index 120d833ea..3140b8d7f 100644 --- a/netbox/project-static/src/search.ts +++ b/netbox/project-static/src/search.ts @@ -107,6 +107,9 @@ function initTableFilter(): void { // Create a regex pattern from the input search text to match against. const filter = new RegExp(target.value.toLowerCase().trim()); + // List of which rows which match the query + const matchedRows: Array = []; + for (const row of rows) { // Find the row's checkbox and deselect it, so that it is not accidentally included in form // submissions. @@ -114,19 +117,26 @@ function initTableFilter(): void { if (checkBox !== null) { checkBox.checked = false; } + // Iterate through each row's cell values for (const value of getRowValues(row)) { if (filter.test(value.toLowerCase())) { - // If this row matches the search pattern, but is already hidden, unhide it and stop - // iterating through the rest of the cells. - row.classList.remove('d-none'); + // If this row matches the search pattern, add it to the list. + matchedRows.push(row); break; - } else { - // If none of the cells in this row match the search pattern, hide the row. - row.classList.add('d-none'); } } } + + // Iterate the rows again to set visibility. + // This results in a single reflow instead of one for each row. + for (const row of rows) { + if (matchedRows.indexOf(row) >= 0) { + row.classList.remove('d-none'); + } else { + row.classList.add('d-none'); + } + } } input.addEventListener('keyup', debounce(handleInput, 300)); } diff --git a/netbox/templates/dcim/interface_edit.html b/netbox/templates/dcim/interface_edit.html index 38b22fe5e..317b49a79 100644 --- a/netbox/templates/dcim/interface_edit.html +++ b/netbox/templates/dcim/interface_edit.html @@ -51,7 +51,7 @@ {% block buttons %} Cancel {% if obj.pk %} - + {% else %} diff --git a/netbox/templates/virtualization/vminterface_edit.html b/netbox/templates/virtualization/vminterface_edit.html index b4d097513..7e5b8599c 100644 --- a/netbox/templates/virtualization/vminterface_edit.html +++ b/netbox/templates/virtualization/vminterface_edit.html @@ -46,7 +46,7 @@ {% block buttons %} Cancel {% if obj.pk %} - + {% else %} diff --git a/netbox/utilities/templatetags/helpers.py b/netbox/utilities/templatetags/helpers.py index 0f3f75bc2..3064cdf38 100644 --- a/netbox/utilities/templatetags/helpers.py +++ b/netbox/utilities/templatetags/helpers.py @@ -51,7 +51,7 @@ def render_markdown(value): value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE) # Sanitize Markdown reference links - pattern = fr'\[(.+)\]:\w?(?!({schemes})).*:(.+)' + pattern = fr'\[(.+)\]:\s*(?!({schemes}))\w*:(.+)' value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE) # Render Markdown