diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index c5b45c31c..80810f2ba 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.4.2 + placeholder: v3.4.3 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 54a7735c5..975fc025a 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.4.2 + placeholder: v3.4.3 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.4.md b/docs/release-notes/version-3.4.md index f4be8190d..bf32f2d26 100644 --- a/docs/release-notes/version-3.4.md +++ b/docs/release-notes/version-3.4.md @@ -1,12 +1,19 @@ # NetBox v3.4 -## v3.4.3 (FUTURE) +## v3.4.4 (FUTURE) + +--- + +## v3.4.3 (2023-01-20) ### Enhancements * [#9996](https://github.com/netbox-community/netbox/issues/9996) - Introduce `CA_CERT_PATH` parameter to define SSL CA path for Redis servers * [#10486](https://github.com/netbox-community/netbox/issues/10486) - Add a cable edit button for connected components in component lists +* [#11118](https://github.com/netbox-community/netbox/issues/11118) - Add L2VPN filters for VLANs and interfaces * [#11150](https://github.com/netbox-community/netbox/issues/11150) - Add primary IPv4/v6 address filters for devices +* [#11227](https://github.com/netbox-community/netbox/issues/11227) - Add 800GE interface types +* [#11228](https://github.com/netbox-community/netbox/issues/11228) - List both devices & VMs under device role view * [#11245](https://github.com/netbox-community/netbox/issues/11245) - Enable export templates for journal entries * [#11371](https://github.com/netbox-community/netbox/issues/11371) - Introduce additional 100M Ethernet interface types @@ -20,10 +27,16 @@ * [#11402](https://github.com/netbox-community/netbox/issues/11402) - Avoid LookupError exception when running scripts with commit disabled * [#11403](https://github.com/netbox-community/netbox/issues/11403) - Fix exception when scheduling a job in the past * [#11416](https://github.com/netbox-community/netbox/issues/11416) - Avoid AttributeError exception when deleting a cabled circuit termination +* [#11433](https://github.com/netbox-community/netbox/issues/11433) - Avoid AttributeError exception when generating API schema for views with custom schema * [#11438](https://github.com/netbox-community/netbox/issues/11438) - Fix deletion of scheduled job using non-default queues * [#11444](https://github.com/netbox-community/netbox/issues/11444) - Adding/removing a device from a device bay should record a pre-change snapshot on the device bay * [#11467](https://github.com/netbox-community/netbox/issues/11467) - Correct count on interfaces tab when viewing a VC master device * [#11483](https://github.com/netbox-community/netbox/issues/11483) - Apply configured formatting to custom date fields +* [#11488](https://github.com/netbox-community/netbox/issues/11488) - Add missing `description` fields to several REST API serializers +* [#11497](https://github.com/netbox-community/netbox/issues/11497) - Enforce `run_script` permission when executing scripts via REST API +* [#11516](https://github.com/netbox-community/netbox/issues/11516) - Prevent text highlight utility from interpreting match as regex +* [#11522](https://github.com/netbox-community/netbox/issues/11522) - Correct tag links under contact & tenant list views +* [#11544](https://github.com/netbox-community/netbox/issues/11544) - Catch ValidationError exception when filtering by invalid MAC address --- diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 2bcb0895a..816c696d2 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -77,6 +77,7 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer): model = CircuitTermination fields = [ 'id', 'url', 'display', 'site', 'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id', + 'description', ] diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index f8a8d4e1e..e041d4c02 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -672,6 +672,22 @@ class DeviceSerializer(NetBoxModelSerializer): return data +class DeviceWithConfigContextSerializer(DeviceSerializer): + config_context = serializers.SerializerMethodField() + + class Meta(DeviceSerializer.Meta): + fields = [ + 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', + 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip', + 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', + 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', + ] + + @swagger_serializer_method(serializer_or_field=serializers.JSONField) + def get_config_context(self, obj): + return obj.get_config_context() + + class VirtualDeviceContextSerializer(NetBoxModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail') device = NestedDeviceSerializer() @@ -687,7 +703,8 @@ class VirtualDeviceContextSerializer(NetBoxModelSerializer): model = VirtualDeviceContext fields = [ 'id', 'url', 'display', 'name', 'device', 'identifier', 'tenant', 'primary_ip', 'primary_ip4', - 'primary_ip6', 'status', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'interface_count', + 'primary_ip6', 'status', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', + 'interface_count', ] @@ -706,22 +723,6 @@ class ModuleSerializer(NetBoxModelSerializer): ] -class DeviceWithConfigContextSerializer(DeviceSerializer): - config_context = serializers.SerializerMethodField() - - class Meta(DeviceSerializer.Meta): - fields = [ - 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', - 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip', - 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', - 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated', - ] - - @swagger_serializer_method(serializer_or_field=serializers.JSONField) - def get_config_context(self, obj): - return obj.get_config_context() - - class DeviceNAPALMSerializer(serializers.Serializer): method = serializers.JSONField() @@ -935,7 +936,7 @@ class FrontPortRearPortSerializer(WritableNestedSerializer): class Meta: model = RearPort - fields = ['id', 'url', 'display', 'name', 'label'] + fields = ['id', 'url', 'display', 'name', 'label', 'description'] class FrontPortSerializer(NetBoxModelSerializer, CabledObjectSerializer): @@ -1059,7 +1060,7 @@ class TracedCableSerializer(serializers.ModelSerializer): class Meta: model = Cable fields = [ - 'id', 'url', 'type', 'status', 'label', 'color', 'length', 'length_unit', + 'id', 'url', 'type', 'status', 'label', 'color', 'length', 'length_unit', 'description', ] diff --git a/netbox/dcim/choices.py b/netbox/dcim/choices.py index bcc3b404b..f1485b67f 100644 --- a/netbox/dcim/choices.py +++ b/netbox/dcim/choices.py @@ -813,6 +813,8 @@ class InterfaceTypeChoices(ChoiceSet): TYPE_200GE_QSFP56 = '200gbase-x-qsfp56' TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd' TYPE_400GE_OSFP = '400gbase-x-osfp' + TYPE_800GE_QSFP_DD = '800gbase-x-qsfpdd' + TYPE_800GE_OSFP = '800gbase-x-osfp' # Ethernet Backplane TYPE_1GE_KX = '1000base-kx' @@ -954,6 +956,8 @@ class InterfaceTypeChoices(ChoiceSet): (TYPE_200GE_QSFP56, 'QSFP56 (200GE)'), (TYPE_400GE_QSFP_DD, 'QSFP-DD (400GE)'), (TYPE_400GE_OSFP, 'OSFP (400GE)'), + (TYPE_800GE_QSFP_DD, 'QSFP-DD (800GE)'), + (TYPE_800GE_OSFP, 'OSFP (800GE)'), ) ), ( diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 534d8e411..c10ef44c3 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -3,7 +3,7 @@ from django.contrib.auth.models import User from django.utils.translation import gettext as _ from extras.filtersets import LocalConfigContextFilterSet -from ipam.models import ASN, IPAddress, VRF +from ipam.models import ASN, L2VPN, IPAddress, VRF from netbox.filtersets import ( BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet, ) @@ -1414,6 +1414,17 @@ class InterfaceFilterSet( to_field_name='name', label='Virtual Device Context', ) + l2vpn_id = django_filters.ModelMultipleChoiceFilter( + field_name='l2vpn_terminations__l2vpn', + queryset=L2VPN.objects.all(), + label=_('L2VPN (ID)'), + ) + l2vpn = django_filters.ModelMultipleChoiceFilter( + field_name='l2vpn_terminations__l2vpn__identifier', + queryset=L2VPN.objects.all(), + to_field_name='identifier', + label=_('L2VPN'), + ) class Meta: model = Interface diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 815f0586a..c00e83672 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -6,7 +6,7 @@ from dcim.choices import * from dcim.constants import * from dcim.models import * from extras.forms import LocalConfigContextFilterForm -from ipam.models import ASN, VRF +from ipam.models import ASN, L2VPN, VRF from netbox.forms import NetBoxModelFilterSetForm from tenancy.forms import ContactModelFilterForm, TenancyFilterForm from utilities.forms import ( @@ -1112,7 +1112,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): fieldsets = ( (None, ('q', 'filter_id', 'tag')), ('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')), - ('Addressing', ('vrf_id', 'mac_address', 'wwn')), + ('Addressing', ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')), ('PoE', ('poe_mode', 'poe_type')), ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')), ('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', @@ -1203,6 +1203,11 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): required=False, label='VRF' ) + l2vpn_id = DynamicModelMultipleChoiceField( + queryset=L2VPN.objects.all(), + required=False, + label=_('L2VPN') + ) tag = TagFilterField(model) diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index feff29e12..272ea2b7d 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -78,7 +78,7 @@ class PowerFeedTable(CableTerminationTable): model = PowerFeed fields = ( 'pk', 'id', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', - 'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'available_power', + 'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'available_power', 'description', 'comments', 'tags', 'created', 'last_updated', ) default_columns = ( diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 32c06818f..1fabc4bd9 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -21,7 +21,9 @@ from utilities.paginator import EnhancedPaginator, get_paginate_count from utilities.permissions import get_permission_for_model from utilities.utils import count_related from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view +from virtualization.filtersets import VirtualMachineFilterSet from virtualization.models import VirtualMachine +from virtualization.tables import VirtualMachineTable from . import filtersets, forms, tables from .choices import DeviceFaceChoices from .constants import NONCONNECTABLE_IFACE_TYPES @@ -1639,6 +1641,42 @@ class DeviceRoleView(generic.ObjectView): queryset = DeviceRole.objects.all() +@register_model_view(DeviceRole, 'devices', path='devices') +class DeviceRoleDevicesView(generic.ObjectChildrenView): + queryset = DeviceRole.objects.all() + child_model = Device + table = tables.DeviceTable + filterset = filtersets.DeviceFilterSet + template_name = 'dcim/devicerole/devices.html' + tab = ViewTab( + label=_('Devices'), + badge=lambda obj: obj.devices.count(), + permission='dcim.view_device', + weight=400 + ) + + def get_children(self, request, parent): + return Device.objects.restrict(request.user, 'view').filter(device_role=parent) + + +@register_model_view(DeviceRole, 'virtual_machines', path='virtual-machines') +class DeviceRoleVirtualMachinesView(generic.ObjectChildrenView): + queryset = DeviceRole.objects.all() + child_model = VirtualMachine + table = VirtualMachineTable + filterset = VirtualMachineFilterSet + template_name = 'dcim/devicerole/virtual_machines.html' + tab = ViewTab( + label=_('Virtual machines'), + badge=lambda obj: obj.virtual_machines.count(), + permission='virtualization.view_virtualmachine', + weight=500 + ) + + def get_children(self, request, parent): + return VirtualMachine.objects.restrict(request.user, 'view').filter(role=parent) + + @register_model_view(DeviceRole, 'edit') class DeviceRoleEditView(generic.ObjectEditView): queryset = DeviceRole.objects.all() diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 56bc8567d..1423824cd 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -318,6 +318,10 @@ class ScriptViewSet(ViewSet): """ Run a Script identified as ".