From 58862e115cdc72a6d7f74c257bb7aac64e5947ef Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 3 Aug 2021 08:32:53 -0700 Subject: [PATCH 1/7] Closes #6863: Add search fields back to filter forms --- netbox/circuits/forms.py | 23 +++- netbox/dcim/forms.py | 111 ++++++++++++++++++-- netbox/extras/forms.py | 46 +++++++- netbox/ipam/forms.py | 67 ++++++++++-- netbox/project-static/dist/netbox-dark.css | Bin 761390 -> 761626 bytes netbox/project-static/dist/netbox-light.css | Bin 473790 -> 473952 bytes netbox/project-static/styles/netbox.scss | 17 ++- netbox/templates/inc/filter_list.html | 9 +- netbox/tenancy/forms.py | 6 ++ netbox/virtualization/forms.py | 20 +++- 10 files changed, 272 insertions(+), 27 deletions(-) diff --git a/netbox/circuits/forms.py b/netbox/circuits/forms.py index 261bb8bd6..126b2a47b 100644 --- a/netbox/circuits/forms.py +++ b/netbox/circuits/forms.py @@ -107,9 +107,15 @@ class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBu class ProviderFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Provider field_groups = [ + ['q'], ['region_id', 'site_id'], ['asn', 'tag'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -196,7 +202,12 @@ class ProviderNetworkBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField class ProviderNetworkFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = ProviderNetwork - field_order = ['provider_id'] + field_order = ['q', 'provider_id', 'tag'] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) provider_id = DynamicModelMultipleChoiceField( queryset=Provider.objects.all(), required=False, @@ -358,16 +369,22 @@ class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBul class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Circuit field_order = [ - 'type_id', 'provider_id', 'provider_network_id', 'status', 'region_id', 'site_id', 'tenant_group_id', 'tenant_id', - 'commit_rate', + 'q', 'type_id', 'provider_id', 'provider_network_id', 'status', 'region_id', 'site_id', 'tenant_group_id', + 'tenant_id', 'commit_rate', ] field_groups = [ + ['q'], ['type_id', 'status', 'commit_rate'], ['provider_id', 'provider_network_id'], ['region_id', 'site_id'], ['tenant_group_id', 'tenant_id'], ['tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) type_id = DynamicModelMultipleChoiceField( queryset=CircuitType.objects.all(), required=False, diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index da9030ca9..348cb3b62 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -56,12 +56,18 @@ def get_device_by_name_or_pk(name): class DeviceComponentFilterForm(BootstrapMixin, CustomFieldModelFilterForm): field_order = [ - 'name', 'label', 'region_id', 'site_group_id', 'site_id', + 'q', 'name', 'label', 'region_id', 'site_group_id', 'site_id', ] field_groups = [ + ['q'], ['name', 'label'], ['region_id', 'site_group_id', 'site_id'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) name = forms.CharField( required=False ) @@ -452,12 +458,18 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEd class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Site - field_order = ['status', 'region_id', 'tenant_group_id', 'tenant_id'] + field_order = ['q', 'status', 'region_id', 'tenant_group_id', 'tenant_id'] field_groups = [ + ['q'], ['status', 'region_id'], ['tenant_group_id', 'tenant_id'], ['tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) status = forms.MultipleChoiceField( choices=SiteStatusChoices, required=False, @@ -568,6 +580,11 @@ class LocationBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): class LocationFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Location + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -862,12 +879,18 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEd class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Rack - field_order = ['region_id', 'site_id', 'location_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id'] + field_order = ['q', 'region_id', 'site_id', 'location_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id'] field_groups = [ + ['q'], ['status', 'role_id'], ['region_id', 'site_id', 'location_id'], ['tenant_group_id', 'tenant_id'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -927,7 +950,8 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo class RackElevationFilterForm(RackFilterForm): field_order = [ - 'region_id', 'site_id', 'location_id', 'id', 'status', 'role_id', 'tenant_group_id', 'tenant_id', + 'q', 'region_id', 'site_id', 'location_id', 'id', 'status', 'role_id', 'tenant_group_id', + 'tenant_id', ] id = DynamicModelMultipleChoiceField( queryset=Rack.objects.all(), @@ -1092,11 +1116,17 @@ class RackReservationBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomField class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = RackReservation - field_order = ['region_id', 'site_id', 'location_id', 'user_id', 'tenant_group_id', 'tenant_id'] + field_order = ['q', 'region_id', 'site_id', 'location_id', 'user_id', 'tenant_group_id', 'tenant_id'] field_groups = [ + ['q'], ['region_id', 'site_id', 'location_id'], ['user_id', 'tenant_group_id', 'tenant_id'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -1246,12 +1276,18 @@ class DeviceTypeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModel class DeviceTypeFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = DeviceType field_groups = [ + ['q'], ['manufacturer_id', 'subdevice_role'], ['console_ports', 'console_server_ports'], ['power_ports', 'power_outlets'], ['interfaces', 'pass_through_ports'], ['tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, @@ -2058,6 +2094,11 @@ class PlatformBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): class PlatformFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Platform + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) manufacturer_id = DynamicModelMultipleChoiceField( queryset=Manufacturer.objects.all(), required=False, @@ -2465,16 +2506,23 @@ class DeviceBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulk class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilterForm, CustomFieldModelFilterForm): model = Device field_order = [ - 'region_id', 'site_id', 'location_id', 'rack_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id', - 'manufacturer_id', 'device_type_id', 'asset_tag', 'mac_address', 'has_primary_ip', + 'q', 'region_id', 'site_id', 'location_id', 'rack_id', 'status', 'role_id', + 'tenant_group_id', 'tenant_id', 'manufacturer_id', 'device_type_id', 'asset_tag', + 'mac_address', 'has_primary_ip', ] field_groups = [ + ['q'], ['region_id', 'site_id', 'location_id', 'rack_id'], ['status', 'role_id', 'asset_tag'], ['tenant_group_id', 'tenant_id'], ['manufacturer_id', 'device_type_id'], ['mac_address', 'has_primary_ip'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -2654,6 +2702,7 @@ class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentFor class ConsolePortFilterForm(DeviceComponentFilterForm): model = ConsolePort field_groups = [ + ['q'], ['name', 'label'], ['type', 'speed'], ['region_id', 'site_group_id', 'site_id'], @@ -2761,6 +2810,7 @@ class ConsolePortCSVForm(CustomFieldModelCSVForm): class ConsoleServerPortFilterForm(DeviceComponentFilterForm): model = ConsoleServerPort field_groups = [ + ['q'], ['name', 'label'], ['type', 'speed'], ['region_id', 'site_group_id', 'site_id'], @@ -2868,6 +2918,7 @@ class ConsoleServerPortCSVForm(CustomFieldModelCSVForm): class PowerPortFilterForm(DeviceComponentFilterForm): model = PowerPort field_groups = [ + ['q'], ['name', 'label', 'type'], ['region_id', 'site_group_id', 'site_id'], ['tag'], @@ -2973,6 +3024,7 @@ class PowerPortCSVForm(CustomFieldModelCSVForm): class PowerOutletFilterForm(DeviceComponentFilterForm): model = PowerOutlet field_groups = [ + ['q'], ['name', 'label', 'type'], ['region_id', 'site_group_id', 'site_id'], ['tag'], @@ -3145,6 +3197,7 @@ class PowerOutletCSVForm(CustomFieldModelCSVForm): class InterfaceFilterForm(DeviceComponentFilterForm): model = Interface field_groups = [ + ['q'], ['name', 'label', 'type', 'enabled'], ['mgmt_only', 'mac_address'], ['region_id', 'site_group_id', 'site_id'], @@ -3493,6 +3546,7 @@ class InterfaceCSVForm(CustomFieldModelCSVForm): class FrontPortFilterForm(DeviceComponentFilterForm): field_groups = [ + ['q'], ['name', 'label', 'type', 'color'], ['region_id', 'site_group_id', 'site_id'], ['tag'] @@ -3682,6 +3736,7 @@ class FrontPortCSVForm(CustomFieldModelCSVForm): class RearPortFilterForm(DeviceComponentFilterForm): model = RearPort field_groups = [ + ['q'], ['name', 'label', 'type', 'color'], ['region_id', 'site_group_id', 'site_id'], ['tag'] @@ -3783,6 +3838,7 @@ class RearPortCSVForm(CustomFieldModelCSVForm): class DeviceBayFilterForm(DeviceComponentFilterForm): model = DeviceBay field_groups = [ + ['q'], ['name', 'label'], ['region_id', 'site_group_id', 'site_id'], ['tag'] @@ -4013,6 +4069,7 @@ class InventoryItemBulkEditForm( class InventoryItemFilterForm(DeviceComponentFilterForm): model = InventoryItem field_groups = [ + ['q'], ['name', 'label', 'manufacturer_id'], ['serial', 'asset_tag', 'discovered'], ['region_id', 'site_group_id', 'site_id'], @@ -4488,11 +4545,17 @@ class CableBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkE class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Cable field_groups = [ + ['q'], ['type', 'status', 'color'], ['device_id', 'rack_id'], ['region_id', 'site_id', 'tenant_id'], ['tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -4556,6 +4619,11 @@ class CableFilterForm(BootstrapMixin, CustomFieldModelFilterForm): # class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form): + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -4583,6 +4651,11 @@ class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form): class PowerConnectionFilterForm(BootstrapMixin, forms.Form): + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -4610,6 +4683,11 @@ class PowerConnectionFilterForm(BootstrapMixin, forms.Form): class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form): + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -4877,12 +4955,18 @@ class VirtualChassisCSVForm(CustomFieldModelCSVForm): class VirtualChassisFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = VirtualChassis - field_order = ['region_id', 'site_group_id', 'site_id', 'tenant_group_id', 'tenant_id'] + field_order = ['q', 'region_id', 'site_group_id', 'site_id', 'tenant_group_id', 'tenant_id'] field_groups = [ + ['q'], ['region_id', 'site_group_id', 'site_id'], ['tenant_group_id', 'tenant_id'], ['tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -5022,6 +5106,11 @@ class PowerPanelBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModel class PowerPanelFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = PowerPanel + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -5260,12 +5349,18 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelB class PowerFeedFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = PowerFeed field_groups = [ + ['q'], ['region_id', 'site_group_id', 'site_id'], ['power_panel_id', 'rack_id'], ['type', 'supply', 'max_utilization'], ['phase', 'voltage', 'amperage'], ['status', 'tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index 16091885c..98f34dfa9 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -77,9 +77,15 @@ class CustomFieldBulkEditForm(BootstrapMixin, BulkEditForm): class CustomFieldFilterForm(BootstrapMixin, forms.Form): field_groups = [ + ['q'], ['type', 'content_types'], ['weight', 'required'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) content_types = ContentTypeMultipleChoiceField( queryset=ContentType.objects.all(), limit_choices_to=FeatureQuery('custom_fields') @@ -167,9 +173,15 @@ class CustomLinkBulkEditForm(BootstrapMixin, BulkEditForm): class CustomLinkFilterForm(BootstrapMixin, forms.Form): field_groups = [ + ['q'], ['content_type'], ['weight', 'new_window'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) content_type = ContentTypeChoiceField( queryset=ContentType.objects.all(), limit_choices_to=FeatureQuery('custom_fields') @@ -252,9 +264,15 @@ class ExportTemplateBulkEditForm(BootstrapMixin, BulkEditForm): class ExportTemplateFilterForm(BootstrapMixin, forms.Form): field_groups = [ + ['q'], ['content_type', 'mime_type'], ['file_extension', 'as_attachment'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) content_type = ContentTypeChoiceField( queryset=ContentType.objects.all(), limit_choices_to=FeatureQuery('custom_fields') @@ -358,9 +376,15 @@ class WebhookBulkEditForm(BootstrapMixin, BulkEditForm): class WebhookFilterForm(BootstrapMixin, forms.Form): field_groups = [ + ['q'], ['content_types', 'http_method'], ['enabled', 'type_create', 'type_update', 'type_delete'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) content_types = ContentTypeMultipleChoiceField( queryset=ContentType.objects.all(), limit_choices_to=FeatureQuery('custom_fields') @@ -664,15 +688,21 @@ class ConfigContextBulkEditForm(BootstrapMixin, BulkEditForm): class ConfigContextFilterForm(BootstrapMixin, forms.Form): field_order = [ - 'region_id', 'site_group_id', 'site_id', 'role_id', 'platform_id', 'cluster_group_id', 'cluster_id', - 'tenant_group_id', 'tenant_id', + 'q', 'region_id', 'site_group_id', 'site_id', 'role_id', 'platform_id', 'cluster_group_id', + 'cluster_id', 'tenant_group_id', 'tenant_id', ] field_groups = [ + ['q'], ['region_id', 'site_group_id', 'site_id'], ['device_type_id', 'role_id', 'platform_id'], ['cluster_group_id', 'cluster_id'], ['tenant_group_id', 'tenant_id', 'tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -812,9 +842,15 @@ class JournalEntryBulkEditForm(BootstrapMixin, BulkEditForm): class JournalEntryFilterForm(BootstrapMixin, forms.Form): model = JournalEntry field_groups = [ + ['q'], ['created_before', 'created_after', 'created_by_id'], ['assigned_object_type_id', 'kind'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) created_after = forms.DateTimeField( required=False, label=_('After'), @@ -857,9 +893,15 @@ class JournalEntryFilterForm(BootstrapMixin, forms.Form): class ObjectChangeFilterForm(BootstrapMixin, forms.Form): model = ObjectChange field_groups = [ + ['q'], ['time_before', 'time_after', 'action'], ['user_id', 'changed_object_type_id'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) time_after = forms.DateTimeField( required=False, label=_('After'), diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index f60853525..4bb628b49 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -106,12 +106,18 @@ class VRFBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEdi class VRFFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = VRF - field_order = ['import_target_id', 'export_target_id', 'tenant_group_id', 'tenant_id'] + field_order = ['q', 'import_target_id', 'export_target_id', 'tenant_group_id', 'tenant_id'] field_groups = [ + ['q'], ['import_target_id', 'export_target_id'], ['tenant_group_id', 'tenant_id'], ['tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) import_target_id = DynamicModelMultipleChoiceField( queryset=RouteTarget.objects.all(), required=False, @@ -179,11 +185,17 @@ class RouteTargetBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldMode class RouteTargetFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = RouteTarget - field_order = ['name', 'tenant_group_id', 'tenant_id', 'importing_vrfs', 'exporting_vrfs'] + field_order = ['q', 'name', 'tenant_group_id', 'tenant_id', 'importing_vrfs', 'exporting_vrfs'] field_groups = [ + ['q'], ['importing_vrf_id', 'exporting_vrf_id'], ['tenant_group_id', 'tenant_id'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) importing_vrf_id = DynamicModelMultipleChoiceField( queryset=VRF.objects.all(), required=False, @@ -335,11 +347,17 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelB class AggregateFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Aggregate - field_order = ['family', 'rir', 'tenant_group_id', 'tenant_id'] + field_order = ['q', 'family', 'rir', 'tenant_group_id', 'tenant_id'] field_groups = [ + ['q'], ['family', 'rir_id'], ['tenant_group_id', 'tenant_id'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) family = forms.ChoiceField( required=False, choices=add_blank_choice(IPAddressFamilyChoices), @@ -610,15 +628,22 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulk class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Prefix field_order = [ - 'within_include', 'family', 'mask_length', 'vrf_id', 'present_in_vrf_id', 'status', 'region_id', - 'site_group_id', 'site_id', 'role_id', 'tenant_group_id', 'tenant_id', 'is_pool', 'mark_utilized', + 'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'present_in_vrf_id', 'status', + 'region_id', 'site_group_id', 'site_id', 'role_id', 'tenant_group_id', 'tenant_id', + 'is_pool', 'mark_utilized', ] field_groups = [ + ['q'], ['role_id', 'within_include', 'family', 'mask_length'], ['vrf_id', 'present_in_vrf_id', 'is_pool', 'mark_utilized'], ['region_id', 'site_group_id', 'site_id'], ['tenant_group_id', 'tenant_id', 'status', 'tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) mask_length__lte = forms.IntegerField( widget=forms.HiddenInput() ) @@ -813,12 +838,18 @@ class IPRangeBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBul class IPRangeFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = IPRange field_order = [ - 'family', 'vrf_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id', + 'q', 'family', 'vrf_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id', ] field_groups = [ + ['q'], ['family', 'vrf_id', 'status', 'role_id'], ['tenant_group_id', 'tenant_id', 'tag'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) family = forms.ChoiceField( required=False, choices=add_blank_choice(IPAddressFamilyChoices), @@ -1244,15 +1275,21 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form): class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = IPAddress field_order = [ - 'parent', 'family', 'mask_length', 'vrf_id', 'present_in_vrf_id', 'status', 'role', + 'q', 'parent', 'family', 'mask_length', 'vrf_id', 'present_in_vrf_id', 'status', 'role', 'assigned_to_interface', 'tenant_group_id', 'tenant_id', ] field_groups = [ + ['q'], ['parent', 'family', 'mask_length'], ['status', 'vrf_id', 'present_in_vrf_id'], ['role', 'assigned_to_interface'], ['tenant_group_id', 'tenant_id'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) parent = forms.CharField( required=False, widget=forms.TextInput( @@ -1444,11 +1481,13 @@ class VLANGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): class VLANGroupFilterForm(BootstrapMixin, forms.Form): field_groups = [ + ['q'], ['region', 'sitegroup', 'site'], ['location', 'rack'] ] q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) region = DynamicModelMultipleChoiceField( @@ -1662,13 +1701,20 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBulkEd class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = VLAN field_order = [ - 'region_id', 'site_group_id', 'site_id', 'group_id', 'status', 'role_id', 'tenant_group_id', 'tenant_id', + 'q', 'region_id', 'site_group_id', 'site_id', 'group_id', 'status', 'role_id', + 'tenant_group_id', 'tenant_id', ] field_groups = [ + ['q'], ['region_id', 'site_group_id', 'site_id'], ['group_id', 'role_id', 'status'], ['tenant_group_id', 'tenant_id'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) region_id = DynamicModelMultipleChoiceField( queryset=Region.objects.all(), required=False, @@ -1765,6 +1811,11 @@ class ServiceForm(BootstrapMixin, CustomFieldModelForm): class ServiceFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Service + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) protocol = forms.ChoiceField( choices=add_blank_choice(ServiceProtocolChoices), required=False, diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css index 1ae150baa9303607adbcd1f963fed5f4088a800d..73e693cbf6d74e7c1b99d3c6de3f28740f07263c 100644 GIT binary patch delta 141 zcmV;80CNAXye^u)E`WpqgaU*Egam{Iga(8Mgb0KQgbK6^Wj7RaZDDL|ATDNaa&0YR zZ*FvQZ)|&$#KI&N=tC@y1Ra%3%HZ)ABXKDT5y3s)nT;6McgmmpdT2MAF7M2#)7Pc1l7LFFq7OpMaspi}Fn{$ULPj0xyyM0SEcbYQ* Dn;Q`R diff --git a/netbox/project-static/dist/netbox-light.css b/netbox/project-static/dist/netbox-light.css index b64440802bc1ab5bf76e0a0d5ca921ca0cf64ff2..50949743c34f3f32ae8d3ef6920fab0f4e63c628 100644 GIT binary patch delta 161 zcmdn@Rp!ArnT8g|7N!>F7M2#)Eo>XVniuCL=Hw{orR5jp>L%ysl@#UYRHp%H-Qvuu zR4Y9TbJL>K+*+%={1Oel`%8@{q7i<^N&6d>X$nPr(NsYTW0nJFb1 zR)z)!s@55)nduoNR)z%?*13sA>6v*}hCss<45sJ*U{mMLFGx(zEUC28Gcw&S$jiP~ F7XXXtI}ZQ= delta 46 zcmaFxO=jO$nT8g|7N!>F7M2#)Eo>XVGAHNfOm6HF+s^cj%~O1OI3GLj_Cvhv^K=2p CD-pK< diff --git a/netbox/project-static/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index e3acd687c..9db1ce35a 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -89,8 +89,13 @@ table td > .progress { min-width: 6rem; } -// Automatically space out adjacent columns. -.col:not(:last-child):not(:only-child) { +// Override Bootstrap form-control font-size when contained by .small element. +.small .form-control { + font-size: $font-size-sm; +} + +// Automatically space out adjacent columns, but not within card bodies. +:not(.card-body) > .col:not(:last-child):not(:only-child) { margin-bottom: $spacer; } @@ -479,6 +484,14 @@ span.color-label { .card-body.small .form-select { font-size: $input-font-size-sm; } + + .card-divider { + width: 100%; + height: 1px; + margin: $hr-margin-y 0; + border-top: 1px solid $card-border-color; + opacity: $hr-opacity; + } } .form-floating { diff --git a/netbox/templates/inc/filter_list.html b/netbox/templates/inc/filter_list.html index 07aa2249b..8837a75e2 100644 --- a/netbox/templates/inc/filter_list.html +++ b/netbox/templates/inc/filter_list.html @@ -22,7 +22,7 @@ {{ field }} {% else %} -
+
{{ field }}
@@ -30,17 +30,20 @@ {% endwith %} {% endfor %}
+ {% if forloop.counter != filter_form.field_groups|length %} +
+ {% endif %} {% endfor %} {% else %} {% for field in filter_form.visible_fields %} -
+
{% if field|widget_type == 'checkboxinput' %}
{{ field }}
{% else %} -
+
{{ field }}
diff --git a/netbox/tenancy/forms.py b/netbox/tenancy/forms.py index 05b47ad4c..58c1ae60a 100644 --- a/netbox/tenancy/forms.py +++ b/netbox/tenancy/forms.py @@ -64,6 +64,11 @@ class TenantGroupBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm): class TenantGroupFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = TenantGroup + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) parent_id = DynamicModelMultipleChoiceField( queryset=TenantGroup.objects.all(), required=False, @@ -132,6 +137,7 @@ class TenantFilterForm(BootstrapMixin, CustomFieldModelFilterForm): model = Tenant q = forms.CharField( required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), label=_('Search') ) group_id = DynamicModelMultipleChoiceField( diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 7cef7434b..2b352ed3f 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -225,14 +225,20 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldModelBul class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm): model = Cluster field_order = [ - 'type_id', 'region_id', 'site_id', 'group_id', 'tenant_group_id', 'tenant_id', + 'q', 'type_id', 'region_id', 'site_id', 'group_id', 'tenant_group_id', 'tenant_id', ] field_groups = [ + ['q'], ['type_id'], ['region_id', 'site_id'], ['tenant_group_id', 'tenant_id'], ['tag'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) type_id = DynamicModelMultipleChoiceField( queryset=ClusterType.objects.all(), required=False, @@ -540,6 +546,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod 'site_id', 'tenant_group_id', 'tenant_id', 'platform_id', 'mac_address', ] field_groups = [ + ['q'], ['status', 'role_id'], ['platform_id', 'mac_address'], ['cluster_group_id', 'cluster_type_id', 'cluster_id'], @@ -547,6 +554,11 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldMod ['tenant_group_id', 'tenant_id'], ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) cluster_group_id = DynamicModelMultipleChoiceField( queryset=ClusterGroup.objects.all(), required=False, @@ -855,10 +867,16 @@ class VMInterfaceBulkRenameForm(BulkRenameForm): class VMInterfaceFilterForm(BootstrapMixin, forms.Form): model = VMInterface field_groups = [ + ['q'], ['cluster_id', 'virtual_machine_id'], ['enabled', 'mac_address'], ['tag'] ] + q = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'placeholder': _('All Fields')}), + label=_('Search') + ) cluster_id = DynamicModelMultipleChoiceField( queryset=Cluster.objects.all(), required=False, From 53e21ceed40c0189a8c8b61af6da9e03266ab392 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 3 Aug 2021 09:19:24 -0700 Subject: [PATCH 2/7] #6797: Improve global search styles --- netbox/project-static/dist/netbox-dark.css | Bin 761626 -> 763611 bytes netbox/project-static/dist/netbox-light.css | Bin 473952 -> 475359 bytes netbox/project-static/styles/netbox.scss | 91 +++++++++++------- .../utilities/templates/search/searchbar.html | 2 +- 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/netbox/project-static/dist/netbox-dark.css b/netbox/project-static/dist/netbox-dark.css index 73e693cbf6d74e7c1b99d3c6de3f28740f07263c..20c91ff9171c0699a550fe20520c51d3fcb4ac92 100644 GIT binary patch delta 1089 zcmbQWPw)0wy@nRX7N!>F7M2#)7Pc1l7LFFq7OpMa9hTD{IIyx!zi-VgxZTT&JCk?% z2TN|L=?g8n{ik0D=4N3lPE9OI&Y1q*lG_<9AP*5x(96s#C@s-VFUl`1P|$-2>gFe9 zP3OJBAu(ORn^P04Tm+l)=?03763l67Y11DDFpKh8=Oz}VXXfeVq^6Zv8BAAPz@|94 zV2{Fdeh+T->E0gPwX&#|r4;2Cq~w?9>6YZDr{|tLXIRbVe(%waVD6 z)kD=dUD1G11&<>Ygc-%Bi!-sxLd-VKV2lH+Rw2Ud)aeudFqx9!6k#lePfz^CB0RnL zH40Bgh^b-+*&o4#C-8R9uSYBc^bDTB?y?WM)+ z%z7laaib2S841c4$TAvEx6fwOB0-O#0E-R@%GYNzrh=8@3n3GT~F?vywfEs N*_5`|MR3n?0{|MgfLs6o delta 220 zcmcb8R&Ulmy@nRX7N!>F7M2#)7Pc1l7LFFq7OpMa9hTGmmvOUAZ+7Pvp04E1?LXab z886yQ t+e7R;62L9RXgd9V0Jj-Lz$6eR#S+9VKYd{mw<*N2KyK7_`vmSeZUF6DOA7!1 diff --git a/netbox/project-static/dist/netbox-light.css b/netbox/project-static/dist/netbox-light.css index 50949743c34f3f32ae8d3ef6920fab0f4e63c628..0e3d911576e3e09428cea98f73cf1d69e323d0c5 100644 GIT binary patch delta 1102 zcmaFxP3C^1Y(ooU3sVbo3rh>@7B;Q#(+~e-6W_k#JKKGZ=>^}}q^9@(XJei2@tsYB zJ1?B%qg>S5R7_n_iS(TA%<`rJJ9WrCXeulbT$Tno>Re;0H!g z30ztxKinfb`AsD6^z$DW+4!t;6N}O_^K^4k(@LxirZZ}=C{F&HCO`edW)}A82R<;e zPyN8oGhL98JyRCd;*_HNf|UI7Jl&G~^z@w6=?kryR3EKd0 z`M@sm$>KnVDx3?{z@-S?wCRCHOv3fVs>W$Iy0KP?$t9U(sYI%`$Vt_MYS1gr$S=1e z);+*@(VNV5ULSYF9oQu{{rP!DVZ18hiF9X9D%4+AY5B>e#YF3xyl|HY(J=yyMuW+3 z&T~c)8wge@nZ=1oIjJecyGQbZGrj;(n!cQoT|gX%1E%w_GD}Q9z{ z4yR$$6S&z$_^NXgD|9nbGt)CltjrCjf8b&l!K*`FHnSu(7pHeXhN?~X<7OAaYl{8$ Q?TqZdIJVE{WOhXG}3sVbo3rh>@7B;Q#Qx)0WrYf*YvE}9GrPkI?U-*MfV!Pvaw)-41 zY57ID3Q46UCHZ-JDMk4ODf#7jx+VGP={c#@lVv*erpuPF3Qqs9nT37&fe(!AllwZP zCi9(QpML%WBiraw8ct`M@rv>0NB>0^GTYnR$9SiIw@KCDT8;GfQlD z=U~6iG5s1FyXmu3NRX~LN^eyb{k`NXP N2fM;{IYIV=`T&e7P!j+E diff --git a/netbox/project-static/styles/netbox.scss b/netbox/project-static/styles/netbox.scss index 9db1ce35a..6aaac6e94 100644 --- a/netbox/project-static/styles/netbox.scss +++ b/netbox/project-static/styles/netbox.scss @@ -118,15 +118,6 @@ table td > .progress { } } -.search-container { - display: flex; - width: 100%; - - @include media-breakpoint-down(lg) { - display: none; - } -} - .card > .table.table-flush { margin-bottom: 0; overflow: hidden; @@ -270,24 +261,67 @@ div.title-container { } } +// Global Search nav.search { // Don't overtake dropdowns z-index: 999; justify-content: center; background-color: var(--nbx-body-bg); - form button.dropdown-toggle { - font-weight: $input-group-addon-font-weight; - line-height: $input-line-height; - color: $input-group-addon-color; - background-color: $input-group-addon-bg; - border: $input-border-width solid $input-group-addon-border-color; - border-color: $input-border-color; - @include border-radius($input-border-radius); - border-left: 1px solid var(--nbx-search-filter-border-left-color); + .search-container { + display: flex; + width: 100%; - &:focus { - box-shadow: unset !important; + @include media-breakpoint-down(lg) { + display: none; + } + } + + // Search Input & Selected Object Value & Object Selector + .input-group { + // Selected Object + .search-obj-selected { + border-color: $input-border-color; + } + + // Object Selector Dropdown Button + .dropdown-toggle { + // Generate the same styles as a regular Bootstrap button. + @include button-variant($input-group-addon-bg, $input-border-color); + margin-left: 0; + font-weight: $input-group-addon-font-weight; + line-height: $input-line-height; + color: $input-group-addon-color; + background-color: $input-group-addon-bg; + border: $input-border-width solid $input-border-color; + @include border-radius($input-border-radius); + border-left: 1px solid var(--nbx-search-filter-border-left-color); + + &:focus { + box-shadow: unset !important; + } + // Don't show the dropdown icon — the filter icon is basically the same thing. + &:after { + display: none; + } + } + + // Object Selector Dropdown Menu + .search-obj-selector { + @include media-breakpoint-down(lg) { + // Limit the height and enable scrolling on mobile devices. + max-height: 70vh; + overflow-y: auto; + } + + .dropdown-item, + .dropdown-header { + font-size: $font-size-sm; + } + + .dropdown-header { + text-transform: uppercase; + } } } } @@ -436,23 +470,6 @@ div.content-container { pointer-events: none; } -.search-obj-selector { - @include media-breakpoint-down(lg) { - // Limit the height and enable scrolling on mobile devices. - max-height: 75vh; - overflow-y: auto; - } - - .dropdown-item, - .dropdown-header { - font-size: $font-size-sm; - } - - .dropdown-header { - text-transform: uppercase; - } -} - span.color-label { display: block; width: 5rem; diff --git a/netbox/utilities/templates/search/searchbar.html b/netbox/utilities/templates/search/searchbar.html index daf9b334b..d71fd8e69 100644 --- a/netbox/utilities/templates/search/searchbar.html +++ b/netbox/utilities/templates/search/searchbar.html @@ -12,7 +12,7 @@ All Objects - From e8ba4b05645e08d16a01046cb93c9697c39a8bad Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 3 Aug 2021 10:03:22 -0700 Subject: [PATCH 3/7] #6797: Improve controls & custom link styling --- netbox/extras/choices.py | 38 +- netbox/extras/templatetags/custom_links.py | 4 +- netbox/project-static/dist/netbox-dark.css | Bin 763611 -> 805578 bytes netbox/project-static/dist/netbox-light.css | Bin 475359 -> 500678 bytes netbox/project-static/styles/netbox.scss | 27 ++ netbox/project-static/styles/theme-dark.scss | 8 + netbox/project-static/styles/theme-light.scss | 14 + .../circuits/inc/circuit_termination.html | 2 +- netbox/templates/dcim/device/base.html | 370 ++++++++---------- netbox/templates/dcim/devicetype.html | 2 +- netbox/templates/dcim/interface.html | 2 +- netbox/templates/dcim/rack.html | 6 +- .../templates/dcim/rack_elevation_list.html | 10 +- netbox/templates/generic/object.html | 6 +- netbox/templates/generic/object_edit.html | 10 +- netbox/templates/generic/object_list.html | 4 +- .../templates/ipam/inc/toggle_available.html | 7 +- .../templates/ipam/iprange/ip_addresses.html | 2 +- .../templates/ipam/prefix/ip_addresses.html | 2 +- netbox/templates/ipam/prefix_list.html | 4 +- .../virtualization/cluster/base.html | 39 +- .../virtualization/virtualmachine/base.html | 4 +- netbox/utilities/templates/buttons/add.html | 7 +- netbox/utilities/templates/buttons/clone.html | 2 +- .../utilities/templates/buttons/delete.html | 2 +- netbox/utilities/templates/buttons/edit.html | 2 +- .../utilities/templates/buttons/export.html | 4 +- .../utilities/templates/buttons/import.html | 2 +- 28 files changed, 311 insertions(+), 269 deletions(-) diff --git a/netbox/extras/choices.py b/netbox/extras/choices.py index 86860b0bc..4452b5aad 100644 --- a/netbox/extras/choices.py +++ b/netbox/extras/choices.py @@ -46,28 +46,40 @@ class CustomFieldFilterLogicChoices(ChoiceSet): class CustomLinkButtonClassChoices(ChoiceSet): CLASS_DEFAULT = 'outline-dark' - CLASS_PRIMARY = 'primary' - CLASS_SUCCESS = 'success' - CLASS_INFO = 'info' - CLASS_WARNING = 'warning' - CLASS_DANGER = 'danger' - CLASS_LINK = 'link' + CLASS_LINK = 'ghost-dark' + CLASS_BLUE = 'blue' + CLASS_INDIGO = 'indigo' + CLASS_PURPLE = 'purple' + CLASS_PINK = 'pink' + CLASS_RED = 'red' + CLASS_ORANGE = 'orange' + CLASS_YELLOW = 'yellow' + CLASS_GREEN = 'green' + CLASS_TEAL = 'teal' + CLASS_CYAN = 'cyan' + CLASS_GRAY = 'secondary' CHOICES = ( (CLASS_DEFAULT, 'Default'), - (CLASS_PRIMARY, 'Primary (blue)'), - (CLASS_SUCCESS, 'Success (green)'), - (CLASS_INFO, 'Info (aqua)'), - (CLASS_WARNING, 'Warning (orange)'), - (CLASS_DANGER, 'Danger (red)'), - (CLASS_LINK, 'None (link)'), + (CLASS_LINK, 'Link'), + (CLASS_BLUE, 'Blue'), + (CLASS_INDIGO, 'Indigo'), + (CLASS_PURPLE, 'Purple'), + (CLASS_PINK, 'Pink'), + (CLASS_RED, 'Red'), + (CLASS_ORANGE, 'Orange'), + (CLASS_YELLOW, 'Yellow'), + (CLASS_GREEN, 'Green'), + (CLASS_TEAL, 'Teal'), + (CLASS_CYAN, 'Cyan'), + (CLASS_GRAY, 'Gray'), ) - # # ObjectChanges # + class ObjectChangeActionChoices(ChoiceSet): ACTION_CREATE = 'create' diff --git a/netbox/extras/templatetags/custom_links.py b/netbox/extras/templatetags/custom_links.py index 814acbbcb..fec5cf65a 100644 --- a/netbox/extras/templatetags/custom_links.py +++ b/netbox/extras/templatetags/custom_links.py @@ -10,10 +10,10 @@ from utilities.utils import render_jinja2 register = template.Library() -LINK_BUTTON = '{}\n' +LINK_BUTTON = '{}\n' GROUP_BUTTON = """ -