Przeglądaj źródła

Merge pull request #7465 from netbox-community/develop

Release v3.0.6
Jeremy Stretch 4 lat temu
rodzic
commit
3eef6363fd
43 zmienionych plików z 343 dodań i 317 usunięć
  1. 1 1
      .github/ISSUE_TEMPLATE/bug_report.yaml
  2. 1 1
      .github/ISSUE_TEMPLATE/feature_request.yaml
  3. 9 2
      docs/administration/housekeeping.md
  4. 2 2
      docs/installation/3-netbox.md
  5. 2 2
      docs/installation/upgrading.md
  6. 20 1
      docs/release-notes/version-3.0.md
  7. 5 5
      netbox/circuits/api/serializers.py
  8. 14 8
      netbox/circuits/tests/test_api.py
  9. 56 70
      netbox/dcim/filtersets.py
  10. 11 7
      netbox/dcim/svg.py
  11. 27 9
      netbox/dcim/tests/test_filtersets.py
  12. 1 0
      netbox/dcim/views.py
  13. 8 0
      netbox/extras/api/serializers.py
  14. 1 1
      netbox/netbox/settings.py
  15. 0 0
      netbox/project-static/dist/netbox-dark.css
  16. 0 0
      netbox/project-static/dist/netbox-light.css
  17. 0 0
      netbox/project-static/dist/netbox-print.css
  18. 17 17
      netbox/project-static/styles/netbox.scss
  19. 1 0
      netbox/project-static/styles/select.scss
  20. 1 0
      netbox/project-static/styles/theme-light.scss
  21. 2 2
      netbox/project-static/styles/variables.scss
  22. 2 3
      netbox/templates/circuits/circuittermination_edit.html
  23. 3 7
      netbox/templates/dcim/cable_connect.html
  24. 73 80
      netbox/templates/dcim/device_edit.html
  25. 0 1
      netbox/templates/dcim/inc/cable_form.html
  26. 3 5
      netbox/templates/dcim/interface_edit.html
  27. 6 0
      netbox/templates/dcim/platform.html
  28. 11 15
      netbox/templates/dcim/rack_edit.html
  29. 3 5
      netbox/templates/dcim/virtualchassis_add.html
  30. 2 4
      netbox/templates/dcim/virtualchassis_edit.html
  31. 6 0
      netbox/templates/generic/object.html
  32. 16 22
      netbox/templates/generic/object_edit.html
  33. 3 1
      netbox/templates/generic/object_list.html
  34. 2 2
      netbox/templates/home.html
  35. 2 2
      netbox/templates/inc/table_controls.html
  36. 3 5
      netbox/templates/ipam/ipaddress_bulk_add.html
  37. 5 9
      netbox/templates/ipam/ipaddress_edit.html
  38. 1 2
      netbox/templates/ipam/service_edit.html
  39. 4 7
      netbox/templates/ipam/vlan_edit.html
  40. 1 1
      netbox/templates/tenancy/tenantgroup.html
  41. 3 5
      netbox/templates/virtualization/vminterface_edit.html
  42. 11 9
      netbox/utilities/tables.py
  43. 4 4
      requirements.txt

+ 1 - 1
.github/ISSUE_TEMPLATE/bug_report.yaml

@@ -17,7 +17,7 @@ body:
         What version of NetBox are you currently running? (If you don't have access to the most
         What version of NetBox are you currently running? (If you don't have access to the most
         recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
         recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
         before opening a bug report to see if your issue has already been addressed.)
         before opening a bug report to see if your issue has already been addressed.)
-      placeholder: v3.0.5
+      placeholder: v3.0.6
     validations:
     validations:
       required: true
       required: true
   - type: dropdown
   - type: dropdown

+ 1 - 1
.github/ISSUE_TEMPLATE/feature_request.yaml

@@ -14,7 +14,7 @@ body:
     attributes:
     attributes:
       label: NetBox version
       label: NetBox version
       description: What version of NetBox are you currently running?
       description: What version of NetBox are you currently running?
-      placeholder: v3.0.5
+      placeholder: v3.0.6
     validations:
     validations:
       required: true
       required: true
   - type: dropdown
   - type: dropdown

+ 9 - 2
docs/administration/housekeeping.md

