diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 17533e2c9..594f23f9a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -14,7 +14,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.1.4 + placeholder: v3.1.5 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 1c31f0c29..b1193ae02 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.1.4 + placeholder: v3.1.5 validations: required: true - type: dropdown diff --git a/docs/release-notes/version-3.1.md b/docs/release-notes/version-3.1.md index 670cc4cce..eb2a8c9dd 100644 --- a/docs/release-notes/version-3.1.md +++ b/docs/release-notes/version-3.1.md @@ -1,5 +1,23 @@ # NetBox v3.1 +## v3.1.5 (2022-01-06) + +### Enhancements + +* [#8231](https://github.com/netbox-community/netbox/issues/8231) - Use in-page dialogs for confirming object deletion +* [#8244](https://github.com/netbox-community/netbox/issues/8244) - Add length & length unit fields to cable filter form +* [#8252](https://github.com/netbox-community/netbox/issues/8252) - Linkify type and group columns in clusters table + +### Bug Fixes + +* [#8213](https://github.com/netbox-community/netbox/issues/8213) - Fix ValueError exception under prefix IP addresses view +* [#8224](https://github.com/netbox-community/netbox/issues/8224) - Fix KeyError exception when creating FHRP group with IP address and protocol "other" +* [#8226](https://github.com/netbox-community/netbox/issues/8226) - Honor return URL after populating a device bay +* [#8228](https://github.com/netbox-community/netbox/issues/8228) - Optional ChoiceVar fields should not force a selection +* [#8255](https://github.com/netbox-community/netbox/issues/8255) - Fix bulk editing of authentication parameters for wireless LANs and links + +--- + ## v3.1.4 (2022-01-03) ### Enhancements diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index 002f12916..cb9575d42 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -578,7 +578,7 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm): field_groups = [ ['q', 'tag'], ['site_id', 'rack_id', 'device_id'], - ['type', 'status', 'color'], + ['type', 'status', 'color', 'length', 'length_unit'], ['tenant_group_id', 'tenant_id'], ] region_id = DynamicModelMultipleChoiceField( @@ -603,6 +603,16 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm): 'site_id': '$site_id' } ) + device_id = DynamicModelMultipleChoiceField( + queryset=Device.objects.all(), + required=False, + query_params={ + 'site_id': '$site_id', + 'tenant_id': '$tenant_id', + 'rack_id': '$rack_id', + }, + label=_('Device') + ) type = forms.MultipleChoiceField( choices=add_blank_choice(CableTypeChoices), required=False, @@ -616,15 +626,12 @@ class CableFilterForm(TenancyFilterForm, CustomFieldModelFilterForm): color = ColorField( required=False ) - device_id = DynamicModelMultipleChoiceField( - queryset=Device.objects.all(), - required=False, - query_params={ - 'site_id': '$site_id', - 'tenant_id': '$tenant_id', - 'rack_id': '$rack_id', - }, - label=_('Device') + length = forms.IntegerField( + required=False + ) + length_unit = forms.ChoiceField( + choices=add_blank_choice(CableLengthUnitChoices), + required=False ) tag = TagFilterField(model) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 7048ae63e..cee516f5c 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -2035,8 +2035,9 @@ class DeviceBayPopulateView(generic.ObjectEditView): device_bay.installed_device = form.cleaned_data['installed_device'] device_bay.save() messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay)) + return_url = self.get_return_url(request) - return redirect('dcim:device', pk=device_bay.device.pk) + return redirect(return_url) return render(request, 'dcim/devicebay_populate.html', { 'device_bay': device_bay, diff --git a/netbox/extras/forms/models.py b/netbox/extras/forms/models.py index 1e619ebec..89ab7aa19 100644 --- a/netbox/extras/forms/models.py +++ b/netbox/extras/forms/models.py @@ -7,8 +7,8 @@ from extras.models import * from extras.utils import FeatureQuery from tenancy.models import Tenant, TenantGroup from utilities.forms import ( - add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, - ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect, + add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, + DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect, ) from virtualization.models import Cluster, ClusterGroup @@ -41,6 +41,10 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm): ('Values', ('default', 'choices')), ('Validation', ('validation_minimum', 'validation_maximum', 'validation_regex')), ) + widgets = { + 'type': StaticSelect(), + 'filter_logic': StaticSelect(), + } class CustomLinkForm(BootstrapMixin, forms.ModelForm): @@ -57,6 +61,7 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm): ('Templates', ('link_text', 'link_url')), ) widgets = { + 'button_class': StaticSelect(), 'link_text': forms.Textarea(attrs={'class': 'font-monospace'}), 'link_url': forms.Textarea(attrs={'class': 'font-monospace'}), } @@ -96,8 +101,7 @@ class WebhookForm(BootstrapMixin, forms.ModelForm): model = Webhook fields = '__all__' fieldsets = ( - ('Webhook', ('name', 'enabled')), - ('Assigned Models', ('content_types',)), + ('Webhook', ('name', 'content_types', 'enabled')), ('Events', ('type_create', 'type_update', 'type_delete')), ('HTTP Request', ( 'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret', @@ -105,7 +109,13 @@ class WebhookForm(BootstrapMixin, forms.ModelForm): ('Conditions', ('conditions',)), ('SSL', ('ssl_verification', 'ca_file_path')), ) + labels = { + 'type_create': 'Creations', + 'type_update': 'Updates', + 'type_delete': 'Deletions', + } widgets = { + 'http_method': StaticSelect(), 'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}), 'body_template': forms.Textarea(attrs={'class': 'font-monospace'}), } diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index b128f7461..3c7ad3c15 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -21,7 +21,7 @@ from extras.models import JobResult from ipam.formfields import IPAddressFormField, IPNetworkFormField from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator from utilities.exceptions import AbortTransaction -from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField +from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField from .context_managers import change_logging from .forms import ScriptForm @@ -164,16 +164,22 @@ class ChoiceVar(ScriptVariable): def __init__(self, choices, *args, **kwargs): super().__init__(*args, **kwargs) - # Set field choices - self.field_attrs['choices'] = choices + # Set field choices, adding a blank choice to avoid forced selections + self.field_attrs['choices'] = add_blank_choice(choices) -class MultiChoiceVar(ChoiceVar): +class MultiChoiceVar(ScriptVariable): """ Like ChoiceVar, but allows for the selection of multiple choices. """ form_field = forms.MultipleChoiceField + def __init__(self, choices, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Set field choices + self.field_attrs['choices'] = choices + class ObjectVar(ScriptVariable): """ diff --git a/netbox/ipam/constants.py b/netbox/ipam/constants.py index b19d4061b..ab88dfc1a 100644 --- a/netbox/ipam/constants.py +++ b/netbox/ipam/constants.py @@ -65,6 +65,7 @@ FHRP_PROTOCOL_ROLE_MAPPINGS = { FHRPGroupProtocolChoices.PROTOCOL_HSRP: IPAddressRoleChoices.ROLE_HSRP, FHRPGroupProtocolChoices.PROTOCOL_GLBP: IPAddressRoleChoices.ROLE_GLBP, FHRPGroupProtocolChoices.PROTOCOL_CARP: IPAddressRoleChoices.ROLE_CARP, + FHRPGroupProtocolChoices.PROTOCOL_OTHER: IPAddressRoleChoices.ROLE_VIP, } diff --git a/netbox/ipam/forms/models.py b/netbox/ipam/forms/models.py index c5e3146e9..4ed8aa267 100644 --- a/netbox/ipam/forms/models.py +++ b/netbox/ipam/forms/models.py @@ -580,7 +580,7 @@ class FHRPGroupForm(CustomFieldModelForm): vrf=self.cleaned_data['ip_vrf'], address=self.cleaned_data['ip_address'], status=self.cleaned_data['ip_status'], - role=FHRP_PROTOCOL_ROLE_MAPPINGS[self.cleaned_data['protocol']], + role=FHRP_PROTOCOL_ROLE_MAPPINGS.get(self.cleaned_data['protocol'], IPAddressRoleChoices.ROLE_VIP), assigned_object=instance ) ipaddress.save() @@ -628,8 +628,7 @@ class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm): class VLANGroupForm(CustomFieldModelForm): scope_type = ContentTypeChoiceField( queryset=ContentType.objects.filter(model__in=VLANGROUP_SCOPE_TYPES), - required=False, - widget=StaticSelect + required=False ) region = DynamicModelChoiceField( queryset=Region.objects.all(), diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 022ea13c3..4f0f9a214 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -1,5 +1,7 @@ import datetime +from django.test import override_settings +from django.urls import reverse from netaddr import IPNetwork from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site @@ -222,6 +224,21 @@ class AggregateTestCase(ViewTestCases.PrimaryObjectViewTestCase): 'description': 'New description', } + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_aggregate_prefixes(self): + rir = RIR.objects.first() + aggregate = Aggregate.objects.create(prefix=IPNetwork('192.168.0.0/16'), rir=rir) + prefixes = ( + Prefix(prefix=IPNetwork('192.168.1.0/24')), + Prefix(prefix=IPNetwork('192.168.2.0/24')), + Prefix(prefix=IPNetwork('192.168.3.0/24')), + ) + Prefix.objects.bulk_create(prefixes) + self.assertEqual(aggregate.get_child_prefixes().count(), 3) + + url = reverse('ipam:aggregate_prefixes', kwargs={'pk': aggregate.pk}) + self.assertHttpStatus(self.client.get(url), 200) + class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase): model = Role @@ -319,6 +336,48 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase): 'description': 'New description', } + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_prefix_prefixes(self): + prefixes = ( + Prefix(prefix=IPNetwork('192.168.0.0/16')), + Prefix(prefix=IPNetwork('192.168.1.0/24')), + Prefix(prefix=IPNetwork('192.168.2.0/24')), + Prefix(prefix=IPNetwork('192.168.3.0/24')), + ) + Prefix.objects.bulk_create(prefixes) + self.assertEqual(prefixes[0].get_child_prefixes().count(), 3) + + url = reverse('ipam:prefix_prefixes', kwargs={'pk': prefixes[0].pk}) + self.assertHttpStatus(self.client.get(url), 200) + + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_prefix_ipranges(self): + prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16')) + ip_ranges = ( + IPRange(start_address='192.168.0.1/24', end_address='192.168.0.100/24', size=99), + IPRange(start_address='192.168.1.1/24', end_address='192.168.1.100/24', size=99), + IPRange(start_address='192.168.2.1/24', end_address='192.168.2.100/24', size=99), + ) + IPRange.objects.bulk_create(ip_ranges) + self.assertEqual(prefix.get_child_ranges().count(), 3) + + url = reverse('ipam:prefix_ipranges', kwargs={'pk': prefix.pk}) + self.assertHttpStatus(self.client.get(url), 200) + + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_prefix_ipaddresses(self): + prefix = Prefix.objects.create(prefix=IPNetwork('192.168.0.0/16')) + ip_addresses = ( + IPAddress(address=IPNetwork('192.168.0.1/16')), + IPAddress(address=IPNetwork('192.168.0.2/16')), + IPAddress(address=IPNetwork('192.168.0.3/16')), + ) + IPAddress.objects.bulk_create(ip_addresses) + self.assertEqual(prefix.get_child_ips().count(), 3) + + url = reverse('ipam:prefix_ipaddresses', kwargs={'pk': prefix.pk}) + self.assertHttpStatus(self.client.get(url), 200) + class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = IPRange @@ -377,6 +436,24 @@ class IPRangeTestCase(ViewTestCases.PrimaryObjectViewTestCase): 'description': 'New description', } + @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) + def test_iprange_ipaddresses(self): + iprange = IPRange.objects.create( + start_address=IPNetwork('192.168.0.1/24'), + end_address=IPNetwork('192.168.0.100/24'), + size=99 + ) + ip_addresses = ( + IPAddress(address=IPNetwork('192.168.0.1/24')), + IPAddress(address=IPNetwork('192.168.0.2/24')), + IPAddress(address=IPNetwork('192.168.0.3/24')), + ) + IPAddress.objects.bulk_create(ip_addresses) + self.assertEqual(iprange.get_child_ips().count(), 3) + + url = reverse('ipam:iprange_ipaddresses', kwargs={'pk': iprange.pk}) + self.assertHttpStatus(self.client.get(url), 200) + class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase): model = IPAddress diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 55ac284d1..38b30e9cc 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -505,9 +505,7 @@ class PrefixIPAddressesView(generic.ObjectChildrenView): template_name = 'ipam/prefix/ip_addresses.html' def get_children(self, request, parent): - return parent.get_child_ips().restrict(request.user, 'view').prefetch_related( - 'vrf', 'role', 'tenant', - ) + return parent.get_child_ips().restrict(request.user, 'view').prefetch_related('vrf', 'tenant') def prep_table_data(self, request, queryset, parent): show_available = bool(request.GET.get('show_available', 'true') == 'true') @@ -531,7 +529,6 @@ class PrefixEditView(generic.ObjectEditView): class PrefixDeleteView(generic.ObjectDeleteView): queryset = Prefix.objects.all() - template_name = 'ipam/prefix_delete.html' class PrefixBulkImportView(generic.BulkImportView): diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index c22443275..b3a32a1fc 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -19,7 +19,7 @@ from netbox.config import PARAMS # Environment setup # -VERSION = '3.1.4' +VERSION = '3.1.5' # Hostname HOSTNAME = platform.node() diff --git a/netbox/netbox/views/generic.py b/netbox/netbox/views/generic.py index feff2ca39..74f8f325b 100644 --- a/netbox/netbox/views/generic.py +++ b/netbox/netbox/views/generic.py @@ -10,6 +10,7 @@ from django.db.models import ManyToManyField, ProtectedError from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse from django.utils.html import escape from django.utils.http import is_safe_url from django.utils.safestring import mark_safe @@ -430,10 +431,21 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): obj = self.get_object(kwargs) form = ConfirmationForm(initial=request.GET) + # If this is an HTMX request, return only the rendered deletion form as modal content + if is_htmx(request): + viewname = f'{self.queryset.model._meta.app_label}:{self.queryset.model._meta.model_name}_delete' + form_url = reverse(viewname, kwargs={'pk': obj.pk}) + return render(request, 'htmx/delete_form.html', { + 'object': obj, + 'object_type': self.queryset.model._meta.verbose_name, + 'form': form, + 'form_url': form_url, + }) + return render(request, self.template_name, { - 'obj': obj, + 'object': obj, + 'object_type': self.queryset.model._meta.verbose_name, 'form': form, - 'obj_type': self.queryset.model._meta.verbose_name, 'return_url': self.get_return_url(request, obj), }) @@ -466,9 +478,9 @@ class ObjectDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): logger.debug("Form validation failed") return render(request, self.template_name, { - 'obj': obj, + 'object': obj, + 'object_type': self.queryset.model._meta.verbose_name, 'form': form, - 'obj_type': self.queryset.model._meta.verbose_name, 'return_url': self.get_return_url(request, obj), }) diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css index e711685bf..a53e70f51 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 23dc8d382..29c3ad3c7 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 dde212a6c..23d0be306 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 d78429bf9..f8e8c3420 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -358,7 +358,7 @@ nav.search { // Don't overtake dropdowns z-index: 999; justify-content: center; - background-color: var(--nbx-body-bg); + background-color: $navbar-light-color; .search-container { display: flex; @@ -452,8 +452,8 @@ main.login-container { } .footer { + background-color: $tab-content-bg; padding: 0; - .nav-link { padding: 0.5rem; } @@ -517,6 +517,10 @@ h6.accordion-item-title { } } +.navbar { + border-bottom: 1px solid $border-color; +} + .navbar-brand { padding-top: 0.75rem; padding-bottom: 0.75rem; @@ -554,6 +558,7 @@ div.content-container { } div.content { + background-color: $tab-content-bg; flex: 1; } @@ -898,6 +903,7 @@ div.card-overlay { // Tabbed content .nav-tabs { + background-color: $body-bg; .nav-link { &:hover { // Don't show a bottom-border on a hovered nav link because it overlaps with the .nav-tab border. @@ -919,14 +925,6 @@ div.card-overlay { display: flex; flex-direction: column; padding: $spacer; - background-color: $tab-content-bg; - border-bottom: 1px solid $nav-tabs-border-color; - - // Remove background and border when printing. - @media print { - background-color: var(--nbx-body-bg) !important; - border-bottom: none !important; - } } // Override masonry-layout styles when printing. diff --git a/netbox/project-static/styles/theme-base.scss b/netbox/project-static/styles/theme-base.scss index 26a1811bc..97f6dd020 100644 --- a/netbox/project-static/styles/theme-base.scss +++ b/netbox/project-static/styles/theme-base.scss @@ -33,95 +33,6 @@ $darkest: #171b1d; @import '../node_modules/bootstrap/scss/variables'; -// Make color palette colors available as theme colors. -// For example, you could use `.bg-red-100`, if needed. -$theme-color-addons: ( - 'darker': $darker, - 'darkest': $darkest, - 'gray': $gray-400, - 'gray-100': $gray-100, - 'gray-200': $gray-200, - 'gray-300': $gray-300, - 'gray-400': $gray-400, - 'gray-500': $gray-500, - 'gray-600': $gray-600, - 'gray-700': $gray-700, - 'gray-800': $gray-800, - 'gray-900': $gray-900, - 'red-100': $red-100, - 'red-200': $red-200, - 'red-300': $red-300, - 'red-400': $red-400, - 'red-500': $red-500, - 'red-600': $red-600, - 'red-700': $red-700, - 'red-800': $red-800, - 'red-900': $red-900, - 'yellow-100': $yellow-100, - 'yellow-200': $yellow-200, - 'yellow-300': $yellow-300, - 'yellow-400': $yellow-400, - 'yellow-500': $yellow-500, - 'yellow-600': $yellow-600, - 'yellow-700': $yellow-700, - 'yellow-800': $yellow-800, - 'yellow-900': $yellow-900, - 'green-100': $green-100, - 'green-200': $green-200, - 'green-300': $green-300, - 'green-400': $green-400, - 'green-500': $green-500, - 'green-600': $green-600, - 'green-700': $green-700, - 'green-800': $green-800, - 'green-900': $green-900, - 'blue-100': $blue-100, - 'blue-200': $blue-200, - 'blue-300': $blue-300, - 'blue-400': $blue-400, - 'blue-500': $blue-500, - 'blue-600': $blue-600, - 'blue-700': $blue-700, - 'blue-800': $blue-800, - 'blue-900': $blue-900, - 'cyan-100': $cyan-100, - 'cyan-200': $cyan-200, - 'cyan-300': $cyan-300, - 'cyan-400': $cyan-400, - 'cyan-500': $cyan-500, - 'cyan-600': $cyan-600, - 'cyan-700': $cyan-700, - 'cyan-800': $cyan-800, - 'cyan-900': $cyan-900, - 'indigo-100': $indigo-100, - 'indigo-200': $indigo-200, - 'indigo-300': $indigo-300, - 'indigo-400': $indigo-400, - 'indigo-500': $indigo-500, - 'indigo-600': $indigo-600, - 'indigo-700': $indigo-700, - 'indigo-800': $indigo-800, - 'indigo-900': $indigo-900, - 'purple-100': $purple-100, - 'purple-200': $purple-200, - 'purple-300': $purple-300, - 'purple-400': $purple-400, - 'purple-500': $purple-500, - 'purple-600': $purple-600, - 'purple-700': $purple-700, - 'purple-800': $purple-800, - 'purple-900': $purple-900, - 'pink-100': $pink-100, - 'pink-200': $pink-200, - 'pink-300': $pink-300, - 'pink-400': $pink-400, - 'pink-500': $pink-500, - 'pink-600': $pink-600, - 'pink-700': $pink-700, - 'pink-800': $pink-800, - 'pink-900': $pink-900, -); - // This is the same value as the default from Bootstrap, but it needs to be in scope prior to // importing _variables.scss from Bootstrap. $btn-close-width: 1em; diff --git a/netbox/project-static/styles/theme-dark.scss b/netbox/project-static/styles/theme-dark.scss index c5fb5dcf1..2db29ad38 100644 --- a/netbox/project-static/styles/theme-dark.scss +++ b/netbox/project-static/styles/theme-dark.scss @@ -3,6 +3,7 @@ @use 'sass:map'; @import './theme-base'; +// Theme colors (BS5 classes) $primary: $blue-300; $secondary: $gray-500; $success: $green-300; @@ -13,6 +14,7 @@ $light: $gray-300; $dark: $gray-500; $theme-colors: ( + // BS5 theme colors 'primary': $primary, 'secondary': $secondary, 'success': $success, @@ -21,18 +23,23 @@ $theme-colors: ( 'danger': $danger, 'light': $light, 'dark': $dark, - 'red': $red-300, - 'yellow': $yellow-300, - 'green': $green-300, + + // General-purpose palette 'blue': $blue-300, - 'cyan': $cyan-300, 'indigo': $indigo-300, 'purple': $purple-300, 'pink': $pink-300, + 'red': $red-300, + 'orange': $orange-300, + 'yellow': $yellow-300, + 'green': $green-300, + 'teal': $teal-300, + 'cyan': $cyan-300, + 'gray': $gray-300, + 'black': $black, + 'white': $white, ); -$theme-colors: map-merge($theme-colors, $theme-color-addons); - // Gradient $gradient: linear-gradient(180deg, rgba($white, 0.15), rgba($white, 0)); @@ -139,7 +146,7 @@ $nav-tabs-link-active-border-color: $gray-800 $gray-800 $nav-tabs-link-active-bg $nav-pills-link-active-color: $component-active-color; $nav-pills-link-active-bg: $component-active-bg; -$navbar-light-color: $gray-500; +$navbar-light-color: $darkest; $navbar-light-toggler-icon-bg: url("data:image/svg+xml,"); $navbar-light-toggler-border-color: $gray-700; diff --git a/netbox/project-static/styles/theme-light.scss b/netbox/project-static/styles/theme-light.scss index 4e638c75e..d417e1bf6 100644 --- a/netbox/project-static/styles/theme-light.scss +++ b/netbox/project-static/styles/theme-light.scss @@ -2,28 +2,47 @@ @import './theme-base.scss'; -$input-border-color: $gray-200; +// Theme colors (BS5 classes) +$primary: #337ab7; +$secondary: $gray-600; +$success: $green-500; +$info: #54d6f0; +$warning: $yellow-500; +$danger: $red-500; +$light: $gray-200; +$dark: $gray-800; -$theme-colors: map-merge( - $theme-colors, - ( - 'primary': #337ab7, - 'info': #54d6f0, - 'red': $red-500, - 'yellow': $yellow-500, - 'green': $green-500, - 'blue': $blue-500, - 'cyan': $cyan-500, - 'indigo': $indigo-500, - 'purple': $purple-500, - 'pink': $pink-500, - ) +$theme-colors: ( + // BS5 theme colors + 'primary': $primary, + 'secondary': $secondary, + 'success': $success, + 'info': $info, + 'warning': $warning, + 'danger': $danger, + 'light': $light, + 'dark': $dark, + + // General-purpose palette + 'blue': $blue-500, + 'indigo': $indigo-500, + 'purple': $purple-500, + 'pink': $pink-500, + 'red': $red-500, + 'orange': $orange-500, + 'yellow': $yellow-500, + 'green': $green-500, + 'teal': $teal-500, + 'cyan': $cyan-500, + 'gray': $gray-500, + 'black': $black, + 'white': $white, ); -$theme-colors: map-merge($theme-colors, $theme-color-addons); - $light: $gray-200; +$navbar-light-color: $gray-100; + $card-cap-color: $gray-800; $accordion-bg: transparent; diff --git a/netbox/templates/base/layout.html b/netbox/templates/base/layout.html index 7b1597bf0..cf3841dd2 100644 --- a/netbox/templates/base/layout.html +++ b/netbox/templates/base/layout.html @@ -20,7 +20,7 @@ {# Top bar #} -