diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index c26584f32..082c6ba7a 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.2.8 + placeholder: v3.2.9 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index e6be95e49..3fcb4a7c3 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.2.8 + placeholder: v3.2.9 validations: required: true - type: dropdown 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 bf6f2f848..d5aad7a69 100644 --- a/docs/release-notes/version-3.2.md +++ b/docs/release-notes/version-3.2.md @@ -1,5 +1,25 @@ # NetBox v3.2 +## v3.2.9 (2022-08-16) + +### Enhancements + +* [#8595](https://github.com/netbox-community/netbox/issues/8595) - Add PON interface types +* [#8723](https://github.com/netbox-community/netbox/issues/8723) - Enable bulk renaming of devices +* [#9161](https://github.com/netbox-community/netbox/issues/9161) - Pretty print JSON custom field data when editing +* [#9505](https://github.com/netbox-community/netbox/issues/9505) - Display extra addressing details for IPv4 prefixes +* [#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 +* [#9933](https://github.com/netbox-community/netbox/issues/9933) - Add DOCSIS interface type + +### Bug Fixes + +* [#9491](https://github.com/netbox-community/netbox/issues/9491) - Remove button for adding inventory item templates to module type components +* [#9979](https://github.com/netbox-community/netbox/issues/9979) - Fix Markdown rendering for custom fields in table columns +* [#9986](https://github.com/netbox-community/netbox/issues/9986) - Workaround for upstream timezone data bug + +--- + ## v3.2.8 (2022-08-08) ### Enhancements diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index 2e96f9c67..fd71e5c29 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -814,6 +814,17 @@ class InterfaceTypeChoices(ChoiceSet): # ATM/DSL TYPE_XDSL = 'xdsl' + # Coaxial + TYPE_DOCSIS = 'docsis' + + # PON + TYPE_GPON = 'gpon' + TYPE_XG_PON = 'xg-pon' + TYPE_XGS_PON = 'xgs-pon' + TYPE_NG_PON2 = 'ng-pon2' + TYPE_EPON = 'epon' + TYPE_10G_EPON = '10g-epon' + # Stacking TYPE_STACKWISE = 'cisco-stackwise' TYPE_STACKWISE_PLUS = 'cisco-stackwise-plus' @@ -950,6 +961,23 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_XDSL, 'xDSL'), ) ), + ( + 'Coaxial', + ( + (TYPE_DOCSIS, 'DOCSIS'), + ) + ), + ( + 'PON', + ( + (TYPE_GPON, 'GPON (2.5 Gbps / 1.25 Gps)'), + (TYPE_XG_PON, 'XG-PON (10 Gbps / 2.5 Gbps)'), + (TYPE_XGS_PON, 'XGS-PON (10 Gbps)'), + (TYPE_NG_PON2, 'NG-PON2 (TWDM-PON) (4x10 Gbps)'), + (TYPE_EPON, 'EPON (1 Gbps)'), + (TYPE_10G_EPON, '10G-EPON (10 Gbps)'), + ) + ), ( 'Stacking', ( diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py index 7124c2b1f..b9ddf6f43 100644 --- a/netbox/dcim/tables/template_code.py +++ b/netbox/dcim/tables/template_code.py @@ -100,7 +100,7 @@ LOCATION_BUTTONS = """ MODULAR_COMPONENT_TEMPLATE_BUTTONS = """ {% load helpers %} -{% if perms.dcim.add_inventoryitemtemplate %} +{% if perms.dcim.add_inventoryitemtemplate and record.device_type_id %} diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index c5cd0fa65..f00bd73e1 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -248,6 +248,7 @@ urlpatterns = [ path('devices/import/', views.DeviceBulkImportView.as_view(), name='device_import'), path('devices/import/child-devices/', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'), path('devices/edit/', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'), + path('devices/rename/', views.DeviceBulkRenameView.as_view(), name='device_bulk_rename'), path('devices/delete/', views.DeviceBulkDeleteView.as_view(), name='device_bulk_delete'), path('devices//', views.DeviceView.as_view(), name='device'), path('devices//edit/', views.DeviceEditView.as_view(), name='device_edit'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 0bdca686d..966d90876 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1784,6 +1784,12 @@ class DeviceBulkDeleteView(generic.BulkDeleteView): table = tables.DeviceTable +class DeviceBulkRenameView(generic.BulkRenameView): + queryset = Device.objects.all() + filterset = filtersets.DeviceFilterSet + table = tables.DeviceTable + + # # Devices # diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index b7d77e550..55b7a9f03 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 @@ -343,7 +343,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/netbox/settings.py b/netbox/netbox/settings.py index 12ab44399..84e93be25 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str # Environment setup # -VERSION = '3.2.8' +VERSION = '3.2.9' # Hostname HOSTNAME = platform.node() diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py index f78b9f37c..6ab50d4c2 100644 --- a/netbox/netbox/tables/columns.py +++ b/netbox/netbox/tables/columns.py @@ -14,6 +14,7 @@ from django_tables2.columns import library from django_tables2.utils import Accessor from extras.choices import CustomFieldTypeChoices +from utilities.templatetags.builtins.filters import render_markdown from utilities.utils import content_type_identifier, content_type_name, get_viewname __all__ = ( @@ -427,7 +428,7 @@ class CustomFieldColumn(tables.Column): super().__init__(*args, **kwargs) @staticmethod - def _likify_item(item): + def _linkify_item(item): if hasattr(item, 'get_absolute_url'): return f'{escape(item)}' return escape(item) @@ -443,11 +444,13 @@ class CustomFieldColumn(tables.Column): return ', '.join(v for v in value) if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT: return mark_safe(', '.join( - self._likify_item(obj) for obj in self.customfield.deserialize(value) + self._linkify_item(obj) for obj in self.customfield.deserialize(value) )) + if self.customfield.type == CustomFieldTypeChoices.TYPE_LONGTEXT and value: + return render_markdown(value) if value is not None: obj = self.customfield.deserialize(value) - return mark_safe(self._likify_item(obj)) + return mark_safe(self._linkify_item(obj)) return self.default def value(self, value): diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index 7e07c57d0..5aea9c469 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -633,7 +633,7 @@ class BulkRenameView(GetReturnURLMixin, BaseMultiObjectView): replace = form.cleaned_data['replace'] if form.cleaned_data['use_regex']: try: - obj.new_name = re.sub(find, replace, obj.name) + obj.new_name = re.sub(find, replace, obj.name or '') # Catch regex group reference errors except re.error: obj.new_name = obj.name 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/consoleports.html b/netbox/templates/dcim/device/consoleports.html index afc306bd4..6f8b383c3 100644 --- a/netbox/templates/dcim/device/consoleports.html +++ b/netbox/templates/dcim/device/consoleports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_consoleport %} - - - - {% endif %} - {% if perms.dcim.delete_consoleport %} - - {% endif %} -
- {% if perms.dcim.add_consoleport %} - +
+ {% if perms.dcim.change_consoleport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_consoleport %} + + {% endif %} + {% if perms.dcim.change_consoleport %} + + {% endif %} +
+
+ {% if perms.dcim.add_consoleport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/consoleserverports.html b/netbox/templates/dcim/device/consoleserverports.html index 5f244cdc7..f246d4a82 100644 --- a/netbox/templates/dcim/device/consoleserverports.html +++ b/netbox/templates/dcim/device/consoleserverports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_consoleserverport %} - - - - {% endif %} - {% if perms.dcim.delete_consoleserverport %} - - {% endif %} -
- {% if perms.dcim.add_consoleserverport %} - +
+ {% if perms.dcim.change_consoleserverport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_consoleserverport %} + + {% endif %} + {% if perms.dcim.change_consoleserverport %} + + {% endif %} +
+
+ {% if perms.dcim.add_consoleserverport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/devicebays.html b/netbox/templates/dcim/device/devicebays.html index 5e33bdae0..d84408962 100644 --- a/netbox/templates/dcim/device/devicebays.html +++ b/netbox/templates/dcim/device/devicebays.html @@ -16,28 +16,30 @@
-
- {% if perms.dcim.change_devicebay %} - - - {% endif %} - {% if perms.dcim.delete_devicebay %} - - {% endif %} -
- {% if perms.dcim.add_devicebay %} - +
+ {% if perms.dcim.change_devicebay %} +
+ + +
{% endif %} + {% if perms.dcim.delete_devicebay %} + + {% endif %} +
+ {% if perms.dcim.add_devicebay %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/frontports.html b/netbox/templates/dcim/device/frontports.html index 0d0f9577c..513d02090 100644 --- a/netbox/templates/dcim/device/frontports.html +++ b/netbox/templates/dcim/device/frontports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_frontport %} - - - - {% endif %} - {% if perms.dcim.delete_frontport %} - - {% endif %} -
- {% if perms.dcim.add_frontport %} - +
+ {% if perms.dcim.change_frontport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_frontport %} + + {% endif %} + {% if perms.dcim.change_frontport %} + + {% endif %} +
+
+ {% if perms.dcim.add_frontport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/inc/interface_table_controls.html b/netbox/templates/dcim/device/inc/interface_table_controls.html new file mode 100644 index 000000000..14e552439 --- /dev/null +++ b/netbox/templates/dcim/device/inc/interface_table_controls.html @@ -0,0 +1,11 @@ +{% extends 'inc/table_controls_htmx.html' %} + +{% block extra_table_controls %} + + +{% endblock extra_table_controls %} diff --git a/netbox/templates/dcim/device/interfaces.html b/netbox/templates/dcim/device/interfaces.html index 22f6d8be5..2019d9135 100644 --- a/netbox/templates/dcim/device/interfaces.html +++ b/netbox/templates/dcim/device/interfaces.html @@ -4,84 +4,63 @@ {% load static %} {% block content %} -
-
-
- -
-
-
-
- {% if request.user.is_authenticated %} - - {% endif %} - - -
+ {% include 'dcim/device/inc/interface_table_controls.html' with table_modal="DeviceInterfaceTable_config" %} + +
+ {% 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.change_interface %} + + {% endif %}
- -
-
- {% if perms.dcim.change_interface %} - - - - {% endif %} - {% if perms.dcim.delete_interface %} - - {% endif %} -
- {% if perms.dcim.add_interface %} - - {% endif %} + {% if perms.dcim.add_interface %} + - + {% endif %} +
+ {% endblock %} {% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} +{{ block.super }} +{% table_config_form table %} +{% endblock modals %} \ No newline at end of file diff --git a/netbox/templates/dcim/device/inventory.html b/netbox/templates/dcim/device/inventory.html index 18a0712f3..8b74acaae 100644 --- a/netbox/templates/dcim/device/inventory.html +++ b/netbox/templates/dcim/device/inventory.html @@ -16,28 +16,30 @@
-
- {% if perms.dcim.change_inventoryitem %} - - - {% endif %} - {% if perms.dcim.delete_inventoryitem %} - - {% endif %} -
- {% if perms.dcim.add_inventoryitem %} - +
+ {% if perms.dcim.change_inventoryitem %} +
+ + +
{% endif %} + {% if perms.dcim.delete_inventoryitem %} + + {% endif %} +
+ {% if perms.dcim.add_inventoryitem %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/modulebays.html b/netbox/templates/dcim/device/modulebays.html index fc1c9a60d..67b9d88d4 100644 --- a/netbox/templates/dcim/device/modulebays.html +++ b/netbox/templates/dcim/device/modulebays.html @@ -16,28 +16,30 @@
-
- {% if perms.dcim.change_modulebay %} - - - {% endif %} - {% if perms.dcim.delete_modulebay %} - - {% endif %} -
- {% if perms.dcim.add_modulebay %} - +
+ {% if perms.dcim.change_modulebay %} +
+ + +
{% endif %} + {% if perms.dcim.delete_modulebay %} + + {% endif %} +
+ {% if perms.dcim.add_modulebay %} + + {% endif %}
{% table_config_form table %} diff --git a/netbox/templates/dcim/device/poweroutlets.html b/netbox/templates/dcim/device/poweroutlets.html index d312fbbd0..61c2b61f4 100644 --- a/netbox/templates/dcim/device/poweroutlets.html +++ b/netbox/templates/dcim/device/poweroutlets.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_powerport %} - - - - {% endif %} - {% if perms.dcim.delete_poweroutlet %} - - {% endif %} -
- {% if perms.dcim.add_poweroutlet %} - +
+ {% if perms.dcim.change_powerport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_poweroutlet %} + + {% endif %} + {% if perms.dcim.change_powerport %} + + {% endif %} +
+
+ {% if perms.dcim.add_poweroutlet %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/powerports.html b/netbox/templates/dcim/device/powerports.html index cf71e81ba..cd8597e63 100644 --- a/netbox/templates/dcim/device/powerports.html +++ b/netbox/templates/dcim/device/powerports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_powerport %} - - - - {% endif %} - {% if perms.dcim.delete_powerport %} - - {% endif %} -
- {% if perms.dcim.add_powerport %} - +
+ {% if perms.dcim.change_powerport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_powerport %} + + {% endif %} + {% if perms.dcim.change_powerport %} + + {% endif %} +
+
+ {% if perms.dcim.add_powerport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device/rearports.html b/netbox/templates/dcim/device/rearports.html index 73341990f..b370de189 100644 --- a/netbox/templates/dcim/device/rearports.html +++ b/netbox/templates/dcim/device/rearports.html @@ -16,31 +16,37 @@
-
- {% if perms.dcim.change_rearport %} - - - - {% endif %} - {% if perms.dcim.delete_rearport %} - - {% endif %} -
- {% if perms.dcim.add_rearport %} - +
+ {% if perms.dcim.change_rearport %} +
+ + +
{% endif %} +
+ {% if perms.dcim.delete_rearport %} + + {% endif %} + {% if perms.dcim.change_rearport %} + + {% endif %} +
+
+ {% if perms.dcim.add_rearport %} + + {% endif %}
{% endblock %} diff --git a/netbox/templates/dcim/device_list.html b/netbox/templates/dcim/device_list.html index 60efc842e..50af50525 100644 --- a/netbox/templates/dcim/device_list.html +++ b/netbox/templates/dcim/device_list.html @@ -1,4 +1,5 @@ {% extends 'generic/object_list.html' %} +{% load buttons %} {% block bulk_buttons %} {% if perms.dcim.change_device %} @@ -73,5 +74,15 @@ {% endif %} - {{ block.super }} + {% if 'bulk_edit' in actions %} +
+ {% bulk_edit_button model query_params=request.GET %} + +
+ {% endif %} + {% if 'bulk_delete' in actions %} + {% bulk_delete_button model query_params=request.GET %} + {% endif %} {% endblock %} 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..6d6926d5d 100644 --- a/netbox/templates/inc/table_controls_htmx.html +++ b/netbox/templates/inc/table_controls_htmx.html @@ -1,29 +1,19 @@ {% load helpers %} -
-
- {% endblock %} + +{% block modals %} + {{ block.super }} + {% if object.prefix.version == 4 %} + + {% endif %} +{% endblock %} diff --git a/netbox/templates/virtualization/virtualmachine/interfaces.html b/netbox/templates/virtualization/virtualmachine/interfaces.html index e3ffb84d4..eff98cdd6 100644 --- a/netbox/templates/virtualization/virtualmachine/interfaces.html +++ b/netbox/templates/virtualization/virtualmachine/interfaces.html @@ -15,27 +15,28 @@
- {% if perms.virtualization.change_vminterface %} - - - {% endif %} - {% if perms.virtualization.delete_vminterface %} - - {% endif %} - {% if perms.virtualization.add_vminterface %} - - {% endif %} -
+ {% if perms.virtualization.change_vminterface %} +
+ + +
+ {% endif %} + {% if perms.virtualization.delete_vminterface %} + + {% endif %} + {% if perms.virtualization.add_vminterface %} + + {% endif %}
{% endblock content %} 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 88aa1a6c2..2f52850bd 100644 --- a/netbox/virtualization/forms/filtersets.py +++ b/netbox/virtualization/forms/filtersets.py @@ -89,7 +89,7 @@ class VirtualMachineFilterForm( (None, ('q', 'tag')), ('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_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 59bd1e8cd..d99e09943 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,13 +19,13 @@ gunicorn==20.1.0 Jinja2==3.1.2 Markdown==3.4.1 markdown-include==0.7.0 -mkdocs-material==8.3.9 +mkdocs-material==8.4.0 mkdocstrings[python-legacy]==0.19.0 netaddr==0.8.0 Pillow==9.2.0 psycopg2-binary==2.9.3 PyYAML==6.0 -sentry-sdk==1.9.2 +sentry-sdk==1.9.5 social-auth-app-django==5.0.0 social-auth-core==4.3.0 svgwrite==1.4.3 @@ -34,3 +34,6 @@ tzdata==2022.1 # Workaround for #7401 jsonschema==3.2.0 + +# Workaround for #9986 +pytz==2022.1