diff --git a/docs/models/dcim/frontport.md b/docs/models/dcim/frontport.md index 0b753c012..6f12e8cbf 100644 --- a/docs/models/dcim/frontport.md +++ b/docs/models/dcim/frontport.md @@ -1,3 +1,3 @@ ## Front Ports -Front ports are pass-through ports used to represent physical cable connections that comprise part of a longer path. For example, the ports on the front face of a UTP patch panel would be modeled in NetBox as front ports. Each port is assigned a physical type, and must be mapped to a specific rear port on the same device. A single rear port may be mapped to multiple rear ports, using numeric positions to annotate the specific alignment of each. +Front ports are pass-through ports used to represent physical cable connections that comprise part of a longer path. For example, the ports on the front face of a UTP patch panel would be modeled in NetBox as front ports. Each port is assigned a physical type, and must be mapped to a specific rear port on the same device. A single rear port may be mapped to multiple front ports, using numeric positions to annotate the specific alignment of each. diff --git a/docs/release-notes/version-3.2.md b/docs/release-notes/version-3.2.md index 56a35cc02..c572328bc 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -2,6 +2,16 @@ ## v3.2.9 (FUTURE) +### Enhancements + +* [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing +* [#9625](https://github.com/netbox-community/netbox/issues/9625) - Add phone & email details to contacts panel +* [#9857](https://github.com/netbox-community/netbox/issues/9857) - Add clear button to quick search fields + +### Bug Fixes + +* [#9986](https://github.com/netbox-community/netbox/issues/9986) - Workaround for upstream timezone data bug + --- ## v3.2.8 (2022-08-08) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index 156e02f74..426565231 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -18,7 +18,7 @@ from netbox.models.features import ExportTemplatesMixin, WebhooksMixin from utilities import filters from utilities.forms import ( CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, - LaxURLField, StaticSelectMultiple, StaticSelect, add_blank_choice, + JSONField, LaxURLField, StaticSelectMultiple, StaticSelect, add_blank_choice, ) from utilities.querysets import RestrictedQuerySet from utilities.validators import validate_regex @@ -355,7 +355,7 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel): # JSON elif self.type == CustomFieldTypeChoices.TYPE_JSON: - field = forms.JSONField(required=required, initial=initial) + field = JSONField(required=required, initial=initial) # Object elif self.type == CustomFieldTypeChoices.TYPE_OBJECT: diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css index b929f176a..94718ac40 100644 Binary files a/netbox/project-static/dist/netbox-dark.css and b/netbox/project-static/dist/netbox-dark.css differ diff --git a/netbox/project-static/dist/netbox-light.css b/netbox/project-static/dist/netbox-light.css index 341369adf..bf0ba2f62 100644 Binary files a/netbox/project-static/dist/netbox-light.css and b/netbox/project-static/dist/netbox-light.css differ diff --git a/netbox/project-static/dist/netbox-print.css b/netbox/project-static/dist/netbox-print.css index 61bcedf9c..4ccd41d8e 100644 Binary files a/netbox/project-static/dist/netbox-print.css and b/netbox/project-static/dist/netbox-print.css differ diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 9ea2e5c7c..5ab9da845 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 60656bc6d..9b92d1489 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/search.ts b/netbox/project-static/src/search.ts index 9e8a31c5b..97fe1826a 100644 --- a/netbox/project-static/src/search.ts +++ b/netbox/project-static/src/search.ts @@ -27,6 +27,23 @@ function handleSearchDropdownClick(event: Event, button: HTMLButtonElement): voi } } +/** + * Show/hide quicksearch clear button. + * + * @param event "keyup" or "search" event for the quicksearch input + */ +function quickSearchEventHandler(event: Event): void { + const quicksearch = event.currentTarget as HTMLInputElement; + const inputgroup = quicksearch.parentElement as HTMLDivElement; + if (isTruthy(inputgroup)) { + if (quicksearch.value === "") { + inputgroup.classList.add("hide-last-child"); + } else { + inputgroup.classList.remove("hide-last-child"); + } + } +} + /** * Initialize Search Bar Elements. */ @@ -40,8 +57,35 @@ function initSearchBar(): void { } } +/** + * Initialize Quicksearch Event listener/handlers. + */ +function initQuickSearch(): void { + const quicksearch = document.getElementById("quicksearch") as HTMLInputElement; + const clearbtn = document.getElementById("quicksearch_clear") as HTMLButtonElement; + if (isTruthy(quicksearch)) { + quicksearch.addEventListener("keyup", quickSearchEventHandler, { + passive: true + }) + quicksearch.addEventListener("search", quickSearchEventHandler, { + passive: true + }) + if (isTruthy(clearbtn)) { + clearbtn.addEventListener("click", async () => { + const search = new Event('search'); + quicksearch.value = ''; + await new Promise(f => setTimeout(f, 100)); + quicksearch.dispatchEvent(search); + }, { + passive: true + }) + } + } +} + export function initSearch(): void { for (const func of [initSearchBar]) { func(); } + initQuickSearch(); } diff --git a/netbox/project-static/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index a54b6c324..d78e9e9b9 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -416,6 +416,27 @@ nav.search { } } +// Styles for the quicksearch and its clear button; +// Overrides input-group styles and adds transition effects +.quicksearch { + input[type="search"] { + border-radius: $border-radius !important; + } + + button { + margin-left: -32px !important; + z-index: 100 !important; + outline: none !important; + border-radius: $border-radius !important; + transition: visibility 0s, opacity 0.2s linear; + } + + button :hover { + opacity: 50%; + transition: visibility 0s, opacity 0.1s linear; + } +} + main.layout { display: flex; flex-wrap: nowrap; @@ -714,11 +735,8 @@ textarea.form-control[rows='10'] { height: 18rem; } -textarea#id_local_context_data, textarea.markdown, -textarea#id_public_key, -textarea.form-control[name='csv'], -textarea.form-control[name='data'] { +textarea.form-control[name='csv'] { font-family: $font-family-monospace; } diff --git a/netbox/project-static/styles/overrides.scss b/netbox/project-static/styles/overrides.scss index 03c72c6e6..7fa366df8 100644 --- a/netbox/project-static/styles/overrides.scss +++ b/netbox/project-static/styles/overrides.scss @@ -34,3 +34,11 @@ a[type='button'] { .badge { font-size: $font-size-xs; } + +/* clears the 'X' in search inputs from webkit browsers */ +input[type='search']::-webkit-search-decoration, +input[type='search']::-webkit-search-cancel-button, +input[type='search']::-webkit-search-results-button, +input[type='search']::-webkit-search-results-decoration { + -webkit-appearance: none !important; +} diff --git a/netbox/project-static/styles/theme-dark.scss b/netbox/project-static/styles/theme-dark.scss index c0933e991..4bbe5cea5 100644 --- a/netbox/project-static/styles/theme-dark.scss +++ b/netbox/project-static/styles/theme-dark.scss @@ -92,6 +92,10 @@ $input-focus-color: $input-color; $input-placeholder-color: $gray-700; $input-plaintext-color: $body-color; +input { + color-scheme: dark; +} + $form-check-input-active-filter: brightness(90%); $form-check-input-bg: $input-bg; $form-check-input-border: 1px solid rgba(255, 255, 255, 0.25); diff --git a/netbox/project-static/styles/theme-light.scss b/netbox/project-static/styles/theme-light.scss index d417e1bf6..c9478f1cc 100644 --- a/netbox/project-static/styles/theme-light.scss +++ b/netbox/project-static/styles/theme-light.scss @@ -22,7 +22,6 @@ $theme-colors: ( 'danger': $danger, 'light': $light, 'dark': $dark, - // General-purpose palette 'blue': $blue-500, 'indigo': $indigo-500, @@ -36,7 +35,7 @@ $theme-colors: ( 'cyan': $cyan-500, 'gray': $gray-500, 'black': $black, - 'white': $white, + 'white': $white ); $light: $gray-200; diff --git a/netbox/project-static/styles/utilities.scss b/netbox/project-static/styles/utilities.scss index cd8ccc46b..a5a4bf038 100644 --- a/netbox/project-static/styles/utilities.scss +++ b/netbox/project-static/styles/utilities.scss @@ -42,3 +42,9 @@ table td { visibility: visible !important; } } + +// Hides the last child of an element +.hide-last-child :last-child { + visibility: hidden; + opacity: 0; +} diff --git a/netbox/templates/dcim/device/interfaces.html b/netbox/templates/dcim/device/interfaces.html index 7db7ea0ae..ffb574ef3 100644 --- a/netbox/templates/dcim/device/interfaces.html +++ b/netbox/templates/dcim/device/interfaces.html @@ -4,81 +4,82 @@ {% load static %} {% block content %} -
-
-
- -
+
+
+
+ +
-
-
- {% if request.user.is_authenticated %} - - {% endif %} - - -
+
+
+
+ {% if request.user.is_authenticated %} + + {% endif %} + + +
+
+
+ +
+ {% csrf_token %} + + +
+
+ {% include 'htmx/table.html' %}
- - {% csrf_token %} - - -
-
- {% include 'htmx/table.html' %} +
+
+ {% if perms.dcim.change_interface %} + + + + {% endif %} + {% if perms.dcim.delete_interface %} + + {% endif %} +
+ {% if perms.dcim.add_interface %} + -
- -
-
- {% if 'bulk_edit' in actions %} - - - - {% endif %} - {% if 'bulk_delete' in actions %} - - {% endif %} -
- {% if perms.dcim.add_interface %} - - {% endif %} -
- + {% endif %} +
+ {% endblock %} {% block modals %} diff --git a/netbox/templates/inc/panels/contacts.html b/netbox/templates/inc/panels/contacts.html index 26961f04a..359ad8d7e 100644 --- a/netbox/templates/inc/panels/contacts.html +++ b/netbox/templates/inc/panels/contacts.html @@ -10,6 +10,8 @@ Name Role Priority + Phone + Email {% for contact in contacts %} @@ -17,6 +19,20 @@ {{ contact.contact|linkify }} {{ contact.role|placeholder }} {{ contact.get_priority_display|placeholder }} + + {% if contact.contact.phone %} + {{ contact.contact.phone }} + {% else %} + {{ ''|placeholder }} + {% endif %} + + + {% if contact.contact.email %} + {{ contact.contact.email }} + {% else %} + {{ ''|placeholder }} + {% endif %} + {% if perms.tenancy.change_contactassignment %} diff --git a/netbox/templates/inc/table_controls_htmx.html b/netbox/templates/inc/table_controls_htmx.html index ab8167bc0..099ad537e 100644 --- a/netbox/templates/inc/table_controls_htmx.html +++ b/netbox/templates/inc/table_controls_htmx.html @@ -2,31 +2,21 @@
-
- +
+ +
{% if request.user.is_authenticated and table_modal %} -
- -
+
+ +
{% endif %}
-
+
\ No newline at end of file diff --git a/netbox/utilities/forms/fields/fields.py b/netbox/utilities/forms/fields/fields.py index 9168189a1..df69339e5 100644 --- a/netbox/utilities/forms/fields/fields.py +++ b/netbox/utilities/forms/fields/fields.py @@ -99,6 +99,7 @@ class JSONField(_JSONField): if not self.help_text: self.help_text = 'Enter context data in
JSON format.' self.widget.attrs['placeholder'] = '' + self.widget.attrs['class'] = 'font-monospace' def prepare_value(self, value): if isinstance(value, InvalidJSONInput): diff --git a/netbox/utilities/forms/forms.py b/netbox/utilities/forms/forms.py index 3b5cd8308..8ad6f103b 100644 --- a/netbox/utilities/forms/forms.py +++ b/netbox/utilities/forms/forms.py @@ -136,7 +136,7 @@ class ImportForm(BootstrapMixin, forms.Form): Generic form for creating an object from JSON/YAML data """ data = forms.CharField( - widget=forms.Textarea, + widget=forms.Textarea(attrs={'class': 'font-monospace'}), help_text="Enter object data in JSON or YAML format. Note: Only a single object/document is supported." ) format = forms.ChoiceField( diff --git a/netbox/virtualization/forms/filtersets.py b/netbox/virtualization/forms/filtersets.py index e15a76a43..4b8ff6d21 100644 --- a/netbox/virtualization/forms/filtersets.py +++ b/netbox/virtualization/forms/filtersets.py @@ -93,7 +93,7 @@ class VirtualMachineFilterForm( (None, ('q', 'tag')), ('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id')), - ('Attriubtes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')), + ('Attributes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')), ('Tenant', ('tenant_group_id', 'tenant_id')), ('Contacts', ('contact', 'contact_role', 'contact_group')), ) diff --git a/requirements.txt b/requirements.txt index d6f7b4349..1a9aa5f46 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,3 +35,6 @@ tzdata==2022.1 # Workaround for #7401 jsonschema==3.2.0 + +# Workaround for #9986 +pytz==2022.1