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