@@ -5,6 +5,13 @@ NetBox includes a `housekeeping` management command that should be run nightly.
 * Clearing expired authentication sessions from the database
 * Clearing expired authentication sessions from the database
 * Deleting changelog records older than the configured [retention time](../configuration/optional-settings.md#changelog_retention)
 * Deleting changelog records older than the configured [retention time](../configuration/optional-settings.md#changelog_retention)
 
 
-This command can be invoked directly, or by using the shell script provided at `/opt/netbox/contrib/netbox-housekeeping.sh`. This script can be copied into your cron scheduler's daily jobs directory (e.g. `/etc/cron.daily`) or referenced directly within the cron configuration file.
+This command can be invoked directly, or by using the shell script provided at `/opt/netbox/contrib/netbox-housekeeping.sh`. This script can be linked from your cron scheduler's daily jobs directory (e.g. `/etc/cron.daily`) or referenced directly within the cron configuration file.
 
 
-The `housekeeping` command can also be run manually at any time: Running the command outside of scheduled execution times will not interfere with its operation.
+```shell
+ln -s /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/netbox-housekeeping
+```
+
+!!! note
+    On Debian-based systems, be sure to omit the `.sh` file extension when linking to the script from within a cron directory. Otherwise, the task may not run.
+
+The `housekeeping` command can also be run manually at any time: Running the command outside scheduled execution times will not interfere with its operation.

+ 2 - 2
docs/installation/3-netbox.md

@@ -259,10 +259,10 @@ python3 manage.py createsuperuser
 
 
 NetBox includes a `housekeeping` management command that handles some recurring cleanup tasks, such as clearing out old sessions and expired change records. Although this command may be run manually, it is recommended to configure a scheduled job using the system's `cron` daemon or a similar utility.
 NetBox includes a `housekeeping` management command that handles some recurring cleanup tasks, such as clearing out old sessions and expired change records. Although this command may be run manually, it is recommended to configure a scheduled job using the system's `cron` daemon or a similar utility.
 
 
-A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be copied to your system's daily cron task directory, or included within the crontab directly. (If installing NetBox into a nonstandard path, be sure to update the system paths within this script first.)
+A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be copied to or linked from your system's daily cron task directory, or included within the crontab directly. (If installing NetBox into a nonstandard path, be sure to update the system paths within this script first.)
 
 
 ```shell
 ```shell
-cp /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/
+ln -s /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/netbox-housekeeping
 ```
 ```
 
 
 See the [housekeeping documentation](../administration/housekeeping.md) for further details.
 See the [housekeeping documentation](../administration/housekeeping.md) for further details.

+ 2 - 2
docs/installation/upgrading.md

@@ -111,10 +111,10 @@ sudo systemctl restart netbox netbox-rq
 
 
 ## Verify Housekeeping Scheduling
 ## Verify Housekeeping Scheduling
 
 
-If upgrading from a release prior to NetBox v3.0, check that a cron task (or similar scheduled process) has been configured to run NetBox's nightly housekeeping command. A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be copied to your system's daily cron task directory, or included within the crontab directly. (If NetBox has been installed in a nonstandard path, be sure to update the system paths within this script first.)
+If upgrading from a release prior to NetBox v3.0, check that a cron task (or similar scheduled process) has been configured to run NetBox's nightly housekeeping command. A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be linked from your system's daily cron task directory, or included within the crontab directly. (If NetBox has been installed in a nonstandard path, be sure to update the system paths within this script first.)
 
 
 ```shell
 ```shell
-cp /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/
+ln -s /opt/netbox/contrib/netbox-housekeeping.sh /etc/cron.daily/netbox-housekeeping
 ```
 ```
 
 
 See the [housekeeping documentation](../administration/housekeeping.md) for further details.
 See the [housekeeping documentation](../administration/housekeeping.md) for further details.

+ 20 - 1
docs/release-notes/version-3.0.md

@@ -1,5 +1,24 @@
 # NetBox v3.0
 # NetBox v3.0
 
 
+## v3.0.6 (2021-10-06)
+
+### Enhancements
+
+* [#6850](https://github.com/netbox-community/netbox/issues/6850) - Default to current user when creating journal entries via REST API
+* [#6955](https://github.com/netbox-community/netbox/issues/6955) - Include type, ID, and slug on object view
+* [#7394](https://github.com/netbox-community/netbox/issues/7394) - Enable filtering cables by termination type & ID in REST API
+* [#7462](https://github.com/netbox-community/netbox/issues/7462) - Include count of assigned virtual machines under platform view
+
+### Bug Fixes
+
+* [#7442](https://github.com/netbox-community/netbox/issues/7442) - Fix missing actions column on user-configured tables
+* [#7446](https://github.com/netbox-community/netbox/issues/7446) - Fix exception when viewing a large number of child IPs within a prefix
+* [#7455](https://github.com/netbox-community/netbox/issues/7455) - Fix site/provider network validation for circuit termination API serializer
+* [#7459](https://github.com/netbox-community/netbox/issues/7459) - Pre-populate location data when adding a device to a rack
+* [#7460](https://github.com/netbox-community/netbox/issues/7460) - Fix filtering connections by site ID
+
+---
+
 ## v3.0.5 (2021-10-04)
 ## v3.0.5 (2021-10-04)
 
 
 ### Enhancements
 ### Enhancements
@@ -8,7 +27,6 @@
 * [#6423](https://github.com/netbox-community/netbox/issues/6423) - Cache rendered REST API specifications
 * [#6423](https://github.com/netbox-community/netbox/issues/6423) - Cache rendered REST API specifications
 * [#6708](https://github.com/netbox-community/netbox/issues/6708) - Add image attachment support for circuits, power panels
 * [#6708](https://github.com/netbox-community/netbox/issues/6708) - Add image attachment support for circuits, power panels
 * [#7387](https://github.com/netbox-community/netbox/issues/7387) - Enable arbitrary ordering of custom scripts
 * [#7387](https://github.com/netbox-community/netbox/issues/7387) - Enable arbitrary ordering of custom scripts
-* [#7427](https://github.com/netbox-community/netbox/issues/7427) - Don't select hidden rows when selecting all in a table
 
 
 ### Bug Fixes
 ### Bug Fixes
 
 
@@ -23,6 +41,7 @@
 * [#7412](https://github.com/netbox-community/netbox/issues/7412) - Fix exception in UI when adding child device to device bay
 * [#7412](https://github.com/netbox-community/netbox/issues/7412) - Fix exception in UI when adding child device to device bay
 * [#7417](https://github.com/netbox-community/netbox/issues/7417) - Prevent exception when filtering objects list by invalid tag
 * [#7417](https://github.com/netbox-community/netbox/issues/7417) - Prevent exception when filtering objects list by invalid tag
 * [#7425](https://github.com/netbox-community/netbox/issues/7425) - Housekeeping command should honor zero verbosity
 * [#7425](https://github.com/netbox-community/netbox/issues/7425) - Housekeeping command should honor zero verbosity
+* [#7427](https://github.com/netbox-community/netbox/issues/7427) - Don't select hidden rows when selecting all in a table
 
 
 ---
 ---
 
 

+ 5 - 5
netbox/circuits/api/serializers.py

@@ -3,10 +3,10 @@ from rest_framework import serializers
 from circuits.choices import CircuitStatusChoices
 from circuits.choices import CircuitStatusChoices
 from circuits.models import *
 from circuits.models import *
 from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
 from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
-from dcim.api.serializers import CableTerminationSerializer, ConnectedEndpointSerializer
+from dcim.api.serializers import CableTerminationSerializer
 from netbox.api import ChoiceField
 from netbox.api import ChoiceField
 from netbox.api.serializers import (
 from netbox.api.serializers import (
-    BaseModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer
+    OrganizationalModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer, WritableNestedSerializer
 )
 )
 from tenancy.api.nested_serializers import NestedTenantSerializer
 from tenancy.api.nested_serializers import NestedTenantSerializer
 from .nested_serializers import *
 from .nested_serializers import *
@@ -90,11 +90,11 @@ class CircuitSerializer(PrimaryModelSerializer):
         ]
         ]
 
 
 
 
-class CircuitTerminationSerializer(BaseModelSerializer, CableTerminationSerializer):
+class CircuitTerminationSerializer(ValidatedModelSerializer, CableTerminationSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
     url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
     circuit = NestedCircuitSerializer()
     circuit = NestedCircuitSerializer()
-    site = NestedSiteSerializer(required=False)
-    provider_network = NestedProviderNetworkSerializer(required=False)
+    site = NestedSiteSerializer(required=False, allow_null=True)
+    provider_network = NestedProviderNetworkSerializer(required=False, allow_null=True)
     cable = NestedCableSerializer(read_only=True)
     cable = NestedCableSerializer(read_only=True)
 
 
     class Meta:
     class Meta:

+ 14 - 8
netbox/circuits/tests/test_api.py

@@ -136,14 +136,20 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
         SIDE_A = CircuitTerminationSideChoices.SIDE_A
         SIDE_A = CircuitTerminationSideChoices.SIDE_A
         SIDE_Z = CircuitTerminationSideChoices.SIDE_Z
         SIDE_Z = CircuitTerminationSideChoices.SIDE_Z
 
 
+        provider = Provider.objects.create(name='Provider 1', slug='provider-1')
+        circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
+
         sites = (
         sites = (
             Site(name='Site 1', slug='site-1'),
             Site(name='Site 1', slug='site-1'),
             Site(name='Site 2', slug='site-2'),
             Site(name='Site 2', slug='site-2'),
         )
         )
         Site.objects.bulk_create(sites)
         Site.objects.bulk_create(sites)
 
 
-        provider = Provider.objects.create(name='Provider 1', slug='provider-1')
-        circuit_type = CircuitType.objects.create(name='Circuit Type 1', slug='circuit-type-1')
+        provider_networks = (
+            ProviderNetwork(provider=provider, name='Provider Network 1'),
+            ProviderNetwork(provider=provider, name='Provider Network 2'),
+        )
+        ProviderNetwork.objects.bulk_create(provider_networks)
 
 
         circuits = (
         circuits = (
             Circuit(cid='Circuit 1', provider=provider, type=circuit_type),
             Circuit(cid='Circuit 1', provider=provider, type=circuit_type),
@@ -153,10 +159,10 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
         Circuit.objects.bulk_create(circuits)
         Circuit.objects.bulk_create(circuits)
 
 
         circuit_terminations = (
         circuit_terminations = (
-            CircuitTermination(circuit=circuits[0], site=sites[0], term_side=SIDE_A),
-            CircuitTermination(circuit=circuits[0], site=sites[1], term_side=SIDE_Z),
-            CircuitTermination(circuit=circuits[1], site=sites[0], term_side=SIDE_A),
-            CircuitTermination(circuit=circuits[1], site=sites[1], term_side=SIDE_Z),
+            CircuitTermination(circuit=circuits[0], term_side=SIDE_A, site=sites[0]),
+            CircuitTermination(circuit=circuits[0], term_side=SIDE_Z, provider_network=provider_networks[0]),
+            CircuitTermination(circuit=circuits[1], term_side=SIDE_A, site=sites[1]),
+            CircuitTermination(circuit=circuits[1], term_side=SIDE_Z, provider_network=provider_networks[1]),
         )
         )
         CircuitTermination.objects.bulk_create(circuit_terminations)
         CircuitTermination.objects.bulk_create(circuit_terminations)
 
 
@@ -164,13 +170,13 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase):
             {
             {
                 'circuit': circuits[2].pk,
                 'circuit': circuits[2].pk,
                 'term_side': SIDE_A,
                 'term_side': SIDE_A,
-                'site': sites[1].pk,
+                'site': sites[0].pk,
                 'port_speed': 200000,
                 'port_speed': 200000,
             },
             },
             {
             {
                 'circuit': circuits[2].pk,
                 'circuit': circuits[2].pk,
                 'term_side': SIDE_Z,
                 'term_side': SIDE_Z,
-                'site': sites[1].pk,
+                'provider_network': provider_networks[0].pk,
                 'port_speed': 200000,
                 'port_speed': 200000,
             },
             },
         ]
         ]

+ 56 - 70
netbox/dcim/filtersets.py

@@ -10,14 +10,14 @@ from tenancy.filtersets import TenancyFilterSet
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.choices import ColorChoices
 from utilities.choices import ColorChoices
 from utilities.filters import (
 from utilities.filters import (
-    MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
+    ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
+    TreeNodeMultipleChoiceFilter,
 )
 )
 from virtualization.models import Cluster
 from virtualization.models import Cluster
 from .choices import *
 from .choices import *
 from .constants import *
 from .constants import *
 from .models import *
 from .models import *
 
 
-
 __all__ = (
 __all__ = (
     'CableFilterSet',
     'CableFilterSet',
     'CableTerminationFilterSet',
     'CableTerminationFilterSet',
@@ -1184,6 +1184,10 @@ class CableFilterSet(PrimaryModelFilterSet):
         method='search',
         method='search',
         label='Search',
         label='Search',
     )
     )
+    termination_a_type = ContentTypeFilter()
+    termination_a_id = MultiValueNumberFilter()
+    termination_b_type = ContentTypeFilter()
+    termination_b_id = MultiValueNumberFilter()
     type = django_filters.MultipleChoiceFilter(
     type = django_filters.MultipleChoiceFilter(
         choices=CableTypeChoices
         choices=CableTypeChoices
     )
     )
@@ -1228,7 +1232,7 @@ class CableFilterSet(PrimaryModelFilterSet):
 
 
     class Meta:
     class Meta:
         model = Cable
         model = Cable
-        fields = ['id', 'label', 'length', 'length_unit']
+        fields = ['id', 'label', 'length', 'length_unit', 'termination_a_id', 'termination_b_id']
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):
         if not value.strip():
         if not value.strip():
@@ -1243,73 +1247,6 @@ class CableFilterSet(PrimaryModelFilterSet):
         return queryset
         return queryset
 
 
 
 
-class ConnectionFilterSet(BaseFilterSet):
-
-    def filter_site(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(device__site__slug=value)
-
-    def filter_device(self, queryset, name, value):
-        if not value:
-            return queryset
-        return queryset.filter(**{f'{name}__in': value})
-
-
-class ConsoleConnectionFilterSet(ConnectionFilterSet):
-    site = django_filters.CharFilter(
-        method='filter_site',
-        label='Site (slug)',
-    )
-    device_id = MultiValueNumberFilter(
-        method='filter_device'
-    )
-    device = MultiValueCharFilter(
-        method='filter_device',
-        field_name='device__name'
-    )
-
-    class Meta:
-        model = ConsolePort
-        fields = ['name']
-
-
-class PowerConnectionFilterSet(ConnectionFilterSet):
-    site = django_filters.CharFilter(
-        method='filter_site',
-        label='Site (slug)',
-    )
-    device_id = MultiValueNumberFilter(
-        method='filter_device'
-    )
-    device = MultiValueCharFilter(
-        method='filter_device',
-        field_name='device__name'
-    )
-
-    class Meta:
-        model = PowerPort
-        fields = ['name']
-
-
-class InterfaceConnectionFilterSet(ConnectionFilterSet):
-    site = django_filters.CharFilter(
-        method='filter_site',
-        label='Site (slug)',
-    )
-    device_id = MultiValueNumberFilter(
-        method='filter_device'
-    )
-    device = MultiValueCharFilter(
-        method='filter_device',
-        field_name='device__name'
-    )
-
-    class Meta:
-        model = Interface
-        fields = []
-
-
 class PowerPanelFilterSet(PrimaryModelFilterSet):
 class PowerPanelFilterSet(PrimaryModelFilterSet):
     q = django_filters.CharFilter(
     q = django_filters.CharFilter(
         method='search',
         method='search',
@@ -1441,3 +1378,52 @@ class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathE
             Q(comments__icontains=value)
             Q(comments__icontains=value)
         )
         )
         return queryset.filter(qs_filter)
         return queryset.filter(qs_filter)
+
+
+#
+# Connection filter sets
+#
+
+class ConnectionFilterSet(BaseFilterSet):
+    site_id = MultiValueNumberFilter(
+        method='filter_connections',
+        field_name='device__site_id'
+    )
+    site = MultiValueCharFilter(
+        method='filter_connections',
+        field_name='device__site__slug'
+    )
+    device_id = MultiValueNumberFilter(
+        method='filter_connections',
+        field_name='device_id'
+    )
+    device = MultiValueCharFilter(
+        method='filter_connections',
+        field_name='device__name'
+    )
+
+    def filter_connections(self, queryset, name, value):
+        if not value:
+            return queryset
+        return queryset.filter(**{f'{name}__in': value})
+
+
+class ConsoleConnectionFilterSet(ConnectionFilterSet):
+
+    class Meta:
+        model = ConsolePort
+        fields = ['name']
+
+
+class PowerConnectionFilterSet(ConnectionFilterSet):
+
+    class Meta:
+        model = PowerPort
+        fields = ['name']
+
+
+class InterfaceConnectionFilterSet(ConnectionFilterSet):
+
+    class Meta:
+        model = Interface
+        fields = []

+ 11 - 7
netbox/dcim/svg.py

@@ -132,14 +132,18 @@ class RackElevationSVG:
 
 
     @staticmethod
     @staticmethod
     def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
     def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
+        link_url = '{}?{}'.format(
+            reverse('dcim:device_add'),
+            urlencode({
+                'site': rack.site.pk,
+                'location': rack.location.pk if rack.location else '',
+                'rack': rack.pk,
+                'face': face_id,
+                'position': id_
+            })
+        )
         link = drawing.add(
         link = drawing.add(
-            drawing.a(
-                href='{}?{}'.format(
-                    reverse('dcim:device_add'),
-                    urlencode({'rack': rack.pk, 'site': rack.site.pk, 'face': face_id, 'position': id_})
-                ),
-                target='_top'
-            )
+            drawing.a(href=link_url, target='_top')
         )
         )
         if reservation:
         if reservation:
             link.set_desc('{} — {} · {}'.format(
             link.set_desc('{} — {} · {}'.format(

+ 27 - 9
netbox/dcim/tests/test_filtersets.py

@@ -2851,6 +2851,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
         )
         )
         Interface.objects.bulk_create(interfaces)
         Interface.objects.bulk_create(interfaces)
 
 
+        console_port = ConsolePort.objects.create(device=devices[0], name='Console Port 1')
+        console_server_port = ConsoleServerPort.objects.create(device=devices[0], name='Console Server Port 1')
+
         # Cables
         # Cables
         Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
         Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
         Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
         Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
@@ -2858,6 +2861,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
         Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
         Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
         Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
         Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
         Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
         Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
+        Cable(termination_a=console_port, termination_b=console_server_port, label='Cable 7').save()
 
 
     def test_label(self):
     def test_label(self):
         params = {'label': ['Cable 1', 'Cable 2']}
         params = {'label': ['Cable 1', 'Cable 2']}
@@ -2877,7 +2881,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
 
 
     def test_status(self):
     def test_status(self):
         params = {'status': [CableStatusChoices.STATUS_CONNECTED]}
         params = {'status': [CableStatusChoices.STATUS_CONNECTED]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
         params = {'status': [CableStatusChoices.STATUS_PLANNED]}
         params = {'status': [CableStatusChoices.STATUS_PLANNED]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
@@ -2888,30 +2892,44 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
     def test_device(self):
     def test_device(self):
         devices = Device.objects.all()[:2]
         devices = Device.objects.all()[:2]
         params = {'device_id': [devices[0].pk, devices[1].pk]}
         params = {'device_id': [devices[0].pk, devices[1].pk]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
         params = {'device': [devices[0].name, devices[1].name]}
         params = {'device': [devices[0].name, devices[1].name]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
 
 
     def test_rack(self):
     def test_rack(self):
         racks = Rack.objects.all()[:2]
         racks = Rack.objects.all()[:2]
         params = {'rack_id': [racks[0].pk, racks[1].pk]}
         params = {'rack_id': [racks[0].pk, racks[1].pk]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
         params = {'rack': [racks[0].name, racks[1].name]}
         params = {'rack': [racks[0].name, racks[1].name]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
 
 
     def test_site(self):
     def test_site(self):
         site = Site.objects.all()[:2]
         site = Site.objects.all()[:2]
         params = {'site_id': [site[0].pk, site[1].pk]}
         params = {'site_id': [site[0].pk, site[1].pk]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
         params = {'site': [site[0].slug, site[1].slug]}
         params = {'site': [site[0].slug, site[1].slug]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
 
 
     def test_tenant(self):
     def test_tenant(self):
         tenant = Tenant.objects.all()[:2]
         tenant = Tenant.objects.all()[:2]
         params = {'tenant_id': [tenant[0].pk, tenant[1].pk]}
         params = {'tenant_id': [tenant[0].pk, tenant[1].pk]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
         params = {'tenant': [tenant[0].slug, tenant[1].slug]}
         params = {'tenant': [tenant[0].slug, tenant[1].slug]}
-        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+
+    def test_termination_types(self):
+        params = {'termination_a_type': 'dcim.consoleport'}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+        params = {'termination_b_type': 'dcim.consoleserverport'}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+    def test_termination_ids(self):
+        interface_ids = Cable.objects.values_list('termination_a_id', flat=True)[:3]
+        params = {
+            'termination_a_type': 'dcim.interface',
+            'termination_a_id': list(interface_ids),
+        }
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
 
 
 
 
 class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests):
 class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests):

+ 1 - 0
netbox/dcim/views.py

@@ -1229,6 +1229,7 @@ class PlatformView(generic.ObjectView):
 
 
         return {
         return {
             'devices_table': devices_table,
             'devices_table': devices_table,
+            'virtualmachine_count': VirtualMachine.objects.filter(platform=instance).count()
         }
         }
 
 
 
 

+ 8 - 0
netbox/extras/api/serializers.py

@@ -1,3 +1,4 @@
+from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.exceptions import ObjectDoesNotExist
 from drf_yasg.utils import swagger_serializer_method
 from drf_yasg.utils import swagger_serializer_method
@@ -30,6 +31,7 @@ __all__ = (
     'ExportTemplateSerializer',
     'ExportTemplateSerializer',
     'ImageAttachmentSerializer',
     'ImageAttachmentSerializer',
     'JobResultSerializer',
     'JobResultSerializer',
+    'JournalEntrySerializer',
     'ObjectChangeSerializer',
     'ObjectChangeSerializer',
     'ReportDetailSerializer',
     'ReportDetailSerializer',
     'ReportSerializer',
     'ReportSerializer',
@@ -192,6 +194,12 @@ class JournalEntrySerializer(ValidatedModelSerializer):
         queryset=ContentType.objects.all()
         queryset=ContentType.objects.all()
     )
     )
     assigned_object = serializers.SerializerMethodField(read_only=True)
     assigned_object = serializers.SerializerMethodField(read_only=True)
+    created_by = serializers.PrimaryKeyRelatedField(
+        allow_null=True,
+        queryset=User.objects.all(),
+        required=False,
+        default=serializers.CurrentUserDefault()
+    )
     kind = ChoiceField(
     kind = ChoiceField(
         choices=JournalEntryKindChoices,
         choices=JournalEntryKindChoices,
         required=False
         required=False

+ 1 - 1
netbox/netbox/settings.py

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

Plik diff jest za duży
+ 0 - 0
netbox/project-static/dist/netbox-dark.css


Plik diff jest za duży
+ 0 - 0
netbox/project-static/dist/netbox-light.css


Plik diff jest za duży
+ 0 - 0
netbox/project-static/dist/netbox-print.css


+ 17 - 17
netbox/project-static/styles/netbox.scss

@@ -73,16 +73,6 @@
       color: color-contrast($value);
       color: color-contrast($value);
     }
     }
   }
   }
-
-  // Use proper foreground color in the alert body. Note: this is applied to p, & small because
-  // we *don't* want to override the h1-h6 colors for alerts, since those are set to a color
-  // similar to the alert color.
-  .alert.alert-#{$color} {
-    p,
-    small {
-      color: color-contrast($value);
-    }
-  }
 }
 }
 
 
 // Ensure progress bars (utilization graph) in tables aren't too narrow to display the percentage.
 // Ensure progress bars (utilization graph) in tables aren't too narrow to display the percentage.
@@ -200,16 +190,21 @@ div#advanced-search-content div.card div.card-body div.col:not(:last-child) {
 }
 }
 
 
 table {
 table {
-  a {
-    text-decoration: none;
-    &:hover {
-      text-decoration: underline;
+  td {
+    a {
+      text-decoration: none;
+      &:hover {
+        text-decoration: underline;
+      }
     }
     }
   }
   }
-  &.table > :not(caption) > * > * {
-    padding-right: $table-cell-padding-x-sm !important;
-    padding-left: $table-cell-padding-x-sm !important;
+  th {
+    a, a:hover {
+      color: $body-color;
+      text-decoration: none;
+    }
   }
   }
+
   td,
   td,
   th {
   th {
     font-size: $font-size-sm;
     font-size: $font-size-sm;
@@ -234,6 +229,11 @@ table {
     }
     }
   }
   }
 
 
+  &.table > :not(caption) > * > * {
+    padding-right: $table-cell-padding-x-sm !important;
+    padding-left: $table-cell-padding-x-sm !important;
+  }
+
   &.object-list {
   &.object-list {
     th {
     th {
       font-size: $font-size-xs;
       font-size: $font-size-xs;

+ 1 - 0
netbox/project-static/styles/select.scss

@@ -70,6 +70,7 @@ $spacing-s: $input-padding-x;
       span.arrow-down,
       span.arrow-down,
       span.arrow-up {
       span.arrow-up {
         border-color: currentColor;
         border-color: currentColor;
+        color: $text-muted;
       }
       }
     }
     }
     // Don't show the depth indicator outside of the menu.
     // Don't show the depth indicator outside of the menu.

+ 1 - 0
netbox/project-static/styles/theme-light.scss

@@ -7,6 +7,7 @@ $input-border-color: $gray-200;
 $theme-colors: map-merge(
 $theme-colors: map-merge(
   $theme-colors,
   $theme-colors,
   (
   (
+    'primary': #337ab7,
     'red': $red-500,
     'red': $red-500,
     'yellow': $yellow-500,
     'yellow': $yellow-500,
     'green': $green-500,
     'green': $green-500,

+ 2 - 2
netbox/project-static/styles/variables.scss

@@ -23,7 +23,7 @@
   --nbx-color-mode-toggle-color: #{$primary};
   --nbx-color-mode-toggle-color: #{$primary};
   --nbx-sidenav-link-color: #{$gray-800};
   --nbx-sidenav-link-color: #{$gray-800};
   --nbx-sidenav-pin-color: #{$orange};
   --nbx-sidenav-pin-color: #{$orange};
-  --nbx-sidenav-parent-color: #{$gray-900};
+  --nbx-sidenav-parent-color: #{$gray-800};
   --nbx-sidenav-group-color: #{$gray-800};
   --nbx-sidenav-group-color: #{$gray-800};
 
 
   &[data-netbox-color-mode='dark'] {
   &[data-netbox-color-mode='dark'] {
@@ -49,7 +49,7 @@
     --nbx-color-mode-toggle-color: #{$yellow-300};
     --nbx-color-mode-toggle-color: #{$yellow-300};
     --nbx-sidenav-link-color: #{$gray-200};
     --nbx-sidenav-link-color: #{$gray-200};
     --nbx-sidenav-pin-color: #{$yellow};
     --nbx-sidenav-pin-color: #{$yellow};
-    --nbx-sidenav-parent-color: #{$gray-100};
+    --nbx-sidenav-parent-color: #{$gray-200};
     --nbx-sidenav-group-color: #{$gray-600};
     --nbx-sidenav-group-color: #{$gray-600};
   }
   }
 }
 }

+ 2 - 3
netbox/templates/circuits/circuittermination_edit.html

@@ -5,7 +5,7 @@
 {% block title %}{{ obj.circuit.provider }} {{ obj.circuit }} - Side {{ form.term_side.value }}{% endblock %}
 {% block title %}{{ obj.circuit.provider }} {{ obj.circuit }} - Side {{ form.term_side.value }}{% endblock %}
 
 
 {% block form %}
 {% block form %}
-  <div class="field-group my-4">
+  <div class="field-group my-5">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Circuit Termination</h5>
       <h5 class="offset-sm-3">Circuit Termination</h5>
     </div>
     </div>
@@ -53,9 +53,8 @@
       </div>
       </div>
     {% endwith %}
     {% endwith %}
   </div>
   </div>
-  <hr />
 
 
-  <div class="field-group my-4">
+  <div class="field-group my-5">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Termination Details</h5>
       <h5 class="offset-sm-3">Termination Details</h5>
     </div>
     </div>

+ 3 - 7
netbox/templates/dcim/cable_connect.html

@@ -17,9 +17,7 @@
       <div class="row my-3">
       <div class="row my-3">
           <div class="col col-md-5">
           <div class="col col-md-5">
               <div class="card h-100">
               <div class="card h-100">
-                  <h5 class="card-header">
-                      A Side
-                  </h5>
+                  <h5 class="card-header offset-sm-3">A Side</h5>
                   <div class="card-body">
                   <div class="card-body">
                       {% if termination_a.device %}
                       {% if termination_a.device %}
                           {# Device component #}
                           {# Device component #}
@@ -100,9 +98,7 @@
           </div>
           </div>
           <div class="col col-md-5">
           <div class="col col-md-5">
               <div class="card h-100">
               <div class="card h-100">
-                  <h5 class="card-header">
-                      B Side
-                  </h5>
+                  <h5 class="card-header offset-sm-3">B Side</h5>
                   <div class="card-body">
                   <div class="card-body">
                       {% if tabs %}
                       {% if tabs %}
                           <ul class="nav nav-tabs">
                           <ul class="nav nav-tabs">
@@ -154,7 +150,7 @@
       <div class="row my-3 justify-content-center">
       <div class="row my-3 justify-content-center">
         <div class="col col-md-8">
         <div class="col col-md-8">
           <div class="card">
           <div class="card">
-            <h5 class="card-header">Cable</h5>
+            <h5 class="card-header offset-sm-3">Cable</h5>
             <div class="card-body">
             <div class="card-body">
               {% include 'dcim/inc/cable_form.html' %}
               {% include 'dcim/inc/cable_form.html' %}
             </div>
             </div>

+ 73 - 80
netbox/templates/dcim/device_edit.html

@@ -4,111 +4,104 @@
 {% block form %}
 {% block form %}
     {% render_errors form %}
     {% render_errors form %}
     
     
-    <div class="field-group my-4">
-        <div class="row mb-2">
-          <h5 class="offset-sm-3">Device</h5>
-        </div>
-        {% render_field form.name %}
-        {% render_field form.device_role %}
-        {% render_field form.tags %}
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">Device</h5>
+      </div>
+      {% render_field form.name %}
+      {% render_field form.device_role %}
+      {% render_field form.tags %}
     </div>
     </div>
-    <hr />
     
     
-    <div class="field-group my-4">
-        <div class="row mb-2">
-          <h5 class="offset-sm-3">Hardware</h5>
-        </div>
-        {% render_field form.manufacturer %}
-        {% render_field form.device_type %}
-        {% render_field form.serial %}
-        {% render_field form.asset_tag %}
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">Hardware</h5>
+      </div>
+      {% render_field form.manufacturer %}
+      {% render_field form.device_type %}
+      {% render_field form.serial %}
+      {% render_field form.asset_tag %}
     </div>
     </div>
-    <hr />
     
     
-    <div class="field-group my-4">
-        <div class="row mb-2">
-          <h5 class="offset-sm-3">Location</h5>
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">Location</h5>
+      </div>
+      {% render_field form.region %}
+      {% render_field form.site_group %}
+      {% render_field form.site %}
+      {% render_field form.location %}
+      {% render_field form.rack %}
+
+      {% if obj.device_type.is_child_device and obj.parent_bay %}
+        <div class="row mb-3">
+          <label class="col-sm-3 col-form-label">Parent Device</label>
+          <div class="col">
+            <input class="form-control" value="{{ obj.parent_bay.device }}" disabled />
+          </div>
         </div>
         </div>
-        {% render_field form.region %}
-        {% render_field form.site_group %}
-        {% render_field form.site %}
-        {% render_field form.location %}
-        {% render_field form.rack %}
-        
-        {% if obj.device_type.is_child_device and obj.parent_bay %}
-            <div class="row mb-3">
-                <label class="col-sm-3 col-form-label">Parent Device</label>
-                <div class="col">
-                    <input class="form-control" value="{{ obj.parent_bay.device }}" disabled />
-                </div>
+        <div class="row mb-3">
+          <label class="col-sm-3 col-form-label">Parent Bay</label>
+          <div class="col">
+            <div class="input-group">
+              <input class="form-control" value="{{ obj.parent_bay.name }}" disabled />
+              <a href="{% url 'dcim:devicebay_depopulate' pk=obj.parent_bay.pk %}" title="Regenerate Slug" class="btn btn-danger d-inline-flex align-items-center">
+                <i class="mdi mdi-close-thick"></i>&nbsp;Remove
+              </a>
             </div>
             </div>
-            <div class="row mb-3">
-                <label class="col-sm-3 col-form-label">Parent Bay</label>
-                <div class="col">
-                    <div class="input-group">
-                        <input class="form-control" value="{{ obj.parent_bay.name }}" disabled />
-                        <a href="{% url 'dcim:devicebay_depopulate' pk=obj.parent_bay.pk %}" title="Regenerate Slug" class="btn btn-danger d-inline-flex align-items-center">
-                            <i class="mdi mdi-close-thick"></i>&nbsp;Remove
-                        </a>
-                    </div>
-                </div>
             </div>
             </div>
-        {% else %}
-            {% render_field form.face %}
-            {% render_field form.position %}
-        {% endif %}
+          </div>
+      {% else %}
+        {% render_field form.face %}
+        {% render_field form.position %}
+      {% endif %}
     </div>
     </div>
-    <hr />
     
     
-    <div class="field-group my-4">
-        <div class="row mb-2">
-          <h5 class="offset-sm-3">Management</h5>
-        </div>
-        {% render_field form.status %}
-        {% render_field form.platform %}
-        {% if obj.pk %}
-            {% render_field form.primary_ip4 %}
-            {% render_field form.primary_ip6 %}
-        {% endif %}
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">Management</h5>
+      </div>
+      {% render_field form.status %}
+      {% render_field form.platform %}
+      {% if obj.pk %}
+        {% render_field form.primary_ip4 %}
+        {% render_field form.primary_ip6 %}
+      {% endif %}
     </div>
     </div>
-    <hr />
     
     
-    <div class="field-group my-4">
-        <div class="row mb-2">
-          <h5 class="offset-sm-3">Virtualization</h5>
-        </div>
-        {% render_field form.cluster_group %}
-        {% render_field form.cluster %}
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">Virtualization</h5>
+      </div>
+      {% render_field form.cluster_group %}
+      {% render_field form.cluster %}
     </div>
     </div>
-    <hr />
     
     
-    <div class="field-group my-4">
-        <div class="row mb-2">
-          <h5 class="offset-sm-3">Tenancy</h5>
-        </div>
-        {% render_field form.tenant_group %}
-        {% render_field form.tenant %}
+    <div class="field-group my-5">
+      <div class="row mb-2">
+        <h5 class="offset-sm-3">Tenancy</h5>
+      </div>
+      {% render_field form.tenant_group %}
+      {% render_field form.tenant %}
     </div>
     </div>
-    <hr />
 
 
     {% if form.custom_fields %}
     {% if form.custom_fields %}
-      <div class="field-group my-4">
+      <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Custom Fields</h5>
           <h5 class="offset-sm-3">Custom Fields</h5>
         </div>
         </div>
         {% render_custom_fields form %}
         {% render_custom_fields form %}
       </div>
       </div>
-      <hr />
     {% endif %}
     {% endif %}
 
 
-    <div class="field-group my-4">
-        <h5 class="text-center">Local Config Context Data</h5>
-        {% render_field form.local_context_data %}
+    <div class="field-group my-5">
+      <h5 class="text-center">Local Config Context Data</h5>
+      {% render_field form.local_context_data %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
-        {% render_field form.comments label='Comments' %}
+    <div class="field-group mb-5">
+      <h5 class="text-center">Comments</h5>
+      {% render_field form.comments %}
     </div>
     </div>
 
 
 {% endblock %}
 {% endblock %}

+ 0 - 1
netbox/templates/dcim/inc/cable_form.html

@@ -16,7 +16,6 @@
 </div>
 </div>
 {% render_field form.tags %}
 {% render_field form.tags %}
 {% if form.custom_fields %}
 {% if form.custom_fields %}
-  <hr />
   <div class="field-group">
   <div class="field-group">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Custom Fields</h5>
       <h5 class="offset-sm-3">Custom Fields</h5>

+ 3 - 5
netbox/templates/dcim/interface_edit.html

@@ -2,7 +2,7 @@
 {% load form_helpers %}
 {% load form_helpers %}
 
 
 {% block form %}
 {% block form %}
-    <div class="field-group my-4">
+    <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Interface</h5>
           <h5 class="offset-sm-3">Interface</h5>
         </div>
         </div>
@@ -27,9 +27,8 @@
         {% render_field form.mgmt_only %}
         {% render_field form.mgmt_only %}
         {% render_field form.mark_connected %}
         {% render_field form.mark_connected %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">802.1Q Switching</h5>
           <h5 class="offset-sm-3">802.1Q Switching</h5>
         </div>
         </div>
@@ -40,8 +39,7 @@
     </div>
     </div>
 
 
     {% if form.custom_fields %}
     {% if form.custom_fields %}
-      <hr />
-      <div class="field-group my-4">
+      <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Custom Fields</h5>
           <h5 class="offset-sm-3">Custom Fields</h5>
         </div>
         </div>

+ 6 - 0
netbox/templates/dcim/platform.html

@@ -46,6 +46,12 @@
               <a href="{% url 'dcim:device_list' %}?platform_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
               <a href="{% url 'dcim:device_list' %}?platform_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
             </td>
             </td>
           </tr>
           </tr>
+          <tr>
+            <th scope="row">Virtual Machines</th>
+            <td>
+              <a href="{% url 'virtualization:virtualmachine_list' %}?platform_id={{ object.pk }}">{{ virtualmachine_count }}</a>
+            </td>
+          </tr>
         </table>
         </table>
       </div>
       </div>
     </div>
     </div>

+ 11 - 15
netbox/templates/dcim/rack_edit.html

@@ -2,7 +2,7 @@
 {% load form_helpers %}
 {% load form_helpers %}
 
 
 {% block form %}
 {% block form %}
-    <div class="field-group my-4">
+    <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Rack</h5>
           <h5 class="offset-sm-3">Rack</h5>
         </div>
         </div>
@@ -15,9 +15,8 @@
         {% render_field form.role %}
         {% render_field form.role %}
         {% render_field form.tags %}
         {% render_field form.tags %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Inventory Control</h5>
           <h5 class="offset-sm-3">Inventory Control</h5>
         </div>
         </div>
@@ -25,18 +24,16 @@
         {% render_field form.serial %}
         {% render_field form.serial %}
         {% render_field form.asset_tag %}
         {% render_field form.asset_tag %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Tenancy</h5>
           <h5 class="offset-sm-3">Tenancy</h5>
         </div>
         </div>
         {% render_field form.tenant_group %}
         {% render_field form.tenant_group %}
         {% render_field form.tenant %}
         {% render_field form.tenant %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Dimensions</h5>
           <h5 class="offset-sm-3">Dimensions</h5>
         </div>
         </div>
@@ -45,34 +42,33 @@
         {% render_field form.u_height %}
         {% render_field form.u_height %}
         <div class="row mb-3">
         <div class="row mb-3">
             <label class="col col-md-3 col-form-label text-lg-end">Outer Dimensions</label>
             <label class="col col-md-3 col-form-label text-lg-end">Outer Dimensions</label>
-            <div class="col col-md-3">
+            <div class="col col-md-3 mb-1">
                 {{ form.outer_width }}
                 {{ form.outer_width }}
                 <div class="form-text">Width</div>
                 <div class="form-text">Width</div>
             </div>
             </div>
-            <div class="col col-md-3">
+            <div class="col col-md-3 mb-1">
                 {{ form.outer_depth }}
                 {{ form.outer_depth }}
                 <div class="form-text">Depth</div>
                 <div class="form-text">Depth</div>
             </div>
             </div>
-            <div class="col col-md-3">
+            <div class="col col-md-3 mb-1">
                 {{ form.outer_unit }}
                 {{ form.outer_unit }}
                 <div class="form-text">Unit</div>
                 <div class="form-text">Unit</div>
             </div>
             </div>
         </div>
         </div>
         {% render_field form.desc_units %}
         {% render_field form.desc_units %}
     </div>
     </div>
-    <hr />
 
 
     {% if form.custom_fields %}
     {% if form.custom_fields %}
-      <div class="field-group my-4">
+      <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Custom Fields</h5>
           <h5 class="offset-sm-3">Custom Fields</h5>
         </div>
         </div>
           {% render_custom_fields form %}
           {% render_custom_fields form %}
       </div>
       </div>
-      <hr />
     {% endif %}
     {% endif %}
 
 
-    <div class="field-group my-4">
-        {% render_field form.comments label='Comments' %}
+    <div class="field-group my-5">
+      <h5 class="text-center">Comments</h5>
+      {% render_field form.comments %}
     </div>
     </div>
 {% endblock %}
 {% endblock %}

+ 3 - 5
netbox/templates/dcim/virtualchassis_add.html

@@ -2,7 +2,7 @@
 {% load form_helpers %}
 {% load form_helpers %}
 
 
 {% block form %}
 {% block form %}
-  <div class="field-group my-4">
+  <div class="field-group my-5">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Virtual Chassis</h5>
       <h5 class="offset-sm-3">Virtual Chassis</h5>
     </div>
     </div>
@@ -10,9 +10,8 @@
     {% render_field form.domain %}
     {% render_field form.domain %}
     {% render_field form.tags %}
     {% render_field form.tags %}
   </div>
   </div>
-  <hr />
 
 
-  <div class="field-group my-4">
+  <div class="field-group my-5">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Member Devices</h5>
       <h5 class="offset-sm-3">Member Devices</h5>
     </div>
     </div>
@@ -25,8 +24,7 @@
   </div>
   </div>
 
 
   {% if form.custom_fields %}
   {% if form.custom_fields %}
-    <hr />
-    <div class="field-group my-4">
+    <div class="field-group my-5">
       <div class="row mb-2">
       <div class="row mb-2">
         <h5 class="offset-sm-3">Custom Fields</h5>
         <h5 class="offset-sm-3">Custom Fields</h5>
       </div>
       </div>

+ 2 - 4
netbox/templates/dcim/virtualchassis_edit.html

@@ -11,7 +11,7 @@
         {% csrf_token %}
         {% csrf_token %}
         {{ pk_form.pk }}
         {{ pk_form.pk }}
         {{ formset.management_form }}
         {{ formset.management_form }}
-        <div class="field-group my-4">
+        <div class="field-group my-5">
           <div class="row mb-2">
           <div class="row mb-2">
             <h5 class="offset-sm-3">Virtual Chassis</h5>
             <h5 class="offset-sm-3">Virtual Chassis</h5>
           </div>
           </div>
@@ -20,16 +20,14 @@
           {% render_field vc_form.master %}
           {% render_field vc_form.master %}
           {% render_field vc_form.tags %}
           {% render_field vc_form.tags %}
         </div>
         </div>
-        <hr />
 
 
         {% if vc_form.custom_fields %}
         {% if vc_form.custom_fields %}
-          <div class="field-group my-4">
+          <div class="field-group my-5">
             <div class="row mb-2">
             <div class="row mb-2">
               <h5 class="offset-sm-3">Custom Fields</h5>
               <h5 class="offset-sm-3">Custom Fields</h5>
             </div>
             </div>
             {% render_custom_fields vc_form %}
             {% render_custom_fields vc_form %}
           </div>
           </div>
-          <hr />
         {% endif %}
         {% endif %}
 
 
         <div class="field-group mb-5">
         <div class="field-group mb-5">

+ 6 - 0
netbox/templates/generic/object.html

@@ -8,6 +8,12 @@
 {% block header %}
 {% block header %}
   {# Breadcrumbs #}
   {# Breadcrumbs #}
   <nav class="breadcrumb-container px-3" aria-label="breadcrumb">
   <nav class="breadcrumb-container px-3" aria-label="breadcrumb">
+    <div class="float-end">
+      <code class="text-muted" title="Object type and ID">
+        {{ object|meta:"app_label" }}.{{ object|meta:"model_name" }}:{{ object.pk }}
+        {% if object.slug %}({{ object.slug }}){% endif %}
+      </code>
+    </div>
     <ol class="breadcrumb">
     <ol class="breadcrumb">
       {% block breadcrumbs %}
       {% block breadcrumbs %}
         <li class="breadcrumb-item"><a href="{% url object|viewname:'list' %}">{{ object|meta:'verbose_name_plural'|bettertitle }}</a></li>
         <li class="breadcrumb-item"><a href="{% url object|viewname:'list' %}">{{ object|meta:'verbose_name_plural'|bettertitle }}</a></li>

+ 16 - 22
netbox/templates/generic/object_edit.html

@@ -6,18 +6,6 @@
   {% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}
   {% if obj.pk %}Editing {{ obj_type }} {{ obj }}{% else %}Add a new {{ obj_type }}{% endif %}
 {% endblock title %}
 {% endblock title %}
 
 
-{% block controls %}
-  {% if obj and settings.DOCS_ROOT %}
-    <div class="controls">
-      <div class="control-group">
-        <a href="{{ obj|get_docs_url }}" target="_blank" class="btn btn-sm btn-outline-secondary" title="View model documentation">
-          <i class="mdi mdi-help-circle"></i> Help
-        </a>
-      </div>
-    </div>
-  {% endif %}
-{% endblock controls %}
-
 {% block tabs %}
 {% block tabs %}
   <ul class="nav nav-tabs px-3">
   <ul class="nav nav-tabs px-3">
     <li class="nav-item" role="presentation">
     <li class="nav-item" role="presentation">
@@ -31,6 +19,16 @@
 {% block content-wrapper %}
 {% block content-wrapper %}
   <div class="tab-content">
   <div class="tab-content">
     <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
     <div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
+
+      {# Link to model documentation #}
+      {% if obj and settings.DOCS_ROOT %}
+        <div class="float-end">
+          <a href="{{ obj|get_docs_url }}" target="_blank" class="btn btn-sm btn-outline-secondary" title="View model documentation">
+            <i class="mdi mdi-help-circle"></i> Help
+          </a>
+        </div>
+      {% endif %}
+
       <form action="" method="post" enctype="multipart/form-data" class="form-object-edit">
       <form action="" method="post" enctype="multipart/form-data" class="form-object-edit">
         {% csrf_token %}
         {% csrf_token %}
         {% for field in form.hidden_fields %}
         {% for field in form.hidden_fields %}
@@ -42,7 +40,7 @@
 
 
             {# Render grouped fields according to Form #}
             {# Render grouped fields according to Form #}
             {% for group, fields in form.Meta.fieldsets %}
             {% for group, fields in form.Meta.fieldsets %}
-              <div class="field-group my-4">
+              <div class="field-group my-5">
                 <div class="row mb-2">
                 <div class="row mb-2">
                   <h5 class="offset-sm-3">{{ group }}</h5>
                   <h5 class="offset-sm-3">{{ group }}</h5>
                 </div>
                 </div>
@@ -50,14 +48,10 @@
                     {% render_field form|getfield:name %}
                     {% render_field form|getfield:name %}
                 {% endfor %}
                 {% endfor %}
               </div>
               </div>
-              {% if not forloop.last %}
-                <hr />
-              {% endif %}
             {% endfor %}
             {% endfor %}
 
 
             {% if form.custom_fields %}
             {% if form.custom_fields %}
-              <hr />
-              <div class="field-group my-4">
+              <div class="field-group my-5">
                 <div class="row mb-2">
                 <div class="row mb-2">
                   <h5 class="offset-sm-3">Custom Fields</h5>
                   <h5 class="offset-sm-3">Custom Fields</h5>
                 </div>
                 </div>
@@ -66,15 +60,15 @@
             {% endif %}
             {% endif %}
 
 
             {% if form.comments %}
             {% if form.comments %}
-              <hr />
-              <div class="field-group my-4">
-                {% render_field form.comments label='Comments' %}
+              <div class="field-group my-5">
+                <h5 class="text-center">Comments</h5>
+                {% render_field form.comments %}
               </div>
               </div>
             {% endif %}
             {% endif %}
 
 
           {% else %}
           {% else %}
             {# Render all fields in a single group #}
             {# Render all fields in a single group #}
-            <div class="field-group my-4">
+            <div class="field-group my-5">
               {% block form_fields %}{% render_form form %}{% endblock %}
               {% block form_fields %}{% render_form form %}{% endblock %}
             </div>
             </div>
           {% endif %}
           {% endif %}

+ 3 - 1
netbox/templates/generic/object_list.html

@@ -4,6 +4,8 @@
 {% load render_table from django_tables2 %}
 {% load render_table from django_tables2 %}
 {% load static %}
 {% load static %}
 
 
+{% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
+
 {% block controls %}
 {% block controls %}
   <div class="controls">
   <div class="controls">
     <div class="control-group">
     <div class="control-group">
@@ -26,7 +28,7 @@
     {% block tab_items %}
     {% block tab_items %}
       <li class="nav-item" role="presentation">
       <li class="nav-item" role="presentation">
         <button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
         <button class="nav-link active" id="object-list-tab" data-bs-toggle="tab" data-bs-target="#object-list" type="button" role="tab" aria-controls="edit-form" aria-selected="true">
-          {% block title %}{{ content_type.model_class|meta:"verbose_name_plural"|bettertitle }}{% endblock %}
+          Records
           {% badge table.page.paginator.count %}
           {% badge table.page.paginator.count %}
         </button>
         </button>
       </li>
       </li>

+ 2 - 2
netbox/templates/home.html

@@ -30,7 +30,7 @@
       {% for section, items, icon in stats %}
       {% for section, items, icon in stats %}
         <div class="col col-sm-12 col-lg-6 col-xl-4 my-2 masonry-item">
         <div class="col col-sm-12 col-lg-6 col-xl-4 my-2 masonry-item">
           <div class="card">
           <div class="card">
-            <h6 class="card-header text-primary text-center">
+            <h6 class="card-header text-center">
               <i class="mdi mdi-{{ icon }}"></i>
               <i class="mdi mdi-{{ icon }}"></i>
               <span class="ms-1">{{ section }}</span>
               <span class="ms-1">{{ section }}</span>
             </h6>
             </h6>
@@ -67,7 +67,7 @@
       <div class="row my-4 flex-grow-1 changelog-container">
       <div class="row my-4 flex-grow-1 changelog-container">
         <div class="col">
         <div class="col">
           <div class="card">
           <div class="card">
-            <h6 class="card-header text-primary text-center">
+            <h6 class="card-header text-center">
               <i class="mdi mdi-clipboard-clock"></i>
               <i class="mdi mdi-clipboard-clock"></i>
               <span class="ms-1">Change Log</span>
               <span class="ms-1">Change Log</span>
             </h6>
             </h6>

+ 2 - 2
netbox/templates/inc/table_controls.html

@@ -4,8 +4,8 @@
       <input
       <input
         type="text"
         type="text"
         class="form-control object-filter"
         class="form-control object-filter"
-        placeholder="Filter"
-        title="Filter text (regular expressions supported)"
+        placeholder="Quick find"
+        title="Find in the results below (regular expressions supported)"
       />
       />
     </div>
     </div>
   </div>
   </div>

+ 3 - 5
netbox/templates/ipam/ipaddress_bulk_add.html

@@ -9,7 +9,7 @@
 {% endblock %}
 {% endblock %}
 
 
 {% block form %}
 {% block form %}
-    <div class="field-group my-4">
+    <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">IP Addresses</h5>
           <h5 class="offset-sm-3">IP Addresses</h5>
         </div>
         </div>
@@ -20,9 +20,8 @@
         {% render_field model_form.description %}
         {% render_field model_form.description %}
         {% render_field model_form.tags %}
         {% render_field model_form.tags %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Tenancy</h5>
           <h5 class="offset-sm-3">Tenancy</h5>
         </div>
         </div>
@@ -30,8 +29,7 @@
         {% render_field model_form.tenant %}
         {% render_field model_form.tenant %}
     </div>
     </div>
     {% if model_form.custom_fields %}
     {% if model_form.custom_fields %}
-        <hr />
-        <div class="field-group my-4">
+        <div class="field-group my-5">
             <div class="row mb-2">
             <div class="row mb-2">
               <h5 class="offset-sm-3">Custom Fields</h5>
               <h5 class="offset-sm-3">Custom Fields</h5>
             </div>
             </div>

+ 5 - 9
netbox/templates/ipam/ipaddress_edit.html

@@ -8,7 +8,7 @@
 {% endblock tabs %}
 {% endblock tabs %}
 
 
 {% block form %}
 {% block form %}
-    <div class="field-group my-4">
+    <div class="field-group my-5">
       <div class="row mb-2">
       <div class="row mb-2">
         <h5 class="offset-sm-3">IP Address</h5>
         <h5 class="offset-sm-3">IP Address</h5>
       </div>
       </div>
@@ -20,18 +20,16 @@
       {% render_field form.description %}
       {% render_field form.description %}
       {% render_field form.tags %}
       {% render_field form.tags %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
       <div class="row mb-2">
       <div class="row mb-2">
         <h5 class="offset-sm-3">Tenancy</h5>
         <h5 class="offset-sm-3">Tenancy</h5>
       </div>
       </div>
       {% render_field form.tenant_group %}
       {% render_field form.tenant_group %}
       {% render_field form.tenant %}
       {% render_field form.tenant %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
       <div class="row mb-2">
       <div class="row mb-2">
         <h5 class="offset-sm-3">Interface Assignment</h5>
         <h5 class="offset-sm-3">Interface Assignment</h5>
       </div>
       </div>
@@ -81,9 +79,8 @@
         </div>
         </div>
       {% endwith %}
       {% endwith %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
       <div class="row mb-2">
       <div class="row mb-2">
         <h5 class="offset-sm-3">NAT IP (Inside)</h5>
         <h5 class="offset-sm-3">NAT IP (Inside)</h5>
       </div>
       </div>
@@ -152,8 +149,7 @@
     </div>
     </div>
 
 
     {% if form.custom_fields %}
     {% if form.custom_fields %}
-      <hr />
-      <div class="field-group my-4">
+      <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Custom Fields</h5>
           <h5 class="offset-sm-3">Custom Fields</h5>
         </div>
         </div>

+ 1 - 2
netbox/templates/ipam/service_edit.html

@@ -2,7 +2,7 @@
 {% load form_helpers %}
 {% load form_helpers %}
 
 
 {% block form %}
 {% block form %}
-  <div class="field-group my-4">
+  <div class="field-group my-5">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Service</h5>
       <h5 class="offset-sm-3">Service</h5>
     </div>
     </div>
@@ -43,7 +43,6 @@
   </div>
   </div>
 
 
   {% if form.custom_fields %}
   {% if form.custom_fields %}
-    <hr />
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Custom Fields</h5>
       <h5 class="offset-sm-3">Custom Fields</h5>
     </div>
     </div>

+ 4 - 7
netbox/templates/ipam/vlan_edit.html

@@ -4,7 +4,7 @@
 {% load helpers %}
 {% load helpers %}
 
 
 {% block form %}
 {% block form %}
-  <div class="field-group my-4">
+  <div class="field-group my-5">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">VLAN</h5>
       <h5 class="offset-sm-3">VLAN</h5>
     </div>
     </div>
@@ -15,18 +15,16 @@
     {% render_field form.description %}
     {% render_field form.description %}
     {% render_field form.tags %}
     {% render_field form.tags %}
   </div>
   </div>
-  <hr />
 
 
-  <div class="field-group my-4">
+  <div class="field-group my-5">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Tenancy</h5>
       <h5 class="offset-sm-3">Tenancy</h5>
     </div>
     </div>
     {% render_field form.tenant_group %}
     {% render_field form.tenant_group %}
     {% render_field form.tenant %}
     {% render_field form.tenant %}
   </div>
   </div>
-  <hr />
 
 
-  <div class="field-group my-4">
+  <div class="field-group my-5">
     <div class="row mb-2">
     <div class="row mb-2">
       <h5 class="offset-sm-3">Assignment</h5>
       <h5 class="offset-sm-3">Assignment</h5>
     </div>
     </div>
@@ -58,8 +56,7 @@
   </div>
   </div>
 
 
   {% if form.custom_fields %}
   {% if form.custom_fields %}
-    <hr />
-    <div class="field-group my-4">
+    <div class="field-group my-5">
       <div class="row mb-2">
       <div class="row mb-2">
         <h5 class="offset-sm-3">Custom Fields</h5>
         <h5 class="offset-sm-3">Custom Fields</h5>
       </div>
       </div>

+ 1 - 1
netbox/templates/tenancy/tenantgroup.html

@@ -37,7 +37,7 @@
             </td>
             </td>
           </tr>
           </tr>
           <tr>
           <tr>
-            <th scope="row">Sites</th>
+            <th scope="row">Tenants</th>
             <td>
             <td>
               <a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}">{{ tenants_table.rows|length }}</a>
               <a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}">{{ tenants_table.rows|length }}</a>
             </td>
             </td>

+ 3 - 5
netbox/templates/virtualization/vminterface_edit.html

@@ -2,7 +2,7 @@
 {% load form_helpers %}
 {% load form_helpers %}
 
 
 {% block form %}
 {% block form %}
-    <div class="field-group my-4">
+    <div class="field-group my-5">
       <div class="row mb-2">
       <div class="row mb-2">
         <h5 class="offset-sm-3">Interface</h5>
         <h5 class="offset-sm-3">Interface</h5>
       </div>
       </div>
@@ -22,9 +22,8 @@
       {% render_field form.description %}
       {% render_field form.description %}
       {% render_field form.tags %}
       {% render_field form.tags %}
     </div>
     </div>
-    <hr />
 
 
-    <div class="field-group my-4">
+    <div class="field-group my-5">
       <div class="row mb-2">
       <div class="row mb-2">
         <h5 class="offset-sm-3">802.1Q Switching</h5>
         <h5 class="offset-sm-3">802.1Q Switching</h5>
       </div>
       </div>
@@ -35,8 +34,7 @@
     </div>
     </div>
 
 
     {% if form.custom_fields %}
     {% if form.custom_fields %}
-      <hr />
-      <div class="field-group my-4">
+      <div class="field-group my-5">
         <div class="row mb-2">
         <div class="row mb-2">
           <h5 class="offset-sm-3">Custom Fields</h5>
           <h5 class="offset-sm-3">Custom Fields</h5>
         </div>
         </div>

+ 11 - 9
netbox/utilities/tables.py

@@ -57,14 +57,14 @@ class BaseTable(tables.Table):
         if user is not None and not isinstance(user, AnonymousUser):
         if user is not None and not isinstance(user, AnonymousUser):
             selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
             selected_columns = user.config.get(f"tables.{self.__class__.__name__}.columns")
             if selected_columns:
             if selected_columns:
-                pk = self.base_columns.pop('pk', None)
-                actions = self.base_columns.pop('actions', None)
 
 
+                # Show only persistent or selected columns
                 for name, column in self.columns.items():
                 for name, column in self.columns.items():
-                    if name in selected_columns:
+                    if name in ['pk', 'actions', *selected_columns]:
                         self.columns.show(name)
                         self.columns.show(name)
                     else:
                     else:
                         self.columns.hide(name)
                         self.columns.hide(name)
+
                 # Rearrange the sequence to list selected columns first, followed by all remaining columns
                 # Rearrange the sequence to list selected columns first, followed by all remaining columns
                 # TODO: There's probably a more clever way to accomplish this
                 # TODO: There's probably a more clever way to accomplish this
                 self.sequence = [
                 self.sequence = [
@@ -72,12 +72,14 @@ class BaseTable(tables.Table):
                     *[c for c in self.columns.names() if c not in selected_columns]
                     *[c for c in self.columns.names() if c not in selected_columns]
                 ]
                 ]
 
 
-                # Always include PK and actions column, if defined on the table
-                if pk:
-                    self.base_columns['pk'] = pk
+                # PK column should always come first
+                if 'pk' in self.sequence:
+                    self.sequence.remove('pk')
                     self.sequence.insert(0, 'pk')
                     self.sequence.insert(0, 'pk')
-                if actions:
-                    self.base_columns['actions'] = actions
+
+                # Actions column should always come last
+                if 'actions' in self.sequence:
+                    self.sequence.remove('actions')
                     self.sequence.append('actions')
                     self.sequence.append('actions')
 
 
         # Dynamically update the table's QuerySet to ensure related fields are pre-fetched
         # Dynamically update the table's QuerySet to ensure related fields are pre-fetched
@@ -128,7 +130,7 @@ class BaseTable(tables.Table):
         prefixes/IP addresses/etc., where some table rows may represent available address space.
         prefixes/IP addresses/etc., where some table rows may represent available address space.
         """
         """
         if not hasattr(self, '_objects_count'):
         if not hasattr(self, '_objects_count'):
-            self._objects_count = sum(1 for obj in self.data if getattr(obj, 'pk'))
+            self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk'))
         return self._objects_count
         return self._objects_count
 
 
 
 

+ 4 - 4
requirements.txt

@@ -1,5 +1,5 @@
-Django==3.2.7
-django-cors-headers==3.9.0
+Django==3.2.8
+django-cors-headers==3.10.0
 django-debug-toolbar==3.2.2
 django-debug-toolbar==3.2.2
 django-filter==21.1
 django-filter==21.1
 django-graphiql-debug-toolbar==0.2.0
 django-graphiql-debug-toolbar==0.2.0
@@ -8,14 +8,14 @@ django-pglocks==1.0.4
 django-prometheus==2.1.0
 django-prometheus==2.1.0
 django-redis==5.0.0
 django-redis==5.0.0
 django-rq==2.4.1
 django-rq==2.4.1
-django-tables2==2.4.0
+django-tables2==2.4.1
 django-taggit==1.5.1
 django-taggit==1.5.1
 django-timezone-field==4.2.1
 django-timezone-field==4.2.1
 djangorestframework==3.12.4
 djangorestframework==3.12.4
 drf-yasg[validation]==1.20.0
 drf-yasg[validation]==1.20.0
 graphene_django==2.15.0
 graphene_django==2.15.0
 gunicorn==20.1.0
 gunicorn==20.1.0
-Jinja2==3.0.1
+Jinja2==3.0.2
 Markdown==3.3.4
 Markdown==3.3.4
 markdown-include==0.6.0
 markdown-include==0.6.0
 mkdocs-material==7.3.1
 mkdocs-material==7.3.1

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików