diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index d9b6f101c..15c7cb649 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -1940,7 +1940,7 @@ class CoolingOutflowFilterForm(DeviceComponentFilterForm): fieldsets = ( FieldSet('q', 'filter_id', 'tag'), FieldSet( - 'name', 'label', 'type', 'diameter', 'diameter_unit', name=_('Attributes') + 'name', 'label', 'type', 'diameter', 'diameter_unit', 'cooling_intake_id', name=_('Attributes') ), FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')), FieldSet( @@ -1963,6 +1963,11 @@ class CoolingOutflowFilterForm(DeviceComponentFilterForm): choices=DiameterUnitChoices, required=False ) + cooling_intake_id = DynamicModelMultipleChoiceField( + label=_('Cooling intake'), + queryset=CoolingIntake.objects.all(), + required=False + ) tag = TagFilterField(model) @@ -1970,7 +1975,7 @@ class CoolingOutflowTemplateFilterForm(ModularDeviceComponentTemplateFilterForm) model = CoolingOutflowTemplate fieldsets = ( FieldSet('q', 'filter_id', 'tag'), - FieldSet('name', 'label', 'type', 'diameter', 'diameter_unit', name=_('Attributes')), + FieldSet('name', 'label', 'type', 'diameter', 'diameter_unit', 'cooling_intake_id', name=_('Attributes')), FieldSet('device_type_id', 'module_type_id', name=_('Device')), ) type = forms.MultipleChoiceField( @@ -1987,6 +1992,11 @@ class CoolingOutflowTemplateFilterForm(ModularDeviceComponentTemplateFilterForm) choices=DiameterUnitChoices, required=False ) + cooling_intake_id = DynamicModelMultipleChoiceField( + label=_('Cooling intake'), + queryset=CoolingIntakeTemplate.objects.all(), + required=False + ) class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm): diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index 9bcf5320e..3e2508d50 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -1068,6 +1068,10 @@ class CoolingIntakeFilter(ModularComponentFilterMixin, NetBoxModelFilter): max_flow_unit: BaseFilterLookup[ Annotated['FlowRateUnitEnum', strawberry.lazy('dcim.graphql.enums')] ] | None = strawberry_django.filter_field() + cooling_outflow: Annotated['CoolingOutflowFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + cooling_outflow_id: ID | None = strawberry_django.filter_field() @register_filter(models.CoolingIntakeTemplate, lookups=True) diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index a46fad9de..dc60a9eac 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -700,7 +700,12 @@ class CoolingIntake( ): """ A coolant intake port within a Device (e.g. a server cold-plate inlet or CDU intake). A - CoolingIntake is supplied by an upstream CoolingOutflow or CoolingFeed. + CoolingIntake is supplied by an upstream CoolingOutflow. The serving CoolingFeed is + derived from the Device's Rack rather than referenced directly. + + Unlike CoolingOutflow (whose parent intake is on the same Device and can therefore be + templated), an intake's upstream outflow typically lives on a different Device (e.g. a + CDU), so there is deliberately no upstream-outflow field on CoolingIntakeTemplate. """ type = models.CharField( verbose_name=_('type'), diff --git a/netbox/dcim/tables/cooling.py b/netbox/dcim/tables/cooling.py index ab623fc80..7587f6d00 100644 --- a/netbox/dcim/tables/cooling.py +++ b/netbox/dcim/tables/cooling.py @@ -166,7 +166,7 @@ class CoolingIntakeTable(ModularDeviceComponentTable): ) default_columns = ( 'pk', 'name', 'device', 'label', 'type', 'diameter', 'max_flow', - 'description', + 'cooling_outflow', 'description', ) @@ -281,7 +281,7 @@ class DeviceCoolingIntakeTable(CoolingIntakeTable): ) default_columns = ( 'pk', 'name', 'label', 'type', 'diameter', 'max_flow', - 'description', + 'cooling_outflow', 'description', )