diff --git a/docs/features/ipam.md b/docs/features/ipam.md index 3cbe4319ded..5879a5fde51 100644 --- a/docs/features/ipam.md +++ b/docs/features/ipam.md @@ -62,8 +62,8 @@ VRF modeling in NetBox very closely follows what you find in real-world network An often overlooked component of IPAM, NetBox also tracks autonomous system (AS) numbers and their assignment to sites. Both 16- and 32-bit AS numbers are supported, and like aggregates each ASN is assigned to an authoritative RIR. -## Service Mapping +## Application Service Mapping NetBox models network applications as discrete service objects associated with devices and/or virtual machines, and optionally with specific IP addresses attached to those parent objects. These can be used to catalog the applications running on your network for reference by other objects or integrated tools. -To model services in NetBox, begin by creating a service template defining the name, protocol, and port number(s) on which the service listens. This template can then be easily instantiated to "attach" new services to a device or virtual machine. It's also possible to create new services by hand, without a template, however this approach can be tedious. +To model application services in NetBox, begin by creating an application service template defining the name, protocol, and port number(s) on which the service listens. This template can then be easily instantiated to "attach" new services to a device or virtual machine. It's also possible to create new application services by hand, without a template, however this approach can be tedious. diff --git a/docs/models/ipam/service.md b/docs/models/ipam/service.md index 0d5f12a170d..fc6ab73d2e6 100644 --- a/docs/models/ipam/service.md +++ b/docs/models/ipam/service.md @@ -1,14 +1,18 @@ -# Services +# Application Services -A service represents a layer seven application available on a device or virtual machine. For example, a service might be created in NetBox to represent an HTTP server running on TCP/8000. Each service may optionally be further bound to one or more specific interfaces assigned to the selected device or virtual machine. +An application service represents a layer seven application available on a device or virtual machine. For example, a service might be created in NetBox to represent an HTTP server running on TCP/8000. Each service may optionally be further bound to one or more specific interfaces assigned to the selected device or virtual machine. -To aid in the efficient creation of services, users may opt to first create a [service template](./servicetemplate.md) from which service definitions can be quickly replicated. +To aid in the efficient creation of application services, users may opt to first create an [application service template](./servicetemplate.md) from which service definitions can be quickly replicated. + +!!! note "Changed in NetBox v4.4" + + Previously, application services were referred to simply as "services". The name has been changed in the UI to better reflect their intended use. There is no change to the name of the model or in any programmatic NetBox APIs. ## Fields ### Parent -The parent object to which the service is assigned. This must be one of [Device](../dcim/device.md), +The parent object to which the application service is assigned. This must be one of [Device](../dcim/device.md), [VirtualMachine](../virtualization/virtualmachine.md), or [FHRP Group](./fhrpgroup.md). !!! note "Changed in NetBox v4.3" diff --git a/docs/models/ipam/servicetemplate.md b/docs/models/ipam/servicetemplate.md index 28c66b6480b..9dd69b3c480 100644 --- a/docs/models/ipam/servicetemplate.md +++ b/docs/models/ipam/servicetemplate.md @@ -1,6 +1,10 @@ -# Service Templates +# Application Service Templates -Service templates can be used to instantiate [services](./service.md) on [devices](../dcim/device.md) and [virtual machines](../virtualization/virtualmachine.md). +Application service templates can be used to instantiate [application services](./service.md) on [devices](../dcim/device.md) and [virtual machines](../virtualization/virtualmachine.md). + +!!! note "Changed in NetBox v4.4" + + Previously, application service templates were referred to simply as "service templates". The name has been changed in the UI to better reflect their intended use. There is no change to the name of the model or in any programmatic NetBox APIs. ## Fields diff --git a/docs/models/virtualization/vminterface.md b/docs/models/virtualization/vminterface.md index ba0c68b15e7..a0968f982c8 100644 --- a/docs/models/virtualization/vminterface.md +++ b/docs/models/virtualization/vminterface.md @@ -1,6 +1,6 @@ ## Interfaces -[Virtual machine](./virtualmachine.md) interfaces behave similarly to device [interfaces](../dcim/interface.md): They can be assigned to VRFs, may have IP addresses, VLANs, and services attached to them, and so on. However, given their virtual nature, they lack properties pertaining to physical attributes. For example, VM interfaces do not have a physical type and cannot have cables attached to them. +[Virtual machine](./virtualmachine.md) interfaces behave similarly to device [interfaces](../dcim/interface.md): They can be assigned to VRFs, may have IP addresses, VLANs, and so on. However, given their virtual nature, they lack properties pertaining to physical attributes. For example, VM interfaces do not have a physical type and cannot have cables attached to them. ## Fields diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py index 5e6ffb2acfc..7f8cd2f04fc 100644 --- a/netbox/ipam/filtersets.py +++ b/netbox/ipam/filtersets.py @@ -660,7 +660,7 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFil service_id = django_filters.ModelMultipleChoiceFilter( field_name='services', queryset=Service.objects.all(), - label=_('Service (ID)'), + label=_('Application Service (ID)'), ) nat_inside_id = django_filters.ModelMultipleChoiceFilter( field_name='nat_inside', diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 83bb42a4f37..30ed2c53009 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -749,7 +749,7 @@ class ServiceTemplateForm(NetBoxModelForm): comments = CommentField() fieldsets = ( - FieldSet('name', 'protocol', 'ports', 'description', 'tags', name=_('Service Template')), + FieldSet('name', 'protocol', 'ports', 'description', 'tags', name=_('Application Service Template')), ) class Meta: @@ -794,7 +794,7 @@ class ServiceForm(NetBoxModelForm): FieldSet( 'parent_object_type', 'parent', 'name', InlineFields('protocol', 'ports', label=_('Port(s)')), - 'ipaddresses', 'description', 'tags', name=_('Service') + 'ipaddresses', 'description', 'tags', name=_('Application Service') ), ) @@ -836,7 +836,7 @@ def clean(self): class ServiceCreateForm(ServiceForm): service_template = DynamicModelChoiceField( - label=_('Service template'), + label=_('Application Service template'), queryset=ServiceTemplate.objects.all(), required=False ) @@ -848,7 +848,7 @@ class ServiceCreateForm(ServiceForm): FieldSet('service_template', name=_('From Template')), FieldSet('name', 'protocol', 'ports', name=_('Custom')), ), - 'ipaddresses', 'description', 'tags', name=_('Service') + 'ipaddresses', 'description', 'tags', name=_('Application Service') ), ) @@ -877,4 +877,6 @@ def clean(self): if not self.cleaned_data['description']: self.cleaned_data['description'] = service_template.description elif not all(self.cleaned_data[f] for f in ('name', 'protocol', 'ports')): - raise forms.ValidationError(_("Must specify name, protocol, and port(s) if not using a service template.")) + raise forms.ValidationError( + _("Must specify name, protocol, and port(s) if not using an application service template.") + ) diff --git a/netbox/ipam/models/services.py b/netbox/ipam/models/services.py index 2afd1607642..c2c9ca4447d 100644 --- a/netbox/ipam/models/services.py +++ b/netbox/ipam/models/services.py @@ -55,8 +55,8 @@ class ServiceTemplate(ServiceBase, PrimaryModel): class Meta: ordering = ('name',) - verbose_name = _('service template') - verbose_name_plural = _('service templates') + verbose_name = _('application service template') + verbose_name_plural = _('application service templates') class Service(ContactsMixin, ServiceBase, PrimaryModel): @@ -84,7 +84,7 @@ class Service(ContactsMixin, ServiceBase, PrimaryModel): related_name='services', blank=True, verbose_name=_('IP addresses'), - help_text=_("The specific IP addresses (if any) to which this service is bound") + help_text=_("The specific IP addresses (if any) to which this application service is bound") ) clone_fields = ['protocol', 'ports', 'description', 'parent', 'ipaddresses', ] @@ -94,5 +94,5 @@ class Meta: models.Index(fields=('parent_object_type', 'parent_object_id')), ) ordering = ('protocol', 'ports', 'pk') # (protocol, port) may be non-unique - verbose_name = _('service') - verbose_name_plural = _('services') + verbose_name = _('application service') + verbose_name_plural = _('application services') diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index a7562a53b88..5e273357717 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -1162,6 +1162,7 @@ class ServiceTemplateTest(APIViewTestCases.APIViewTestCase): bulk_update_data = { 'description': 'New description', } + graphql_base_name = 'service_template' @classmethod def setUpTestData(cls): @@ -1197,6 +1198,7 @@ class ServiceTest(APIViewTestCases.APIViewTestCase): bulk_update_data = { 'description': 'New description', } + graphql_base_name = 'service' @classmethod def setUpTestData(cls): diff --git a/netbox/ipam/tests/test_filtersets.py b/netbox/ipam/tests/test_filtersets.py index 852fd3ea9f3..54ad5df9085 100644 --- a/netbox/ipam/tests/test_filtersets.py +++ b/netbox/ipam/tests/test_filtersets.py @@ -1101,6 +1101,9 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests): queryset = IPAddress.objects.all() filterset = IPAddressFilterSet ignore_fields = ('fhrpgroup',) + filter_name_map = { + 'application_service': 'service', + } @classmethod def setUpTestData(cls): diff --git a/netbox/netbox/navigation/menu.py b/netbox/netbox/navigation/menu.py index 982d4082991..ea8d886c4ad 100644 --- a/netbox/netbox/navigation/menu.py +++ b/netbox/netbox/navigation/menu.py @@ -209,8 +209,8 @@ label=_('Other'), items=( get_model_item('ipam', 'fhrpgroup', _('FHRP Groups')), - get_model_item('ipam', 'servicetemplate', _('Service Templates')), - get_model_item('ipam', 'service', _('Services')), + get_model_item('ipam', 'servicetemplate', _('Application Service Templates')), + get_model_item('ipam', 'service', _('Application Services')), ), ), ), diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index 10352d45a50..f8b8e95c2c1 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -305,11 +305,11 @@

