diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 26ece9049..7b54d9248 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.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 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.) - placeholder: v3.0.5 + placeholder: v3.0.6 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index ecbef8b09..7af2cf0b8 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.0.5 + placeholder: v3.0.6 validations: required: true - type: dropdown diff --git a/docs/administration/housekeeping.md b/docs/administration/housekeeping.md index c562613eb..9a3444ca0 100644 --- a/docs/administration/housekeeping.md +++ b/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 * 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. diff --git a/docs/installation/3-netbox.md b/docs/installation/3-netbox.md index d20bfaf6b..87a64b325 100644 --- a/docs/installation/3-netbox.md +++ b/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. -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 -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. diff --git a/docs/installation/upgrading.md b/docs/installation/upgrading.md index cd14bf8f0..6f199f9b0 100644 --- a/docs/installation/upgrading.md +++ b/docs/installation/upgrading.md @@ -111,10 +111,10 @@ sudo systemctl restart netbox netbox-rq ## 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 -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. diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 978c860ea..41c551d9c 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -1,5 +1,24 @@ # 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) ### Enhancements @@ -8,7 +27,6 @@ * [#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 * [#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 @@ -23,6 +41,7 @@ * [#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 * [#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 --- diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 014ec0fc8..ac6285610 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -3,10 +3,10 @@ from rest_framework import serializers from circuits.choices import CircuitStatusChoices from circuits.models import * 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.serializers import ( - BaseModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer + OrganizationalModelSerializer, PrimaryModelSerializer, ValidatedModelSerializer, WritableNestedSerializer ) from tenancy.api.nested_serializers import NestedTenantSerializer 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') 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) class Meta: diff --git a/netbox/circuits/tests/test_api.py b/netbox/circuits/tests/test_api.py index 424b13d40..830c7d9ca 100644 --- a/netbox/circuits/tests/test_api.py +++ b/netbox/circuits/tests/test_api.py @@ -136,14 +136,20 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase): SIDE_A = CircuitTerminationSideChoices.SIDE_A 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 = ( Site(name='Site 1', slug='site-1'), Site(name='Site 2', slug='site-2'), ) 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 = ( Circuit(cid='Circuit 1', provider=provider, type=circuit_type), @@ -153,10 +159,10 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase): Circuit.objects.bulk_create(circuits) 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) @@ -164,13 +170,13 @@ class CircuitTerminationTest(APIViewTestCases.APIViewTestCase): { 'circuit': circuits[2].pk, 'term_side': SIDE_A, - 'site': sites[1].pk, + 'site': sites[0].pk, 'port_speed': 200000, }, { 'circuit': circuits[2].pk, 'term_side': SIDE_Z, - 'site': sites[1].pk, + 'provider_network': provider_networks[0].pk, 'port_speed': 200000, }, ] diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index 02749ba1c..6f2c23c90 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -10,14 +10,14 @@ from tenancy.filtersets import TenancyFilterSet from tenancy.models import Tenant from utilities.choices import ColorChoices from utilities.filters import ( - MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter, + ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, + TreeNodeMultipleChoiceFilter, ) from virtualization.models import Cluster from .choices import * from .constants import * from .models import * - __all__ = ( 'CableFilterSet', 'CableTerminationFilterSet', @@ -1184,6 +1184,10 @@ class CableFilterSet(PrimaryModelFilterSet): method='search', label='Search', ) + termination_a_type = ContentTypeFilter() + termination_a_id = MultiValueNumberFilter() + termination_b_type = ContentTypeFilter() + termination_b_id = MultiValueNumberFilter() type = django_filters.MultipleChoiceFilter( choices=CableTypeChoices ) @@ -1228,7 +1232,7 @@ class CableFilterSet(PrimaryModelFilterSet): class Meta: 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): if not value.strip(): @@ -1243,73 +1247,6 @@ class CableFilterSet(PrimaryModelFilterSet): 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): q = django_filters.CharFilter( method='search', @@ -1441,3 +1378,52 @@ class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathE Q(comments__icontains=value) ) 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 = [] diff --git a/netbox/dcim/svg.py b/netbox/dcim/svg.py index 2064734ad..f8e7bdf10 100644 --- a/netbox/dcim/svg.py +++ b/netbox/dcim/svg.py @@ -132,14 +132,18 @@ class RackElevationSVG: @staticmethod 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( - 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: link.set_desc('{} — {} · {}'.format( diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 2afe77638..fb94bde08 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -2851,6 +2851,9 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): ) 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 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() @@ -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[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=console_port, termination_b=console_server_port, label='Cable 7').save() def test_label(self): params = {'label': ['Cable 1', 'Cable 2']} @@ -2877,7 +2881,7 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): def test_status(self): 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]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) @@ -2888,30 +2892,44 @@ class CableTestCase(TestCase, ChangeLoggedFilterSetTests): def test_device(self): devices = Device.objects.all()[:2] 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]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) def test_rack(self): racks = Rack.objects.all()[:2] 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]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) def test_site(self): site = Site.objects.all()[:2] 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]} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5) + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) def test_tenant(self): tenant = Tenant.objects.all()[:2] 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]} - 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): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index a82d7dadf..16f88b9c3 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -1229,6 +1229,7 @@ class PlatformView(generic.ObjectView): return { 'devices_table': devices_table, + 'virtualmachine_count': VirtualMachine.objects.filter(platform=instance).count() } diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 536df1c75..b2049e836 100644 --- a/netbox/extras/api/serializers.py +++ b/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.core.exceptions import ObjectDoesNotExist from drf_yasg.utils import swagger_serializer_method @@ -30,6 +31,7 @@ __all__ = ( 'ExportTemplateSerializer', 'ImageAttachmentSerializer', 'JobResultSerializer', + 'JournalEntrySerializer', 'ObjectChangeSerializer', 'ReportDetailSerializer', 'ReportSerializer', @@ -192,6 +194,12 @@ class JournalEntrySerializer(ValidatedModelSerializer): queryset=ContentType.objects.all() ) 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( choices=JournalEntryKindChoices, required=False diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 66dbdea84..51f7265c5 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -16,7 +16,7 @@ from django.core.validators import URLValidator # Environment setup # -VERSION = '3.0.6-dev' +VERSION = '3.0.7-dev' # Hostname HOSTNAME = platform.node() diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css index 7104afe1c..48745de12 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 c9b3b9453..6ca1d7884 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 b8157e2e3..a159c81ec 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/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index e7b67a3e4..bd081f569 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -73,16 +73,6 @@ 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. @@ -200,16 +190,21 @@ div#advanced-search-content div.card div.card-body div.col:not(:last-child) { } 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, th { 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 { th { font-size: $font-size-xs; diff --git a/netbox/project-static/styles/select.scss b/netbox/project-static/styles/select.scss index e306e7c69..675b0e722 100644 --- a/netbox/project-static/styles/select.scss +++ b/netbox/project-static/styles/select.scss @@ -70,6 +70,7 @@ $spacing-s: $input-padding-x; span.arrow-down, span.arrow-up { border-color: currentColor; + color: $text-muted; } } // Don't show the depth indicator outside of the menu. diff --git a/netbox/project-static/styles/theme-light.scss b/netbox/project-static/styles/theme-light.scss index d4b9303bd..c14f7f314 100644 --- a/netbox/project-static/styles/theme-light.scss +++ b/netbox/project-static/styles/theme-light.scss @@ -7,6 +7,7 @@ $input-border-color: $gray-200; $theme-colors: map-merge( $theme-colors, ( + 'primary': #337ab7, 'red': $red-500, 'yellow': $yellow-500, 'green': $green-500, diff --git a/netbox/project-static/styles/variables.scss b/netbox/project-static/styles/variables.scss index 9b03d460c..ddeb6025a 100644 --- a/netbox/project-static/styles/variables.scss +++ b/netbox/project-static/styles/variables.scss @@ -23,7 +23,7 @@ --nbx-color-mode-toggle-color: #{$primary}; --nbx-sidenav-link-color: #{$gray-800}; --nbx-sidenav-pin-color: #{$orange}; - --nbx-sidenav-parent-color: #{$gray-900}; + --nbx-sidenav-parent-color: #{$gray-800}; --nbx-sidenav-group-color: #{$gray-800}; &[data-netbox-color-mode='dark'] { @@ -49,7 +49,7 @@ --nbx-color-mode-toggle-color: #{$yellow-300}; --nbx-sidenav-link-color: #{$gray-200}; --nbx-sidenav-pin-color: #{$yellow}; - --nbx-sidenav-parent-color: #{$gray-100}; + --nbx-sidenav-parent-color: #{$gray-200}; --nbx-sidenav-group-color: #{$gray-600}; } } diff --git a/netbox/templates/circuits/circuittermination_edit.html b/netbox/templates/circuits/circuittermination_edit.html index 76ef4a5d2..9ba41216d 100644 --- a/netbox/templates/circuits/circuittermination_edit.html +++ b/netbox/templates/circuits/circuittermination_edit.html @@ -5,7 +5,7 @@ {% block title %}{{ obj.circuit.provider }} {{ obj.circuit }} - Side {{ form.term_side.value }}{% endblock %} {% block form %} -
+
Circuit Termination
@@ -53,9 +53,8 @@
{% endwith %}
-
-
+
Termination Details
diff --git a/netbox/templates/dcim/cable_connect.html b/netbox/templates/dcim/cable_connect.html index 83891bc21..03dcaa2e4 100644 --- a/netbox/templates/dcim/cable_connect.html +++ b/netbox/templates/dcim/cable_connect.html @@ -17,9 +17,7 @@
-
- A Side -
+
A Side
{% if termination_a.device %} {# Device component #} @@ -100,9 +98,7 @@
-
- B Side -
+
B Side
{% if tabs %}