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 #}
-