{% trans "Power Utilization" %}

{% endif %}

- {% trans "Services" %} + {% trans "Application Services" %} {% if perms.ipam.add_service %} {% endif %} diff --git a/netbox/templates/ipam/ipaddress.html b/netbox/templates/ipam/ipaddress.html index 42e61040a67..8b447319219 100644 --- a/netbox/templates/ipam/ipaddress.html +++ b/netbox/templates/ipam/ipaddress.html @@ -115,7 +115,7 @@

{% trans "IP Address" %}

{% include 'inc/panel_table.html' with table=duplicate_ips_table heading='Duplicate IPs' panel_class='danger' %} {% endif %}
-

{% trans "Services" %}

+

{% trans "Application Services" %}

{% htmx_table 'ipam:service_list' ip_address_id=object.pk %}
{% plugin_right_page object %} diff --git a/netbox/templates/ipam/servicetemplate.html b/netbox/templates/ipam/servicetemplate.html index 3048e97ab44..5c3e53621bb 100644 --- a/netbox/templates/ipam/servicetemplate.html +++ b/netbox/templates/ipam/servicetemplate.html @@ -9,7 +9,7 @@
-

{% trans "Service Template" %}

+

{% trans "Application Service Template" %}

diff --git a/netbox/templates/virtualization/virtualmachine.html b/netbox/templates/virtualization/virtualmachine.html index 70c31a83b9c..2e682ff2917 100644 --- a/netbox/templates/virtualization/virtualmachine.html +++ b/netbox/templates/virtualization/virtualmachine.html @@ -151,11 +151,11 @@

