From 999cae939e0caabf8e8e3630c9705ac59252a6cb Mon Sep 17 00:00:00 2001 From: Jeremy Sanders <23668453+jeremypng@users.noreply.github.com> Date: Tue, 7 Jan 2025 13:58:43 -0600 Subject: [PATCH] Closes #7598: GraphQL Filter Redesign --- netbox/circuits/graphql/enums.py | 86 ++ netbox/circuits/graphql/filter_mixins.py | 17 + netbox/circuits/graphql/filters.py | 193 +++- netbox/core/graphql/filter_lookups.py | 218 +++++ netbox/core/graphql/filter_mixins.py | 31 + netbox/core/graphql/filters.py | 83 +- netbox/core/graphql/mixins.py | 5 +- netbox/core/graphql/types.py | 8 +- netbox/dcim/graphql/enums.py | 857 ++++++++++++++++++ netbox/dcim/graphql/filter_mixins.py | 138 +++ netbox/dcim/graphql/filters.py | 800 +++++++++++++--- netbox/extras/graphql/enums.py | 190 ++++ netbox/extras/graphql/filter_mixins.py | 53 ++ netbox/extras/graphql/filters.py | 283 +++++- netbox/ipam/graphql/enums.py | 132 +++ netbox/ipam/graphql/filter_mixins.py | 22 + netbox/ipam/graphql/filters.py | 339 +++++-- netbox/netbox/graphql/enums.py | 120 +++ netbox/netbox/graphql/filter_mixins.py | 313 +++---- netbox/netbox/graphql/schema.py | 3 +- netbox/netbox/settings.py | 2 +- netbox/netbox/tests/test_graphql.py | 5 +- netbox/tenancy/graphql/filter_mixins.py | 33 + netbox/tenancy/graphql/filters.py | 150 ++- netbox/tenancy/graphql/types.py | 120 ++- netbox/users/graphql/filters.py | 46 +- netbox/virtualization/graphql/enums.py | 33 + .../virtualization/graphql/filter_mixins.py | 23 + netbox/virtualization/graphql/filters.py | 151 ++- netbox/vpn/graphql/enums.py | 161 ++++ netbox/vpn/graphql/filters.py | 180 +++- netbox/wireless/graphql/enums.py | 249 +++++ netbox/wireless/graphql/filter_mixins.py | 24 + netbox/wireless/graphql/filters.py | 67 +- 34 files changed, 4501 insertions(+), 634 deletions(-) create mode 100644 netbox/circuits/graphql/enums.py create mode 100644 netbox/circuits/graphql/filter_mixins.py create mode 100644 netbox/core/graphql/filter_lookups.py create mode 100644 netbox/core/graphql/filter_mixins.py create mode 100644 netbox/dcim/graphql/enums.py create mode 100644 netbox/dcim/graphql/filter_mixins.py create mode 100644 netbox/extras/graphql/enums.py create mode 100644 netbox/extras/graphql/filter_mixins.py create mode 100644 netbox/ipam/graphql/enums.py create mode 100644 netbox/ipam/graphql/filter_mixins.py create mode 100644 netbox/netbox/graphql/enums.py create mode 100644 netbox/tenancy/graphql/filter_mixins.py create mode 100644 netbox/virtualization/graphql/enums.py create mode 100644 netbox/virtualization/graphql/filter_mixins.py create mode 100644 netbox/vpn/graphql/enums.py create mode 100644 netbox/wireless/graphql/enums.py create mode 100644 netbox/wireless/graphql/filter_mixins.py diff --git a/netbox/circuits/graphql/enums.py b/netbox/circuits/graphql/enums.py new file mode 100644 index 00000000000..9fd3b8b0918 --- /dev/null +++ b/netbox/circuits/graphql/enums.py @@ -0,0 +1,86 @@ +from enum import Enum +import strawberry + +__all__ = [ + 'CircuitStatusEnum', + 'CircuitCommitRateEnum', + 'CircuitTerminationSideEnum', + 'CircuitTerminationPortSpeedEnum', + 'CircuitPriorityEnum', + 'VirtualCircuitTerminationRoleEnum', +] + +# +# Circuits +# + + +@strawberry.enum +class CircuitStatusEnum(Enum): + STATUS_DEPROVISIONING = 'deprovisioning' + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_PROVISIONING = 'provisioning' + STATUS_OFFLINE = 'offline' + STATUS_DECOMMISSIONED = 'decommissioned' + + +@strawberry.enum +class CircuitCommitRateEnum(Enum): + TEN_MBPS = 10000 + HUNDRED_MBPS = 100000 + ONE_GBPS = 1000000 + TEN_GBPS = 10000000 + TWENTY_FIVE_GBPS = 25000000 + FORTY_GBPS = 40000000 + HUNDRED_GBPS = 100000000 + TWO_HUNDRED_GBPS = 200000000 + FOUR_HUNDRED_GBPS = 400000000 + T1 = 1544 + E1 = 2048 + + +# +# CircuitTerminations +# + + +@strawberry.enum +class CircuitTerminationSideEnum(Enum): + SIDE_A = 'A' + SIDE_Z = 'Z' + + +@strawberry.enum +class CircuitTerminationPortSpeedEnum(Enum): + TEN_MBPS = 10000 + HUNDRED_MBPS = 100000 + ONE_GBPS = 1000000 + TEN_GBPS = 10000000 + TWENTY_FIVE_GBPS = 25000000 + FORTY_GBPS = 40000000 + HUNDRED_GBPS = 100000000 + TWO_HUNDRED_GBPS = 200000000 + FOUR_HUNDRED_GBPS = 400000000 + T1 = 1544 + E1 = 2048 + + +@strawberry.enum +class CircuitPriorityEnum(Enum): + PRIORITY_PRIMARY = 'primary' + PRIORITY_SECONDARY = 'secondary' + PRIORITY_TERTIARY = 'tertiary' + PRIORITY_INACTIVE = 'inactive' + + +# +# Virtual circuits +# + + +@strawberry.enum +class VirtualCircuitTerminationRoleEnum(Enum): + ROLE_PEER = 'peer' + ROLE_HUB = 'hub' + ROLE_SPOKE = 'spoke' diff --git a/netbox/circuits/graphql/filter_mixins.py b/netbox/circuits/graphql/filter_mixins.py new file mode 100644 index 00000000000..0e70fbec4f4 --- /dev/null +++ b/netbox/circuits/graphql/filter_mixins.py @@ -0,0 +1,17 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING +import strawberry +import strawberry_django +from netbox.graphql.filter_mixins import OrganizationalModelFilterMixin + +if TYPE_CHECKING: + from .filters import * + from core.graphql.filter_lookups import * + from netbox.graphql.enums import * + +__all__ = ['BaseCircuitTypeFilterMixin'] + + +@dataclass +class BaseCircuitTypeFilterMixin(OrganizationalModelFilterMixin): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() diff --git a/netbox/circuits/graphql/filters.py b/netbox/circuits/graphql/filters.py index 7d066f42860..420367f6e62 100644 --- a/netbox/circuits/graphql/filters.py +++ b/netbox/circuits/graphql/filters.py @@ -1,7 +1,33 @@ +from datetime import date +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry.scalars import ID import strawberry_django - -from circuits import filtersets, models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin +from strawberry_django import FilterLookup, DateFilterLookup +from extras.graphql.filter_mixins import * +from netbox.graphql.filter_mixins import * +from core.graphql.filter_mixins import * +from tenancy.graphql.filter_mixins import * +from dcim.graphql.filter_mixins import * +from .filter_mixins import * + +from circuits import models + +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.enums import * + from wireless.graphql.enums import * + from core.graphql.filter_lookups import * + from core.graphql.filters import * + from extras.graphql.filters import * + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from tenancy.graphql.filters import * + from wireless.graphql.filters import * + from users.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * __all__ = ( 'CircuitFilter', @@ -19,66 +45,165 @@ @strawberry_django.filter(models.CircuitTermination, lookups=True) -@autotype_decorator(filtersets.CircuitTerminationFilterSet) -class CircuitTerminationFilter(BaseFilterMixin): - pass +class CircuitTerminationFilter( + BaseObjectTypeFilterMixin, + CustomFieldsFilterMixin, + TagsFilterMixin, + ChangeLogFilterMixin, + CabledObjectModelFilterMixin, +): + circuit: Annotated['CircuitFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + term_side: Annotated['CircuitTerminationSideEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + termination_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + termination_id: ID | None = strawberry_django.filter_field() + port_speed: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + upstream_speed: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + xconnect_id: FilterLookup[str] | None = strawberry_django.filter_field() + pp_info: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.Circuit, lookups=True) -@autotype_decorator(filtersets.CircuitFilterSet) -class CircuitFilter(BaseFilterMixin): - pass +class CircuitFilter(ContactFilterMixin, ImageAttachmentFilterMixin, DistanceFilterMixin, PrimaryModelFilterMixin): + cid: FilterLookup[str] | None = strawberry_django.filter_field() + provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + provider_id: ID | None = strawberry_django.filter_field() + provider_account: Annotated['ProviderAccountFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + provider_account_id: ID | None = strawberry_django.filter_field() + type: Annotated['CircuitTypeFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + type_id: ID | None = strawberry_django.filter_field() + status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + install_date: DateFilterLookup[date] | None = strawberry_django.filter_field() + termination_date: DateFilterLookup[date] | None = strawberry_django.filter_field() + commit_rate: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.CircuitType, lookups=True) -@autotype_decorator(filtersets.CircuitTypeFilterSet) -class CircuitTypeFilter(BaseFilterMixin): +class CircuitTypeFilter(BaseCircuitTypeFilterMixin): pass @strawberry_django.filter(models.CircuitGroup, lookups=True) -@autotype_decorator(filtersets.CircuitGroupFilterSet) -class CircuitGroupFilter(BaseFilterMixin): - pass +class CircuitGroupFilter(OrganizationalModelFilterMixin): + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.CircuitGroupAssignment, lookups=True) -@autotype_decorator(filtersets.CircuitGroupAssignmentFilterSet) -class CircuitGroupAssignmentFilter(BaseFilterMixin): - pass +class CircuitGroupAssignmentFilter( + BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin +): + member_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + member_type_id: ID | None = strawberry_django.filter_field() + group: Annotated['CircuitGroupFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + priority: Annotated['CircuitPriorityEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.Provider, lookups=True) -@autotype_decorator(filtersets.ProviderFilterSet) -class ProviderFilter(BaseFilterMixin): - pass +class ProviderFilter(ContactFilterMixin, PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() @strawberry_django.filter(models.ProviderAccount, lookups=True) -@autotype_decorator(filtersets.ProviderAccountFilterSet) -class ProviderAccountFilter(BaseFilterMixin): - pass +class ProviderAccountFilter(ContactFilterMixin, PrimaryModelFilterMixin): + provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + provider_id: ID | None = strawberry_django.filter_field() + account: FilterLookup[str] | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.ProviderNetwork, lookups=True) -@autotype_decorator(filtersets.ProviderNetworkFilterSet) -class ProviderNetworkFilter(BaseFilterMixin): - pass +class ProviderNetworkFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + provider: Annotated['ProviderFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + provider_id: ID | None = strawberry_django.filter_field() + service_id: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.VirtualCircuitType, lookups=True) -@autotype_decorator(filtersets.VirtualCircuitTypeFilterSet) -class VirtualCircuitTypeFilter(BaseFilterMixin): +class VirtualCircuitTypeFilter(BaseCircuitTypeFilterMixin): pass @strawberry_django.filter(models.VirtualCircuit, lookups=True) -@autotype_decorator(filtersets.VirtualCircuitFilterSet) -class VirtualCircuitFilter(BaseFilterMixin): - pass +class VirtualCircuitFilter(PrimaryModelFilterMixin): + cid: FilterLookup[str] | None = strawberry_django.filter_field() + provider_network: Annotated['ProviderNetworkFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + provider_network_id: ID | None = strawberry_django.filter_field() + provider_account: Annotated['ProviderAccountFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + provider_account_id: ID | None = strawberry_django.filter_field() + type: Annotated['VirtualCircuitTypeFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + type_id: ID | None = strawberry_django.filter_field() + status: Annotated['CircuitStatusEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + group_assignments: Annotated['CircuitGroupAssignmentFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.VirtualCircuitTermination, lookups=True) -@autotype_decorator(filtersets.VirtualCircuitTerminationFilterSet) -class VirtualCircuitTerminationFilter(BaseFilterMixin): - pass +class VirtualCircuitTerminationFilter( + BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin +): + virtual_circuit: Annotated['VirtualCircuitFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + virtual_circuit_id: ID | None = strawberry_django.filter_field() + role: Annotated['VirtualCircuitTerminationRoleEnum', strawberry.lazy('circuits.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + interface: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + interface_id: ID | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/filter_lookups.py b/netbox/core/graphql/filter_lookups.py new file mode 100644 index 00000000000..09f470c96cb --- /dev/null +++ b/netbox/core/graphql/filter_lookups.py @@ -0,0 +1,218 @@ +from enum import Enum +from typing import TypeVar, Tuple, Generic +from django.db.models import Q, QuerySet +from django.core.exceptions import FieldDoesNotExist +from django.db.models.fields.related import ForeignKey, ManyToManyField, ManyToManyRel, ManyToOneRel +import strawberry +from strawberry import ID +from strawberry.types import Info +import strawberry_django +from strawberry_django import ( + process_filters, + FilterLookup, + RangeLookup, + ComparisonFilterLookup, + DateFilterLookup, + DatetimeFilterLookup, + TimeFilterLookup, +) + +__all__ = [ + 'JSONFilter', + 'TreeNodeFilter', + 'IntegerLookup', + 'FloatLookup', + 'ArrayLookup', + 'IntegerArrayLookup', + 'FloatArrayLookup', + 'StringArrayLookup', +] + +T = TypeVar('T') +SKIP_MSG = 'Filter will be skipped on `null` value' + + +@strawberry.input(one_of=True, description='Lookup for JSON field. Only one of the lookup fields can be set.') +class JSONLookup: + string_lookup: FilterLookup[str] | None = strawberry_django.filter_field() + int_range_lookup: RangeLookup[int] | None = strawberry_django.filter_field() + int_comparison_lookup: ComparisonFilterLookup[int] | None = strawberry_django.filter_field() + float_range_lookup: RangeLookup[float] | None = strawberry_django.filter_field() + float_comparison_lookup: ComparisonFilterLookup[float] | None = strawberry_django.filter_field() + date_lookup: DateFilterLookup[str] | None = strawberry_django.filter_field() + datetime_lookup: DatetimeFilterLookup[str] | None = strawberry_django.filter_field() + time_lookup: TimeFilterLookup[str] | None = strawberry_django.filter_field() + boolean_lookup: FilterLookup[bool] | None = strawberry_django.filter_field() + + def get_filter(self): + for field in self.__strawberry_definition__.fields: + value = getattr(self, field.name, None) + if value is not strawberry.UNSET: + return value + return None + + +@strawberry.input(one_of=True, description='Lookup for Integer fields. Only one of the lookup fields can be set.') +class IntegerLookup: + filter_lookup: FilterLookup[int] | None = strawberry_django.filter_field() + range_lookup: RangeLookup[int] | None = strawberry_django.filter_field() + comparison_lookup: ComparisonFilterLookup[int] | None = strawberry_django.filter_field() + + def get_filter(self): + for field in self.__strawberry_definition__.fields: + value = getattr(self, field.name, None) + if value is not strawberry.UNSET: + return value + return None + + @strawberry_django.filter_field + def filter(self, info: Info, queryset: QuerySet, prefix: str = '') -> Tuple[QuerySet, Q]: + filters = self.get_filter() + + if not filters: + return queryset, Q() + + return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) + + +@strawberry.input(one_of=True, description='Lookup for Float fields. Only one of the lookup fields can be set.') +class FloatLookup: + filter_lookup: FilterLookup[float] | None = strawberry_django.filter_field() + range_lookup: RangeLookup[float] | None = strawberry_django.filter_field() + comparison_lookup: ComparisonFilterLookup[float] | None = strawberry_django.filter_field() + + def get_filter(self): + for field in self.__strawberry_definition__.fields: + value = getattr(self, field.name, None) + if value is not strawberry.UNSET: + return value + return None + + @strawberry_django.filter_field + def filter(self, info: Info, queryset: QuerySet, prefix: str = '') -> Tuple[QuerySet, Q]: + filters = self.get_filter() + + if not filters: + return queryset, Q() + + return process_filters(filters=filters, queryset=queryset, info=info, prefix=prefix) + + +@strawberry.input +class JSONFilter: + """ + Class for JSON field lookups with paths + """ + + path: str + lookup: JSONLookup + + @strawberry_django.filter_field + def filter(self, info: Info, queryset: QuerySet, prefix: str = '') -> Tuple[QuerySet, Q]: + filters = self.lookup.get_filter() + + if not filters: + return queryset, Q() + + json_path = f'{prefix}{self.path}__' + return process_filters(filters=filters, queryset=queryset, info=info, prefix=json_path) + + +@strawberry.enum +class TreeNodeMatch(Enum): + EXACT = 'exact' # Just the node itself + DESCENDANTS = 'descendants' # Node and all descendants + SELF_AND_DESCENDANTS = 'self_and_descendants' # Node and all descendants + CHILDREN = 'children' # Just immediate children + SIBLINGS = 'siblings' # Nodes with same parent + ANCESTORS = 'ancestors' # All parent nodes + PARENT = 'parent' # Just immediate parent + + +@strawberry.input +class TreeNodeFilter: + id: ID + match_type: TreeNodeMatch + + @strawberry_django.filter_field + def filter(self, info: Info, queryset: QuerySet, prefix: str = '') -> Tuple[QuerySet, Q]: + model_field_name = prefix.removesuffix('__').removesuffix('_id') + model_field = None + try: + model_field = queryset.model._meta.get_field(model_field_name) + except FieldDoesNotExist: + try: + model_field = queryset.model._meta.get_field(f'{model_field_name}s') + except FieldDoesNotExist: + return queryset, Q(pk__in=[]) + + if hasattr(model_field, 'related_model'): + related_model = model_field.related_model + else: + return queryset, Q(pk__in=[]) + + # Generate base Q filter for the related model without prefix + q_filter = generate_tree_node_q_filter(related_model, self) + + # Handle different relationship types + if isinstance(model_field, (ManyToManyField, ManyToManyRel)): + return queryset, Q(**{f'{model_field_name}__in': related_model.objects.filter(q_filter)}) + elif isinstance(model_field, ForeignKey): + return queryset, Q(**{f'{model_field_name}__{k}': v for k, v in q_filter.children}) + elif isinstance(model_field, ManyToOneRel): + return queryset, Q(**{f'{model_field_name}__in': related_model.objects.filter(q_filter)}) + else: + return queryset, Q(**{f'{model_field_name}__{k}': v for k, v in q_filter.children}) + + +def generate_tree_node_q_filter(model_class, filter_value: TreeNodeFilter) -> Q: + """ + Generate appropriate Q filter for MPTT tree filtering based on match type + """ + try: + node = model_class.objects.get(id=filter_value.id) + except model_class.DoesNotExist: + return Q(pk__in=[]) + + if filter_value.match_type == TreeNodeMatch.EXACT: + return Q(id=filter_value.id) + elif filter_value.match_type == TreeNodeMatch.DESCENDANTS: + return Q(tree_id=node.tree_id, lft__gt=node.lft, rght__lt=node.rght) + elif filter_value.match_type == TreeNodeMatch.SELF_AND_DESCENDANTS: + return Q(tree_id=node.tree_id, lft__gte=node.lft, rght__lte=node.rght) + elif filter_value.match_type == TreeNodeMatch.CHILDREN: + return Q(tree_id=node.tree_id, level=node.level + 1, lft__gt=node.lft, rght__lt=node.rght) + elif filter_value.match_type == TreeNodeMatch.SIBLINGS: + return Q(tree_id=node.tree_id, level=node.level, parent=node.parent) & ~Q(id=node.id) + elif filter_value.match_type == TreeNodeMatch.ANCESTORS: + return Q(tree_id=node.tree_id, lft__lt=node.lft, rght__gt=node.rght) + elif filter_value.match_type == TreeNodeMatch.PARENT: + return Q(id=node.parent_id) if node.parent_id else Q(pk__in=[]) + return Q() + + +@strawberry.input(one_of=True, description='Lookup for Array fields. Only one of the lookup fields can be set.') +class ArrayLookup(Generic[T]): + """ + Class for Array field lookups + """ + + contains: list[T] | None = strawberry_django.filter_field(description='Contains the value') + contained_by: list[T] | None = strawberry_django.filter_field(description='Contained by the value') + overlap: list[T] | None = strawberry_django.filter_field(description='Overlaps with the value') + length: int | None = strawberry_django.filter_field(description='Length of the array') + + +@strawberry.input(one_of=True, description='Lookup for Array fields. Only one of the lookup fields can be set.') +class IntegerArrayLookup(ArrayLookup[int]): + pass + + +@strawberry.input(one_of=True, description='Lookup for Array fields. Only one of the lookup fields can be set.') +class FloatArrayLookup(ArrayLookup[float]): + pass + + +@strawberry.input(one_of=True, description='Lookup for Array fields. Only one of the lookup fields can be set.') +class StringArrayLookup(ArrayLookup[str]): + pass diff --git a/netbox/core/graphql/filter_mixins.py b/netbox/core/graphql/filter_mixins.py new file mode 100644 index 00000000000..1748deab50a --- /dev/null +++ b/netbox/core/graphql/filter_mixins.py @@ -0,0 +1,31 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry import ID +import strawberry_django +from strawberry_django import DatetimeFilterLookup + + +if TYPE_CHECKING: + from .filters import * + +__all__ = ['BaseFilterMixin', 'BaseObjectTypeFilterMixin', 'ChangeLogFilterMixin'] + + +# @strawberry.input +class BaseFilterMixin: ... + + +@dataclass +class BaseObjectTypeFilterMixin(BaseFilterMixin): + id: ID | None = strawberry.UNSET + + +@dataclass +class ChangeLogFilterMixin(BaseFilterMixin): + changelog: Annotated['ObjectChangeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/filters.py b/netbox/core/graphql/filters.py index 82da685a590..dcbc6f4f85f 100644 --- a/netbox/core/graphql/filters.py +++ b/netbox/core/graphql/filters.py @@ -1,28 +1,93 @@ +from datetime import datetime +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry.scalars import ID import strawberry_django +from strawberry_django import ( + DatetimeFilterLookup, + FilterLookup, +) +from django.contrib.contenttypes.models import ContentType as DjangoContentType +from core.graphql.filter_mixins import BaseFilterMixin +from core.graphql.filter_lookups import JSONFilter +from netbox.graphql.filter_mixins import ( + PrimaryModelFilterMixin, +) + +from core import models + +if TYPE_CHECKING: + from core.graphql.filter_lookups import * + from users.graphql.filters import * -from core import filtersets, models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin __all__ = ( 'DataFileFilter', 'DataSourceFilter', 'ObjectChangeFilter', + 'ContentTypeFilter', ) @strawberry_django.filter(models.DataFile, lookups=True) -@autotype_decorator(filtersets.DataFileFilterSet) class DataFileFilter(BaseFilterMixin): - pass + id: ID | None = strawberry_django.filter_field() + created: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + last_updated: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + source: Annotated['DataSourceFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + source_id: ID | None = strawberry_django.filter_field() + path: FilterLookup[str] | None = strawberry_django.filter_field() + size: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + hash: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.DataSource, lookups=True) -@autotype_decorator(filtersets.DataSourceFilterSet) -class DataSourceFilter(BaseFilterMixin): - pass +class DataSourceFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + type: FilterLookup[str] | None = strawberry_django.filter_field() + source_url: FilterLookup[str] | None = strawberry_django.filter_field() + status: FilterLookup[str] | None = strawberry_django.filter_field() + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + ignore_rules: FilterLookup[str] | None = strawberry_django.filter_field() + parameters: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + last_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + datafiles: Annotated['DataFileFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ObjectChange, lookups=True) -@autotype_decorator(filtersets.ObjectChangeFilterSet) class ObjectChangeFilter(BaseFilterMixin): - pass + id: ID | None = strawberry_django.filter_field() + time: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() + user_name: FilterLookup[str] | None = strawberry_django.filter_field() + request_id: FilterLookup[str] | None = strawberry_django.filter_field() + action: FilterLookup[str] | None = strawberry_django.filter_field() + changed_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + changed_object_type_id: ID | None = strawberry_django.filter_field() + changed_object_id: ID | None = strawberry_django.filter_field() + related_object_type_id: ID | None = strawberry_django.filter_field() + related_object_id: ID | None = strawberry_django.filter_field() + object_repr: FilterLookup[str] | None = strawberry_django.filter_field() + prechange_data: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + postchange_data: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@strawberry_django.filter(DjangoContentType, lookups=True) +class ContentTypeFilter(BaseFilterMixin): + id: ID | None = strawberry_django.filter_field() + app_label: FilterLookup[str] | None = strawberry_django.filter_field() + model: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/core/graphql/mixins.py b/netbox/core/graphql/mixins.py index 5195b52a0d8..72191e6fd64 100644 --- a/netbox/core/graphql/mixins.py +++ b/netbox/core/graphql/mixins.py @@ -1,4 +1,4 @@ -from typing import Annotated, List +from typing import Annotated, List, TYPE_CHECKING import strawberry import strawberry_django @@ -6,6 +6,9 @@ from core.models import ObjectChange +if TYPE_CHECKING: + from netbox.core.graphql.types import ObjectChangeType + __all__ = ( 'ChangelogMixin', ) diff --git a/netbox/core/graphql/types.py b/netbox/core/graphql/types.py index 09385d7c124..fe28bdc4d07 100644 --- a/netbox/core/graphql/types.py +++ b/netbox/core/graphql/types.py @@ -2,7 +2,7 @@ import strawberry import strawberry_django - +from django.contrib.contenttypes.models import ContentType as DjangoContentType from core import models from netbox.graphql.types import BaseObjectType, NetBoxObjectType from .filters import * @@ -11,6 +11,7 @@ 'DataFileType', 'DataSourceType', 'ObjectChangeType', + 'ContentType', ) @@ -40,3 +41,8 @@ class DataSourceType(NetBoxObjectType): ) class ObjectChangeType(BaseObjectType): pass + + +@strawberry_django.type(DjangoContentType, fields='__all__') +class ContentType: + pass diff --git a/netbox/dcim/graphql/enums.py b/netbox/dcim/graphql/enums.py new file mode 100644 index 00000000000..2e99ab5e991 --- /dev/null +++ b/netbox/dcim/graphql/enums.py @@ -0,0 +1,857 @@ +from enum import Enum +import strawberry + + +# +# Sites +# + +@strawberry.enum +class SiteStatusEnum(Enum): + STATUS_PLANNED = 'planned' + STATUS_STAGING = 'staging' + STATUS_ACTIVE = 'active' + STATUS_DECOMMISSIONING = 'decommissioning' + STATUS_RETIRED = 'retired' + + +# +# Locations +# + +@strawberry.enum +class LocationStatusEnum(Enum): + STATUS_PLANNED = 'planned' + STATUS_STAGING = 'staging' + STATUS_ACTIVE = 'active' + STATUS_DECOMMISSIONING = 'decommissioning' + STATUS_RETIRED = 'retired' + + +# +# Racks +# + +@strawberry.enum +class RackFormFactorEnum(Enum): + TYPE_2POST = '2-post-frame' + TYPE_4POST = '4-post-frame' + TYPE_CABINET = '4-post-cabinet' + TYPE_WALLFRAME = 'wall-frame' + TYPE_WALLFRAME_VERTICAL = 'wall-frame-vertical' + TYPE_WALLCABINET = 'wall-cabinet' + TYPE_WALLCABINET_VERTICAL = 'wall-cabinet-vertical' + + +@strawberry.enum +class RackWidthEnum(Enum): + + WIDTH_10IN = 10 + WIDTH_19IN = 19 + WIDTH_21IN = 21 + WIDTH_23IN = 23 + + +@strawberry.enum +class RackStatusEnum(Enum): + STATUS_RESERVED = 'reserved' + STATUS_AVAILABLE = 'available' + STATUS_PLANNED = 'planned' + STATUS_ACTIVE = 'active' + STATUS_DEPRECATED = 'deprecated' + + +@strawberry.enum +class RackDimensionUnitEnum(Enum): + UNIT_MILLIMETER = 'mm' + UNIT_INCH = 'in' + + +@strawberry.enum +class RackElevationDetailRenderEnum(Enum): + RENDER_JSON = 'json' + RENDER_SVG = 'svg' + + +@strawberry.enum +class RackAirflowEnum(Enum): + FRONT_TO_REAR = 'front-to-rear' + REAR_TO_FRONT = 'rear-to-front' + + +# +# DeviceTypes +# + +@strawberry.enum +class SubdeviceRoleEnum(Enum): + ROLE_PARENT = 'parent' + ROLE_CHILD = 'child' + + +# +# Devices +# + +@strawberry.enum +class DeviceFaceEnum(Enum): + FACE_FRONT = 'front' + FACE_REAR = 'rear' + + +@strawberry.enum +class DeviceStatusEnum(Enum): + STATUS_OFFLINE = 'offline' + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_STAGED = 'staged' + STATUS_FAILED = 'failed' + STATUS_INVENTORY = 'inventory' + STATUS_DECOMMISSIONING = 'decommissioning' + + +@strawberry.enum +class DeviceAirflowEnum(Enum): + + AIRFLOW_FRONT_TO_REAR = 'front-to-rear' + AIRFLOW_REAR_TO_FRONT = 'rear-to-front' + AIRFLOW_LEFT_TO_RIGHT = 'left-to-right' + AIRFLOW_RIGHT_TO_LEFT = 'right-to-left' + AIRFLOW_SIDE_TO_REAR = 'side-to-rear' + AIRFLOW_REAR_TO_SIDE = 'rear-to-side' + AIRFLOW_BOTTOM_TO_TOP = 'bottom-to-top' + AIRFLOW_TOP_TO_BOTTOM = 'top-to-bottom' + AIRFLOW_PASSIVE = 'passive' + AIRFLOW_MIXED = 'mixed' + + +# +# Modules +# + +@strawberry.enum +class ModuleStatusEnum(Enum): + key = 'Module.status' + + STATUS_OFFLINE = 'offline' + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_STAGED = 'staged' + STATUS_FAILED = 'failed' + STATUS_DECOMMISSIONING = 'decommissioning' + + +@strawberry.enum +class ModuleAirflowEnum(Enum): + + FRONT_TO_REAR = 'front-to-rear' + REAR_TO_FRONT = 'rear-to-front' + LEFT_TO_RIGHT = 'left-to-right' + RIGHT_TO_LEFT = 'right-to-left' + SIDE_TO_REAR = 'side-to-rear' + PASSIVE = 'passive' + + +# +# ConsolePorts +# + +@strawberry.enum +class ConsolePortTypeEnum(Enum): + + TYPE_DE9 = 'de-9' + TYPE_DB25 = 'db-25' + TYPE_RJ11 = 'rj-11' + TYPE_RJ12 = 'rj-12' + TYPE_RJ45 = 'rj-45' + TYPE_MINI_DIN_8 = 'mini-din-8' + TYPE_USB_A = 'usb-a' + TYPE_USB_B = 'usb-b' + TYPE_USB_C = 'usb-c' + TYPE_USB_MINI_A = 'usb-mini-a' + TYPE_USB_MINI_B = 'usb-mini-b' + TYPE_USB_MICRO_A = 'usb-micro-a' + TYPE_USB_MICRO_B = 'usb-micro-b' + TYPE_USB_MICRO_AB = 'usb-micro-ab' + TYPE_OTHER = 'other' + + +@strawberry.enum +class ConsolePortSpeedEnum(Enum): + + SPEED_1200 = 1200 + SPEED_2400 = 2400 + SPEED_4800 = 4800 + SPEED_9600 = 9600 + SPEED_19200 = 19200 + SPEED_38400 = 38400 + SPEED_57600 = 57600 + SPEED_115200 = 115200 + + +# +# PowerPorts +# + +@strawberry.enum +class PowerPortTypeEnum(Enum): + + # IEC 60320 + TYPE_IEC_C6 = 'iec-60320-c6' + TYPE_IEC_C8 = 'iec-60320-c8' + TYPE_IEC_C14 = 'iec-60320-c14' + TYPE_IEC_C16 = 'iec-60320-c16' + TYPE_IEC_C20 = 'iec-60320-c20' + TYPE_IEC_C22 = 'iec-60320-c22' + # IEC 60309 + TYPE_IEC_PNE4H = 'iec-60309-p-n-e-4h' + TYPE_IEC_PNE6H = 'iec-60309-p-n-e-6h' + TYPE_IEC_PNE9H = 'iec-60309-p-n-e-9h' + TYPE_IEC_2PE4H = 'iec-60309-2p-e-4h' + TYPE_IEC_2PE6H = 'iec-60309-2p-e-6h' + TYPE_IEC_2PE9H = 'iec-60309-2p-e-9h' + TYPE_IEC_3PE4H = 'iec-60309-3p-e-4h' + TYPE_IEC_3PE6H = 'iec-60309-3p-e-6h' + TYPE_IEC_3PE9H = 'iec-60309-3p-e-9h' + TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h' + TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h' + TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h' + # IEC 60906-1 + TYPE_IEC_60906_1 = 'iec-60906-1' + TYPE_NBR_14136_10A = 'nbr-14136-10a' + TYPE_NBR_14136_20A = 'nbr-14136-20a' + # NEMA non-locking + TYPE_NEMA_115P = 'nema-1-15p' + TYPE_NEMA_515P = 'nema-5-15p' + TYPE_NEMA_520P = 'nema-5-20p' + TYPE_NEMA_530P = 'nema-5-30p' + TYPE_NEMA_550P = 'nema-5-50p' + TYPE_NEMA_615P = 'nema-6-15p' + TYPE_NEMA_620P = 'nema-6-20p' + TYPE_NEMA_630P = 'nema-6-30p' + TYPE_NEMA_650P = 'nema-6-50p' + TYPE_NEMA_1030P = 'nema-10-30p' + TYPE_NEMA_1050P = 'nema-10-50p' + TYPE_NEMA_1420P = 'nema-14-20p' + TYPE_NEMA_1430P = 'nema-14-30p' + TYPE_NEMA_1450P = 'nema-14-50p' + TYPE_NEMA_1460P = 'nema-14-60p' + TYPE_NEMA_1515P = 'nema-15-15p' + TYPE_NEMA_1520P = 'nema-15-20p' + TYPE_NEMA_1530P = 'nema-15-30p' + TYPE_NEMA_1550P = 'nema-15-50p' + TYPE_NEMA_1560P = 'nema-15-60p' + # NEMA locking + TYPE_NEMA_L115P = 'nema-l1-15p' + TYPE_NEMA_L515P = 'nema-l5-15p' + TYPE_NEMA_L520P = 'nema-l5-20p' + TYPE_NEMA_L530P = 'nema-l5-30p' + TYPE_NEMA_L550P = 'nema-l5-50p' + TYPE_NEMA_L615P = 'nema-l6-15p' + TYPE_NEMA_L620P = 'nema-l6-20p' + TYPE_NEMA_L630P = 'nema-l6-30p' + TYPE_NEMA_L650P = 'nema-l6-50p' + TYPE_NEMA_L1030P = 'nema-l10-30p' + TYPE_NEMA_L1420P = 'nema-l14-20p' + TYPE_NEMA_L1430P = 'nema-l14-30p' + TYPE_NEMA_L1450P = 'nema-l14-50p' + TYPE_NEMA_L1460P = 'nema-l14-60p' + TYPE_NEMA_L1520P = 'nema-l15-20p' + TYPE_NEMA_L1530P = 'nema-l15-30p' + TYPE_NEMA_L1550P = 'nema-l15-50p' + TYPE_NEMA_L1560P = 'nema-l15-60p' + TYPE_NEMA_L2120P = 'nema-l21-20p' + TYPE_NEMA_L2130P = 'nema-l21-30p' + TYPE_NEMA_L2220P = 'nema-l22-20p' + TYPE_NEMA_L2230P = 'nema-l22-30p' + # California style + TYPE_CS6361C = 'cs6361c' + TYPE_CS6365C = 'cs6365c' + TYPE_CS8165C = 'cs8165c' + TYPE_CS8265C = 'cs8265c' + TYPE_CS8365C = 'cs8365c' + TYPE_CS8465C = 'cs8465c' + # ITA/international + TYPE_ITA_C = 'ita-c' + TYPE_ITA_E = 'ita-e' + TYPE_ITA_F = 'ita-f' + TYPE_ITA_EF = 'ita-ef' + TYPE_ITA_G = 'ita-g' + TYPE_ITA_H = 'ita-h' + TYPE_ITA_I = 'ita-i' + TYPE_ITA_J = 'ita-j' + TYPE_ITA_K = 'ita-k' + TYPE_ITA_L = 'ita-l' + TYPE_ITA_M = 'ita-m' + TYPE_ITA_N = 'ita-n' + TYPE_ITA_O = 'ita-o' + # USB + TYPE_USB_A = 'usb-a' + TYPE_USB_B = 'usb-b' + TYPE_USB_C = 'usb-c' + TYPE_USB_MINI_A = 'usb-mini-a' + TYPE_USB_MINI_B = 'usb-mini-b' + TYPE_USB_MICRO_A = 'usb-micro-a' + TYPE_USB_MICRO_B = 'usb-micro-b' + TYPE_USB_MICRO_AB = 'usb-micro-ab' + TYPE_USB_3_B = 'usb-3-b' + TYPE_USB_3_MICROB = 'usb-3-micro-b' + # Molex + TYPE_MOLEX_MICRO_FIT_1X2 = 'molex-micro-fit-1x2' + TYPE_MOLEX_MICRO_FIT_2X2 = 'molex-micro-fit-2x2' + TYPE_MOLEX_MICRO_FIT_2X4 = 'molex-micro-fit-2x4' + # Direct current (DC) + TYPE_DC = 'dc-terminal' + # Proprietary + TYPE_SAF_D_GRID = 'saf-d-grid' + TYPE_NEUTRIK_POWERCON_20A = 'neutrik-powercon-20' + TYPE_NEUTRIK_POWERCON_32A = 'neutrik-powercon-32' + TYPE_NEUTRIK_POWERCON_TRUE1 = 'neutrik-powercon-true1' + TYPE_NEUTRIK_POWERCON_TRUE1_TOP = 'neutrik-powercon-true1-top' + TYPE_UBIQUITI_SMARTPOWER = 'ubiquiti-smartpower' + # Other + TYPE_HARDWIRED = 'hardwired' + TYPE_OTHER = 'other' + + +# +# PowerOutlets +# + +@strawberry.enum +class PowerOutletTypeEnum(Enum): + + # IEC 60320 + TYPE_IEC_C5 = 'iec-60320-c5' + TYPE_IEC_C7 = 'iec-60320-c7' + TYPE_IEC_C13 = 'iec-60320-c13' + TYPE_IEC_C15 = 'iec-60320-c15' + TYPE_IEC_C19 = 'iec-60320-c19' + TYPE_IEC_C21 = 'iec-60320-c21' + # IEC 60309 + TYPE_IEC_PNE4H = 'iec-60309-p-n-e-4h' + TYPE_IEC_PNE6H = 'iec-60309-p-n-e-6h' + TYPE_IEC_PNE9H = 'iec-60309-p-n-e-9h' + TYPE_IEC_2PE4H = 'iec-60309-2p-e-4h' + TYPE_IEC_2PE6H = 'iec-60309-2p-e-6h' + TYPE_IEC_2PE9H = 'iec-60309-2p-e-9h' + TYPE_IEC_3PE4H = 'iec-60309-3p-e-4h' + TYPE_IEC_3PE6H = 'iec-60309-3p-e-6h' + TYPE_IEC_3PE9H = 'iec-60309-3p-e-9h' + TYPE_IEC_3PNE4H = 'iec-60309-3p-n-e-4h' + TYPE_IEC_3PNE6H = 'iec-60309-3p-n-e-6h' + TYPE_IEC_3PNE9H = 'iec-60309-3p-n-e-9h' + # IEC 60906-1 + TYPE_IEC_60906_1 = 'iec-60906-1' + TYPE_NBR_14136_10A = 'nbr-14136-10a' + TYPE_NBR_14136_20A = 'nbr-14136-20a' + # NEMA non-locking + TYPE_NEMA_115R = 'nema-1-15r' + TYPE_NEMA_515R = 'nema-5-15r' + TYPE_NEMA_520R = 'nema-5-20r' + TYPE_NEMA_530R = 'nema-5-30r' + TYPE_NEMA_550R = 'nema-5-50r' + TYPE_NEMA_615R = 'nema-6-15r' + TYPE_NEMA_620R = 'nema-6-20r' + TYPE_NEMA_630R = 'nema-6-30r' + TYPE_NEMA_650R = 'nema-6-50r' + TYPE_NEMA_1030R = 'nema-10-30r' + TYPE_NEMA_1050R = 'nema-10-50r' + TYPE_NEMA_1420R = 'nema-14-20r' + TYPE_NEMA_1430R = 'nema-14-30r' + TYPE_NEMA_1450R = 'nema-14-50r' + TYPE_NEMA_1460R = 'nema-14-60r' + TYPE_NEMA_1515R = 'nema-15-15r' + TYPE_NEMA_1520R = 'nema-15-20r' + TYPE_NEMA_1530R = 'nema-15-30r' + TYPE_NEMA_1550R = 'nema-15-50r' + TYPE_NEMA_1560R = 'nema-15-60r' + # NEMA locking + TYPE_NEMA_L115R = 'nema-l1-15r' + TYPE_NEMA_L515R = 'nema-l5-15r' + TYPE_NEMA_L520R = 'nema-l5-20r' + TYPE_NEMA_L530R = 'nema-l5-30r' + TYPE_NEMA_L550R = 'nema-l5-50r' + TYPE_NEMA_L615R = 'nema-l6-15r' + TYPE_NEMA_L620R = 'nema-l6-20r' + TYPE_NEMA_L630R = 'nema-l6-30r' + TYPE_NEMA_L650R = 'nema-l6-50r' + TYPE_NEMA_L1030R = 'nema-l10-30r' + TYPE_NEMA_L1420R = 'nema-l14-20r' + TYPE_NEMA_L1430R = 'nema-l14-30r' + TYPE_NEMA_L1450R = 'nema-l14-50r' + TYPE_NEMA_L1460R = 'nema-l14-60r' + TYPE_NEMA_L1520R = 'nema-l15-20r' + TYPE_NEMA_L1530R = 'nema-l15-30r' + TYPE_NEMA_L1550R = 'nema-l15-50r' + TYPE_NEMA_L1560R = 'nema-l15-60r' + TYPE_NEMA_L2120R = 'nema-l21-20r' + TYPE_NEMA_L2130R = 'nema-l21-30r' + TYPE_NEMA_L2220R = 'nema-l22-20r' + TYPE_NEMA_L2230R = 'nema-l22-30r' + # California style + TYPE_CS6360C = 'CS6360C' + TYPE_CS6364C = 'CS6364C' + TYPE_CS8164C = 'CS8164C' + TYPE_CS8264C = 'CS8264C' + TYPE_CS8364C = 'CS8364C' + TYPE_CS8464C = 'CS8464C' + # ITA/international + TYPE_ITA_E = 'ita-e' + TYPE_ITA_F = 'ita-f' + TYPE_ITA_G = 'ita-g' + TYPE_ITA_H = 'ita-h' + TYPE_ITA_I = 'ita-i' + TYPE_ITA_J = 'ita-j' + TYPE_ITA_K = 'ita-k' + TYPE_ITA_L = 'ita-l' + TYPE_ITA_M = 'ita-m' + TYPE_ITA_N = 'ita-n' + TYPE_ITA_O = 'ita-o' + TYPE_ITA_MULTISTANDARD = 'ita-multistandard' + # USB + TYPE_USB_A = 'usb-a' + TYPE_USB_MICROB = 'usb-micro-b' + TYPE_USB_C = 'usb-c' + # Molex + TYPE_MOLEX_MICRO_FIT_1X2 = 'molex-micro-fit-1x2' + TYPE_MOLEX_MICRO_FIT_2X2 = 'molex-micro-fit-2x2' + TYPE_MOLEX_MICRO_FIT_2X4 = 'molex-micro-fit-2x4' + # Direct current (DC) + TYPE_DC = 'dc-terminal' + # Proprietary + TYPE_EATON_C39 = 'eaton-c39' + TYPE_HDOT_CX = 'hdot-cx' + TYPE_SAF_D_GRID = 'saf-d-grid' + TYPE_NEUTRIK_POWERCON_20A = 'neutrik-powercon-20a' + TYPE_NEUTRIK_POWERCON_32A = 'neutrik-powercon-32a' + TYPE_NEUTRIK_POWERCON_TRUE1 = 'neutrik-powercon-true1' + TYPE_NEUTRIK_POWERCON_TRUE1_TOP = 'neutrik-powercon-true1-top' + TYPE_UBIQUITI_SMARTPOWER = 'ubiquiti-smartpower' + # Other + TYPE_HARDWIRED = 'hardwired' + TYPE_OTHER = 'other' + + +@strawberry.enum +class PowerOutletFeedLegEnum(Enum): + + FEED_LEG_A = 'A' + FEED_LEG_B = 'B' + FEED_LEG_C = 'C' + + +# +# Interfaces +# + +@strawberry.enum +class InterfaceKindEnum(Enum): + KIND_PHYSICAL = 'physical' + KIND_VIRTUAL = 'virtual' + KIND_WIRELESS = 'wireless' + + +@strawberry.enum +class InterfaceTypeEnum(Enum): + + # Virtual + TYPE_VIRTUAL = 'virtual' + TYPE_BRIDGE = 'bridge' + TYPE_LAG = 'lag' + + # Ethernet + TYPE_100ME_FX = '100base-fx' + TYPE_100ME_LFX = '100base-lfx' + TYPE_100ME_FIXED = '100base-tx' + TYPE_100ME_T1 = '100base-t1' + TYPE_100ME_SFP = '100base-x-sfp' + TYPE_1GE_FIXED = '1000base-t' + TYPE_1GE_LX_FIXED = '1000base-lx' + TYPE_1GE_TX_FIXED = '1000base-tx' + TYPE_1GE_GBIC = '1000base-x-gbic' + TYPE_1GE_SFP = '1000base-x-sfp' + TYPE_2GE_FIXED = '2.5gbase-t' + TYPE_5GE_FIXED = '5gbase-t' + TYPE_10GE_FIXED = '10gbase-t' + TYPE_10GE_CX4 = '10gbase-cx4' + TYPE_10GE_SFP_PLUS = '10gbase-x-sfpp' + TYPE_10GE_XFP = '10gbase-x-xfp' + TYPE_10GE_XENPAK = '10gbase-x-xenpak' + TYPE_10GE_X2 = '10gbase-x-x2' + TYPE_25GE_SFP28 = '25gbase-x-sfp28' + TYPE_50GE_SFP56 = '50gbase-x-sfp56' + TYPE_40GE_QSFP_PLUS = '40gbase-x-qsfpp' + TYPE_50GE_QSFP28 = '50gbase-x-sfp28' + TYPE_100GE_CFP = '100gbase-x-cfp' + TYPE_100GE_CFP2 = '100gbase-x-cfp2' + TYPE_100GE_CFP4 = '100gbase-x-cfp4' + TYPE_100GE_CXP = '100gbase-x-cxp' + TYPE_100GE_CPAK = '100gbase-x-cpak' + TYPE_100GE_DSFP = '100gbase-x-dsfp' + TYPE_100GE_SFP_DD = '100gbase-x-sfpdd' + TYPE_100GE_QSFP28 = '100gbase-x-qsfp28' + TYPE_100GE_QSFP_DD = '100gbase-x-qsfpdd' + TYPE_200GE_CFP2 = '200gbase-x-cfp2' + TYPE_200GE_QSFP56 = '200gbase-x-qsfp56' + TYPE_200GE_QSFP_DD = '200gbase-x-qsfpdd' + TYPE_400GE_CFP2 = '400gbase-x-cfp2' + TYPE_400GE_QSFP112 = '400gbase-x-qsfp112' + TYPE_400GE_QSFP_DD = '400gbase-x-qsfpdd' + TYPE_400GE_OSFP = '400gbase-x-osfp' + TYPE_400GE_OSFP_RHS = '400gbase-x-osfp-rhs' + TYPE_400GE_CDFP = '400gbase-x-cdfp' + TYPE_400GE_CFP8 = '400gbase-x-cfp8' + TYPE_800GE_QSFP_DD = '800gbase-x-qsfpdd' + TYPE_800GE_OSFP = '800gbase-x-osfp' + + # Ethernet Backplane + TYPE_1GE_KX = '1000base-kx' + TYPE_2GE_KX = '2.5gbase-kx' + TYPE_5GE_KR = '5gbase-kr' + TYPE_10GE_KR = '10gbase-kr' + TYPE_10GE_KX4 = '10gbase-kx4' + TYPE_25GE_KR = '25gbase-kr' + TYPE_40GE_KR4 = '40gbase-kr4' + TYPE_50GE_KR = '50gbase-kr' + TYPE_100GE_KP4 = '100gbase-kp4' + TYPE_100GE_KR2 = '100gbase-kr2' + TYPE_100GE_KR4 = '100gbase-kr4' + + # Wireless + TYPE_80211A = 'ieee802.11a' + TYPE_80211G = 'ieee802.11g' + TYPE_80211N = 'ieee802.11n' + TYPE_80211AC = 'ieee802.11ac' + TYPE_80211AD = 'ieee802.11ad' + TYPE_80211AX = 'ieee802.11ax' + TYPE_80211AY = 'ieee802.11ay' + TYPE_80211BE = 'ieee802.11be' + TYPE_802151 = 'ieee802.15.1' + TYPE_802154 = 'ieee802.15.4' + TYPE_OTHER_WIRELESS = 'other-wireless' + + # Cellular + TYPE_GSM = 'gsm' + TYPE_CDMA = 'cdma' + TYPE_LTE = 'lte' + TYPE_4G = '4g' + TYPE_5G = '5g' + + # SONET + TYPE_SONET_OC3 = 'sonet-oc3' + TYPE_SONET_OC12 = 'sonet-oc12' + TYPE_SONET_OC48 = 'sonet-oc48' + TYPE_SONET_OC192 = 'sonet-oc192' + TYPE_SONET_OC768 = 'sonet-oc768' + TYPE_SONET_OC1920 = 'sonet-oc1920' + TYPE_SONET_OC3840 = 'sonet-oc3840' + + # Fibrechannel + TYPE_1GFC_SFP = '1gfc-sfp' + TYPE_2GFC_SFP = '2gfc-sfp' + TYPE_4GFC_SFP = '4gfc-sfp' + TYPE_8GFC_SFP_PLUS = '8gfc-sfpp' + TYPE_16GFC_SFP_PLUS = '16gfc-sfpp' + TYPE_32GFC_SFP28 = '32gfc-sfp28' + TYPE_32GFC_SFP_PLUS = '32gfc-sfpp' + TYPE_64GFC_QSFP_PLUS = '64gfc-qsfpp' + TYPE_64GFC_SFP_DD = '64gfc-sfpdd' + TYPE_64GFC_SFP_PLUS = '64gfc-sfpp' + TYPE_128GFC_QSFP28 = '128gfc-qsfp28' + + # InfiniBand + TYPE_INFINIBAND_SDR = 'infiniband-sdr' + TYPE_INFINIBAND_DDR = 'infiniband-ddr' + TYPE_INFINIBAND_QDR = 'infiniband-qdr' + TYPE_INFINIBAND_FDR10 = 'infiniband-fdr10' + TYPE_INFINIBAND_FDR = 'infiniband-fdr' + TYPE_INFINIBAND_EDR = 'infiniband-edr' + TYPE_INFINIBAND_HDR = 'infiniband-hdr' + TYPE_INFINIBAND_NDR = 'infiniband-ndr' + TYPE_INFINIBAND_XDR = 'infiniband-xdr' + + # Serial + TYPE_T1 = 't1' + TYPE_E1 = 'e1' + TYPE_T3 = 't3' + TYPE_E3 = 'e3' + + # ATM/DSL + TYPE_XDSL = 'xdsl' + + # Coaxial + TYPE_DOCSIS = 'docsis' + + # PON + TYPE_BPON = 'bpon' + TYPE_EPON = 'epon' + TYPE_10G_EPON = '10g-epon' + TYPE_GPON = 'gpon' + TYPE_XG_PON = 'xg-pon' + TYPE_XGS_PON = 'xgs-pon' + TYPE_NG_PON2 = 'ng-pon2' + TYPE_25G_PON = '25g-pon' + TYPE_50G_PON = '50g-pon' + + # Stacking + TYPE_STACKWISE = 'cisco-stackwise' + TYPE_STACKWISE_PLUS = 'cisco-stackwise-plus' + TYPE_FLEXSTACK = 'cisco-flexstack' + TYPE_FLEXSTACK_PLUS = 'cisco-flexstack-plus' + TYPE_STACKWISE80 = 'cisco-stackwise-80' + TYPE_STACKWISE160 = 'cisco-stackwise-160' + TYPE_STACKWISE320 = 'cisco-stackwise-320' + TYPE_STACKWISE480 = 'cisco-stackwise-480' + TYPE_STACKWISE1T = 'cisco-stackwise-1t' + TYPE_JUNIPER_VCP = 'juniper-vcp' + TYPE_SUMMITSTACK = 'extreme-summitstack' + TYPE_SUMMITSTACK128 = 'extreme-summitstack-128' + TYPE_SUMMITSTACK256 = 'extreme-summitstack-256' + TYPE_SUMMITSTACK512 = 'extreme-summitstack-512' + + # Other + TYPE_OTHER = 'other' + + +@strawberry.enum +class InterfaceSpeedEnum(Enum): + SPEED_10MBPS = 10000 + SPEED_100MBPS = 100000 + SPEED_1GBPS = 1000000 + SPEED_10GBPS = 10000000 + SPEED_25GBPS = 25000000 + SPEED_40GBPS = 40000000 + SPEED_100GBPS = 100000000 + SPEED_200GBPS = 200000000 + SPEED_400GBPS = 400000000 + + +@strawberry.enum +class InterfaceDuplexEnum(Enum): + + DUPLEX_HALF = 'half' + DUPLEX_FULL = 'full' + DUPLEX_AUTO = 'auto' + + +@strawberry.enum +class InterfaceModeEnum(Enum): + + MODE_ACCESS = 'access' + MODE_TAGGED = 'tagged' + MODE_TAGGED_ALL = 'tagged-all' + MODE_Q_IN_Q = 'q-in-q' + + +@strawberry.enum +class InterfacePoEModeEnum(Enum): + + MODE_PD = 'pd' + MODE_PSE = 'pse' + + +@strawberry.enum +class InterfacePoETypeEnum(Enum): + + TYPE_1_8023AF = 'type1-ieee802.3af' + TYPE_2_8023AT = 'type2-ieee802.3at' + TYPE_3_8023BT = 'type3-ieee802.3bt' + TYPE_4_8023BT = 'type4-ieee802.3bt' + + PASSIVE_24V_2PAIR = 'passive-24v-2pair' + PASSIVE_24V_4PAIR = 'passive-24v-4pair' + PASSIVE_48V_2PAIR = 'passive-48v-2pair' + PASSIVE_48V_4PAIR = 'passive-48v-4pair' + + +# +# FrontPorts/RearPorts +# + +@strawberry.enum +class PortTypeEnum(Enum): + + TYPE_8P8C = '8p8c' + TYPE_8P6C = '8p6c' + TYPE_8P4C = '8p4c' + TYPE_8P2C = '8p2c' + TYPE_6P6C = '6p6c' + TYPE_6P4C = '6p4c' + TYPE_6P2C = '6p2c' + TYPE_4P4C = '4p4c' + TYPE_4P2C = '4p2c' + TYPE_GG45 = 'gg45' + TYPE_TERA4P = 'tera-4p' + TYPE_TERA2P = 'tera-2p' + TYPE_TERA1P = 'tera-1p' + TYPE_110_PUNCH = '110-punch' + TYPE_BNC = 'bnc' + TYPE_F = 'f' + TYPE_N = 'n' + TYPE_MRJ21 = 'mrj21' + TYPE_ST = 'st' + TYPE_SC = 'sc' + TYPE_SC_PC = 'sc-pc' + TYPE_SC_UPC = 'sc-upc' + TYPE_SC_APC = 'sc-apc' + TYPE_FC = 'fc' + TYPE_LC = 'lc' + TYPE_LC_PC = 'lc-pc' + TYPE_LC_UPC = 'lc-upc' + TYPE_LC_APC = 'lc-apc' + TYPE_MTRJ = 'mtrj' + TYPE_MPO = 'mpo' + TYPE_LSH = 'lsh' + TYPE_LSH_PC = 'lsh-pc' + TYPE_LSH_UPC = 'lsh-upc' + TYPE_LSH_APC = 'lsh-apc' + TYPE_LX5 = 'lx5' + TYPE_LX5_PC = 'lx5-pc' + TYPE_LX5_UPC = 'lx5-upc' + TYPE_LX5_APC = 'lx5-apc' + TYPE_SPLICE = 'splice' + TYPE_CS = 'cs' + TYPE_SN = 'sn' + TYPE_SMA_905 = 'sma-905' + TYPE_SMA_906 = 'sma-906' + TYPE_URM_P2 = 'urm-p2' + TYPE_URM_P4 = 'urm-p4' + TYPE_URM_P8 = 'urm-p8' + TYPE_USB_A = 'usb-a' + TYPE_USB_B = 'usb-b' + TYPE_USB_C = 'usb-c' + TYPE_USB_MINI_A = 'usb-mini-a' + TYPE_USB_MINI_B = 'usb-mini-b' + TYPE_USB_MICRO_A = 'usb-micro-a' + TYPE_USB_MICRO_B = 'usb-micro-b' + TYPE_USB_MICRO_AB = 'usb-micro-ab' + TYPE_OTHER = 'other' + + +# +# Cables/links +# + +@strawberry.enum +class CableTypeEnum(Enum): + + TYPE_CAT3 = 'cat3' + TYPE_CAT5 = 'cat5' + TYPE_CAT5E = 'cat5e' + TYPE_CAT6 = 'cat6' + TYPE_CAT6A = 'cat6a' + TYPE_CAT7 = 'cat7' + TYPE_CAT7A = 'cat7a' + TYPE_CAT8 = 'cat8' + TYPE_DAC_ACTIVE = 'dac-active' + TYPE_DAC_PASSIVE = 'dac-passive' + TYPE_MRJ21_TRUNK = 'mrj21-trunk' + TYPE_COAXIAL = 'coaxial' + TYPE_MMF = 'mmf' + TYPE_MMF_OM1 = 'mmf-om1' + TYPE_MMF_OM2 = 'mmf-om2' + TYPE_MMF_OM3 = 'mmf-om3' + TYPE_MMF_OM4 = 'mmf-om4' + TYPE_MMF_OM5 = 'mmf-om5' + TYPE_SMF = 'smf' + TYPE_SMF_OS1 = 'smf-os1' + TYPE_SMF_OS2 = 'smf-os2' + TYPE_AOC = 'aoc' + TYPE_POWER = 'power' + TYPE_USB = 'usb' + + +@strawberry.enum +class LinkStatusEnum(Enum): + + STATUS_CONNECTED = 'connected' + STATUS_PLANNED = 'planned' + STATUS_DECOMMISSIONING = 'decommissioning' + + +@strawberry.enum +class CableLengthUnitEnum(Enum): + + # Metric + UNIT_KILOMETER = 'km' + UNIT_METER = 'm' + UNIT_CENTIMETER = 'cm' + + # Imperial + UNIT_MILE = 'mi' + UNIT_FOOT = 'ft' + UNIT_INCH = 'in' + + +# +# CableTerminations +# + +@strawberry.enum +class CableEndEnum(Enum): + SIDE_A = 'A' + SIDE_B = 'B' + + +# +# PowerFeeds +# + +@strawberry.enum +class PowerFeedStatusEnum(Enum): + key = 'PowerFeed.status' + + STATUS_OFFLINE = 'offline' + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_FAILED = 'failed' + + +@strawberry.enum +class PowerFeedTypeEnum(Enum): + + TYPE_PRIMARY = 'primary' + TYPE_REDUNDANT = 'redundant' + + +@strawberry.enum +class PowerFeedSupplyEnum(Enum): + + SUPPLY_AC = 'ac' + SUPPLY_DC = 'dc' + + +@strawberry.enum +class PowerFeedPhaseEnum(Enum): + + PHASE_SINGLE = 'single-phase' + PHASE_3PHASE = 'three-phase' + + +# +# VDC +# +@strawberry.enum +class VirtualDeviceContextStatusEnum(Enum): + key = 'VirtualDeviceContext.status' + + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_OFFLINE = 'offline' + + +# +# InventoryItem +# + +@strawberry.enum +class InventoryItemStatusEnum(Enum): + key = 'InventoryItem.status' + + STATUS_OFFLINE = 'offline' + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_STAGED = 'staged' + STATUS_FAILED = 'failed' + STATUS_DECOMMISSIONING = 'decommissioning' diff --git a/netbox/dcim/graphql/filter_mixins.py b/netbox/dcim/graphql/filter_mixins.py new file mode 100644 index 00000000000..cd1586134d4 --- /dev/null +++ b/netbox/dcim/graphql/filter_mixins.py @@ -0,0 +1,138 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry import ID +import strawberry_django +from strawberry_django import FilterLookup +from core.graphql.filter_mixins import * +from netbox.graphql.filter_mixins import * +from .enums import * + +if TYPE_CHECKING: + from .filters import * + from core.graphql.filter_lookups import * + from extras.graphql.filters import * + from ipam.graphql.filters import * + +__all__ = [ + 'ComponentModelFilterMixin', + 'ModularComponentModelFilterMixin', + 'CabledObjectModelFilterMixin', + 'ComponentTemplateFilterMixin', + 'ModularComponentTemplateFilterMixin', + 'RenderConfigFilterMixin', + 'InterfaceBaseFilterMixin', + 'RackBaseFilterMixin', +] + + +@dataclass +class ComponentModelFilterMixin(NetBoxModelFilterMixin): + device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + device_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + lable: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@dataclass +class ModularComponentModelFilterMixin(ComponentModelFilterMixin): + module: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + module_id: ID | None = strawberry_django.filter_field() + inventory_items: Annotated['InventoryItemFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class CabledObjectModelFilterMixin(BaseFilterMixin): + cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + cable_id: ID | None = strawberry_django.filter_field() + cable_end: CableEndEnum | None = strawberry_django.filter_field() + mark_connected: FilterLookup[bool] | None = strawberry_django.filter_field() + + +@dataclass +class ComponentTemplateFilterMixin(ChangeLogFilterMixin): + device_type: Annotated['DeviceTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + device_type_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + label: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@dataclass +class ModularComponentTemplateFilterMixin(ComponentTemplateFilterMixin): + module_type: Annotated['ModuleTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class RenderConfigFilterMixin(BaseFilterMixin): + config_template: Annotated['ConfigTemplateFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + config_template_id: ID | None = strawberry_django.filter_field() + + +@dataclass +class InterfaceBaseFilterMixin(BaseFilterMixin): + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + mtu: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + mode: InterfaceModeEnum | None = strawberry_django.filter_field() + parent: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry_django.filter_field() + bridge: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + bridge_id: ID | None = strawberry_django.filter_field() + untagged_vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tagged_vlans: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + vlan_tranlation_policy: Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + primary_mac_address: Annotated['MACAddressFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + primary_mac_address_id: ID | None = strawberry_django.filter_field() + + +@dataclass +class RackBaseFilterMixin(WeightFilterMixin, PrimaryModelFilterMixin): + width: Annotated['RackWidthEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + u_height: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + starting_unit: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + desc_units: FilterLookup[bool] | None = strawberry_django.filter_field() + outer_width: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + outer_depth: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + outer_unit: Annotated['RackDimensionUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + mounting_depth: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + max_weight: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py index 94f2c6d38a6..b9fcedfaf7c 100644 --- a/netbox/dcim/graphql/filters.py +++ b/netbox/dcim/graphql/filters.py @@ -1,7 +1,41 @@ +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry.scalars import ID import strawberry_django +from strawberry_django import ( + FilterLookup, +) +from extras.graphql.filter_mixins import ( + ConfigContextFilterMixin, +) +from netbox.graphql.filter_mixins import ( + PrimaryModelFilterMixin, + OrganizationalModelFilterMixin, + NestedGroupModelFilterMixin, + ImageAttachmentFilterMixin, + WeightFilterMixin, +) +from .filter_mixins import * +from dcim import models +from core.graphql.filter_mixins import * +from tenancy.graphql.filter_mixins import TenancyFilterMixin, ContactFilterMixin + +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.enums import * + from wireless.graphql.enums import * + from core.graphql.filter_lookups import * + from core.graphql.filters import * + from extras.graphql.filters import * + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from tenancy.graphql.filters import * + from wireless.graphql.filters import * + from users.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * -from dcim import filtersets, models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin __all__ = ( 'CableFilter', @@ -51,258 +85,766 @@ @strawberry_django.filter(models.Cable, lookups=True) -@autotype_decorator(filtersets.CableFilterSet) -class CableFilter(BaseFilterMixin): - pass +class CableFilter(PrimaryModelFilterMixin, TenancyFilterMixin): + type: Annotated['CableTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + status: Annotated['LinkStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + label: FilterLookup[str] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + length: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + length_unit: Annotated['CableLengthUnitEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + terminations: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.CableTermination, lookups=True) -@autotype_decorator(filtersets.CableTerminationFilterSet) -class CableTerminationFilter(BaseFilterMixin): - pass +class CableTerminationFilter(ChangeLogFilterMixin): + cable: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + cable_id: ID | None = strawberry_django.filter_field() + cable_end: Annotated['CableEndEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + termination_type: Annotated['CableTerminationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + termination_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.ConsolePort, lookups=True) -@autotype_decorator(filtersets.ConsolePortFilterSet) -class ConsolePortFilter(BaseFilterMixin): - pass +class ConsolePortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): + type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ConsolePortTemplate, lookups=True) -@autotype_decorator(filtersets.ConsolePortTemplateFilterSet) -class ConsolePortTemplateFilter(BaseFilterMixin): - pass +class ConsolePortTemplateFilter(ModularComponentTemplateFilterMixin): + type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ConsoleServerPort, lookups=True) -@autotype_decorator(filtersets.ConsoleServerPortFilterSet) -class ConsoleServerPortFilter(BaseFilterMixin): - pass +class ConsoleServerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): + type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + speed: Annotated['ConsolePortSpeedEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ConsoleServerPortTemplate, lookups=True) -@autotype_decorator(filtersets.ConsoleServerPortTemplateFilterSet) -class ConsoleServerPortTemplateFilter(BaseFilterMixin): - pass +class ConsoleServerPortTemplateFilter(ModularComponentTemplateFilterMixin): + type: Annotated['ConsolePortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.Device, lookups=True) -@autotype_decorator(filtersets.DeviceFilterSet) -class DeviceFilter(BaseFilterMixin): - pass +class DeviceFilter( + ContactFilterMixin, + TenancyFilterMixin, + ImageAttachmentFilterMixin, + RenderConfigFilterMixin, + ConfigContextFilterMixin, + PrimaryModelFilterMixin, +): + device_type: Annotated['DeviceTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + device_type_id: ID | None = strawberry_django.filter_field() + role: Annotated['DeviceRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + serial: FilterLookup[str] | None = strawberry_django.filter_field() + asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() + site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + site_id: ID | None = strawberry_django.filter_field() + location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + location_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + rack_id: ID | None = strawberry_django.filter_field() + position: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + face: Annotated['DeviceFaceEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + status: Annotated['DeviceStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4_id: ID | None = strawberry_django.filter_field() + primary_ip6: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + primary_ip6_id: ID | None = strawberry_django.filter_field() + oob_ip: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + oob_ip_id: ID | None = strawberry_django.filter_field() + cluster: Annotated['ClusterFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + cluster_id: ID | None = strawberry_django.filter_field() + virtual_chassis: Annotated['VirtualChassisFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + virtual_chassis_id: ID | None = strawberry_django.filter_field() + vc_position: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + vc_priority: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + latitude: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + longitude: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + interfaces: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + consoleports: Annotated['ConsolePortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + consoleserverports: Annotated['ConsoleServerPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + poweroutlets: Annotated['PowerOutletFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + powerports: Annotated['PowerPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + devicebays: Annotated['DeviceBayFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + frontports: Annotated['FrontPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + rearports: Annotated['RearPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + modulebays: Annotated['ModuleBayFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + modules: Annotated['ModuleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.DeviceBay, lookups=True) -@autotype_decorator(filtersets.DeviceBayFilterSet) -class DeviceBayFilter(BaseFilterMixin): - pass +class DeviceBayFilter(ComponentModelFilterMixin): + installed_device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + installed_device_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.DeviceBayTemplate, lookups=True) -@autotype_decorator(filtersets.DeviceBayTemplateFilterSet) -class DeviceBayTemplateFilter(BaseFilterMixin): +class DeviceBayTemplateFilter(ComponentTemplateFilterMixin): pass @strawberry_django.filter(models.InventoryItemTemplate, lookups=True) -@autotype_decorator(filtersets.InventoryItemTemplateFilterSet) -class InventoryItemTemplateFilter(BaseFilterMixin): - pass +class InventoryItemTemplateFilter(ComponentTemplateFilterMixin): + parent: Annotated['InventoryItemTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + component_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + component_id: ID | None = strawberry_django.filter_field() + role: Annotated['InventoryItemRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + part_id: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.DeviceRole, lookups=True) -@autotype_decorator(filtersets.DeviceRoleFilterSet) -class DeviceRoleFilter(BaseFilterMixin): - pass +class DeviceRoleFilter(OrganizationalModelFilterMixin, RenderConfigFilterMixin): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + vm_role: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter(models.DeviceType, lookups=True) -@autotype_decorator(filtersets.DeviceTypeFilterSet) -class DeviceTypeFilter(BaseFilterMixin): - pass +class DeviceTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, WeightFilterMixin): + manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + model: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + default_platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + default_platform_id: ID | None = strawberry_django.filter_field() + part_number: FilterLookup[str] | None = strawberry_django.filter_field() + u_height: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + exclude_from_utilization: FilterLookup[bool] | None = strawberry_django.filter_field() + is_full_depth: FilterLookup[bool] | None = strawberry_django.filter_field() + subdevice_role: Annotated['SubdeviceRoleEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + airflow: Annotated['DeviceAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + front_image: Annotated['ImageAttachmentFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + rear_image: Annotated['ImageAttachmentFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.FrontPort, lookups=True) -@autotype_decorator(filtersets.FrontPortFilterSet) -class FrontPortFilter(BaseFilterMixin): - pass +class FrontPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): + type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + rear_port: Annotated['RearPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + rear_port_id: ID | None = strawberry_django.filter_field() + rear_port_position: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.FrontPortTemplate, lookups=True) -@autotype_decorator(filtersets.FrontPortTemplateFilterSet) -class FrontPortTemplateFilter(BaseFilterMixin): - pass +class FrontPortTemplateFilter(ModularComponentTemplateFilterMixin): + type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + rear_port: Annotated['RearPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + rear_port_id: ID | None = strawberry_django.filter_field() + rear_port_position: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.MACAddress, lookups=True) -@autotype_decorator(filtersets.MACAddressFilterSet) -class MACAddressFilter(BaseFilterMixin): - pass +class MACAddressFilter(PrimaryModelFilterMixin): + mac_address: FilterLookup[str] | None = strawberry_django.filter_field() + assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.Interface, lookups=True) -@autotype_decorator(filtersets.InterfaceFilterSet) -class InterfaceFilter(BaseFilterMixin): - pass +class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin, CabledObjectModelFilterMixin): + vcdcs: Annotated['VirtualDeviceContextFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + lag: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + lag_id: ID | None = strawberry_django.filter_field() + type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() + speed: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + duplex: Annotated['InterfaceDuplexEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + wwn: FilterLookup[str] | None = strawberry_django.filter_field() + rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + rf_channel: Annotated['WirelessChannelEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + rf_channel_frequency: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + rf_channel_width: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tx_power: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + wireless_link: Annotated['WirelessLinkFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + wireless_link_id: ID | None = strawberry_django.filter_field() + wireless_lans: Annotated['WirelessLANFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + mac_addresses: Annotated['MACAddressFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + fhrp_group_assignments: Annotated['FHRPGroupAssignmentFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tunnel_terminations: Annotated['TunnelTerminationFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + l2vpn_terminations: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.InterfaceTemplate, lookups=True) -@autotype_decorator(filtersets.InterfaceTemplateFilterSet) -class InterfaceTemplateFilter(BaseFilterMixin): - pass +class InterfaceTemplateFilter(ModularComponentTemplateFilterMixin): + type: Annotated['InterfaceTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + mgmt_only: FilterLookup[bool] | None = strawberry_django.filter_field() + bridge: Annotated['InterfaceTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + bridge_id: ID | None = strawberry_django.filter_field() + poe_mode: Annotated['InterfacePoEModeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + poe_type: Annotated['InterfacePoETypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + rf_role: Annotated['WirelessRoleEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.InventoryItem, lookups=True) -@autotype_decorator(filtersets.InventoryItemFilterSet) -class InventoryItemFilter(BaseFilterMixin): - pass +class InventoryItemFilter(ComponentModelFilterMixin): + parent: Annotated['InventoryItemFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry_django.filter_field() + component_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + component_id: ID | None = strawberry_django.filter_field() + status: Annotated['InventoryItemStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['InventoryItemRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + part_id: FilterLookup[str] | None = strawberry_django.filter_field() + serial: FilterLookup[str] | None = strawberry_django.filter_field() + asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() + discovered: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter(models.InventoryItemRole, lookups=True) -@autotype_decorator(filtersets.InventoryItemRoleFilterSet) -class InventoryItemRoleFilter(BaseFilterMixin): - pass +class InventoryItemRoleFilter(OrganizationalModelFilterMixin): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() @strawberry_django.filter(models.Location, lookups=True) -@autotype_decorator(filtersets.LocationFilterSet) -class LocationFilter(BaseFilterMixin): - pass +class LocationFilter(ContactFilterMixin, ImageAttachmentFilterMixin, NestedGroupModelFilterMixin): + site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + site_id: ID | None = strawberry_django.filter_field() + status: Annotated['LocationStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + facility: FilterLookup[str] | None = strawberry_django.filter_field() + prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.Manufacturer, lookups=True) -@autotype_decorator(filtersets.ManufacturerFilterSet) -class ManufacturerFilter(BaseFilterMixin): +class ManufacturerFilter(ContactFilterMixin, OrganizationalModelFilterMixin): pass @strawberry_django.filter(models.Module, lookups=True) -@autotype_decorator(filtersets.ModuleFilterSet) -class ModuleFilter(BaseFilterMixin): - pass +class ModuleFilter(PrimaryModelFilterMixin, ConfigContextFilterMixin): + device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + device_id: ID | None = strawberry_django.filter_field() + module_bay: Annotated['ModuleBayFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + module_bay_id: ID | None = strawberry_django.filter_field() + module_type: Annotated['ModuleTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + module_type_id: ID | None = strawberry_django.filter_field() + status: Annotated['ModuleStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + serial: FilterLookup[str] | None = strawberry_django.filter_field() + asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.ModuleBay, lookups=True) -@autotype_decorator(filtersets.ModuleBayFilterSet) -class ModuleBayFilter(BaseFilterMixin): - pass +class ModuleBayFilter(ModularComponentModelFilterMixin): + parent: Annotated['ModuleBayFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry_django.filter_field() + position: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.ModuleBayTemplate, lookups=True) -@autotype_decorator(filtersets.ModuleBayTemplateFilterSet) -class ModuleBayTemplateFilter(BaseFilterMixin): - pass +class ModuleBayTemplateFilter(ModularComponentTemplateFilterMixin): + position: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.ModuleType, lookups=True) -@autotype_decorator(filtersets.ModuleTypeFilterSet) -class ModuleTypeFilter(BaseFilterMixin): - pass +class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, WeightFilterMixin): + manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + model: FilterLookup[str] | None = strawberry_django.filter_field() + part_number: FilterLookup[str] | None = strawberry_django.filter_field() + airflow: Annotated['ModuleAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.Platform, lookups=True) -@autotype_decorator(filtersets.PlatformFilterSet) -class PlatformFilter(BaseFilterMixin): - pass +class PlatformFilter(OrganizationalModelFilterMixin): + manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + config_template: Annotated['ConfigTemplateFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + config_template_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.PowerFeed, lookups=True) -@autotype_decorator(filtersets.PowerFeedFilterSet) -class PowerFeedFilter(BaseFilterMixin): - pass +class PowerFeedFilter(PrimaryModelFilterMixin, CabledObjectModelFilterMixin): + power_panel: Annotated['PowerPanelFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + power_panel_id: ID | None = strawberry_django.filter_field() + rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + rack_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['PowerFeedStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + type: Annotated['PowerFeedTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + supply: Annotated['PowerFeedSupplyEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + phase: Annotated['PowerFeedPhaseEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + voltage: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + amperage: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + max_utilization: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + available_power: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.PowerOutlet, lookups=True) -@autotype_decorator(filtersets.PowerOutletFilterSet) -class PowerOutletFilter(BaseFilterMixin): - pass +class PowerOutletFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): + type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + power_port: Annotated['PowerPortFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + power_port_id: ID | None = strawberry_django.filter_field() + feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() @strawberry_django.filter(models.PowerOutletTemplate, lookups=True) -@autotype_decorator(filtersets.PowerOutletTemplateFilterSet) -class PowerOutletTemplateFilter(BaseFilterMixin): - pass +class PowerOutletTemplateFilter(ModularComponentModelFilterMixin): + type: Annotated['PowerOutletTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + power_port: Annotated['PowerPortTemplateFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + power_port_id: ID | None = strawberry_django.filter_field() + feed_leg: Annotated['PowerOutletFeedLegEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.PowerPanel, lookups=True) -@autotype_decorator(filtersets.PowerPanelFilterSet) -class PowerPanelFilter(BaseFilterMixin): - pass +class PowerPanelFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryModelFilterMixin): + site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + site_id: ID | None = strawberry_django.filter_field() + location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + location_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.PowerPort, lookups=True) -@autotype_decorator(filtersets.PowerPortFilterSet) -class PowerPortFilter(BaseFilterMixin): - pass +class PowerPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): + type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + maximum_draw: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + allocated_draw: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.PowerPortTemplate, lookups=True) -@autotype_decorator(filtersets.PowerPortTemplateFilterSet) -class PowerPortTemplateFilter(BaseFilterMixin): - pass +class PowerPortTemplateFilter(ModularComponentTemplateFilterMixin): + type: Annotated['PowerPortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + maximum_draw: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + allocated_draw: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.RackType, lookups=True) -@autotype_decorator(filtersets.RackTypeFilterSet) -class RackTypeFilter(BaseFilterMixin): - pass +class RackTypeFilter(RackBaseFilterMixin): + form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + manufacturer_id: ID | None = strawberry_django.filter_field() + model: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.Rack, lookups=True) -@autotype_decorator(filtersets.RackFilterSet) -class RackFilter(BaseFilterMixin): - pass +class RackFilter(ContactFilterMixin, ImageAttachmentFilterMixin, RackBaseFilterMixin): + form_factor: Annotated['RackFormFactorEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + rack_type: Annotated['RackTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + rack_type_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + facility_id: FilterLookup[str] | None = strawberry_django.filter_field() + site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + site_id: ID | None = strawberry_django.filter_field() + location: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + location_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + status: Annotated['RackStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + role: Annotated['RackRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + role_id: ID | None = strawberry_django.filter_field() + serial: FilterLookup[str] | None = strawberry_django.filter_field() + asset_tag: FilterLookup[str] | None = strawberry_django.filter_field() + airflow: Annotated['RackAirflowEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.RackReservation, lookups=True) -@autotype_decorator(filtersets.RackReservationFilterSet) -class RackReservationFilter(BaseFilterMixin): - pass +class RackReservationFilter(PrimaryModelFilterMixin): + rack: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + rack_id: ID | None = strawberry_django.filter_field() + units: Annotated['IntegerArrayLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() + user_id: ID | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.RackRole, lookups=True) -@autotype_decorator(filtersets.RackRoleFilterSet) -class RackRoleFilter(BaseFilterMixin): - pass +class RackRoleFilter(OrganizationalModelFilterMixin): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() @strawberry_django.filter(models.RearPort, lookups=True) -@autotype_decorator(filtersets.RearPortFilterSet) -class RearPortFilter(BaseFilterMixin): - pass +class RearPortFilter(ModularComponentModelFilterMixin, CabledObjectModelFilterMixin): + type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + positions: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.RearPortTemplate, lookups=True) -@autotype_decorator(filtersets.RearPortTemplateFilterSet) -class RearPortTemplateFilter(BaseFilterMixin): - pass +class RearPortTemplateFilter(ModularComponentTemplateFilterMixin): + type: Annotated['PortTypeEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + positions: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.Region, lookups=True) -@autotype_decorator(filtersets.RegionFilterSet) -class RegionFilter(BaseFilterMixin): - pass +class RegionFilter(ContactFilterMixin, NestedGroupModelFilterMixin): + prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.Site, lookups=True) -@autotype_decorator(filtersets.SiteFilterSet) -class SiteFilter(BaseFilterMixin): - pass +class SiteFilter(ContactFilterMixin, ImageAttachmentFilterMixin, PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['SiteStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = strawberry_django.filter_field() + region: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + region_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + group: Annotated['SiteGroupFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + facility: FilterLookup[str] | None = strawberry_django.filter_field() + asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + time_zone: FilterLookup[str] | None = strawberry_django.filter_field() + physical_address: FilterLookup[str] | None = strawberry_django.filter_field() + shipping_address: FilterLookup[str] | None = strawberry_django.filter_field() + latitude: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + longitude: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.SiteGroup, lookups=True) -@autotype_decorator(filtersets.SiteGroupFilterSet) -class SiteGroupFilter(BaseFilterMixin): - pass +class SiteGroupFilter(ContactFilterMixin, NestedGroupModelFilterMixin): + prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.VirtualChassis, lookups=True) -@autotype_decorator(filtersets.VirtualChassisFilterSet) -class VirtualChassisFilter(BaseFilterMixin): - pass +class VirtualChassisFilter(PrimaryModelFilterMixin): + master: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + master_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + domain: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.VirtualDeviceContext, lookups=True) -@autotype_decorator(filtersets.VirtualDeviceContextFilterSet) -class VirtualDeviceContextFilter(BaseFilterMixin): - pass +class VirtualDeviceContextFilter(PrimaryModelFilterMixin): + device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + device_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['VirtualDeviceContextStatusEnum', strawberry.lazy('dcim.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + identifier: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4_id: ID | None = strawberry_django.filter_field() + primary_ip6: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + primary_ip6_id: ID | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + comments: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/extras/graphql/enums.py b/netbox/extras/graphql/enums.py new file mode 100644 index 00000000000..959314d9e2f --- /dev/null +++ b/netbox/extras/graphql/enums.py @@ -0,0 +1,190 @@ +from enum import Enum +import strawberry + +__all__ = [ + 'CustomFieldTypeEnum', + 'CustomFieldFilterLogicEnum', + 'CustomFieldUIVisibleEnum', + 'CustomFieldUIEditableEnum', + 'CustomFieldChoiceSetBaseEnum', + 'CustomLinkButtonClassEnum', + 'BookmarkOrderingEnum', + 'JournalEntryKindEnum', + 'LogLevelEnum', + 'DurationEnum', + 'WebhookHttpMethodEnum', + 'ChangeActionEnum', + 'DashboardWidgetColorEnum', + 'EventRuleActionEnum', +] + +# +# CustomFields +# + + +@strawberry.enum +class CustomFieldTypeEnum(Enum): + TYPE_TEXT = 'text' + TYPE_LONGTEXT = 'longtext' + TYPE_INTEGER = 'integer' + TYPE_DECIMAL = 'decimal' + TYPE_BOOLEAN = 'boolean' + TYPE_DATE = 'date' + TYPE_DATETIME = 'datetime' + TYPE_URL = 'url' + TYPE_JSON = 'json' + TYPE_SELECT = 'select' + TYPE_MULTISELECT = 'multiselect' + TYPE_OBJECT = 'object' + TYPE_MULTIOBJECT = 'multiobject' + + +@strawberry.enum +class CustomFieldFilterLogicEnum(Enum): + FILTER_DISABLED = 'disabled' + FILTER_LOOSE = 'loose' + FILTER_EXACT = 'exact' + + +@strawberry.enum +class CustomFieldUIVisibleEnum(Enum): + ALWAYS = 'always' + IF_SET = 'if-set' + HIDDEN = 'hidden' + + +@strawberry.enum +class CustomFieldUIEditableEnum(Enum): + YES = 'yes' + NO = 'no' + HIDDEN = 'hidden' + + +@strawberry.enum +class CustomFieldChoiceSetBaseEnum(Enum): + IATA = 'IATA' + ISO_3166 = 'ISO_3166' + UN_LOCODE = 'UN_LOCODE' + + +# +# CustomLinks +# + + +@strawberry.enum +class CustomLinkButtonClassEnum(Enum): + LINK = 'ghost-dark' + + +# +# Bookmarks +# + + +@strawberry.enum +class BookmarkOrderingEnum(Enum): + ORDERING_NEWEST = '-created' + ORDERING_OLDEST = 'created' + ORDERING_ALPHABETICAL_AZ = 'name' + ORDERING_ALPHABETICAL_ZA = '-name' + + +# +# Journal entries +# + + +@strawberry.enum +class JournalEntryKindEnum(Enum): + key = 'JournalEntry.kind' + + KIND_INFO = 'info' + KIND_SUCCESS = 'success' + KIND_WARNING = 'warning' + KIND_DANGER = 'danger' + + +# +# Reports and Scripts +# + + +@strawberry.enum +class LogLevelEnum(Enum): + LOG_DEBUG = 'debug' + LOG_DEFAULT = 'default' + LOG_INFO = 'info' + LOG_SUCCESS = 'success' + LOG_WARNING = 'warning' + LOG_FAILURE = 'failure' + + +@strawberry.enum +class DurationEnum(Enum): + HOURLY = 60 + TWELVE_HOURS = 720 + DAILY = 1440 + WEEKLY = 10080 + THIRTY_DAYS = 43200 + + +# +# Webhooks +# + + +@strawberry.enum +class WebhookHttpMethodEnum(Enum): + METHOD_GET = 'GET' + METHOD_POST = 'POST' + METHOD_PUT = 'PUT' + METHOD_PATCH = 'PATCH' + METHOD_DELETE = 'DELETE' + + +# +# Staging +# + + +@strawberry.enum +class ChangeActionEnum(Enum): + ACTION_CREATE = 'create' + ACTION_UPDATE = 'update' + ACTION_DELETE = 'delete' + + +# +# Dashboard widgets +# + + +@strawberry.enum +class DashboardWidgetColorEnum(Enum): + BLUE = 'blue' + INDIGO = 'indigo' + PURPLE = 'purple' + PINK = 'pink' + RED = 'red' + ORANGE = 'orange' + YELLOW = 'yellow' + GREEN = 'green' + TEAL = 'teal' + CYAN = 'cyan' + GRAY = 'gray' + BLACK = 'black' + WHITE = 'white' + + +# +# Event Rules +# + + +@strawberry.enum +class EventRuleActionEnum(Enum): + WEBHOOK = 'webhook' + SCRIPT = 'script' + NOTIFICATION = 'notification' diff --git a/netbox/extras/graphql/filter_mixins.py b/netbox/extras/graphql/filter_mixins.py new file mode 100644 index 00000000000..726ae046e74 --- /dev/null +++ b/netbox/extras/graphql/filter_mixins.py @@ -0,0 +1,53 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING +import strawberry +import strawberry_django +from strawberry_django import FilterLookup +from core.graphql.filter_mixins import BaseFilterMixin + +if TYPE_CHECKING: + from core.graphql.filter_lookups import * + from .filters import * + +__all__ = [ + 'CustomFieldsFilterMixin', + 'JournalEntriesFilterMixin', + 'TagsFilterMixin', + 'ConfigContextFilterMixin', + 'TagBaseFilterMixin', +] + + +@dataclass +class CustomFieldsFilterMixin(BaseFilterMixin): + custom_field_data: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class JournalEntriesFilterMixin(BaseFilterMixin): + journal_entries: Annotated['JournalEntryFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class TagsFilterMixin(BaseFilterMixin): + tags: Annotated['TagFilter', strawberry.lazy('extras.graphql.filters')] | None = strawberry_django.filter_field() + + +@dataclass +class ConfigContextFilterMixin(BaseFilterMixin): + local_config_context: Annotated['ConfigContextFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + config_context: Annotated['ConfigContextFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class TagBaseFilterMixin(BaseFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/extras/graphql/filters.py b/netbox/extras/graphql/filters.py index ff2e6a0f177..e80487fa040 100644 --- a/netbox/extras/graphql/filters.py +++ b/netbox/extras/graphql/filters.py @@ -1,7 +1,33 @@ +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry.scalars import ID import strawberry_django +from strawberry_django import ( + FilterLookup, +) +from netbox.graphql.filter_mixins import ( + BaseObjectTypeFilterMixin, + ChangeLogFilterMixin, + SyncedDataFilterMixin, +) +from extras import models +from extras.graphql.filter_mixins import TagBaseFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin + +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.enums import * + from wireless.graphql.enums import * + from core.graphql.filter_lookups import * + from extras.graphql.filters import * + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from tenancy.graphql.filters import * + from wireless.graphql.filters import * + from users.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * -from extras import filtersets, models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin __all__ = ( 'ConfigContextFilter', @@ -21,78 +47,253 @@ @strawberry_django.filter(models.ConfigContext, lookups=True) -@autotype_decorator(filtersets.ConfigContextFilterSet) -class ConfigContextFilter(BaseFilterMixin): - pass +class ConfigContextFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] = strawberry_django.filter_field() + weight: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + description: FilterLookup[str] = strawberry_django.filter_field() + is_active: FilterLookup[bool] = strawberry_django.filter_field() + regions: Annotated['RegionFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + region_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + site_groups: Annotated['SiteGroupFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + site_group_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + sites: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + locations: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + device_types: Annotated['DeviceTypeFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + roles: Annotated['DeviceRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + platforms: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + cluster_types: Annotated['ClusterTypeFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + cluster_groups: Annotated['ClusterGroupFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + clusters: Annotated['ClusterFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_groups: Annotated['TenantGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_group_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tenants: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tags: Annotated['TagFilter', strawberry.lazy('extras.graphql.filters')] | None = strawberry_django.filter_field() + data: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ConfigTemplate, lookups=True) -@autotype_decorator(filtersets.ConfigTemplateFilterSet) -class ConfigTemplateFilter(BaseFilterMixin): - pass +class ConfigTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + template_code: FilterLookup[str] | None = strawberry_django.filter_field() + environment_params: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.CustomField, lookups=True) -@autotype_decorator(filtersets.CustomFieldFilterSet) -class CustomFieldFilter(BaseFilterMixin): - pass +class CustomFieldFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): + type: Annotated['CustomFieldTypeEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + related_object_type_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + label: FilterLookup[str] | None = strawberry_django.filter_field() + group_name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + required: FilterLookup[bool] | None = strawberry_django.filter_field() + unique: FilterLookup[bool] | None = strawberry_django.filter_field() + search_weight: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + filter_logic: Annotated['CustomFieldFilterLogicEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + default: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + related_object_filter: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + weight: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + validation_minimum: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + validation_maximum: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + validation_regex: FilterLookup[str] | None = strawberry_django.filter_field() + choice_set: Annotated['CustomFieldChoiceSetFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + choice_set_id: ID | None = strawberry_django.filter_field() + ui_visible: Annotated['CustomFieldUIVisibleEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + ui_editable: Annotated['CustomFieldUIEditableEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + is_cloneable: FilterLookup[bool] | None = strawberry_django.filter_field() + comments: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.CustomFieldChoiceSet, lookups=True) -@autotype_decorator(filtersets.CustomFieldChoiceSetFilterSet) -class CustomFieldChoiceSetFilter(BaseFilterMixin): - pass +class CustomFieldChoiceSetFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + base_choices: Annotated['CustomFieldChoiceSetBaseEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + extra_choices: Annotated['StringArrayLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + order_alphabetically: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter(models.CustomLink, lookups=True) -@autotype_decorator(filtersets.CustomLinkFilterSet) -class CustomLinkFilter(BaseFilterMixin): - pass +class CustomLinkFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + link_text: FilterLookup[str] | None = strawberry_django.filter_field() + link_url: FilterLookup[str] | None = strawberry_django.filter_field() + weight: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + group_name: FilterLookup[str] | None = strawberry_django.filter_field() + button_class: FilterLookup[str] | None = strawberry_django.filter_field() + new_window: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter(models.ExportTemplate, lookups=True) -@autotype_decorator(filtersets.ExportTemplateFilterSet) -class ExportTemplateFilter(BaseFilterMixin): - pass +class ExportTemplateFilter(BaseObjectTypeFilterMixin, SyncedDataFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + template_code: FilterLookup[str] | None = strawberry_django.filter_field() + mime_type: FilterLookup[str] | None = strawberry_django.filter_field() + file_extension: FilterLookup[str] | None = strawberry_django.filter_field() + as_attachment: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter(models.ImageAttachment, lookups=True) -@autotype_decorator(filtersets.ImageAttachmentFilterSet) -class ImageAttachmentFilter(BaseFilterMixin): - pass +class ImageAttachmentFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): + object_id: ID | None = strawberry_django.filter_field() + image_height: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + image_width: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.JournalEntry, lookups=True) -@autotype_decorator(filtersets.JournalEntryFilterSet) -class JournalEntryFilter(BaseFilterMixin): - pass +class JournalEntryFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin): + assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_type_id: ID | None = strawberry_django.filter_field() + assigned_object_id: ID | None = strawberry_django.filter_field() + created_by: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + kind: Annotated['JournalEntryKindEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + comments: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.NotificationGroup, lookups=True) -@autotype_decorator(filtersets.NotificationGroupFilterSet) -class NotificationGroupFilter(BaseFilterMixin): - pass +class NotificationGroupFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() + users: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() @strawberry_django.filter(models.SavedFilter, lookups=True) -@autotype_decorator(filtersets.SavedFilterFilterSet) -class SavedFilterFilter(BaseFilterMixin): - pass +class SavedFilterFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + user: Annotated['UserFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() + user_id: ID | None = strawberry_django.filter_field() + weight: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + shared: FilterLookup[bool] | None = strawberry_django.filter_field() + parameters: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.Tag, lookups=True) -@autotype_decorator(filtersets.TagFilterSet) -class TagFilter(BaseFilterMixin): - pass +class TagFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin, TagBaseFilterMixin): + color: Annotated['ColorEnum', strawberry.lazy('netbox.graphql.enums')] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.Webhook, lookups=True) -@autotype_decorator(filtersets.WebhookFilterSet) -class WebhookFilter(BaseFilterMixin): - pass +class WebhookFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + payload_url: FilterLookup[str] | None = strawberry_django.filter_field() + http_method: Annotated['WebhookHttpMethodEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + http_content_type: FilterLookup[str] | None = strawberry_django.filter_field() + additional_headers: FilterLookup[str] | None = strawberry_django.filter_field() + body_template: FilterLookup[str] | None = strawberry_django.filter_field() + secret: FilterLookup[str] | None = strawberry_django.filter_field() + ssl_verification: FilterLookup[bool] | None = strawberry_django.filter_field() + ca_file_path: FilterLookup[str] | None = strawberry_django.filter_field() + events: Annotated['EventRuleFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.EventRule, lookups=True) -@autotype_decorator(filtersets.EventRuleFilterSet) -class EventRuleFilter(BaseFilterMixin): - pass +class EventRuleFilter(BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + event_types: Annotated['StringArrayLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + conditions: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + action_type: Annotated['EventRuleActionEnum', strawberry.lazy('extras.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + action_object_type: FilterLookup[str] | None = strawberry_django.filter_field() + action_object_type_id: ID | None = strawberry_django.filter_field() + action_object_id: ID | None = strawberry_django.filter_field() + action_data: Annotated['JSONFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + comments: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/ipam/graphql/enums.py b/netbox/ipam/graphql/enums.py new file mode 100644 index 00000000000..3e73773cb59 --- /dev/null +++ b/netbox/ipam/graphql/enums.py @@ -0,0 +1,132 @@ +from enum import Enum +import strawberry + +__all__ = [ + 'IPAddressFamilyEnum', + 'PrefixStatusEnum', + 'IPRangeStatusEnum', + 'IPAddressStatusEnum', + 'IPAddressRoleEnum', + 'FHRPGroupProtocolEnum', + 'FHRPGroupAuthTypeEnum', + 'VLANStatusEnum', + 'VLANQinQRoleEnum', + 'ServiceProtocolEnum', +] + + +@strawberry.enum +class IPAddressFamilyEnum(Enum): + FAMILY_4 = 4 + FAMILY_6 = 6 + + +# +# Prefixes +# + + +@strawberry.enum +class PrefixStatusEnum(Enum): + key = 'Prefix.status' + + STATUS_CONTAINER = 'container' + STATUS_ACTIVE = 'active' + STATUS_RESERVED = 'reserved' + STATUS_DEPRECATED = 'deprecated' + + +# +# IP Ranges +# + + +@strawberry.enum +class IPRangeStatusEnum(Enum): + key = 'IPRange.status' + + STATUS_ACTIVE = 'active' + STATUS_RESERVED = 'reserved' + STATUS_DEPRECATED = 'deprecated' + + +# +# IP Addresses +# + + +@strawberry.enum +class IPAddressStatusEnum(Enum): + key = 'IPAddress.status' + + STATUS_ACTIVE = 'active' + STATUS_RESERVED = 'reserved' + STATUS_DEPRECATED = 'deprecated' + STATUS_DHCP = 'dhcp' + STATUS_SLAAC = 'slaac' + + +@strawberry.enum +class IPAddressRoleEnum(Enum): + ROLE_LOOPBACK = 'loopback' + ROLE_SECONDARY = 'secondary' + ROLE_ANYCAST = 'anycast' + ROLE_VIP = 'vip' + ROLE_VRRP = 'vrrp' + ROLE_HSRP = 'hsrp' + ROLE_GLBP = 'glbp' + ROLE_CARP = 'carp' + + +# +# FHRP +# + + +@strawberry.enum +class FHRPGroupProtocolEnum(Enum): + PROTOCOL_VRRP2 = 'vrrp2' + PROTOCOL_VRRP3 = 'vrrp3' + PROTOCOL_HSRP = 'hsrp' + PROTOCOL_GLBP = 'glbp' + PROTOCOL_CARP = 'carp' + PROTOCOL_CLUSTERXL = 'clusterxl' + PROTOCOL_OTHER = 'other' + + +@strawberry.enum +class FHRPGroupAuthTypeEnum(Enum): + AUTHENTICATION_PLAINTEXT = 'plaintext' + AUTHENTICATION_MD5 = 'md5' + + +# +# VLANs +# + + +@strawberry.enum +class VLANStatusEnum(Enum): + key = 'VLAN.status' + + STATUS_ACTIVE = 'active' + STATUS_RESERVED = 'reserved' + STATUS_DEPRECATED = 'deprecated' + + +@strawberry.enum +class VLANQinQRoleEnum(Enum): + ROLE_SERVICE = 'svlan' + ROLE_CUSTOMER = 'cvlan' + + +# +# Services +# + + +@strawberry.enum +class ServiceProtocolEnum(Enum): + PROTOCOL_TCP = 'tcp' + PROTOCOL_UDP = 'udp' + PROTOCOL_SCTP = 'sctp' diff --git a/netbox/ipam/graphql/filter_mixins.py b/netbox/ipam/graphql/filter_mixins.py new file mode 100644 index 00000000000..74c189344b0 --- /dev/null +++ b/netbox/ipam/graphql/filter_mixins.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING +import strawberry +import strawberry_django + +from core.graphql.filter_mixins import * + +if TYPE_CHECKING: + from .enums import * + from core.graphql.filter_lookups import * + +__all__ = ['ServiceBaseFilterMixin'] + + +@dataclass +class ServiceBaseFilterMixin(BaseFilterMixin): + protocol: Annotated['ServiceProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + ports: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/ipam/graphql/filters.py b/netbox/ipam/graphql/filters.py index 1b0e0133b6d..40c98a99c7a 100644 --- a/netbox/ipam/graphql/filters.py +++ b/netbox/ipam/graphql/filters.py @@ -1,7 +1,37 @@ +from datetime import date +from typing import Annotated, TYPE_CHECKING +import netaddr +from netaddr.core import AddrFormatError +from django.db.models import Q +import strawberry +from strawberry.scalars import ID import strawberry_django - -from ipam import filtersets, models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin +from strawberry_django import ( + FilterLookup, + DateFilterLookup, +) +from core.graphql.filter_mixins import * +from netbox.graphql.filter_mixins import * +from tenancy.graphql.filter_mixins import * + +from ipam import models +from ipam.graphql.filter_mixins import * + +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.enums import * + from wireless.graphql.enums import * + from core.graphql.filter_lookups import * + from core.graphql.filters import * + from extras.graphql.filters import * + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from tenancy.graphql.filters import * + from wireless.graphql.filters import * + from users.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * __all__ = ( 'ASNFilter', @@ -26,108 +56,303 @@ @strawberry_django.filter(models.ASN, lookups=True) -@autotype_decorator(filtersets.ASNFilterSet) -class ASNFilter(BaseFilterMixin): - pass +class ASNFilter(PrimaryModelFilterMixin): + rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + rir_id: ID | None = strawberry_django.filter_field() + asn: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.ASNRange, lookups=True) -@autotype_decorator(filtersets.ASNRangeFilterSet) -class ASNRangeFilter(BaseFilterMixin): - pass +class ASNRangeFilter(OrganizationalModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + rir_id: ID | None = strawberry_django.filter_field() + start: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + end: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.Aggregate, lookups=True) -@autotype_decorator(filtersets.AggregateFilterSet) -class AggregateFilter(BaseFilterMixin): - pass +class AggregateFilter(ContactFilterMixin, PrimaryModelFilterMixin): + prefix: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + prefix_id: ID | None = strawberry_django.filter_field() + rir: Annotated['RIRFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + rir_id: ID | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + date_added: DateFilterLookup[date] | None = strawberry_django.filter_field() @strawberry_django.filter(models.FHRPGroup, lookups=True) -@autotype_decorator(filtersets.FHRPGroupFilterSet) -class FHRPGroupFilter(BaseFilterMixin): - pass +class FHRPGroupFilter(PrimaryModelFilterMixin): + group_id: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + protocol: Annotated['FHRPGroupProtocolEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + auth_type: Annotated['FHRPGroupAuthTypeEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + auth_key: FilterLookup[str] | None = strawberry_django.filter_field() + ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.FHRPGroupAssignment, lookups=True) -@autotype_decorator(filtersets.FHRPGroupAssignmentFilterSet) -class FHRPGroupAssignmentFilter(BaseFilterMixin): - pass +class FHRPGroupAssignmentFilter(BaseObjectTypeFilterMixin, ChangeLogFilterMixin): + inteface_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + interface_id: FilterLookup[str] | None = strawberry_django.filter_field() + group: Annotated['FHRPGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + priority: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.IPAddress, lookups=True) -@autotype_decorator(filtersets.IPAddressFilterSet) -class IPAddressFilter(BaseFilterMixin): - pass +class IPAddressFilter(ContactFilterMixin, PrimaryModelFilterMixin): + address: FilterLookup[str] | None = strawberry_django.filter_field() + vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + status: Annotated['IPAddressStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_id: ID | None = strawberry_django.filter_field() + nat_inside: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + nat_inside_id: ID | None = strawberry_django.filter_field() + nat_outside: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + nat_outside_id: ID | None = strawberry_django.filter_field() + dns_name: FilterLookup[str] | None = strawberry_django.filter_field() + + @strawberry_django.filter_field() + def parent(self, value: list[str], prefix) -> Q: + if not value: + return Q() + q = Q() + for subnet in value: + try: + query = str(netaddr.IPNetwork(subnet.strip()).cidr) + q |= Q(address__net_host_contained=query) + except (AddrFormatError, ValueError): + return Q() + return q @strawberry_django.filter(models.IPRange, lookups=True) -@autotype_decorator(filtersets.IPRangeFilterSet) -class IPRangeFilter(BaseFilterMixin): - pass +class IPRangeFilter(ContactFilterMixin, PrimaryModelFilterMixin): + start_address: FilterLookup[str] | None = strawberry_django.filter_field() + end_address: FilterLookup[str] | None = strawberry_django.filter_field() + size: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + status: Annotated['IPRangeStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['IPAddressRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field() + + @strawberry_django.filter_field() + def parent(self, value: list[str], prefix) -> Q: + if not value: + return Q() + q = Q() + for subnet in value: + try: + query = str(netaddr.IPNetwork(subnet.strip()).cidr) + q |= Q(start_address__net_host_contained=query, end_address__net_host_contained=query) + except (AddrFormatError, ValueError): + return Q() + return q @strawberry_django.filter(models.Prefix, lookups=True) -@autotype_decorator(filtersets.PrefixFilterSet) -class PrefixFilter(BaseFilterMixin): - pass +class PrefixFilter(ContactFilterMixin, PrimaryModelFilterMixin): + prefix: FilterLookup[str] | None = strawberry_django.filter_field() + vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + vlan_id: ID | None = strawberry_django.filter_field() + status: Annotated['PrefixStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + role_id: ID | None = strawberry_django.filter_field() + is_pool: FilterLookup[bool] | None = strawberry_django.filter_field() + mark_utilized: FilterLookup[bool] | None = strawberry_django.filter_field() + scope_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + scope_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.RIR, lookups=True) -@autotype_decorator(filtersets.RIRFilterSet) -class RIRFilter(BaseFilterMixin): - pass +class RIRFilter(OrganizationalModelFilterMixin): + is_private: FilterLookup[bool] | None = strawberry_django.filter_field() @strawberry_django.filter(models.Role, lookups=True) -@autotype_decorator(filtersets.RoleFilterSet) -class RoleFilter(BaseFilterMixin): - pass +class RoleFilter(OrganizationalModelFilterMixin): + weight: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.RouteTarget, lookups=True) -@autotype_decorator(filtersets.RouteTargetFilterSet) -class RouteTargetFilter(BaseFilterMixin): - pass +class RouteTargetFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.Service, lookups=True) -@autotype_decorator(filtersets.ServiceFilterSet) -class ServiceFilter(BaseFilterMixin): - pass +class ServiceFilter(ContactFilterMixin, ServiceBaseFilterMixin, PrimaryModelFilterMixin): + device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + device_id: ID | None = strawberry_django.filter_field() + virtual_machine: Annotated['VirtualMachineFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + virtual_machine_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + ipaddresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ServiceTemplate, lookups=True) -@autotype_decorator(filtersets.ServiceTemplateFilterSet) -class ServiceTemplateFilter(BaseFilterMixin): - pass +class ServiceTemplateFilter(ServiceBaseFilterMixin, PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.VLAN, lookups=True) -@autotype_decorator(filtersets.VLANFilterSet) -class VLANFilter(BaseFilterMixin): - pass +class VLANFilter(PrimaryModelFilterMixin): + site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + site_id: ID | None = strawberry_django.filter_field() + group: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + vid: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + name: FilterLookup[str] | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + status: Annotated['VLANStatusEnum', strawberry.lazy('ipam.graphql.enums')] | None = strawberry_django.filter_field() + role: Annotated['RoleFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + role_id: ID | None = strawberry_django.filter_field() + qinq_svlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + qinq_svlan_id: ID | None = strawberry_django.filter_field() + qinq_cvlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + qinq_cvlan_id: ID | None = strawberry_django.filter_field() + qinq_role: Annotated['VLANQinQRoleEnum', strawberry.lazy('ipam.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + l2vpn_terminations: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.VLANGroup, lookups=True) -@autotype_decorator(filtersets.VLANGroupFilterSet) -class VLANGroupFilter(BaseFilterMixin): - pass +class VLANGroupFilter(OrganizationalModelFilterMixin): + scope_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + scope_type_id: ID | None = strawberry_django.filter_field() + scope_id: ID | None = strawberry_django.filter_field() + vid_ranges: Annotated['IntegerArrayLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.VLANTranslationPolicy, lookups=True) -@autotype_decorator(filtersets.VLANTranslationPolicyFilterSet) -class VLANTranslationPolicyFilter(BaseFilterMixin): - pass +class VLANTranslationPolicyFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.VLANTranslationRule, lookups=True) -@autotype_decorator(filtersets.VLANTranslationRuleFilterSet) -class VLANTranslationRuleFilter(BaseFilterMixin): - pass +class VLANTranslationRuleFilter(NetBoxModelFilterMixin): + policy: Annotated['VLANTranslationPolicyFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + policy_id: ID | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + local_vid: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + remote_vid: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.VRF, lookups=True) -@autotype_decorator(filtersets.VRFFilterSet) -class VRFFilter(BaseFilterMixin): - pass +class VRFFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + rd: FilterLookup[str] | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + enforce_unique: FilterLookup[bool] | None = strawberry_django.filter_field() + import_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + export_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/netbox/graphql/enums.py b/netbox/netbox/graphql/enums.py new file mode 100644 index 00000000000..d3d7a66905a --- /dev/null +++ b/netbox/netbox/graphql/enums.py @@ -0,0 +1,120 @@ +from enum import Enum +import strawberry + +__all__ = [ + 'ColorEnum', + 'ButtonColorEnum', + 'ImportMethodEnumEnum', + 'ImportFormatEnum', + 'DistanceUnitEnum', + 'WeightUnitEnum', +] + +# +# Generic color choices +# + + +@strawberry.enum +class ColorEnum(Enum): + COLOR_DARK_RED = 'aa1409' + COLOR_RED = 'f44336' + COLOR_PINK = 'e91e63' + COLOR_ROSE = 'ffe4e1' + COLOR_FUCHSIA = 'ff66ff' + COLOR_PURPLE = '9c27b0' + COLOR_DARK_PURPLE = '673ab7' + COLOR_INDIGO = '3f51b5' + COLOR_BLUE = '2196f3' + COLOR_LIGHT_BLUE = '03a9f4' + COLOR_CYAN = '00bcd4' + COLOR_TEAL = '009688' + COLOR_AQUA = '00ffff' + COLOR_DARK_GREEN = '2f6a31' + COLOR_GREEN = '4caf50' + COLOR_LIGHT_GREEN = '8bc34a' + COLOR_LIME = 'cddc39' + COLOR_YELLOW = 'ffeb3b' + COLOR_AMBER = 'ffc107' + COLOR_ORANGE = 'ff9800' + COLOR_DARK_ORANGE = 'ff5722' + COLOR_BROWN = '795548' + COLOR_LIGHT_GREY = 'c0c0c0' + COLOR_GREY = '9e9e9e' + COLOR_DARK_GREY = '607d8b' + COLOR_BLACK = '111111' + COLOR_WHITE = 'ffffff' + + +# +# Button color choices +# + + +@strawberry.enum +class ButtonColorEnum(Enum): + DEFAULT = 'default' + BLUE = 'blue' + INDIGO = 'indigo' + PURPLE = 'purple' + PINK = 'pink' + RED = 'red' + ORANGE = 'orange' + YELLOW = 'yellow' + GREEN = 'green' + TEAL = 'teal' + CYAN = 'cyan' + GRAY = 'gray' + GREY = 'gray' # Backward compatability for <3.2 + BLACK = 'black' + WHITE = 'white' + + +# +# Import +# + + +@strawberry.enum +class ImportMethodEnumEnum(Enum): + DIRECT = 'direct' + UPLOAD = 'upload' + DATA_FILE = 'datafile' + + +@strawberry.enum +class ImportFormatEnum(Enum): + AUTO = 'auto' + CSV = 'csv' + JSON = 'json' + YAML = 'yaml' + + +# @strawberry.enum +# class CSVDelimiterEnum(Enum): +# AUTO = 'auto' +# COMMA = CSV_DELIMITERS['comma'] +# SEMICOLON = CSV_DELIMITERS['semicolon'] +# TAB = CSV_DELIMITERS['tab'] + + +@strawberry.enum +class DistanceUnitEnum(Enum): + # Metric + UNIT_KILOMETER = 'km' + UNIT_METER = 'm' + + # Imperial + UNIT_MILE = 'mi' + UNIT_FOOT = 'ft' + + +@strawberry.enum +class WeightUnitEnum(Enum): + # Metric + UNIT_KILOGRAM = 'kg' + UNIT_GRAM = 'g' + + # Imperial + UNIT_POUND = 'lb' + UNIT_OUNCE = 'oz' diff --git a/netbox/netbox/graphql/filter_mixins.py b/netbox/netbox/graphql/filter_mixins.py index 2044a1ddeeb..078bf81edc3 100644 --- a/netbox/netbox/graphql/filter_mixins.py +++ b/netbox/netbox/graphql/filter_mixins.py @@ -1,209 +1,114 @@ -from functools import partialmethod -from typing import List +from dataclasses import dataclass +from typing import TypeVar, TYPE_CHECKING, Annotated -import django_filters + +from datetime import datetime import strawberry +from strawberry import ID import strawberry_django -from django.core.exceptions import FieldDoesNotExist -from strawberry import auto +from strawberry_django import FilterLookup, DatetimeFilterLookup -from ipam.fields import ASNField -from netbox.graphql.scalars import BigInt -from utilities.fields import ColorField, CounterCacheField +from extras.models import * from utilities.filters import * - - -def map_strawberry_type(field): - should_create_function = False - attr_type = None - - # NetBox Filter types - put base classes after derived classes - if isinstance(field, ContentTypeFilter): - should_create_function = True - attr_type = str | None - elif isinstance(field, MultiValueArrayFilter): - pass - elif isinstance(field, MultiValueCharFilter): - # Note: Need to use the legacy FilterLookup from filters, not from - # strawberry_django.FilterLookup as we currently have USE_DEPRECATED_FILTERS - attr_type = strawberry_django.filters.FilterLookup[str] | None - elif isinstance(field, MultiValueDateFilter): - attr_type = auto - elif isinstance(field, MultiValueDateTimeFilter): - attr_type = auto - elif isinstance(field, MultiValueDecimalFilter): - pass - elif isinstance(field, MultiValueMACAddressFilter): - should_create_function = True - attr_type = List[str] | None - elif isinstance(field, MultiValueNumberFilter): - should_create_function = True - attr_type = List[str] | None - elif isinstance(field, MultiValueTimeFilter): - pass - elif isinstance(field, MultiValueWWNFilter): - should_create_function = True - attr_type = List[str] | None - elif isinstance(field, NullableCharFieldFilter): - pass - elif isinstance(field, NumericArrayFilter): - should_create_function = True - attr_type = int | None - elif isinstance(field, TreeNodeMultipleChoiceFilter): - should_create_function = True - attr_type = List[str] | None - - # From django_filters - ordering of these matters as base classes must - # come after derived classes so the base class doesn't get matched first - # a pass for the check (no attr_type) means we don't currently handle - # or use that type - elif issubclass(type(field), django_filters.OrderingFilter): - pass - elif issubclass(type(field), django_filters.BaseRangeFilter): - pass - elif issubclass(type(field), django_filters.BaseInFilter): - pass - elif issubclass(type(field), django_filters.LookupChoiceFilter): - pass - elif issubclass(type(field), django_filters.AllValuesMultipleFilter): - pass - elif issubclass(type(field), django_filters.AllValuesFilter): - pass - elif issubclass(type(field), django_filters.TimeRangeFilter): - pass - elif issubclass(type(field), django_filters.IsoDateTimeFromToRangeFilter): - should_create_function = True - attr_type = str | None - elif issubclass(type(field), django_filters.DateTimeFromToRangeFilter): - should_create_function = True - attr_type = str | None - elif issubclass(type(field), django_filters.DateFromToRangeFilter): - should_create_function = True - attr_type = str | None - elif issubclass(type(field), django_filters.DateRangeFilter): - should_create_function = True - attr_type = str | None - elif issubclass(type(field), django_filters.RangeFilter): - pass - elif issubclass(type(field), django_filters.NumericRangeFilter): - pass - elif issubclass(type(field), django_filters.NumberFilter): - should_create_function = True - attr_type = int | None - elif issubclass(type(field), django_filters.ModelMultipleChoiceFilter): - should_create_function = True - attr_type = List[str] | None - elif issubclass(type(field), django_filters.ModelChoiceFilter): - should_create_function = True - attr_type = str | None - elif issubclass(type(field), django_filters.DurationFilter): - pass - elif issubclass(type(field), django_filters.IsoDateTimeFilter): - pass - elif issubclass(type(field), django_filters.DateTimeFilter): - attr_type = auto - elif issubclass(type(field), django_filters.TimeFilter): - attr_type = auto - elif issubclass(type(field), django_filters.DateFilter): - attr_type = auto - elif issubclass(type(field), django_filters.TypedMultipleChoiceFilter): - pass - elif issubclass(type(field), django_filters.MultipleChoiceFilter): - attr_type = str | None - elif issubclass(type(field), django_filters.TypedChoiceFilter): - pass - elif issubclass(type(field), django_filters.ChoiceFilter): - pass - elif issubclass(type(field), django_filters.BooleanFilter): - should_create_function = True - attr_type = bool | None - elif issubclass(type(field), django_filters.UUIDFilter): - should_create_function = True - attr_type = str | None - elif issubclass(type(field), django_filters.CharFilter): - # looks like only used by 'q' - should_create_function = True - attr_type = str | None - - return should_create_function, attr_type - - -def autotype_decorator(filterset): - """ - Decorator used to auto creates a dataclass used by Strawberry based on a filterset. - Must go after the Strawberry decorator as follows: - - @strawberry_django.filter(models.Example, lookups=True) - @autotype_decorator(filtersets.ExampleFilterSet) - class ExampleFilter(BaseFilterMixin): - pass - - The Filter itself must be derived from BaseFilterMixin. For items listed in meta.fields - of the filterset, usually just a type specifier is generated, so for - `fields = [created, ]` the dataclass would be: - - class ExampleFilter(BaseFilterMixin): - created: auto - - For other filter fields a function needs to be created for Strawberry with the - naming convention `filter_{fieldname}` which is auto detected and called by - Strawberry, this function uses the filterset to handle the query. - """ - def create_attribute_and_function(cls, fieldname, attr_type, should_create_function): - if fieldname not in cls.__annotations__ and attr_type: - cls.__annotations__[fieldname] = attr_type - - filter_name = f"filter_{fieldname}" - if should_create_function and not hasattr(cls, filter_name): - filter_by_filterset = getattr(cls, 'filter_by_filterset') - setattr(cls, filter_name, partialmethod(filter_by_filterset, key=fieldname)) - - def wrapper(cls): - cls.filterset = filterset - fields = filterset.get_fields() - model = filterset._meta.model - for fieldname in fields.keys(): - should_create_function = False - attr_type = auto - if fieldname not in cls.__annotations__: - try: - field = model._meta.get_field(fieldname) - except FieldDoesNotExist: - continue - - if isinstance(field, CounterCacheField): - should_create_function = True - attr_type = BigInt | None - elif isinstance(field, ASNField): - should_create_function = True - attr_type = List[str] | None - elif isinstance(field, ColorField): - should_create_function = True - attr_type = List[str] | None - - create_attribute_and_function(cls, fieldname, attr_type, should_create_function) - - declared_filters = filterset.declared_filters - for fieldname, field in declared_filters.items(): - - should_create_function, attr_type = map_strawberry_type(field) - if attr_type is None: - raise NotImplementedError(f"GraphQL Filter field unknown: {fieldname}: {field}") - - create_attribute_and_function(cls, fieldname, attr_type, should_create_function) - - return cls - - return wrapper - - -@strawberry.input -class BaseFilterMixin: - - def filter_by_filterset(self, queryset, key): - filterset = self.filterset(data={key: getattr(self, key)}, queryset=queryset) - if not filterset.is_valid(): - # We could raise validation error but strawberry logs it all to the - # console i.e. raise ValidationError(f"{k}: {v[0]}") - return filterset.qs.none() - return filterset.qs +from core.graphql.filter_lookups import * +from core.graphql.filter_mixins import * +from extras.graphql.filter_mixins import * + +__all__ = [ + 'NetBoxModelFilterMixin', + 'OrganizationalModelFilterMixin', + 'PrimaryModelFilterMixin', + # 'autotype_decorator', + 'NestedGroupModelFilterMixin', + 'ImageAttachmentFilterMixin', + 'WeightFilterMixin', + 'SyncedDataFilterMixin', + 'DistanceFilterMixin', +] + +T = TypeVar('T') + + +if TYPE_CHECKING: + from .enums import * + from core.graphql.filters import * + from tenancy.graphql.filters import * + from extras.graphql.filters import * + + +class NetBoxModelFilterMixin( + ChangeLogFilterMixin, + CustomFieldsFilterMixin, + JournalEntriesFilterMixin, + TagsFilterMixin, + BaseObjectTypeFilterMixin, +): + pass + + +@dataclass +class NestedGroupModelFilterMixin(NetBoxModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + lft: IntegerLookup | None = strawberry_django.filter_field() + rght: IntegerLookup | None = strawberry_django.filter_field() + tree_id: IntegerLookup | None = strawberry_django.filter_field() + level: IntegerLookup | None = strawberry_django.filter_field() + parent_id: ID | None = strawberry_django.filter_field() + + +@dataclass +class OrganizationalModelFilterMixin( + ChangeLogFilterMixin, + CustomFieldsFilterMixin, + TagsFilterMixin, + BaseObjectTypeFilterMixin, +): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() + + +@dataclass +class PrimaryModelFilterMixin(NetBoxModelFilterMixin): + description: FilterLookup[str] | None = strawberry_django.filter_field() + comments: FilterLookup[str] | None = strawberry_django.filter_field() + + +@dataclass +class ImageAttachmentFilterMixin(BaseFilterMixin): + images: Annotated['ImageAttachmentFilter', strawberry.lazy('extras.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class WeightFilterMixin(BaseFilterMixin): + weight: FilterLookup[float] | None = strawberry_django.filter_field() + weight_unit: Annotated['WeightUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class SyncedDataFilterMixin(BaseFilterMixin): + data_source: Annotated['DataSourceFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + data_source_id: FilterLookup[int] | None = strawberry_django.filter_field() + data_file: Annotated['DataFileFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + data_file_id: FilterLookup[int] | None = strawberry_django.filter_field() + data_path: FilterLookup[str] | None = strawberry_django.filter_field() + auto_sync_enabled: FilterLookup[bool] | None = strawberry_django.filter_field() + data_synced: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + + +@dataclass +class DistanceFilterMixin(BaseFilterMixin): + distance: FilterLookup[float] | None = strawberry_django.filter_field() + distance_unit: Annotated['DistanceUnitEnum', strawberry.lazy('netbox.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/netbox/graphql/schema.py b/netbox/netbox/graphql/schema.py index a7609c9d206..625f7cd1b29 100644 --- a/netbox/netbox/graphql/schema.py +++ b/netbox/netbox/graphql/schema.py @@ -38,7 +38,8 @@ class Query( query=Query, config=StrawberryConfig(auto_camel_case=False), extensions=[ - DjangoOptimizerExtension(prefetch_custom_queryset=True), + # DjangoOptimizerExtension(prefetch_custom_queryset=True), + DjangoOptimizerExtension(prefetch_custom_queryset=True, enable_prefetch_related_optimization=False), MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES), ] ) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 84b86ba13ad..9f514684fbe 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -775,7 +775,7 @@ def _setting(name, default=None): STRAWBERRY_DJANGO = { "DEFAULT_PK_FIELD_NAME": "id", "TYPE_DESCRIPTION_FROM_MODEL_DOCSTRING": True, - "USE_DEPRECATED_FILTERS": True, + "USE_DEPRECATED_FILTERS": False, } # diff --git a/netbox/netbox/tests/test_graphql.py b/netbox/netbox/tests/test_graphql.py index b04d42d2447..60a8ec5ae25 100644 --- a/netbox/netbox/tests/test_graphql.py +++ b/netbox/netbox/tests/test_graphql.py @@ -99,12 +99,13 @@ def test_graphql_filter_objects(self): # Test OR logic query = """{ location_list( filters: { - status: \"""" + LocationStatusChoices.STATUS_PLANNED + """\", - OR: {status: \"""" + LocationStatusChoices.STATUS_STAGING + """\"} + status: STATUS_PLANNED, + OR: {status: STATUS_STAGING} }) { id site {id} } }""" + print(query) response = self.client.post(url, data={'query': query}, format="json", **self.header) self.assertHttpStatus(response, status.HTTP_200_OK) data = json.loads(response.content) diff --git a/netbox/tenancy/graphql/filter_mixins.py b/netbox/tenancy/graphql/filter_mixins.py new file mode 100644 index 00000000000..dffd5d85bcf --- /dev/null +++ b/netbox/tenancy/graphql/filter_mixins.py @@ -0,0 +1,33 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry import ID +import strawberry_django +from core.graphql.filter_mixins import BaseFilterMixin + +if TYPE_CHECKING: + from .filters import * + from core.graphql.filter_lookups import * + +__all__ = ['TenancyFilterMixin', 'ContactFilterMixin'] + + +@dataclass +class ContactFilterMixin(BaseFilterMixin): + contacts: Annotated['ContactFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + + +@dataclass +class TenancyFilterMixin(BaseFilterMixin): + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + group: Annotated['TenantGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/tenancy/graphql/filters.py b/netbox/tenancy/graphql/filters.py index e82b1cd07c8..97074226b3e 100644 --- a/netbox/tenancy/graphql/filters.py +++ b/netbox/tenancy/graphql/filters.py @@ -1,7 +1,28 @@ +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry.scalars import ID import strawberry_django +from strawberry_django import ( + FilterLookup, +) +from netbox.graphql.filter_mixins import ( + PrimaryModelFilterMixin, + OrganizationalModelFilterMixin, + NestedGroupModelFilterMixin, +) +from extras.graphql.filter_mixins import CustomFieldsFilterMixin, TagsFilterMixin +from core.graphql.filter_mixins import ChangeLogFilterMixin +from tenancy import models +from .filter_mixins import ContactFilterMixin -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin -from tenancy import filtersets, models +if TYPE_CHECKING: + from core.graphql.filter_lookups import TreeNodeFilter + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from wireless.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * __all__ = ( 'TenantFilter', @@ -14,36 +35,127 @@ @strawberry_django.filter(models.Tenant, lookups=True) -@autotype_decorator(filtersets.TenantFilterSet) -class TenantFilter(BaseFilterMixin): - pass +class TenantFilter(PrimaryModelFilterMixin, ContactFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + group: Annotated['TenantGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + asns: Annotated['ASNFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + circuits: Annotated['CircuitFilter', strawberry.lazy('circuits.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + sites: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + vlans: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + wireless_lans: Annotated['WirelessLANFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + route_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + locations: Annotated['LocationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + ip_ranges: Annotated['IPRangeFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + rackreservations: Annotated['RackReservationFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + racks: Annotated['RackFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + vdcs: Annotated['VirtualDeviceContextFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + prefixes: Annotated['PrefixFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + cables: Annotated['CableFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + virtual_machines: Annotated['VirtualMachineFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + vrfs: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + asn_ranges: Annotated['ASNRangeFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + wireless_links: Annotated['WirelessLinkFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + aggregates: Annotated['AggregateFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + power_feeds: Annotated['PowerFeedFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + devices: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tunnels: Annotated['TunnelFilter', strawberry.lazy('vpn.graphql.filters')] | None = strawberry_django.filter_field() + ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + clusters: Annotated['ClusterFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + l2vpns: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = strawberry_django.filter_field() @strawberry_django.filter(models.TenantGroup, lookups=True) -@autotype_decorator(filtersets.TenantGroupFilterSet) -class TenantGroupFilter(BaseFilterMixin): - pass +class TenantGroupFilter(OrganizationalModelFilterMixin): + parent: Annotated['TenantGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + parent_id: ID | None = strawberry.UNSET + tenants: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + children: Annotated['TenantGroupFilter', strawberry.lazy('tenancy.graphql.filters'), True] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.Contact, lookups=True) -@autotype_decorator(filtersets.ContactFilterSet) -class ContactFilter(BaseFilterMixin): - pass +class ContactFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + title: FilterLookup[str] | None = strawberry_django.filter_field() + phone: FilterLookup[str] | None = strawberry_django.filter_field() + email: FilterLookup[str] | None = strawberry_django.filter_field() + address: FilterLookup[str] | None = strawberry_django.filter_field() + link: FilterLookup[str] | None = strawberry_django.filter_field() + group: Annotated['ContactGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: Annotated['TreeNodeFilter', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + assignments: Annotated['ContactAssignmentFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ContactRole, lookups=True) -@autotype_decorator(filtersets.ContactRoleFilterSet) -class ContactRoleFilter(BaseFilterMixin): +class ContactRoleFilter(OrganizationalModelFilterMixin): pass @strawberry_django.filter(models.ContactGroup, lookups=True) -@autotype_decorator(filtersets.ContactGroupFilterSet) -class ContactGroupFilter(BaseFilterMixin): - pass +class ContactGroupFilter(NestedGroupModelFilterMixin): + parent: Annotated['ContactGroupFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ContactAssignment, lookups=True) -@autotype_decorator(filtersets.ContactAssignmentFilterSet) -class ContactAssignmentFilter(BaseFilterMixin): - pass +class ContactAssignmentFilter(CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin): + object_id: ID | None = strawberry_django.filter_field() + contact: Annotated['ContactFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + contact_id: ID | None = strawberry_django.filter_field() + role: Annotated['ContactRoleFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + priority: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/tenancy/graphql/types.py b/netbox/tenancy/graphql/types.py index 7baa136b37e..9d537a05c85 100644 --- a/netbox/tenancy/graphql/types.py +++ b/netbox/tenancy/graphql/types.py @@ -1,4 +1,4 @@ -from typing import Annotated, List +from typing import Annotated, List, TYPE_CHECKING import strawberry import strawberry_django @@ -9,6 +9,14 @@ from .mixins import ContactAssignmentsMixin from .filters import * +if TYPE_CHECKING: + from circuits.graphql.types import * + from dcim.graphql.types import * + from ipam.graphql.types import * + from wireless.graphql.types import * + from virtualization.graphql.types import * + from vpn.graphql.types import * + __all__ = ( 'ContactAssignmentType', 'ContactGroupType', @@ -23,92 +31,70 @@ # Tenants # -@strawberry_django.type( - models.Tenant, - fields='__all__', - filters=TenantFilter -) + +@strawberry_django.type(models.Tenant, fields='__all__', filters=TenantFilter) class TenantType(NetBoxObjectType): - group: Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')] | None - - asns: List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]] - circuits: List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]] - sites: List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]] - vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]] - wireless_lans: List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]] - route_targets: List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]] - locations: List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]] - ip_ranges: List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]] - rackreservations: List[Annotated["RackReservationType", strawberry.lazy('dcim.graphql.types')]] - racks: List[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]] - vdcs: List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]] - prefixes: List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]] - cables: List[Annotated["CableType", strawberry.lazy('dcim.graphql.types')]] - virtual_machines: List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]] - vrfs: List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]] - asn_ranges: List[Annotated["ASNRangeType", strawberry.lazy('ipam.graphql.types')]] - wireless_links: List[Annotated["WirelessLinkType", strawberry.lazy('wireless.graphql.types')]] - aggregates: List[Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')]] - power_feeds: List[Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')]] - devices: List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]] - tunnels: List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]] - ip_addresses: List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]] - clusters: List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]] - l2vpns: List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]] - - -@strawberry_django.type( - models.TenantGroup, - fields='__all__', - filters=TenantGroupFilter -) + group: Annotated['TenantGroupType', strawberry.lazy('tenancy.graphql.types')] + contacts: List[Annotated['ContactType', strawberry.lazy('tenancy.graphql.types')]] + asns: List[Annotated['ASNType', strawberry.lazy('ipam.graphql.types')]] + circuits: List[Annotated['CircuitType', strawberry.lazy('circuits.graphql.types')]] + sites: List[Annotated['SiteType', strawberry.lazy('dcim.graphql.types')]] + vlans: List[Annotated['VLANType', strawberry.lazy('ipam.graphql.types')]] + wireless_lans: List[Annotated['WirelessLANType', strawberry.lazy('wireless.graphql.types')]] + route_targets: List[Annotated['RouteTargetType', strawberry.lazy('ipam.graphql.types')]] + locations: List[Annotated['LocationType', strawberry.lazy('dcim.graphql.types')]] + ip_ranges: List[Annotated['IPRangeType', strawberry.lazy('ipam.graphql.types')]] + rackreservations: List[Annotated['RackReservationType', strawberry.lazy('dcim.graphql.types')]] + racks: List[Annotated['RackType', strawberry.lazy('dcim.graphql.types')]] + vdcs: List[Annotated['VirtualDeviceContextType', strawberry.lazy('dcim.graphql.types')]] + prefixes: List[Annotated['PrefixType', strawberry.lazy('ipam.graphql.types')]] + cables: List[Annotated['CableType', strawberry.lazy('dcim.graphql.types')]] + virtual_machines: List[Annotated['VirtualMachineType', strawberry.lazy('virtualization.graphql.types')]] + vrfs: List[Annotated['VRFType', strawberry.lazy('ipam.graphql.types')]] + asn_ranges: List[Annotated['ASNRangeType', strawberry.lazy('ipam.graphql.types')]] + wireless_links: List[Annotated['WirelessLinkType', strawberry.lazy('wireless.graphql.types')]] + aggregates: List[Annotated['AggregateType', strawberry.lazy('ipam.graphql.types')]] + power_feeds: List[Annotated['PowerFeedType', strawberry.lazy('dcim.graphql.types')]] + devices: List[Annotated['DeviceType', strawberry.lazy('dcim.graphql.types')]] + tunnels: List[Annotated['TunnelType', strawberry.lazy('vpn.graphql.types')]] + ip_addresses: List[Annotated['IPAddressType', strawberry.lazy('ipam.graphql.types')]] + clusters: List[Annotated['ClusterType', strawberry.lazy('virtualization.graphql.types')]] + l2vpns: List[Annotated['L2VPNType', strawberry.lazy('vpn.graphql.types')]] + + +@strawberry_django.type(models.TenantGroup, fields='__all__', filters=TenantGroupFilter) class TenantGroupType(OrganizationalObjectType): - parent: Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')] | None + parent: Annotated['TenantGroupType', strawberry.lazy('tenancy.graphql.types')] | None tenants: List[TenantType] - children: List[Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')]] + children: List[Annotated['TenantGroupType', strawberry.lazy('tenancy.graphql.types')]] # # Contacts # -@strawberry_django.type( - models.Contact, - fields='__all__', - filters=ContactFilter -) + +@strawberry_django.type(models.Contact, fields='__all__', filters=ContactFilter) class ContactType(ContactAssignmentsMixin, NetBoxObjectType): - group: Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')] | None + group: Annotated['ContactGroupType', strawberry.lazy('tenancy.graphql.types')] | None -@strawberry_django.type( - models.ContactRole, - fields='__all__', - filters=ContactRoleFilter -) +@strawberry_django.type(models.ContactRole, fields='__all__', filters=ContactRoleFilter) class ContactRoleType(ContactAssignmentsMixin, OrganizationalObjectType): pass -@strawberry_django.type( - models.ContactGroup, - fields='__all__', - filters=ContactGroupFilter -) +@strawberry_django.type(models.ContactGroup, fields='__all__', filters=ContactGroupFilter) class ContactGroupType(OrganizationalObjectType): - parent: Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')] | None + parent: Annotated['ContactGroupType', strawberry.lazy('tenancy.graphql.types')] | None contacts: List[ContactType] - children: List[Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')]] + children: List[Annotated['ContactGroupType', strawberry.lazy('tenancy.graphql.types')]] -@strawberry_django.type( - models.ContactAssignment, - fields='__all__', - filters=ContactAssignmentFilter -) +@strawberry_django.type(models.ContactAssignment, fields='__all__', filters=ContactAssignmentFilter) class ContactAssignmentType(CustomFieldsMixin, TagsMixin, BaseObjectType): - object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None - contact: Annotated["ContactType", strawberry.lazy('tenancy.graphql.types')] | None - role: Annotated["ContactRoleType", strawberry.lazy('tenancy.graphql.types')] | None + object_type: Annotated['ContentTypeType', strawberry.lazy('netbox.graphql.types')] | None + contact: Annotated['ContactType', strawberry.lazy('tenancy.graphql.types')] | None + role: Annotated['ContactRoleType', strawberry.lazy('tenancy.graphql.types')] | None diff --git a/netbox/users/graphql/filters.py b/netbox/users/graphql/filters.py index d30781b1c00..e9d6e51f199 100644 --- a/netbox/users/graphql/filters.py +++ b/netbox/users/graphql/filters.py @@ -1,7 +1,31 @@ +from datetime import datetime +from typing import Annotated, TYPE_CHECKING +import strawberry import strawberry_django +from strawberry_django import ( + FilterLookup, + DatetimeFilterLookup, +) +from core.graphql.filter_mixins import * +from netbox.graphql.filter_mixins import * +from tenancy.graphql.filter_mixins import * + +from users import models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin -from users import filtersets, models +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.enums import * + from wireless.graphql.enums import * + from core.graphql.filter_lookups import * + from extras.graphql.filters import * + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from tenancy.graphql.filters import * + from wireless.graphql.filters import * + from users.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * __all__ = ( 'GroupFilter', @@ -10,12 +34,18 @@ @strawberry_django.filter(models.Group, lookups=True) -@autotype_decorator(filtersets.GroupFilterSet) -class GroupFilter(BaseFilterMixin): - pass +class GroupFilter(BaseObjectTypeFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.User, lookups=True) -@autotype_decorator(filtersets.UserFilterSet) -class UserFilter(BaseFilterMixin): - pass +class UserFilter(BaseObjectTypeFilterMixin): + username: FilterLookup[str] | None = strawberry_django.filter_field() + first_name: FilterLookup[str] | None = strawberry_django.filter_field() + last_name: FilterLookup[str] | None = strawberry_django.filter_field() + email: FilterLookup[str] | None = strawberry_django.filter_field() + is_staff: FilterLookup[bool] | None = strawberry_django.filter_field() + is_active: FilterLookup[bool] | None = strawberry_django.filter_field() + date_joined: DatetimeFilterLookup[datetime] | None = strawberry_django.filter_field() + groups: Annotated['GroupFilter', strawberry.lazy('users.graphql.filters')] | None = strawberry_django.filter_field() diff --git a/netbox/virtualization/graphql/enums.py b/netbox/virtualization/graphql/enums.py new file mode 100644 index 00000000000..081669f1f21 --- /dev/null +++ b/netbox/virtualization/graphql/enums.py @@ -0,0 +1,33 @@ +from enum import Enum +import strawberry + +__all__ = ['ClusterStatusEnum', 'VirtualMachineStatusEnum'] + + +# +# Clusters +# + +@strawberry.enum +class ClusterStatusEnum(Enum): + + STATUS_PLANNED = 'planned' + STATUS_STAGING = 'staging' + STATUS_ACTIVE = 'active' + STATUS_DECOMMISSIONING = 'decommissioning' + STATUS_OFFLINE = 'offline' + + +# +# VirtualMachines +# + +@strawberry.enum +class VirtualMachineStatusEnum(Enum): + + STATUS_OFFLINE = 'offline' + STATUS_ACTIVE = 'active' + STATUS_PLANNED = 'planned' + STATUS_STAGED = 'staged' + STATUS_FAILED = 'failed' + STATUS_DECOMMISSIONING = 'decommissioning' diff --git a/netbox/virtualization/graphql/filter_mixins.py b/netbox/virtualization/graphql/filter_mixins.py new file mode 100644 index 00000000000..4a7386dbee5 --- /dev/null +++ b/netbox/virtualization/graphql/filter_mixins.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry import ID +import strawberry_django +from strawberry_django import FilterLookup + +from netbox.graphql.filter_mixins import NetBoxModelFilterMixin + +if TYPE_CHECKING: + from .filters import * + +__all__ = ['VMComponentFilterMixin'] + + +@dataclass +class VMComponentFilterMixin(NetBoxModelFilterMixin): + virtual_manchine: Annotated['VirtualMachineFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + virtual_machine_id: ID | None = strawberry_django.filter_field() + name: FilterLookup[str] | None = strawberry_django.filter_field() + description: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/virtualization/graphql/filters.py b/netbox/virtualization/graphql/filters.py index 610275d376d..b3910719da6 100644 --- a/netbox/virtualization/graphql/filters.py +++ b/netbox/virtualization/graphql/filters.py @@ -1,7 +1,33 @@ +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry.scalars import ID import strawberry_django +from strawberry_django import ( + FilterLookup, +) +from core.graphql.filter_mixins import * +from netbox.graphql.filter_mixins import * +from tenancy.graphql.filter_mixins import * +from dcim.graphql.filter_mixins import * +from extras.graphql.filter_mixins import * +from virtualization.graphql.filter_mixins import * + +from virtualization import models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin -from virtualization import filtersets, models +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.enums import * + from wireless.graphql.enums import * + from core.graphql.filter_lookups import * + from extras.graphql.filters import * + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from tenancy.graphql.filters import * + from wireless.graphql.filters import * + from users.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * __all__ = ( 'ClusterFilter', @@ -14,36 +40,123 @@ @strawberry_django.filter(models.Cluster, lookups=True) -@autotype_decorator(filtersets.ClusterFilterSet) -class ClusterFilter(BaseFilterMixin): - pass +class ClusterFilter(ContactFilterMixin, PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + type: Annotated['ClusterTypeFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + type_id: ID | None = strawberry_django.filter_field() + group: Annotated['ClusterGroupFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + status: Annotated['ClusterStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ClusterGroup, lookups=True) -@autotype_decorator(filtersets.ClusterGroupFilterSet) -class ClusterGroupFilter(BaseFilterMixin): - pass +class ClusterGroupFilter(ContactFilterMixin, OrganizationalModelFilterMixin): + vlan_groups: Annotated['VLANGroupFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.ClusterType, lookups=True) -@autotype_decorator(filtersets.ClusterTypeFilterSet) -class ClusterTypeFilter(BaseFilterMixin): +class ClusterTypeFilter(OrganizationalModelFilterMixin): pass @strawberry_django.filter(models.VirtualMachine, lookups=True) -@autotype_decorator(filtersets.VirtualMachineFilterSet) -class VirtualMachineFilter(BaseFilterMixin): - pass +class VirtualMachineFilter( + ContactFilterMixin, + ImageAttachmentFilterMixin, + RenderConfigFilterMixin, + ConfigContextFilterMixin, + PrimaryModelFilterMixin, +): + site: Annotated['SiteFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + site_id: ID | None = strawberry_django.filter_field() + cluster: Annotated['ClusterFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + cluster_id: ID | None = strawberry_django.filter_field() + device: Annotated['DeviceFilter', strawberry.lazy('dcim.graphql.filters')] | None = strawberry_django.filter_field() + device_id: ID | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + platform: Annotated['PlatformFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + platform_id: ID | None = strawberry_django.filter_field() + status: Annotated['VirtualMachineStatusEnum', strawberry.lazy('virtualization.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + role: Annotated['DeviceRoleFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + role_id: ID | None = strawberry_django.filter_field() + primary_ip4: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + primary_ip4_id: ID | None = strawberry_django.filter_field() + primary_ip6: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + primary_ip6_id: ID | None = strawberry_django.filter_field() + vcpus: Annotated['FloatLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + memory: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + disk: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + serial: FilterLookup[str] | None = strawberry_django.filter_field() + interfaces: Annotated['VMInterfaceFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + services: Annotated['ServiceFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + virtual_disks: Annotated['VirtualDiskFilter', strawberry.lazy('virtualization.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.VMInterface, lookups=True) -@autotype_decorator(filtersets.VMInterfaceFilterSet) -class VMInterfaceFilter(BaseFilterMixin): - pass +class VMInterfaceFilter(VMComponentFilterMixin, InterfaceBaseFilterMixin): + ip_addresses: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + vrf: Annotated['VRFFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + vrf_id: ID | None = strawberry_django.filter_field() + fhrp_group_assignments: Annotated['FHRPGroupAssignmentFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tunnel_terminations: Annotated['TunnelTerminationFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + l2vpn_terminations: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + mac_addresses: Annotated['MACAddressFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.VirtualDisk, lookups=True) -@autotype_decorator(filtersets.VirtualDiskFilterSet) -class VirtualDiskFilter(BaseFilterMixin): - pass +class VirtualDiskFilter(VMComponentFilterMixin): + size: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/vpn/graphql/enums.py b/netbox/vpn/graphql/enums.py new file mode 100644 index 00000000000..7b66818adfd --- /dev/null +++ b/netbox/vpn/graphql/enums.py @@ -0,0 +1,161 @@ +from enum import Enum +import strawberry + +__all__ = [ + 'TunnelStatusEnum', + 'TunnelEncapsulationEnum', + 'TunnelTerminationTypeEnum', + 'TunnelTerminationRoleEnum', + 'IKEVersionEnum', + 'IKEModeEnum', + 'AuthenticationMethodEnum', + 'IPSecModeEnum', + 'EncryptionAlgorithmEnum', + 'AuthenticationAlgorithmEnum', + 'DHGroupEnum', + 'L2VPNTypeEnum', +] + +# +# Tunnels +# + + +@strawberry.enum +class TunnelStatusEnum(Enum): + key = 'Tunnel.status' + + STATUS_PLANNED = 'planned' + STATUS_ACTIVE = 'active' + STATUS_DISABLED = 'disabled' + + +@strawberry.enum +class TunnelEncapsulationEnum(Enum): + ENCAP_GRE = 'gre' + ENCAP_IPSEC_TRANSPORT = 'ipsec-transport' + ENCAP_IPSEC_TUNNEL = 'ipsec-tunnel' + ENCAP_IP_IP = 'ip-ip' + ENCAP_L2TP = 'l2tp' + ENCAP_OPENVPN = 'openvpn' + ENCAP_PPTP = 'pptp' + ENCAP_WIREGUARD = 'wireguard' + + +@strawberry.enum +class TunnelTerminationTypeEnum(Enum): + # For TunnelCreateForm + TYPE_DEVICE = 'dcim.device' + TYPE_VIRTUALMACHINE = 'virtualization.virtualmachine' + + +@strawberry.enum +class TunnelTerminationRoleEnum(Enum): + ROLE_PEER = 'peer' + ROLE_HUB = 'hub' + ROLE_SPOKE = 'spoke' + + +# +# Crypto +# + + +@strawberry.enum +class IKEVersionEnum(Enum): + VERSION_1 = 1 + VERSION_2 = 2 + + +@strawberry.enum +class IKEModeEnum(Enum): + AGGRESSIVE = 'aggressive' + MAIN = 'main' + + +@strawberry.enum +class AuthenticationMethodEnum(Enum): + PRESHARED_KEYS = 'preshared-keys' + CERTIFICATES = 'certificates' + RSA_SIGNATURES = 'rsa-signatures' + DSA_SIGNATURES = 'dsa-signatures' + + +@strawberry.enum +class IPSecModeEnum(Enum): + ESP = 'esp' + AH = 'ah' + + +@strawberry.enum +class EncryptionAlgorithmEnum(Enum): + ENCRYPTION_AES128_CBC = 'aes-128-cbc' + ENCRYPTION_AES128_GCM = 'aes-128-gcm' + ENCRYPTION_AES192_CBC = 'aes-192-cbc' + ENCRYPTION_AES192_GCM = 'aes-192-gcm' + ENCRYPTION_AES256_CBC = 'aes-256-cbc' + ENCRYPTION_AES256_GCM = 'aes-256-gcm' + ENCRYPTION_3DES = '3des-cbc' + ENCRYPTION_DES = 'des-cbc' + + +@strawberry.enum +class AuthenticationAlgorithmEnum(Enum): + AUTH_HMAC_SHA1 = 'hmac-sha1' + AUTH_HMAC_SHA256 = 'hmac-sha256' + AUTH_HMAC_SHA384 = 'hmac-sha384' + AUTH_HMAC_SHA512 = 'hmac-sha512' + AUTH_HMAC_MD5 = 'hmac-md5' + + +@strawberry.enum +class DHGroupEnum(Enum): + # https://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.xhtml#ikev2-parameters-8 + GROUP_1 = 1 # 768-bit MODP + GROUP_2 = 2 # 1024-but MODP + # Groups 3-4 reserved + GROUP_5 = 5 # 1536-bit MODP + # Groups 6-13 unassigned + GROUP_14 = 14 # 2048-bit MODP + GROUP_15 = 15 # 3072-bit MODP + GROUP_16 = 16 # 4096-bit MODP + GROUP_17 = 17 # 6144-bit MODP + GROUP_18 = 18 # 8192-bit MODP + GROUP_19 = 19 # 256-bit random ECP + GROUP_20 = 20 # 384-bit random ECP + GROUP_21 = 21 # 521-bit random ECP (521 is not a typo) + GROUP_22 = 22 # 1024-bit MODP w/160-bit prime + GROUP_23 = 23 # 2048-bit MODP w/224-bit prime + GROUP_24 = 24 # 2048-bit MODP w/256-bit prime + GROUP_25 = 25 # 192-bit ECP + GROUP_26 = 26 # 224-bit ECP + GROUP_27 = 27 # brainpoolP224r1 + GROUP_28 = 28 # brainpoolP256r1 + GROUP_29 = 29 # brainpoolP384r1 + GROUP_30 = 30 # brainpoolP512r1 + GROUP_31 = 31 # Curve25519 + GROUP_32 = 32 # Curve448 + GROUP_33 = 33 # GOST3410_2012_256 + GROUP_34 = 34 # GOST3410_2012_512 + + +# +# L2VPN +# + + +@strawberry.enum +class L2VPNTypeEnum(Enum): + TYPE_VPLS = 'vpls' + TYPE_VPWS = 'vpws' + TYPE_EPL = 'epl' + TYPE_EVPL = 'evpl' + TYPE_EPLAN = 'ep-lan' + TYPE_EVPLAN = 'evp-lan' + TYPE_EPTREE = 'ep-tree' + TYPE_EVPTREE = 'evp-tree' + TYPE_VXLAN = 'vxlan' + TYPE_VXLAN_EVPN = 'vxlan-evpn' + TYPE_MPLS_EVPN = 'mpls-evpn' + TYPE_PBB_EVPN = 'pbb-evpn' + TYPE_EVPN_VPWS = 'evpn-vpws' diff --git a/netbox/vpn/graphql/filters.py b/netbox/vpn/graphql/filters.py index 34594458b9f..f910ce18ab1 100644 --- a/netbox/vpn/graphql/filters.py +++ b/netbox/vpn/graphql/filters.py @@ -1,7 +1,34 @@ +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry.scalars import ID import strawberry_django +from strawberry_django import ( + FilterLookup, +) +from extras.graphql.filter_mixins import * +from netbox.graphql.filter_mixins import * +from core.graphql.filter_mixins import * +from tenancy.graphql.filter_mixins import * +# from .filter_mixins import * + +from vpn import models + +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.enums import * + from wireless.graphql.enums import * + from core.graphql.filter_lookups import * + from core.graphql.filters import * + from extras.graphql.filters import * + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from tenancy.graphql.filters import * + from wireless.graphql.filters import * + from users.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin -from vpn import filtersets, models __all__ = ( 'TunnelGroupFilter', @@ -18,60 +45,151 @@ @strawberry_django.filter(models.TunnelGroup, lookups=True) -@autotype_decorator(filtersets.TunnelGroupFilterSet) -class TunnelGroupFilter(BaseFilterMixin): +class TunnelGroupFilter(OrganizationalModelFilterMixin): pass @strawberry_django.filter(models.TunnelTermination, lookups=True) -@autotype_decorator(filtersets.TunnelTerminationFilterSet) -class TunnelTerminationFilter(BaseFilterMixin): - pass +class TunnelTerminationFilter( + BaseObjectTypeFilterMixin, CustomFieldsFilterMixin, TagsFilterMixin, ChangeLogFilterMixin +): + tunnel: Annotated['TunnelFilter', strawberry.lazy('vpn.graphql.filters')] | None = strawberry_django.filter_field() + tunnel_id: ID | None = strawberry_django.filter_field() + role: Annotated['TunnelTerminationRoleEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + termination_type: Annotated['TunnelTerminationTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + termination_type_id: ID | None = strawberry_django.filter_field() + termination_id: ID | None = strawberry_django.filter_field() + outside_ip: Annotated['IPAddressFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + outside_ip_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.Tunnel, lookups=True) -@autotype_decorator(filtersets.TunnelFilterSet) -class TunnelFilter(BaseFilterMixin): - pass +class TunnelFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['TunnelStatusEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + group: Annotated['TunnelGroupFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + encapsulation: Annotated['TunnelEncapsulationEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + ipsec_profile: Annotated['IPSecProfileFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() + tunnel_id: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.IKEProposal, lookups=True) -@autotype_decorator(filtersets.IKEProposalFilterSet) -class IKEProposalFilter(BaseFilterMixin): - pass +class IKEProposalFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + authentication_method: Annotated['AuthenticationMethodEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + sa_lifetime: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.IKEPolicy, lookups=True) -@autotype_decorator(filtersets.IKEPolicyFilterSet) -class IKEPolicyFilter(BaseFilterMixin): - pass +class IKEPolicyFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + version: Annotated['IKEVersionEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + mode: Annotated['IKEModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + proposals: Annotated['IKEProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + preshared_key: FilterLookup[str] | None = strawberry_django.filter_field() @strawberry_django.filter(models.IPSecProposal, lookups=True) -@autotype_decorator(filtersets.IPSecProposalFilterSet) -class IPSecProposalFilter(BaseFilterMixin): - pass +class IPSecProposalFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + encryption_algorithm: Annotated['EncryptionAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + authentication_algorithm: Annotated['AuthenticationAlgorithmEnum', strawberry.lazy('vpn.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + sa_lifetime_seconds: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + sa_lifetime_data: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) @strawberry_django.filter(models.IPSecPolicy, lookups=True) -@autotype_decorator(filtersets.IPSecPolicyFilterSet) -class IPSecPolicyFilter(BaseFilterMixin): - pass +class IPSecPolicyFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + proposals: Annotated['IPSecProposalFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + pfs_group: Annotated['DHGroupEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() @strawberry_django.filter(models.IPSecProfile, lookups=True) -@autotype_decorator(filtersets.IPSecProfileFilterSet) -class IPSecProfileFilter(BaseFilterMixin): - pass +class IPSecProfileFilter(PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + mode: Annotated['IPSecModeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + ike_policy: Annotated['IKEPolicyFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + ike_policy_id: ID | None = strawberry_django.filter_field() + ipsec_policy: Annotated['IPSecPolicyFilter', strawberry.lazy('vpn.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + ipsec_policy_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.L2VPN, lookups=True) -@autotype_decorator(filtersets.L2VPNFilterSet) -class L2VPNFilter(BaseFilterMixin): - pass +class L2VPNFilter(ContactFilterMixin, PrimaryModelFilterMixin): + name: FilterLookup[str] | None = strawberry_django.filter_field() + slug: FilterLookup[str] | None = strawberry_django.filter_field() + type: Annotated['L2VPNTypeEnum', strawberry.lazy('vpn.graphql.enums')] | None = strawberry_django.filter_field() + identifier: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) + import_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + export_targets: Annotated['RouteTargetFilter', strawberry.lazy('ipam.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.L2VPNTermination, lookups=True) -@autotype_decorator(filtersets.L2VPNTerminationFilterSet) -class L2VPNTerminationFilter(BaseFilterMixin): - pass +class L2VPNTerminationFilter(NetBoxModelFilterMixin): + l2vpn: Annotated['L2VPNFilter', strawberry.lazy('vpn.graphql.filters')] | None = strawberry_django.filter_field() + l2vpn_id: ID | None = strawberry_django.filter_field() + assigned_object_type: Annotated['ContentTypeFilter', strawberry.lazy('core.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + assigned_object_id: Annotated['IntegerLookup', strawberry.lazy('core.graphql.filter_lookups')] | None = ( + strawberry_django.filter_field() + ) diff --git a/netbox/wireless/graphql/enums.py b/netbox/wireless/graphql/enums.py new file mode 100644 index 00000000000..1526b2ff3b9 --- /dev/null +++ b/netbox/wireless/graphql/enums.py @@ -0,0 +1,249 @@ +from enum import Enum +import strawberry + +__all__ = [ + 'WirelessRoleEnum', + 'WirelessLANStatusEnum', + 'WirelessChannelEnum', + 'WirelessAuthTypeEnum', + 'WirelessAuthCipherEnum', +] + + +@strawberry.enum +class WirelessRoleEnum(Enum): + ROLE_AP = 'ap' + ROLE_STATION = 'station' + + +@strawberry.enum +class WirelessLANStatusEnum(Enum): + key = 'WirelessLAN.status' + + STATUS_ACTIVE = 'active' + STATUS_RESERVED = 'reserved' + STATUS_DISABLED = 'disabled' + STATUS_DEPRECATED = 'deprecated' + + +@strawberry.enum +class WirelessChannelEnum(Enum): + # 2.4 GHz + CHANNEL_24G_1 = '2.4g-1-2412-22' + CHANNEL_24G_2 = '2.4g-2-2417-22' + CHANNEL_24G_3 = '2.4g-3-2422-22' + CHANNEL_24G_4 = '2.4g-4-2427-22' + CHANNEL_24G_5 = '2.4g-5-2432-22' + CHANNEL_24G_6 = '2.4g-6-2437-22' + CHANNEL_24G_7 = '2.4g-7-2442-22' + CHANNEL_24G_8 = '2.4g-8-2447-22' + CHANNEL_24G_9 = '2.4g-9-2452-22' + CHANNEL_24G_10 = '2.4g-10-2457-22' + CHANNEL_24G_11 = '2.4g-11-2462-22' + CHANNEL_24G_12 = '2.4g-12-2467-22' + CHANNEL_24G_13 = '2.4g-13-2472-22' + + # 5 GHz + CHANNEL_5G_32 = '5g-32-5160-20' + CHANNEL_5G_34 = '5g-34-5170-40' + CHANNEL_5G_36 = '5g-36-5180-20' + CHANNEL_5G_38 = '5g-38-5190-40' + CHANNEL_5G_40 = '5g-40-5200-20' + CHANNEL_5G_42 = '5g-42-5210-80' + CHANNEL_5G_44 = '5g-44-5220-20' + CHANNEL_5G_46 = '5g-46-5230-40' + CHANNEL_5G_48 = '5g-48-5240-20' + CHANNEL_5G_50 = '5g-50-5250-160' + CHANNEL_5G_52 = '5g-52-5260-20' + CHANNEL_5G_54 = '5g-54-5270-40' + CHANNEL_5G_56 = '5g-56-5280-20' + CHANNEL_5G_58 = '5g-58-5290-80' + CHANNEL_5G_60 = '5g-60-5300-20' + CHANNEL_5G_62 = '5g-62-5310-40' + CHANNEL_5G_64 = '5g-64-5320-20' + CHANNEL_5G_100 = '5g-100-5500-20' + CHANNEL_5G_102 = '5g-102-5510-40' + CHANNEL_5G_104 = '5g-104-5520-20' + CHANNEL_5G_106 = '5g-106-5530-80' + CHANNEL_5G_108 = '5g-108-5540-20' + CHANNEL_5G_110 = '5g-110-5550-40' + CHANNEL_5G_112 = '5g-112-5560-20' + CHANNEL_5G_114 = '5g-114-5570-160' + CHANNEL_5G_116 = '5g-116-5580-20' + CHANNEL_5G_118 = '5g-118-5590-40' + CHANNEL_5G_120 = '5g-120-5600-20' + CHANNEL_5G_122 = '5g-122-5610-80' + CHANNEL_5G_124 = '5g-124-5620-20' + CHANNEL_5G_126 = '5g-126-5630-40' + CHANNEL_5G_128 = '5g-128-5640-20' + CHANNEL_5G_132 = '5g-132-5660-20' + CHANNEL_5G_134 = '5g-134-5670-40' + CHANNEL_5G_136 = '5g-136-5680-20' + CHANNEL_5G_138 = '5g-138-5690-80' + CHANNEL_5G_140 = '5g-140-5700-20' + CHANNEL_5G_142 = '5g-142-5710-40' + CHANNEL_5G_144 = '5g-144-5720-20' + CHANNEL_5G_149 = '5g-149-5745-20' + CHANNEL_5G_151 = '5g-151-5755-40' + CHANNEL_5G_153 = '5g-153-5765-20' + CHANNEL_5G_155 = '5g-155-5775-80' + CHANNEL_5G_157 = '5g-157-5785-20' + CHANNEL_5G_159 = '5g-159-5795-40' + CHANNEL_5G_161 = '5g-161-5805-20' + CHANNEL_5G_163 = '5g-163-5815-160' + CHANNEL_5G_165 = '5g-165-5825-20' + CHANNEL_5G_167 = '5g-167-5835-40' + CHANNEL_5G_169 = '5g-169-5845-20' + CHANNEL_5G_171 = '5g-171-5855-80' + CHANNEL_5G_173 = '5g-173-5865-20' + CHANNEL_5G_175 = '5g-175-5875-40' + CHANNEL_5G_177 = '5g-177-5885-20' + + # 6 GHz + CHANNEL_6G_1 = '6g-1-5955-20' + CHANNEL_6G_3 = '6g-3-5965-40' + CHANNEL_6G_5 = '6g-5-5975-20' + CHANNEL_6G_7 = '6g-7-5985-80' + CHANNEL_6G_9 = '6g-9-5995-20' + CHANNEL_6G_11 = '6g-11-6005-40' + CHANNEL_6G_13 = '6g-13-6015-20' + CHANNEL_6G_15 = '6g-15-6025-160' + CHANNEL_6G_17 = '6g-17-6035-20' + CHANNEL_6G_19 = '6g-19-6045-40' + CHANNEL_6G_21 = '6g-21-6055-20' + CHANNEL_6G_23 = '6g-23-6065-80' + CHANNEL_6G_25 = '6g-25-6075-20' + CHANNEL_6G_27 = '6g-27-6085-40' + CHANNEL_6G_29 = '6g-29-6095-20' + CHANNEL_6G_31 = '6g-31-6105-320' + CHANNEL_6G_33 = '6g-33-6115-20' + CHANNEL_6G_35 = '6g-35-6125-40' + CHANNEL_6G_37 = '6g-37-6135-20' + CHANNEL_6G_39 = '6g-39-6145-80' + CHANNEL_6G_41 = '6g-41-6155-20' + CHANNEL_6G_43 = '6g-43-6165-40' + CHANNEL_6G_45 = '6g-45-6175-20' + CHANNEL_6G_47 = '6g-47-6185-160' + CHANNEL_6G_49 = '6g-49-6195-20' + CHANNEL_6G_51 = '6g-51-6205-40' + CHANNEL_6G_53 = '6g-53-6215-20' + CHANNEL_6G_55 = '6g-55-6225-80' + CHANNEL_6G_57 = '6g-57-6235-20' + CHANNEL_6G_59 = '6g-59-6245-40' + CHANNEL_6G_61 = '6g-61-6255-20' + CHANNEL_6G_65 = '6g-65-6275-20' + CHANNEL_6G_67 = '6g-67-6285-40' + CHANNEL_6G_69 = '6g-69-6295-20' + CHANNEL_6G_71 = '6g-71-6305-80' + CHANNEL_6G_73 = '6g-73-6315-20' + CHANNEL_6G_75 = '6g-75-6325-40' + CHANNEL_6G_77 = '6g-77-6335-20' + CHANNEL_6G_79 = '6g-79-6345-160' + CHANNEL_6G_81 = '6g-81-6355-20' + CHANNEL_6G_83 = '6g-83-6365-40' + CHANNEL_6G_85 = '6g-85-6375-20' + CHANNEL_6G_87 = '6g-87-6385-80' + CHANNEL_6G_89 = '6g-89-6395-20' + CHANNEL_6G_91 = '6g-91-6405-40' + CHANNEL_6G_93 = '6g-93-6415-20' + CHANNEL_6G_95 = '6g-95-6425-320' + CHANNEL_6G_97 = '6g-97-6435-20' + CHANNEL_6G_99 = '6g-99-6445-40' + CHANNEL_6G_101 = '6g-101-6455-20' + CHANNEL_6G_103 = '6g-103-6465-80' + CHANNEL_6G_105 = '6g-105-6475-20' + CHANNEL_6G_107 = '6g-107-6485-40' + CHANNEL_6G_109 = '6g-109-6495-20' + CHANNEL_6G_111 = '6g-111-6505-160' + CHANNEL_6G_113 = '6g-113-6515-20' + CHANNEL_6G_115 = '6g-115-6525-40' + CHANNEL_6G_117 = '6g-117-6535-20' + CHANNEL_6G_119 = '6g-119-6545-80' + CHANNEL_6G_121 = '6g-121-6555-20' + CHANNEL_6G_123 = '6g-123-6565-40' + CHANNEL_6G_125 = '6g-125-6575-20' + CHANNEL_6G_129 = '6g-129-6595-20' + CHANNEL_6G_131 = '6g-131-6605-40' + CHANNEL_6G_133 = '6g-133-6615-20' + CHANNEL_6G_135 = '6g-135-6625-80' + CHANNEL_6G_137 = '6g-137-6635-20' + CHANNEL_6G_139 = '6g-139-6645-40' + CHANNEL_6G_141 = '6g-141-6655-20' + CHANNEL_6G_143 = '6g-143-6665-160' + CHANNEL_6G_145 = '6g-145-6675-20' + CHANNEL_6G_147 = '6g-147-6685-40' + CHANNEL_6G_149 = '6g-149-6695-20' + CHANNEL_6G_151 = '6g-151-6705-80' + CHANNEL_6G_153 = '6g-153-6715-20' + CHANNEL_6G_155 = '6g-155-6725-40' + CHANNEL_6G_157 = '6g-157-6735-20' + CHANNEL_6G_159 = '6g-159-6745-320' + CHANNEL_6G_161 = '6g-161-6755-20' + CHANNEL_6G_163 = '6g-163-6765-40' + CHANNEL_6G_165 = '6g-165-6775-20' + CHANNEL_6G_167 = '6g-167-6785-80' + CHANNEL_6G_169 = '6g-169-6795-20' + CHANNEL_6G_171 = '6g-171-6805-40' + CHANNEL_6G_173 = '6g-173-6815-20' + CHANNEL_6G_175 = '6g-175-6825-160' + CHANNEL_6G_177 = '6g-177-6835-20' + CHANNEL_6G_179 = '6g-179-6845-40' + CHANNEL_6G_181 = '6g-181-6855-20' + CHANNEL_6G_183 = '6g-183-6865-80' + CHANNEL_6G_185 = '6g-185-6875-20' + CHANNEL_6G_187 = '6g-187-6885-40' + CHANNEL_6G_189 = '6g-189-6895-20' + CHANNEL_6G_193 = '6g-193-6915-20' + CHANNEL_6G_195 = '6g-195-6925-40' + CHANNEL_6G_197 = '6g-197-6935-20' + CHANNEL_6G_199 = '6g-199-6945-80' + CHANNEL_6G_201 = '6g-201-6955-20' + CHANNEL_6G_203 = '6g-203-6965-40' + CHANNEL_6G_205 = '6g-205-6975-20' + CHANNEL_6G_207 = '6g-207-6985-160' + CHANNEL_6G_209 = '6g-209-6995-20' + CHANNEL_6G_211 = '6g-211-7005-40' + CHANNEL_6G_213 = '6g-213-7015-20' + CHANNEL_6G_215 = '6g-215-7025-80' + CHANNEL_6G_217 = '6g-217-7035-20' + CHANNEL_6G_219 = '6g-219-7045-40' + CHANNEL_6G_221 = '6g-221-7055-20' + CHANNEL_6G_225 = '6g-225-7075-20' + CHANNEL_6G_227 = '6g-227-7085-40' + CHANNEL_6G_229 = '6g-229-7095-20' + CHANNEL_6G_233 = '6g-233-7115-20' + + # 60 GHz + CHANNEL_60G_1 = '60g-1-58320-2160' + CHANNEL_60G_2 = '60g-2-60480-2160' + CHANNEL_60G_3 = '60g-3-62640-2160' + CHANNEL_60G_4 = '60g-4-64800-2160' + CHANNEL_60G_5 = '60g-5-66960-2160' + CHANNEL_60G_6 = '60g-6-69120-2160' + CHANNEL_60G_9 = '60g-9-59400-4320' + CHANNEL_60G_10 = '60g-10-61560-4320' + CHANNEL_60G_11 = '60g-11-63720-4320' + CHANNEL_60G_12 = '60g-12-65880-4320' + CHANNEL_60G_13 = '60g-13-68040-4320' + CHANNEL_60G_17 = '60g-17-60480-6480' + CHANNEL_60G_18 = '60g-18-62640-6480' + CHANNEL_60G_19 = '60g-19-64800-6480' + CHANNEL_60G_20 = '60g-20-66960-6480' + CHANNEL_60G_25 = '60g-25-61560-6480' + CHANNEL_60G_26 = '60g-26-63720-6480' + CHANNEL_60G_27 = '60g-27-65880-6480' + + +@strawberry.enum +class WirelessAuthTypeEnum(Enum): + TYPE_OPEN = 'open' + TYPE_WEP = 'wep' + TYPE_WPA_PERSONAL = 'wpa-personal' + TYPE_WPA_ENTERPRISE = 'wpa-enterprise' + + +@strawberry.enum +class WirelessAuthCipherEnum(Enum): + CIPHER_AUTO = 'auto' + CIPHER_TKIP = 'tkip' + CIPHER_AES = 'aes' diff --git a/netbox/wireless/graphql/filter_mixins.py b/netbox/wireless/graphql/filter_mixins.py new file mode 100644 index 00000000000..2028bba4ed8 --- /dev/null +++ b/netbox/wireless/graphql/filter_mixins.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass +from typing import Annotated, TYPE_CHECKING +import strawberry +import strawberry_django +from strawberry_django import FilterLookup + +from core.graphql.filter_mixins import BaseFilterMixin + +if TYPE_CHECKING: + from .filters import * + from .enums import * + +__all__ = ['WirelessAuthenticationBaseFilterMixin'] + + +@dataclass +class WirelessAuthenticationBaseFilterMixin(BaseFilterMixin): + auth_type: Annotated['WirelessAuthTypeEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + auth_cipher: Annotated['WirelessAuthCipherEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + auth_psk: FilterLookup[str] | None = strawberry_django.filter_field() diff --git a/netbox/wireless/graphql/filters.py b/netbox/wireless/graphql/filters.py index 47d04bedcbc..890991a4dfc 100644 --- a/netbox/wireless/graphql/filters.py +++ b/netbox/wireless/graphql/filters.py @@ -1,7 +1,32 @@ +from typing import Annotated, TYPE_CHECKING +import strawberry +from strawberry.scalars import ID import strawberry_django +from strawberry_django import ( + FilterLookup, +) +from extras.graphql.filter_mixins import * +from netbox.graphql.filter_mixins import * +from core.graphql.filter_mixins import * +from tenancy.graphql.filter_mixins import * +from .filter_mixins import * + +from wireless import models -from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin -from wireless import filtersets, models +if TYPE_CHECKING: + from .enums import * + from netbox.graphql.enums import * + from wireless.graphql.enums import * + from core.graphql.filter_lookups import * + from extras.graphql.filters import * + from circuits.graphql.filters import * + from dcim.graphql.filters import * + from ipam.graphql.filters import * + from tenancy.graphql.filters import * + from wireless.graphql.filters import * + from users.graphql.filters import * + from virtualization.graphql.filters import * + from vpn.graphql.filters import * __all__ = ( 'WirelessLANGroupFilter', @@ -11,18 +36,40 @@ @strawberry_django.filter(models.WirelessLANGroup, lookups=True) -@autotype_decorator(filtersets.WirelessLANGroupFilterSet) -class WirelessLANGroupFilter(BaseFilterMixin): +class WirelessLANGroupFilter(NestedGroupModelFilterMixin): pass @strawberry_django.filter(models.WirelessLAN, lookups=True) -@autotype_decorator(filtersets.WirelessLANFilterSet) -class WirelessLANFilter(BaseFilterMixin): - pass +class WirelessLANFilter(WirelessAuthenticationBaseFilterMixin, PrimaryModelFilterMixin): + ssid: FilterLookup[str] | None = strawberry_django.filter_field() + group: Annotated['WirelessLANGroupFilter', strawberry.lazy('wireless.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + group_id: ID | None = strawberry_django.filter_field() + vlan: Annotated['VLANFilter', strawberry.lazy('ipam.graphql.filters')] | None = strawberry_django.filter_field() + vlan_id: ID | None = strawberry_django.filter_field() + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field() @strawberry_django.filter(models.WirelessLink, lookups=True) -@autotype_decorator(filtersets.WirelessLinkFilterSet) -class WirelessLinkFilter(BaseFilterMixin): - pass +class WirelessLinkFilter(WirelessAuthenticationBaseFilterMixin, DistanceFilterMixin, PrimaryModelFilterMixin): + interface_a: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + interface_a_id: ID | None = strawberry_django.filter_field() + interface_b: Annotated['InterfaceFilter', strawberry.lazy('dcim.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + interface_b_id: ID | None = strawberry_django.filter_field() + ssid: FilterLookup[str] | None = strawberry_django.filter_field() + status: Annotated['WirelessLANStatusEnum', strawberry.lazy('wireless.graphql.enums')] | None = ( + strawberry_django.filter_field() + ) + tenant: Annotated['TenantFilter', strawberry.lazy('tenancy.graphql.filters')] | None = ( + strawberry_django.filter_field() + ) + tenant_id: ID | None = strawberry_django.filter_field()