Merge pull request #7984 from netbox-community/develop

Release v3.0.12
This commit is contained in:
Jeremy Stretch 2021-12-06 12:07:04 -05:00 committed by GitHub
commit b7129e1456
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 94 additions and 17 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'),

View File

@ -95,6 +95,9 @@ class VLANTable(BaseTable):
template_code=VLAN_LINK,
verbose_name='VID'
)
name = tables.Column(
linkify=True
)
site = tables.Column(
linkify=True
)

View File

@ -29,6 +29,9 @@ class TokenAuthentication(authentication.TokenAuthentication):
if settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend':
from netbox.authentication import LDAPBackend
ldap_backend = LDAPBackend()
# 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:

View File

@ -17,7 +17,7 @@ from django.core.validators import URLValidator
# Environment setup
#
VERSION = '3.0.11'
VERSION = '3.0.12'
# Hostname
HOSTNAME = platform.node()

Binary file not shown.

Binary file not shown.

View File

@ -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
* <button type="button" return-url="/special-url/">
* Save & Continue
* </button>
* ```
*
* @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<HTMLButtonElement>('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();
}

View File

@ -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<HTMLTableRowElement> = [];
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));
}

View File

@ -51,7 +51,7 @@
{% block buttons %}
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
{% if obj.pk %}
<button type="submit" formaction="?return_url={% url 'dcim:interface_edit' pk=obj.pk %}" class="btn btn-outline-primary">Save & Continue Editing</button>
<button type="button" return-url="?return_url={% url 'dcim:interface_edit' pk=obj.pk %}" class="btn btn-outline-primary">Save & Continue Editing</button>
<button type="submit" name="_update" class="btn btn-primary">Save</button>
{% else %}
<button type="submit" name="_addanother" class="btn btn-outline-primary">Create & Add Another</button>

View File

@ -46,7 +46,7 @@
{% block buttons %}
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
{% if obj.pk %}
<button type="submit" formaction="?return_url={% url 'virtualization:vminterface_edit' pk=obj.pk %}" class="btn btn-outline-primary">Save & Continue Editing</button>
<button type="button" return-url="?return_url={% url 'virtualization:vminterface_edit' pk=obj.pk %}" class="btn btn-outline-primary">Save & Continue Editing</button>
<button type="submit" name="_update" class="btn btn-primary">Save</button>
{% else %}
<button type="submit" name="_addanother" class="btn btn-outline-primary">Create & Add Another</button>

View File

@ -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