{% trans "Resources" %}

- {% trans "Services" %} + {% trans "Application Services" %} {% if perms.ipam.add_service %} {% endif %} diff --git a/netbox/utilities/testing/filtersets.py b/netbox/utilities/testing/filtersets.py index 0b3d4b198dd..260c092db35 100644 --- a/netbox/utilities/testing/filtersets.py +++ b/netbox/utilities/testing/filtersets.py @@ -33,6 +33,7 @@ class BaseFilterSetTests: queryset = None filterset = None ignore_fields = tuple() + filter_name_map = {} def get_m2m_filter_name(self, field): """ @@ -46,7 +47,13 @@ def get_filters_for_model_field(self, field): """ Given a model field, return an iterable of (name, class) for each filter that should be defined on the model's FilterSet class. If the appropriate filter class cannot be determined, it will be None. + + filter_name_map provides a mechanism for developers to provide an actual field name for the + filter that is being resolved, given the field's actual name. """ + # If an alias is not present in filter_name_map, then use field.name + filter_name = self.filter_name_map.get(field.name, field.name) + # ForeignKey & OneToOneField if issubclass(field.__class__, ForeignKey) or type(field) is OneToOneRel: @@ -57,19 +64,20 @@ def get_filters_for_model_field(self, field): # ForeignKeys to ObjectType need two filters: 'app.model' & PK if field.related_model is ObjectType: return [ - (field.name, ContentTypeFilter), - (f'{field.name}_id', django_filters.ModelMultipleChoiceFilter), + (filter_name, ContentTypeFilter), + (f'{filter_name}_id', django_filters.ModelMultipleChoiceFilter), ] # ForeignKey to an MPTT-enabled model if issubclass(field.related_model, MPTTModel) and field.model is not field.related_model: - return [(f'{field.name}_id', TreeNodeMultipleChoiceFilter)] + return [(f'{filter_name}_id', TreeNodeMultipleChoiceFilter)] - return [(f'{field.name}_id', django_filters.ModelMultipleChoiceFilter)] + return [(f'{filter_name}_id', django_filters.ModelMultipleChoiceFilter)] # Many-to-many relationships (forward & backward) elif type(field) in (ManyToManyField, ManyToManyRel): filter_name = self.get_m2m_filter_name(field) + filter_name = self.filter_name_map.get(filter_name, filter_name) # ManyToManyFields to ObjectType need two filters: 'app.model' & PK if field.related_model is ObjectType: @@ -85,7 +93,7 @@ def get_filters_for_model_field(self, field): return [('tag', TagFilter)] # Unable to determine the correct filter class - return [(field.name, None)] + return [(filter_name, None)] def test_id(self): """

{% trans "Name" %}