Răsfoiți Sursa

9856 Replace graphene with Strawberry (#15141)

* 9856 base strawberry integration

* 9856 user and group

* 9856 user and circuits base

* 9856 extras and mixins

* 9856 fk

* 9856 update strawberry version

* 9856 update imports

* 9856 compatability fixes

* 9856 compatability fixes

* 9856 update strawberry types

* 9856 update strawberry types

* 9856 core schema

* 9856 dcim schema

* 9856 extras schema

* 9856 ipam and tenant schema

* 9856 virtualization, vpn, wireless schema

* 9856 fix old decorator

* 9856 cleanup

* 9856 cleanup

* 9856 fixes to circuits type specifiers

* 9856 fixes to circuits type specifiers

* 9856 update types

* 9856 GFK working

* 9856 GFK working

* 9856 _name

* 9856 misc fixes

* 9856 type updates

* 9856 _name to types

* 9856 update types

* 9856 update types

* 9856 update types

* 9856 update types

* 9856 update types

* 9856 update types

* 9856 update types

* 9856 update types

* 9856 update types

* 9856 GraphQLView

* 9856 GraphQLView

* 9856 fix OrganizationalObjectType

* 9856 single item query for schema

* 9856 circuits graphql tests working

* 9856 test fixes

* 9856 test fixes

* 9856 test fixes

* 9856 test fix vpn

* 9856 test fixes

* 9856 test fixes

* 9856 test fixes

* 9856 circuits test sans DjangoModelType

* 9856 core test sans DjangoModelType

* 9856 temp checkin

* 9856 fix extas FK

* 9856 fix tenancy FK

* 9856 fix virtualization FK

* 9856 fix vpn FK

* 9856 fix wireless FK

* 9856 fix ipam FK

* 9856 fix partial dcim FK

* 9856 fix dcim FK

* 9856 fix virtualization FK

* 9856 fix tests / remove debug code

* 9856 fix test imagefield

* 9856 cleanup graphene

* 9856 fix plugin schema

* 9856 fix requirements

* 9856 fix requirements

* 9856 fix docs

* 9856 fix docs

* 9856 temp fix tests

* 9856 first filterset

* 9856 first filterset

* 9856 fix tests

* 9856 fix tests

* 9856 working auto filter generation

* 9856 filter types

* 9856 filter types

* 9856 filter types

* 9856 fix graphiql test

* 9856 fix counter fields and merge feature

* 9856 temp fix tests

* 9856 fix tests

* 9856 fix tenancy, ipam filter definitions

* 9856 cleanup

* 9856 cleanup

* 9856 cleanup

* 9856 review changes

* 9856 review changes

* 9856 review changes

* 9856 fix base-requirements

* 9856 add wrapper to graphiql

* 9856 remove old graphiql debug toolbar

* 9856 review changes

* 9856 update strawberry

* 9856 remove superfluous check

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
Arthur Hanson 1 an în urmă
părinte
comite
45c99e4477
70 a modificat fișierele cu 5437 adăugiri și 2216 ștergeri
  1. 9 10
      base_requirements.txt
  2. 22 25
      docs/plugins/development/graphql-api.md
  3. 50 0
      netbox/circuits/graphql/filters.py
  4. 35 36
      netbox/circuits/graphql/schema.py
  5. 84 34
      netbox/circuits/graphql/types.py
  6. 21 0
      netbox/core/graphql/filters.py
  7. 14 14
      netbox/core/graphql/schema.py
  8. 22 9
      netbox/core/graphql/types.py
  9. 294 0
      netbox/dcim/graphql/filters.py
  10. 3 76
      netbox/dcim/graphql/gfk_mixins.py
  11. 37 10
      netbox/dcim/graphql/mixins.py
  12. 206 245
      netbox/dcim/graphql/schema.py
  13. 815 306
      netbox/dcim/graphql/types.py
  14. 98 0
      netbox/extras/graphql/filters.py
  15. 32 19
      netbox/extras/graphql/mixins.py
  16. 65 75
      netbox/extras/graphql/schema.py
  17. 167 63
      netbox/extras/graphql/types.py
  18. 119 0
      netbox/ipam/graphql/filters.py
  19. 0 95
      netbox/ipam/graphql/gfk_mixins.py
  20. 12 9
      netbox/ipam/graphql/mixins.py
  21. 85 99
      netbox/ipam/graphql/schema.py
  22. 278 101
      netbox/ipam/graphql/types.py
  23. 0 69
      netbox/netbox/graphql/__init__.py
  24. 0 70
      netbox/netbox/graphql/fields.py
  25. 198 0
      netbox/netbox/graphql/filter_mixins.py
  26. 8 21
      netbox/netbox/graphql/scalars.py
  27. 11 3
      netbox/netbox/graphql/schema.py
  28. 33 32
      netbox/netbox/graphql/types.py
  29. 0 25
      netbox/netbox/graphql/utils.py
  30. 18 8
      netbox/netbox/graphql/views.py
  31. 1 1
      netbox/netbox/plugins/registration.py
  32. 9 14
      netbox/netbox/settings.py
  33. 18 13
      netbox/netbox/tests/dummy_plugin/graphql.py
  34. 3 3
      netbox/netbox/urls.py
  35. 48 13
      netbox/project-static/bundle.js
  36. 0 0
      netbox/project-static/dist/graphiql.css
  37. 8 0
      netbox/project-static/dist/graphiql.min.css
  38. 18 0
      netbox/project-static/dist/graphiql.min.js
  39. 0 0
      netbox/project-static/dist/index.umd.js
  40. 2 0
      netbox/project-static/dist/js.cookie.min.js
  41. 1 0
      netbox/project-static/dist/plugin-explorer-style.css
  42. 267 0
      netbox/project-static/dist/react-dom.production.min.js
  43. 31 0
      netbox/project-static/dist/react.production.min.js
  44. 0 3
      netbox/project-static/netbox-graphiql/graphiql.scss
  45. 0 17
      netbox/project-static/netbox-graphiql/index.ts
  46. 8 7
      netbox/project-static/netbox-graphiql/package.json
  47. 1286 0
      netbox/project-static/yarn.lock
  48. 117 23
      netbox/templates/graphiql.html
  49. 1 2
      netbox/tenancy/filtersets.py
  50. 49 0
      netbox/tenancy/graphql/filters.py
  51. 17 0
      netbox/tenancy/graphql/mixins.py
  52. 35 39
      netbox/tenancy/graphql/schema.py
  53. 146 36
      netbox/tenancy/graphql/types.py
  54. 1 0
      netbox/users/filtersets.py
  55. 23 0
      netbox/users/graphql/filters.py
  56. 16 16
      netbox/users/graphql/schema.py
  57. 26 25
      netbox/users/graphql/types.py
  58. 0 252
      netbox/utilities/graphql_optimizer.py
  59. 39 30
      netbox/utilities/testing/api.py
  60. 49 0
      netbox/virtualization/graphql/filters.py
  61. 36 40
      netbox/virtualization/graphql/schema.py
  62. 113 41
      netbox/virtualization/graphql/types.py
  63. 77 0
      netbox/vpn/graphql/filters.py
  64. 0 30
      netbox/vpn/graphql/gfk_mixins.py
  65. 55 64
      netbox/vpn/graphql/schema.py
  66. 115 45
      netbox/vpn/graphql/types.py
  67. 28 0
      netbox/wireless/graphql/filters.py
  68. 19 20
      netbox/wireless/graphql/schema.py
  69. 37 26
      netbox/wireless/graphql/types.py
  70. 2 2
      requirements.txt

+ 9 - 10
base_requirements.txt

@@ -14,10 +14,6 @@ django-debug-toolbar
 # https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
 # https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
 django-filter
 django-filter
 
 
-# Django debug toolbar extension with support for GraphiQL
-# https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst
-django-graphiql-debug-toolbar
-
 # HTMX utilities for Django
 # HTMX utilities for Django
 # https://django-htmx.readthedocs.io/en/latest/changelog.html
 # https://django-htmx.readthedocs.io/en/latest/changelog.html
 django-htmx
 django-htmx
@@ -75,11 +71,6 @@ drf-spectacular-sidecar
 # https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
 # https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
 feedparser
 feedparser
 
 
-# Django wrapper for Graphene (GraphQL support)
-# https://github.com/graphql-python/graphene-django/releases
-# Pinned to v3.0.0 for GraphiQL UI issue (see #12762)
-graphene_django==3.0.0
-
 # WSGI HTTP server
 # WSGI HTTP server
 # https://docs.gunicorn.org/en/latest/news.html
 # https://docs.gunicorn.org/en/latest/news.html
 gunicorn
 gunicorn
@@ -136,8 +127,16 @@ social-auth-core
 # https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md
 # https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md
 social-auth-app-django
 social-auth-app-django
 
 
+# Strawberry GraphQL
+# https://github.com/strawberry-graphql/strawberry/blob/main/CHANGELOG.md
+strawberry-graphql
+
+# Strawberry GraphQL Django extension
+# https://github.com/strawberry-graphql/strawberry-django/blob/main/CHANGELOG.md
+strawberry-graphql-django
+
 # SVG image rendering (used for rack elevations)
 # SVG image rendering (used for rack elevations)
-# hhttps://github.com/mozman/svgwrite/blob/master/NEWS.rst
+# https://github.com/mozman/svgwrite/blob/master/NEWS.rst
 svgwrite
 svgwrite
 
 
 # Tabular dataset library (for table-based exports)
 # Tabular dataset library (for table-based exports)

+ 22 - 25
docs/plugins/development/graphql-api.md

@@ -8,23 +8,32 @@ A plugin can extend NetBox's GraphQL API by registering its own schema class. By
 
 
 ```python
 ```python
 # graphql.py
 # graphql.py
-import graphene
-from netbox.graphql.types import NetBoxObjectType
-from netbox.graphql.fields import ObjectField, ObjectListField
-from . import filtersets, models
+from typing import List
+import strawberry
+import strawberry_django
 
 
-class MyModelType(NetBoxObjectType):
+from . import models
 
 
-    class Meta:
-        model = models.MyModel
-        fields = '__all__'
-        filterset_class = filtersets.MyModelFilterSet
 
 
-class MyQuery(graphene.ObjectType):
-    mymodel = ObjectField(MyModelType)
-    mymodel_list = ObjectListField(MyModelType)
+@strawberry_django.type(
+    models.MyModel,
+    fields='__all__',
+)
+class MyModelType:
+    pass
 
 
-schema = MyQuery
+
+@strawberry.type
+class MyQuery:
+    @strawberry.field
+    def dummymodel(self, id: int) -> DummyModelType:
+        return None
+    dummymodel_list: List[DummyModelType] = strawberry_django.field()
+
+
+schema = [
+    MyQuery,
+]
 ```
 ```
 
 
 ## GraphQL Objects
 ## GraphQL Objects
@@ -38,15 +47,3 @@ NetBox provides two object type classes for use by plugins.
 ::: netbox.graphql.types.NetBoxObjectType
 ::: netbox.graphql.types.NetBoxObjectType
     options:
     options:
       members: false
       members: false
-
-## GraphQL Fields
-
-NetBox provides two field classes for use by plugins.
-
-::: netbox.graphql.fields.ObjectField
-    options:
-      members: false
-
-::: netbox.graphql.fields.ObjectListField
-    options:
-      members: false

+ 50 - 0
netbox/circuits/graphql/filters.py

@@ -0,0 +1,50 @@
+import strawberry
+import strawberry_django
+from circuits import filtersets, models
+
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+
+__all__ = (
+    'CircuitTerminationFilter',
+    'CircuitFilter',
+    'CircuitTypeFilter',
+    'ProviderFilter',
+    'ProviderAccountFilter',
+    'ProviderNetworkFilter',
+)
+
+
+@strawberry_django.filter(models.CircuitTermination, lookups=True)
+@autotype_decorator(filtersets.CircuitTerminationFilterSet)
+class CircuitTerminationFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Circuit, lookups=True)
+@autotype_decorator(filtersets.CircuitFilterSet)
+class CircuitFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.CircuitType, lookups=True)
+@autotype_decorator(filtersets.CircuitTypeFilterSet)
+class CircuitTypeFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Provider, lookups=True)
+@autotype_decorator(filtersets.ProviderFilterSet)
+class ProviderFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ProviderAccount, lookups=True)
+@autotype_decorator(filtersets.ProviderAccountFilterSet)
+class ProviderAccountFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ProviderNetwork, lookups=True)
+@autotype_decorator(filtersets.ProviderNetworkFilterSet)
+class ProviderNetworkFilter(BaseFilterMixin):
+    pass

+ 35 - 36
netbox/circuits/graphql/schema.py

@@ -1,41 +1,40 @@
-import graphene
+from typing import List
+
+import strawberry
+import strawberry_django
 
 
 from circuits import models
 from circuits import models
-from netbox.graphql.fields import ObjectField, ObjectListField
 from .types import *
 from .types import *
-from utilities.graphql_optimizer import gql_query_optimizer
-
-
-class CircuitsQuery(graphene.ObjectType):
-    circuit = ObjectField(CircuitType)
-    circuit_list = ObjectListField(CircuitType)
-
-    def resolve_circuit_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Circuit.objects.all(), info)
-
-    circuit_termination = ObjectField(CircuitTerminationType)
-    circuit_termination_list = ObjectListField(CircuitTerminationType)
-
-    def resolve_circuit_termination_list(root, info, **kwargs):
-        return gql_query_optimizer(models.CircuitTermination.objects.all(), info)
-
-    circuit_type = ObjectField(CircuitTypeType)
-    circuit_type_list = ObjectListField(CircuitTypeType)
-
-    def resolve_circuit_type_list(root, info, **kwargs):
-        return gql_query_optimizer(models.CircuitType.objects.all(), info)
-
-    provider = ObjectField(ProviderType)
-    provider_list = ObjectListField(ProviderType)
-
-    def resolve_provider_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Provider.objects.all(), info)
-
-    provider_account = ObjectField(ProviderAccountType)
-    provider_account_list = ObjectListField(ProviderAccountType)
 
 
-    provider_network = ObjectField(ProviderNetworkType)
-    provider_network_list = ObjectListField(ProviderNetworkType)
 
 
-    def resolve_provider_network_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ProviderNetwork.objects.all(), info)
+@strawberry.type
+class CircuitsQuery:
+    @strawberry.field
+    def circuit(self, id: int) -> CircuitType:
+        return models.Circuit.objects.get(pk=id)
+    circuit_list: List[CircuitType] = strawberry_django.field()
+
+    @strawberry.field
+    def circuit_termination(self, id: int) -> CircuitTerminationType:
+        return models.CircuitTermination.objects.get(pk=id)
+    circuit_termination_list: List[CircuitTerminationType] = strawberry_django.field()
+
+    @strawberry.field
+    def circuit_type(self, id: int) -> CircuitTypeType:
+        return models.CircuitType.objects.get(pk=id)
+    circuit_type_list: List[CircuitTypeType] = strawberry_django.field()
+
+    @strawberry.field
+    def provider(self, id: int) -> ProviderType:
+        return models.Provider.objects.get(pk=id)
+    provider_list: List[ProviderType] = strawberry_django.field()
+
+    @strawberry.field
+    def provider_account(self, id: int) -> ProviderAccountType:
+        return models.ProviderAccount.objects.get(pk=id)
+    provider_account_list: List[ProviderAccountType] = strawberry_django.field()
+
+    @strawberry.field
+    def provider_network(self, id: int) -> ProviderNetworkType:
+        return models.ProviderNetwork.objects.get(pk=id)
+    provider_network_list: List[ProviderNetworkType] = strawberry_django.field()

+ 84 - 34
netbox/circuits/graphql/types.py

@@ -1,9 +1,14 @@
-import graphene
+from typing import Annotated, List
 
 
-from circuits import filtersets, models
+import strawberry
+import strawberry_django
+
+from circuits import models
 from dcim.graphql.mixins import CabledObjectMixin
 from dcim.graphql.mixins import CabledObjectMixin
-from extras.graphql.mixins import CustomFieldsMixin, TagsMixin, ContactsMixin
-from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
+from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
+from netbox.graphql.types import NetBoxObjectType, ObjectType, OrganizationalObjectType
+from tenancy.graphql.types import TenantType
+from .filters import *
 
 
 __all__ = (
 __all__ = (
     'CircuitTerminationType',
     'CircuitTerminationType',
@@ -15,48 +20,93 @@ __all__ = (
 )
 )
 
 
 
 
-class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
+@strawberry_django.type(
+    models.Provider,
+    fields='__all__',
+    filters=ProviderFilter
+)
+class ProviderType(NetBoxObjectType, ContactsMixin):
 
 
-    class Meta:
-        model = models.CircuitTermination
-        fields = '__all__'
-        filterset_class = filtersets.CircuitTerminationFilterSet
+    @strawberry_django.field
+    def networks(self) -> List[Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')]]:
+        return self.networks.all()
 
 
+    @strawberry_django.field
+    def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
+        return self.circuits.all()
 
 
-class CircuitType(NetBoxObjectType, ContactsMixin):
-    class Meta:
-        model = models.Circuit
-        fields = '__all__'
-        filterset_class = filtersets.CircuitFilterSet
+    @strawberry_django.field
+    def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.asns.all()
 
 
+    @strawberry_django.field
+    def accounts(self) -> List[Annotated["ProviderAccountType", strawberry.lazy('circuits.graphql.types')]]:
+        return self.accounts.all()
 
 
-class CircuitTypeType(OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.CircuitType
-        fields = '__all__'
-        filterset_class = filtersets.CircuitTypeFilterSet
+@strawberry_django.type(
+    models.ProviderAccount,
+    fields='__all__',
+    filters=ProviderAccountFilter
+)
+class ProviderAccountType(NetBoxObjectType):
+    provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
 
 
+    @strawberry_django.field
+    def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
+        return self.circuits.all()
 
 
-class ProviderType(NetBoxObjectType, ContactsMixin):
 
 
-    class Meta:
-        model = models.Provider
-        fields = '__all__'
-        filterset_class = filtersets.ProviderFilterSet
+@strawberry_django.type(
+    models.ProviderNetwork,
+    fields='__all__',
+    filters=ProviderNetworkFilter
+)
+class ProviderNetworkType(NetBoxObjectType):
+    provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
 
 
+    @strawberry_django.field
+    def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
+        return self.circuit_terminations.all()
 
 
-class ProviderAccountType(NetBoxObjectType):
 
 
-    class Meta:
-        model = models.ProviderAccount
-        fields = '__all__'
-        filterset_class = filtersets.ProviderAccountFilterSet
+@strawberry_django.type(
+    models.CircuitTermination,
+    fields='__all__',
+    filters=CircuitTerminationFilter
+)
+class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
+    circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
+    provider_network: Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')] | None
+    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
 
 
 
 
-class ProviderNetworkType(NetBoxObjectType):
+@strawberry_django.type(
+    models.CircuitType,
+    fields='__all__',
+    filters=CircuitTypeFilter
+)
+class CircuitTypeType(OrganizationalObjectType):
+    color: str
+
+    @strawberry_django.field
+    def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
+        return self.circuits.all()
+
 
 
-    class Meta:
-        model = models.ProviderNetwork
-        fields = '__all__'
-        filterset_class = filtersets.ProviderNetworkFilterSet
+@strawberry_django.type(
+    models.Circuit,
+    fields='__all__',
+    filters=CircuitFilter
+)
+class CircuitType(NetBoxObjectType, ContactsMixin):
+    provider: ProviderType
+    provider_account: ProviderAccountType | None
+    termination_a: CircuitTerminationType | None
+    termination_z: CircuitTerminationType | None
+    type: CircuitTypeType
+    tenant: TenantType | None
+
+    @strawberry_django.field
+    def terminations(self) -> List[CircuitTerminationType]:
+        return self.terminations.all()

+ 21 - 0
netbox/core/graphql/filters.py

@@ -0,0 +1,21 @@
+import strawberry_django
+
+from core import filtersets, models
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+
+__all__ = (
+    'DataFileFilter',
+    'DataSourceFilter',
+)
+
+
+@strawberry_django.filter(models.DataFile, lookups=True)
+@autotype_decorator(filtersets.DataFileFilterSet)
+class DataFileFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.DataSource, lookups=True)
+@autotype_decorator(filtersets.DataSourceFilterSet)
+class DataSourceFilter(BaseFilterMixin):
+    pass

+ 14 - 14
netbox/core/graphql/schema.py

@@ -1,20 +1,20 @@
-import graphene
+from typing import List
+
+import strawberry
+import strawberry_django
 
 
 from core import models
 from core import models
-from netbox.graphql.fields import ObjectField, ObjectListField
 from .types import *
 from .types import *
-from utilities.graphql_optimizer import gql_query_optimizer
-
-
-class CoreQuery(graphene.ObjectType):
-    data_file = ObjectField(DataFileType)
-    data_file_list = ObjectListField(DataFileType)
 
 
-    def resolve_data_file_list(root, info, **kwargs):
-        return gql_query_optimizer(models.DataFile.objects.all(), info)
 
 
-    data_source = ObjectField(DataSourceType)
-    data_source_list = ObjectListField(DataSourceType)
+@strawberry.type
+class CoreQuery:
+    @strawberry.field
+    def data_file(self, id: int) -> DataFileType:
+        return models.DataFile.objects.get(pk=id)
+    data_file_list: List[DataFileType] = strawberry_django.field()
 
 
-    def resolve_data_source_list(root, info, **kwargs):
-        return gql_query_optimizer(models.DataSource.objects.all(), info)
+    @strawberry.field
+    def data_source(self, id: int) -> DataSourceType:
+        return models.DataSource.objects.get(pk=id)
+    data_source_list: List[DataSourceType] = strawberry_django.field()

+ 22 - 9
netbox/core/graphql/types.py

@@ -1,5 +1,11 @@
-from core import filtersets, models
+from typing import Annotated, List
+
+import strawberry
+import strawberry_django
+
+from core import models
 from netbox.graphql.types import BaseObjectType, NetBoxObjectType
 from netbox.graphql.types import BaseObjectType, NetBoxObjectType
+from .filters import *
 
 
 __all__ = (
 __all__ = (
     'DataFileType',
     'DataFileType',
@@ -7,15 +13,22 @@ __all__ = (
 )
 )
 
 
 
 
+@strawberry_django.type(
+    models.DataFile,
+    exclude=['data',],
+    filters=DataFileFilter
+)
 class DataFileType(BaseObjectType):
 class DataFileType(BaseObjectType):
-    class Meta:
-        model = models.DataFile
-        exclude = ('data',)
-        filterset_class = filtersets.DataFileFilterSet
+    source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')]
 
 
 
 
+@strawberry_django.type(
+    models.DataSource,
+    fields='__all__',
+    filters=DataSourceFilter
+)
 class DataSourceType(NetBoxObjectType):
 class DataSourceType(NetBoxObjectType):
-    class Meta:
-        model = models.DataSource
-        fields = '__all__'
-        filterset_class = filtersets.DataSourceFilterSet
+
+    @strawberry_django.field
+    def datafiles(self) -> List[Annotated["DataFileType", strawberry.lazy('core.graphql.types')]]:
+        return self.datafiles.all()

+ 294 - 0
netbox/dcim/graphql/filters.py

@@ -0,0 +1,294 @@
+import strawberry_django
+
+from dcim import filtersets, models
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+
+__all__ = (
+    'CableFilter',
+    'CableTerminationFilter',
+    'ConsolePortFilter',
+    'ConsolePortTemplateFilter',
+    'ConsoleServerPortFilter',
+    'ConsoleServerPortTemplateFilter',
+    'DeviceFilter',
+    'DeviceBayFilter',
+    'DeviceBayTemplateFilter',
+    'InventoryItemTemplateFilter',
+    'DeviceRoleFilter',
+    'DeviceTypeFilter',
+    'FrontPortFilter',
+    'FrontPortTemplateFilter',
+    'InterfaceFilter',
+    'InterfaceTemplateFilter',
+    'InventoryItemFilter',
+    'InventoryItemRoleFilter',
+    'LocationFilter',
+    'ManufacturerFilter',
+    'ModuleFilter',
+    'ModuleBayFilter',
+    'ModuleBayTemplateFilter',
+    'ModuleTypeFilter',
+    'PlatformFilter',
+    'PowerFeedFilter',
+    'PowerOutletFilter',
+    'PowerOutletTemplateFilter',
+    'PowerPanelFilter',
+    'PowerPortFilter',
+    'PowerPortTemplateFilter',
+    'RackFilter',
+    'RackReservationFilter',
+    'RackRoleFilter',
+    'RearPortFilter',
+    'RearPortTemplateFilter',
+    'RegionFilter',
+    'SiteFilter',
+    'SiteGroupFilter',
+    'VirtualChassisFilter',
+    'VirtualDeviceContextFilter',
+)
+
+
+@strawberry_django.filter(models.Cable, lookups=True)
+@autotype_decorator(filtersets.CableFilterSet)
+class CableFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.CableTermination, lookups=True)
+@autotype_decorator(filtersets.CableTerminationFilterSet)
+class CableTerminationFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ConsolePort, lookups=True)
+@autotype_decorator(filtersets.ConsolePortFilterSet)
+class ConsolePortFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ConsolePortTemplate, lookups=True)
+@autotype_decorator(filtersets.ConsolePortTemplateFilterSet)
+class ConsolePortTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ConsoleServerPort, lookups=True)
+@autotype_decorator(filtersets.ConsoleServerPortFilterSet)
+class ConsoleServerPortFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ConsoleServerPortTemplate, lookups=True)
+@autotype_decorator(filtersets.ConsoleServerPortTemplateFilterSet)
+class ConsoleServerPortTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Device, lookups=True)
+@autotype_decorator(filtersets.DeviceFilterSet)
+class DeviceFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.DeviceBay, lookups=True)
+@autotype_decorator(filtersets.DeviceBayFilterSet)
+class DeviceBayFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.DeviceBayTemplate, lookups=True)
+@autotype_decorator(filtersets.DeviceBayTemplateFilterSet)
+class DeviceBayTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.InventoryItemTemplate, lookups=True)
+@autotype_decorator(filtersets.InventoryItemTemplateFilterSet)
+class InventoryItemTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.DeviceRole, lookups=True)
+@autotype_decorator(filtersets.DeviceRoleFilterSet)
+class DeviceRoleFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.DeviceType, lookups=True)
+@autotype_decorator(filtersets.DeviceTypeFilterSet)
+class DeviceTypeFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.FrontPort, lookups=True)
+@autotype_decorator(filtersets.FrontPortFilterSet)
+class FrontPortFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.FrontPortTemplate, lookups=True)
+@autotype_decorator(filtersets.FrontPortTemplateFilterSet)
+class FrontPortTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Interface, lookups=True)
+@autotype_decorator(filtersets.InterfaceFilterSet)
+class InterfaceFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.InterfaceTemplate, lookups=True)
+@autotype_decorator(filtersets.InterfaceTemplateFilterSet)
+class InterfaceTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.InventoryItem, lookups=True)
+@autotype_decorator(filtersets.InventoryItemFilterSet)
+class InventoryItemFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.InventoryItemRole, lookups=True)
+@autotype_decorator(filtersets.InventoryItemRoleFilterSet)
+class InventoryItemRoleFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Location, lookups=True)
+@autotype_decorator(filtersets.LocationFilterSet)
+class LocationFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Manufacturer, lookups=True)
+@autotype_decorator(filtersets.ManufacturerFilterSet)
+class ManufacturerFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Module, lookups=True)
+@autotype_decorator(filtersets.ModuleFilterSet)
+class ModuleFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ModuleBay, lookups=True)
+@autotype_decorator(filtersets.ModuleBayFilterSet)
+class ModuleBayFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ModuleBayTemplate, lookups=True)
+@autotype_decorator(filtersets.ModuleBayTemplateFilterSet)
+class ModuleBayTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ModuleType, lookups=True)
+@autotype_decorator(filtersets.ModuleTypeFilterSet)
+class ModuleTypeFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Platform, lookups=True)
+@autotype_decorator(filtersets.PlatformFilterSet)
+class PlatformFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.PowerFeed, lookups=True)
+@autotype_decorator(filtersets.PowerFeedFilterSet)
+class PowerFeedFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.PowerOutlet, lookups=True)
+@autotype_decorator(filtersets.PowerOutletFilterSet)
+class PowerOutletFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.PowerOutletTemplate, lookups=True)
+@autotype_decorator(filtersets.PowerOutletTemplateFilterSet)
+class PowerOutletTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.PowerPanel, lookups=True)
+@autotype_decorator(filtersets.PowerPanelFilterSet)
+class PowerPanelFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.PowerPort, lookups=True)
+@autotype_decorator(filtersets.PowerPortFilterSet)
+class PowerPortFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.PowerPortTemplate, lookups=True)
+@autotype_decorator(filtersets.PowerPortTemplateFilterSet)
+class PowerPortTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Rack, lookups=True)
+@autotype_decorator(filtersets.RackFilterSet)
+class RackFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.RackReservation, lookups=True)
+@autotype_decorator(filtersets.RackReservationFilterSet)
+class RackReservationFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.RackRole, lookups=True)
+@autotype_decorator(filtersets.RackRoleFilterSet)
+class RackRoleFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.RearPort, lookups=True)
+@autotype_decorator(filtersets.RearPortFilterSet)
+class RearPortFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.RearPortTemplate, lookups=True)
+@autotype_decorator(filtersets.RearPortTemplateFilterSet)
+class RearPortTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Region, lookups=True)
+@autotype_decorator(filtersets.RegionFilterSet)
+class RegionFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Site, lookups=True)
+@autotype_decorator(filtersets.SiteFilterSet)
+class SiteFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.SiteGroup, lookups=True)
+@autotype_decorator(filtersets.SiteGroupFilterSet)
+class SiteGroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.VirtualChassis, lookups=True)
+@autotype_decorator(filtersets.VirtualChassisFilterSet)
+class VirtualChassisFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.VirtualDeviceContext, lookups=True)
+@autotype_decorator(filtersets.VirtualDeviceContextFilterSet)
+class VirtualDeviceContextFilter(BaseFilterMixin):
+    pass

+ 3 - 76
netbox/dcim/graphql/gfk_mixins.py

@@ -1,4 +1,3 @@
-import graphene
 from circuits.graphql.types import CircuitTerminationType, ProviderNetworkType
 from circuits.graphql.types import CircuitTerminationType, ProviderNetworkType
 from circuits.models import CircuitTermination, ProviderNetwork
 from circuits.models import CircuitTermination, ProviderNetwork
 from dcim.graphql.types import (
 from dcim.graphql.types import (
@@ -37,79 +36,7 @@ from dcim.models import (
 )
 )
 
 
 
 
-class LinkPeerType(graphene.Union):
-    class Meta:
-        types = (
-            CircuitTerminationType,
-            ConsolePortType,
-            ConsoleServerPortType,
-            FrontPortType,
-            InterfaceType,
-            PowerFeedType,
-            PowerOutletType,
-            PowerPortType,
-            RearPortType,
-        )
-
-    @classmethod
-    def resolve_type(cls, instance, info):
-        if type(instance) is CircuitTermination:
-            return CircuitTerminationType
-        if type(instance) is ConsolePortType:
-            return ConsolePortType
-        if type(instance) is ConsoleServerPort:
-            return ConsoleServerPortType
-        if type(instance) is FrontPort:
-            return FrontPortType
-        if type(instance) is Interface:
-            return InterfaceType
-        if type(instance) is PowerFeed:
-            return PowerFeedType
-        if type(instance) is PowerOutlet:
-            return PowerOutletType
-        if type(instance) is PowerPort:
-            return PowerPortType
-        if type(instance) is RearPort:
-            return RearPortType
-
-
-class CableTerminationTerminationType(graphene.Union):
-    class Meta:
-        types = (
-            CircuitTerminationType,
-            ConsolePortType,
-            ConsoleServerPortType,
-            FrontPortType,
-            InterfaceType,
-            PowerFeedType,
-            PowerOutletType,
-            PowerPortType,
-            RearPortType,
-        )
-
-    @classmethod
-    def resolve_type(cls, instance, info):
-        if type(instance) is CircuitTermination:
-            return CircuitTerminationType
-        if type(instance) is ConsolePortType:
-            return ConsolePortType
-        if type(instance) is ConsoleServerPort:
-            return ConsoleServerPortType
-        if type(instance) is FrontPort:
-            return FrontPortType
-        if type(instance) is Interface:
-            return InterfaceType
-        if type(instance) is PowerFeed:
-            return PowerFeedType
-        if type(instance) is PowerOutlet:
-            return PowerOutletType
-        if type(instance) is PowerPort:
-            return PowerPortType
-        if type(instance) is RearPort:
-            return RearPortType
-
-
-class InventoryItemTemplateComponentType(graphene.Union):
+class InventoryItemTemplateComponentType:
     class Meta:
     class Meta:
         types = (
         types = (
             ConsolePortTemplateType,
             ConsolePortTemplateType,
@@ -139,7 +66,7 @@ class InventoryItemTemplateComponentType(graphene.Union):
             return RearPortTemplateType
             return RearPortTemplateType
 
 
 
 
-class InventoryItemComponentType(graphene.Union):
+class InventoryItemComponentType:
     class Meta:
     class Meta:
         types = (
         types = (
             ConsolePortType,
             ConsolePortType,
@@ -169,7 +96,7 @@ class InventoryItemComponentType(graphene.Union):
             return RearPortType
             return RearPortType
 
 
 
 
-class ConnectedEndpointType(graphene.Union):
+class ConnectedEndpointType:
     class Meta:
     class Meta:
         types = (
         types = (
             CircuitTerminationType,
             CircuitTerminationType,

+ 37 - 10
netbox/dcim/graphql/mixins.py

@@ -1,20 +1,47 @@
-import graphene
+from typing import Annotated, List, Union
 
 
+import strawberry
+import strawberry_django
+
+__all__ = (
+    'CabledObjectMixin',
+    'PathEndpointMixin',
+)
 
 
-class CabledObjectMixin:
-    link_peers = graphene.List('dcim.graphql.gfk_mixins.LinkPeerType')
 
 
-    def resolve_cable_end(self, info):
-        # Handle empty values
-        return self.cable_end or None
+@strawberry.type
+class CabledObjectMixin:
+    cable: Annotated["CableType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-    def resolve_link_peers(self, info):
+    @strawberry_django.field
+    def link_peers(self) -> List[Annotated[Union[
+        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
+        Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
+    ], strawberry.union("LinkPeerType")]]:
         return self.link_peers
         return self.link_peers
 
 
 
 
+@strawberry.type
 class PathEndpointMixin:
 class PathEndpointMixin:
-    connected_endpoints = graphene.List('dcim.graphql.gfk_mixins.ConnectedEndpointType')
 
 
-    def resolve_connected_endpoints(self, info):
-        # Handle empty values
+    @strawberry_django.field
+    def connected_endpoints(self) -> List[Annotated[Union[
+        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
+        Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')],
+        Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
+    ], strawberry.union("ConnectedEndpointType")]]:
         return self.connected_endpoints or None
         return self.connected_endpoints or None

+ 206 - 245
netbox/dcim/graphql/schema.py

@@ -1,249 +1,210 @@
-import graphene
+from typing import List
 
 
-from netbox.graphql.fields import ObjectField, ObjectListField
-from .types import *
-from dcim import models
-from .types import VirtualDeviceContextType
-from utilities.graphql_optimizer import gql_query_optimizer
-
-
-class DCIMQuery(graphene.ObjectType):
-    cable = ObjectField(CableType)
-    cable_list = ObjectListField(CableType)
-
-    def resolve_cable_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Cable.objects.all(), info)
-
-    console_port = ObjectField(ConsolePortType)
-    console_port_list = ObjectListField(ConsolePortType)
-
-    def resolve_console_port_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ConsolePort.objects.all(), info)
-
-    console_port_template = ObjectField(ConsolePortTemplateType)
-    console_port_template_list = ObjectListField(ConsolePortTemplateType)
-
-    def resolve_console_port_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ConsolePortTemplate.objects.all(), info)
-
-    console_server_port = ObjectField(ConsoleServerPortType)
-    console_server_port_list = ObjectListField(ConsoleServerPortType)
-
-    def resolve_console_server_port_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ConsoleServerPort.objects.all(), info)
-
-    console_server_port_template = ObjectField(ConsoleServerPortTemplateType)
-    console_server_port_template_list = ObjectListField(ConsoleServerPortTemplateType)
-
-    def resolve_console_server_port_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ConsoleServerPortTemplate.objects.all(), info)
-
-    device = ObjectField(DeviceType)
-    device_list = ObjectListField(DeviceType)
-
-    def resolve_device_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Device.objects.all(), info)
-
-    device_bay = ObjectField(DeviceBayType)
-    device_bay_list = ObjectListField(DeviceBayType)
-
-    def resolve_device_bay_list(root, info, **kwargs):
-        return gql_query_optimizer(models.DeviceBay.objects.all(), info)
-
-    device_bay_template = ObjectField(DeviceBayTemplateType)
-    device_bay_template_list = ObjectListField(DeviceBayTemplateType)
-
-    def resolve_device_bay_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.DeviceBayTemplate.objects.all(), info)
-
-    device_role = ObjectField(DeviceRoleType)
-    device_role_list = ObjectListField(DeviceRoleType)
-
-    def resolve_device_role_list(root, info, **kwargs):
-        return gql_query_optimizer(models.DeviceRole.objects.all(), info)
-
-    device_type = ObjectField(DeviceTypeType)
-    device_type_list = ObjectListField(DeviceTypeType)
-
-    def resolve_device_type_list(root, info, **kwargs):
-        return gql_query_optimizer(models.DeviceType.objects.all(), info)
-
-    front_port = ObjectField(FrontPortType)
-    front_port_list = ObjectListField(FrontPortType)
-
-    def resolve_front_port_list(root, info, **kwargs):
-        return gql_query_optimizer(models.FrontPort.objects.all(), info)
-
-    front_port_template = ObjectField(FrontPortTemplateType)
-    front_port_template_list = ObjectListField(FrontPortTemplateType)
-
-    def resolve_front_port_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.FrontPortTemplate.objects.all(), info)
-
-    interface = ObjectField(InterfaceType)
-    interface_list = ObjectListField(InterfaceType)
-
-    def resolve_interface_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Interface.objects.all(), info)
-
-    interface_template = ObjectField(InterfaceTemplateType)
-    interface_template_list = ObjectListField(InterfaceTemplateType)
-
-    def resolve_interface_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.InterfaceTemplate.objects.all(), info)
-
-    inventory_item = ObjectField(InventoryItemType)
-    inventory_item_list = ObjectListField(InventoryItemType)
-
-    def resolve_inventory_item_list(root, info, **kwargs):
-        return gql_query_optimizer(models.InventoryItem.objects.all(), info)
-
-    inventory_item_role = ObjectField(InventoryItemRoleType)
-    inventory_item_role_list = ObjectListField(InventoryItemRoleType)
-
-    def resolve_inventory_item_role_list(root, info, **kwargs):
-        return gql_query_optimizer(models.InventoryItemRole.objects.all(), info)
-
-    inventory_item_template = ObjectField(InventoryItemTemplateType)
-    inventory_item_template_list = ObjectListField(InventoryItemTemplateType)
-
-    def resolve_inventory_item_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.InventoryItemTemplate.objects.all(), info)
-
-    location = ObjectField(LocationType)
-    location_list = ObjectListField(LocationType)
-
-    def resolve_location_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Location.objects.all(), info)
-
-    manufacturer = ObjectField(ManufacturerType)
-    manufacturer_list = ObjectListField(ManufacturerType)
-
-    def resolve_manufacturer_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Manufacturer.objects.all(), info)
+import strawberry
+import strawberry_django
 
 
-    module = ObjectField(ModuleType)
-    module_list = ObjectListField(ModuleType)
-
-    def resolve_module_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Module.objects.all(), info)
-
-    module_bay = ObjectField(ModuleBayType)
-    module_bay_list = ObjectListField(ModuleBayType)
-
-    def resolve_module_bay_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ModuleBay.objects.all(), info)
-
-    module_bay_template = ObjectField(ModuleBayTemplateType)
-    module_bay_template_list = ObjectListField(ModuleBayTemplateType)
-
-    def resolve_module_bay_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ModuleBayTemplate.objects.all(), info)
-
-    module_type = ObjectField(ModuleTypeType)
-    module_type_list = ObjectListField(ModuleTypeType)
-
-    def resolve_module_type_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ModuleType.objects.all(), info)
-
-    platform = ObjectField(PlatformType)
-    platform_list = ObjectListField(PlatformType)
-
-    def resolve_platform_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Platform.objects.all(), info)
-
-    power_feed = ObjectField(PowerFeedType)
-    power_feed_list = ObjectListField(PowerFeedType)
-
-    def resolve_power_feed_list(root, info, **kwargs):
-        return gql_query_optimizer(models.PowerFeed.objects.all(), info)
-
-    power_outlet = ObjectField(PowerOutletType)
-    power_outlet_list = ObjectListField(PowerOutletType)
-
-    def resolve_power_outlet_list(root, info, **kwargs):
-        return gql_query_optimizer(models.PowerOutlet.objects.all(), info)
-
-    power_outlet_template = ObjectField(PowerOutletTemplateType)
-    power_outlet_template_list = ObjectListField(PowerOutletTemplateType)
-
-    def resolve_power_outlet_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.PowerOutletTemplate.objects.all(), info)
-
-    power_panel = ObjectField(PowerPanelType)
-    power_panel_list = ObjectListField(PowerPanelType)
-
-    def resolve_power_panel_list(root, info, **kwargs):
-        return gql_query_optimizer(models.PowerPanel.objects.all(), info)
-
-    power_port = ObjectField(PowerPortType)
-    power_port_list = ObjectListField(PowerPortType)
-
-    def resolve_power_port_list(root, info, **kwargs):
-        return gql_query_optimizer(models.PowerPort.objects.all(), info)
-
-    power_port_template = ObjectField(PowerPortTemplateType)
-    power_port_template_list = ObjectListField(PowerPortTemplateType)
-
-    def resolve_power_port_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.PowerPortTemplate.objects.all(), info)
-
-    rack = ObjectField(RackType)
-    rack_list = ObjectListField(RackType)
-
-    def resolve_rack_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Rack.objects.all(), info)
-
-    rack_reservation = ObjectField(RackReservationType)
-    rack_reservation_list = ObjectListField(RackReservationType)
-
-    def resolve_rack_reservation_list(root, info, **kwargs):
-        return gql_query_optimizer(models.RackReservation.objects.all(), info)
-
-    rack_role = ObjectField(RackRoleType)
-    rack_role_list = ObjectListField(RackRoleType)
-
-    def resolve_rack_role_list(root, info, **kwargs):
-        return gql_query_optimizer(models.RackRole.objects.all(), info)
-
-    rear_port = ObjectField(RearPortType)
-    rear_port_list = ObjectListField(RearPortType)
-
-    def resolve_rear_port_list(root, info, **kwargs):
-        return gql_query_optimizer(models.RearPort.objects.all(), info)
-
-    rear_port_template = ObjectField(RearPortTemplateType)
-    rear_port_template_list = ObjectListField(RearPortTemplateType)
-
-    def resolve_rear_port_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.RearPortTemplate.objects.all(), info)
-
-    region = ObjectField(RegionType)
-    region_list = ObjectListField(RegionType)
-
-    def resolve_region_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Region.objects.all(), info)
-
-    site = ObjectField(SiteType)
-    site_list = ObjectListField(SiteType)
-
-    def resolve_site_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Site.objects.all(), info)
-
-    site_group = ObjectField(SiteGroupType)
-    site_group_list = ObjectListField(SiteGroupType)
-
-    def resolve_site_group_list(root, info, **kwargs):
-        return gql_query_optimizer(models.SiteGroup.objects.all(), info)
-
-    virtual_chassis = ObjectField(VirtualChassisType)
-    virtual_chassis_list = ObjectListField(VirtualChassisType)
-
-    def resolve_virtual_chassis_list(root, info, **kwargs):
-        return gql_query_optimizer(models.VirtualChassis.objects.all(), info)
+from dcim import models
+from .types import *
 
 
-    virtual_device_context = ObjectField(VirtualDeviceContextType)
-    virtual_device_context_list = ObjectListField(VirtualDeviceContextType)
 
 
-    def resolve_virtual_device_context_list(root, info, **kwargs):
-        return gql_query_optimizer(models.VirtualDeviceContext.objects.all(), info)
+@strawberry.type
+class DCIMQuery:
+    @strawberry.field
+    def cable(self, id: int) -> CableType:
+        return models.Cable.objects.get(pk=id)
+    cable_list: List[CableType] = strawberry_django.field()
+
+    @strawberry.field
+    def console_port(self, id: int) -> ConsolePortType:
+        return models.ConsolePort.objects.get(pk=id)
+    console_port_list: List[ConsolePortType] = strawberry_django.field()
+
+    @strawberry.field
+    def console_port_template(self, id: int) -> ConsolePortTemplateType:
+        return models.ConsolePortTemplate.objects.get(pk=id)
+    console_port_template_list: List[ConsolePortTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def console_server_port(self, id: int) -> ConsoleServerPortType:
+        return models.ConsoleServerPort.objects.get(pk=id)
+    console_server_port_list: List[ConsoleServerPortType] = strawberry_django.field()
+
+    @strawberry.field
+    def console_server_port_template(self, id: int) -> ConsoleServerPortTemplateType:
+        return models.ConsoleServerPortTemplate.objects.get(pk=id)
+    console_server_port_template_list: List[ConsoleServerPortTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def device(self, id: int) -> DeviceType:
+        return models.Device.objects.get(pk=id)
+    device_list: List[DeviceType] = strawberry_django.field()
+
+    @strawberry.field
+    def device_bay(self, id: int) -> DeviceBayType:
+        return models.DeviceBay.objects.get(pk=id)
+    device_bay_list: List[DeviceBayType] = strawberry_django.field()
+
+    @strawberry.field
+    def device_bay_template(self, id: int) -> DeviceBayTemplateType:
+        return models.DeviceBayTemplate.objects.get(pk=id)
+    device_bay_template_list: List[DeviceBayTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def device_role(self, id: int) -> DeviceRoleType:
+        return models.DeviceRole.objects.get(pk=id)
+    device_role_list: List[DeviceRoleType] = strawberry_django.field()
+
+    @strawberry.field
+    def device_type(self, id: int) -> DeviceTypeType:
+        return models.DeviceType.objects.get(pk=id)
+    device_type_list: List[DeviceTypeType] = strawberry_django.field()
+
+    @strawberry.field
+    def front_port(self, id: int) -> FrontPortType:
+        return models.FrontPort.objects.get(pk=id)
+    front_port_list: List[FrontPortType] = strawberry_django.field()
+
+    @strawberry.field
+    def front_port_template(self, id: int) -> FrontPortTemplateType:
+        return models.FrontPortTemplate.objects.get(pk=id)
+    front_port_template_list: List[FrontPortTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def interface(self, id: int) -> InterfaceType:
+        return models.Interface.objects.get(pk=id)
+    interface_list: List[InterfaceType] = strawberry_django.field()
+
+    @strawberry.field
+    def interface_template(self, id: int) -> InterfaceTemplateType:
+        return models.InterfaceTemplate.objects.get(pk=id)
+    interface_template_list: List[InterfaceTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def inventory_item(self, id: int) -> InventoryItemType:
+        return models.InventoryItem.objects.get(pk=id)
+    inventory_item_list: List[InventoryItemType] = strawberry_django.field()
+
+    @strawberry.field
+    def inventory_item_role(self, id: int) -> InventoryItemRoleType:
+        return models.InventoryItemRole.objects.get(pk=id)
+    inventory_item_role_list: List[InventoryItemRoleType] = strawberry_django.field()
+
+    @strawberry.field
+    def inventory_item_template(self, id: int) -> InventoryItemTemplateType:
+        return models.InventoryItemTemplate.objects.get(pk=id)
+    inventory_item_template_list: List[InventoryItemTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def location(self, id: int) -> LocationType:
+        return models.Location.objects.get(pk=id)
+    location_list: List[LocationType] = strawberry_django.field()
+
+    @strawberry.field
+    def manufacturer(self, id: int) -> ManufacturerType:
+        return models.Manufacturer.objects.get(pk=id)
+    manufacturer_list: List[ManufacturerType] = strawberry_django.field()
+
+    @strawberry.field
+    def module(self, id: int) -> ModuleType:
+        return models.Module.objects.get(pk=id)
+    module_list: List[ModuleType] = strawberry_django.field()
+
+    @strawberry.field
+    def module_bay(self, id: int) -> ModuleBayType:
+        return models.ModuleBay.objects.get(pk=id)
+    module_bay_list: List[ModuleBayType] = strawberry_django.field()
+
+    @strawberry.field
+    def module_bay_template(self, id: int) -> ModuleBayTemplateType:
+        return models.ModuleBayTemplate.objects.get(pk=id)
+    module_bay_template_list: List[ModuleBayTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def module_type(self, id: int) -> ModuleTypeType:
+        return models.ModuleType.objects.get(pk=id)
+    module_type_list: List[ModuleTypeType] = strawberry_django.field()
+
+    @strawberry.field
+    def platform(self, id: int) -> PlatformType:
+        return models.Platform.objects.get(pk=id)
+    platform_list: List[PlatformType] = strawberry_django.field()
+
+    @strawberry.field
+    def power_feed(self, id: int) -> PowerFeedType:
+        return models.PowerFeed.objects.get(pk=id)
+    power_feed_list: List[PowerFeedType] = strawberry_django.field()
+
+    @strawberry.field
+    def power_outlet(self, id: int) -> PowerOutletType:
+        return models.PowerOutlet.objects.get(pk=id)
+    power_outlet_list: List[PowerOutletType] = strawberry_django.field()
+
+    @strawberry.field
+    def power_outlet_template(self, id: int) -> PowerOutletTemplateType:
+        return models.PowerOutletTemplate.objects.get(pk=id)
+    power_outlet_template_list: List[PowerOutletTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def power_panel(self, id: int) -> PowerPanelType:
+        return models.PowerPanel.objects.get(id=id)
+    power_panel_list: List[PowerPanelType] = strawberry_django.field()
+
+    @strawberry.field
+    def power_port(self, id: int) -> PowerPortType:
+        return models.PowerPort.objects.get(id=id)
+    power_port_list: List[PowerPortType] = strawberry_django.field()
+
+    @strawberry.field
+    def power_port_template(self, id: int) -> PowerPortTemplateType:
+        return models.PowerPortTemplate.objects.get(id=id)
+    power_port_template_list: List[PowerPortTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def rack(self, id: int) -> RackType:
+        return models.Rack.objects.get(id=id)
+    rack_list: List[RackType] = strawberry_django.field()
+
+    @strawberry.field
+    def rack_reservation(self, id: int) -> RackReservationType:
+        return models.RackReservation.objects.get(id=id)
+    rack_reservation_list: List[RackReservationType] = strawberry_django.field()
+
+    @strawberry.field
+    def rack_role(self, id: int) -> RackRoleType:
+        return models.RackRole.objects.get(id=id)
+    rack_role_list: List[RackRoleType] = strawberry_django.field()
+
+    @strawberry.field
+    def rear_port(self, id: int) -> RearPortType:
+        return models.RearPort.objects.get(id=id)
+    rear_port_list: List[RearPortType] = strawberry_django.field()
+
+    @strawberry.field
+    def rear_port_template(self, id: int) -> RearPortTemplateType:
+        return models.RearPortTemplate.objects.get(id=id)
+    rear_port_template_list: List[RearPortTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def region(self, id: int) -> RegionType:
+        return models.Region.objects.get(id=id)
+    region_list: List[RegionType] = strawberry_django.field()
+
+    @strawberry.field
+    def site(self, id: int) -> SiteType:
+        return models.Site.objects.get(id=id)
+    site_list: List[SiteType] = strawberry_django.field()
+
+    @strawberry.field
+    def site_group(self, id: int) -> SiteGroupType:
+        return models.SiteGroup.objects.get(id=id)
+    site_group_list: List[SiteGroupType] = strawberry_django.field()
+
+    @strawberry.field
+    def virtual_chassis(self, id: int) -> VirtualChassisType:
+        return models.VirtualChassis.objects.get(id=id)
+    virtual_chassis_list: List[VirtualChassisType] = strawberry_django.field()
+
+    @strawberry.field
+    def virtual_device_context(self, id: int) -> VirtualDeviceContextType:
+        return models.VirtualDeviceContext.objects.get(id=id)
+    virtual_device_context_list: List[VirtualDeviceContextType] = strawberry_django.field()

+ 815 - 306
netbox/dcim/graphql/types.py

@@ -1,17 +1,26 @@
-import graphene
+from typing import Annotated, List, Union
 
 
-from dcim import filtersets, models
+import strawberry
+import strawberry_django
+
+from dcim import models
 from extras.graphql.mixins import (
 from extras.graphql.mixins import (
-    ChangelogMixin, ConfigContextMixin, ContactsMixin, CustomFieldsMixin, ImageAttachmentsMixin, TagsMixin,
+    ChangelogMixin,
+    ConfigContextMixin,
+    ContactsMixin,
+    CustomFieldsMixin,
+    ImageAttachmentsMixin,
+    TagsMixin,
 )
 )
 from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
 from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
 from netbox.graphql.scalars import BigInt
 from netbox.graphql.scalars import BigInt
-from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
+from netbox.graphql.types import BaseObjectType, NetBoxObjectType, OrganizationalObjectType
+from .filters import *
 from .mixins import CabledObjectMixin, PathEndpointMixin
 from .mixins import CabledObjectMixin, PathEndpointMixin
 
 
 __all__ = (
 __all__ = (
     'CableType',
     'CableType',
-    'ComponentObjectType',
+    'ComponentType',
     'ConsolePortType',
     'ConsolePortType',
     'ConsolePortTemplateType',
     'ConsolePortTemplateType',
     'ConsoleServerPortType',
     'ConsoleServerPortType',
@@ -30,6 +39,7 @@ __all__ = (
     'InventoryItemTemplateType',
     'InventoryItemTemplateType',
     'LocationType',
     'LocationType',
     'ManufacturerType',
     'ManufacturerType',
+    'ModularComponentType',
     'ModuleType',
     'ModuleType',
     'ModuleBayType',
     'ModuleBayType',
     'ModuleBayTemplateType',
     'ModuleBayTemplateType',
@@ -50,6 +60,7 @@ __all__ = (
     'SiteType',
     'SiteType',
     'SiteGroupType',
     'SiteGroupType',
     'VirtualChassisType',
     'VirtualChassisType',
+    'VirtualDeviceContextType',
 )
 )
 
 
 
 
@@ -58,7 +69,8 @@ __all__ = (
 #
 #
 
 
 
 
-class ComponentObjectType(
+@strawberry.type
+class ComponentType(
     ChangelogMixin,
     ChangelogMixin,
     CustomFieldsMixin,
     CustomFieldsMixin,
     TagsMixin,
     TagsMixin,
@@ -67,447 +79,944 @@ class ComponentObjectType(
     """
     """
     Base type for device/VM components
     Base type for device/VM components
     """
     """
-    class Meta:
-        abstract = True
+    _name: str
+    device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
 
 
 
 
-class ComponentTemplateObjectType(
+@strawberry.type
+class ModularComponentType(ComponentType):
+    module: Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')] | None
+
+
+@strawberry.type
+class ComponentTemplateType(
     ChangelogMixin,
     ChangelogMixin,
     BaseObjectType
     BaseObjectType
 ):
 ):
     """
     """
     Base type for device/VM components
     Base type for device/VM components
     """
     """
-    class Meta:
-        abstract = True
+    _name: str
+    device_type: Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]
+
 
 
+@strawberry.type
+class ModularComponentTemplateType(ComponentTemplateType):
+    """
+    Base type for ComponentTemplateModel which supports optional assignment to a ModuleType.
+    """
+    device_type: Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')] | None
+    module_type: Annotated["ModuleTypeType", strawberry.lazy('dcim.graphql.types')] | None
 
 
 #
 #
 # Model types
 # Model types
 #
 #
 
 
-class CableType(NetBoxObjectType):
-    a_terminations = graphene.List('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
-    b_terminations = graphene.List('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
-
-    class Meta:
-        model = models.Cable
-        fields = '__all__'
-        filterset_class = filtersets.CableFilterSet
 
 
-    def resolve_type(self, info):
-        return self.type or None
-
-    def resolve_length_unit(self, info):
-        return self.length_unit or None
+@strawberry_django.type(
+    models.CableTermination,
+    exclude=('termination_type', 'termination_id'),
+    filters=CableTerminationFilter
+)
+class CableTerminationType(NetBoxObjectType):
 
 
-    def resolve_a_terminations(self, info):
+    @strawberry_django.field
+    def termination(self) -> List[Annotated[Union[
+        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
+        Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
+    ], strawberry.union("CableTerminationTerminationType")]]:
+        return self.termination
+
+
+@strawberry_django.type(
+    models.Cable,
+    fields='__all__',
+    filters=CableFilter
+)
+class CableType(NetBoxObjectType):
+    color: str
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+
+    @strawberry_django.field
+    def terminations(self) -> List[CableTerminationType]:
+        return self.terminations.all()
+
+    @strawberry_django.field
+    def a_terminations(self) -> List[Annotated[Union[
+        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
+        Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
+    ], strawberry.union("CableTerminationTerminationType")]]:
         return self.a_terminations
         return self.a_terminations
 
 
-    def resolve_b_terminations(self, info):
+    @strawberry_django.field
+    def b_terminations(self) -> List[Annotated[Union[
+        Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
+        Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
+    ], strawberry.union("CableTerminationTerminationType")]]:
         return self.b_terminations
         return self.b_terminations
 
 
 
 
-class CableTerminationType(NetBoxObjectType):
-    termination = graphene.Field('dcim.graphql.gfk_mixins.CableTerminationTerminationType')
-
-    class Meta:
-        model = models.CableTermination
-        exclude = ('termination_type', 'termination_id')
-        filterset_class = filtersets.CableTerminationFilterSet
-
-
-class ConsolePortType(ComponentObjectType, CabledObjectMixin, PathEndpointMixin):
-
-    class Meta:
-        model = models.ConsolePort
-        exclude = ('_path',)
-        filterset_class = filtersets.ConsolePortFilterSet
-
-    def resolve_type(self, info):
-        return self.type or None
-
-
-class ConsolePortTemplateType(ComponentTemplateObjectType):
-
-    class Meta:
-        model = models.ConsolePortTemplate
-        fields = '__all__'
-        filterset_class = filtersets.ConsolePortTemplateFilterSet
-
-    def resolve_type(self, info):
-        return self.type or None
-
-
-class ConsoleServerPortType(ComponentObjectType, CabledObjectMixin, PathEndpointMixin):
+@strawberry_django.type(
+    models.ConsolePort,
+    exclude=('_path',),
+    filters=ConsolePortFilter
+)
+class ConsolePortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
+    pass
 
 
-    class Meta:
-        model = models.ConsoleServerPort
-        exclude = ('_path',)
-        filterset_class = filtersets.ConsoleServerPortFilterSet
 
 
-    def resolve_type(self, info):
-        return self.type or None
+@strawberry_django.type(
+    models.ConsolePortTemplate,
+    fields='__all__',
+    filters=ConsolePortTemplateFilter
+)
+class ConsolePortTemplateType(ModularComponentTemplateType):
+    _name: str
 
 
 
 
-class ConsoleServerPortTemplateType(ComponentTemplateObjectType):
+@strawberry_django.type(
+    models.ConsoleServerPort,
+    exclude=('_path',),
+    filters=ConsoleServerPortFilter
+)
+class ConsoleServerPortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
+    pass
 
 
-    class Meta:
-        model = models.ConsoleServerPortTemplate
-        fields = '__all__'
-        filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
 
 
-    def resolve_type(self, info):
-        return self.type or None
+@strawberry_django.type(
+    models.ConsoleServerPortTemplate,
+    fields='__all__',
+    filters=ConsoleServerPortTemplateFilter
+)
+class ConsoleServerPortTemplateType(ModularComponentTemplateType):
+    _name: str
 
 
 
 
+@strawberry_django.type(
+    models.Device,
+    fields='__all__',
+    filters=DeviceFilter
+)
 class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
 class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
+    _name: str
+    console_port_count: BigInt
+    console_server_port_count: BigInt
+    power_port_count: BigInt
+    power_outlet_count: BigInt
+    interface_count: BigInt
+    front_port_count: BigInt
+    rear_port_count: BigInt
+    device_bay_count: BigInt
+    module_bay_count: BigInt
+    inventory_item_count: BigInt
+    config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None
+    device_type: Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]
+    role: Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    platform: Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')] | None
+    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
+    location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
+    rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')] | None
+    primary_ip4: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
+    primary_ip6: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
+    oob_ip: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
+    cluster: Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')] | None
+    virtual_chassis: Annotated["VirtualChassisType", strawberry.lazy('dcim.graphql.types')] | None
+
+    @strawberry_django.field
+    def vc_master_for(self) -> Annotated["VirtualChassisType", strawberry.lazy('dcim.graphql.types')] | None:
+        return self.vc_master_for if hasattr(self, 'vc_master_for') else None
+
+    @strawberry_django.field
+    def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.virtual_machines.all()
+
+    @strawberry_django.field
+    def modules(self) -> List[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.modules.all()
+
+    @strawberry_django.field
+    def parent_bay(self) -> Annotated["DeviceBayType", strawberry.lazy('dcim.graphql.types')] | None:
+        return self.parent_bay if hasattr(self, 'parent_bay') else None
+
+    @strawberry_django.field
+    def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfaces.all()
+
+    @strawberry_django.field
+    def rearports(self) -> List[Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.rearports.all()
+
+    @strawberry_django.field
+    def consoleports(self) -> List[Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.consoleports.all()
+
+    @strawberry_django.field
+    def powerports(self) -> List[Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.powerports.all()
+
+    @strawberry_django.field
+    def cabletermination_set(self) -> List[Annotated["CableTerminationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.cabletermination_set.all()
+
+    @strawberry_django.field
+    def consoleserverports(self) -> List[Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.consoleserverports.all()
+
+    @strawberry_django.field
+    def poweroutlets(self) -> List[Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.poweroutlets.all()
+
+    @strawberry_django.field
+    def frontports(self) -> List[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.frontports.all()
+
+    @strawberry_django.field
+    def modulebays(self) -> List[Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.modulebays.all()
+
+    @strawberry_django.field
+    def services(self) -> List[Annotated["ServiceType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.services.all()
+
+    @strawberry_django.field
+    def inventoryitems(self) -> List[Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.inventoryitems.all()
+
+    @strawberry_django.field
+    def vdcs(self) -> List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.vdcs.all()
+
+
+@strawberry_django.type(
+    models.DeviceBay,
+    fields='__all__',
+    filters=DeviceBayFilter
+)
+class DeviceBayType(ComponentType):
+    installed_device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-    class Meta:
-        model = models.Device
-        fields = '__all__'
-        filterset_class = filtersets.DeviceFilterSet
-
-    def resolve_face(self, info):
-        return self.face or None
-
-    def resolve_airflow(self, info):
-        return self.airflow or None
-
-
-class DeviceBayType(ComponentObjectType):
-
-    class Meta:
-        model = models.DeviceBay
-        fields = '__all__'
-        filterset_class = filtersets.DeviceBayFilterSet
-
-
-class DeviceBayTemplateType(ComponentTemplateObjectType):
-
-    class Meta:
-        model = models.DeviceBayTemplate
-        fields = '__all__'
-        filterset_class = filtersets.DeviceBayTemplateFilterSet
-
-
-class InventoryItemTemplateType(ComponentTemplateObjectType):
-    component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemTemplateComponentType')
 
 
-    class Meta:
-        model = models.InventoryItemTemplate
-        exclude = ('component_type', 'component_id')
-        filterset_class = filtersets.InventoryItemTemplateFilterSet
+@strawberry_django.type(
+    models.DeviceBayTemplate,
+    fields='__all__',
+    filters=DeviceBayTemplateFilter
+)
+class DeviceBayTemplateType(ComponentTemplateType):
+    _name: str
 
 
 
 
+@strawberry_django.type(
+    models.InventoryItemTemplate,
+    exclude=('component_type', 'component_id', 'parent'),
+    filters=InventoryItemTemplateFilter
+)
+class InventoryItemTemplateType(ComponentTemplateType):
+    _name: str
+    role: Annotated["InventoryItemRoleType", strawberry.lazy('dcim.graphql.types')] | None
+    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
+
+    @strawberry_django.field
+    def parent(self) -> Annotated["InventoryItemTemplateType", strawberry.lazy('dcim.graphql.types')] | None:
+        return self.parent
+
+    @strawberry_django.field
+    def child_items(self) -> List[Annotated["InventoryItemTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.child_items.all()
+
+    @strawberry_django.field
+    def component(self) -> List[Annotated[Union[
+        Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
+    ], strawberry.union("InventoryItemTemplateComponentType")]]:
+        return self.component
+
+
+@strawberry_django.type(
+    models.DeviceRole,
+    fields='__all__',
+    filters=DeviceRoleFilter
+)
 class DeviceRoleType(OrganizationalObjectType):
 class DeviceRoleType(OrganizationalObjectType):
+    color: str
+    config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None
 
 
-    class Meta:
-        model = models.DeviceRole
-        fields = '__all__'
-        filterset_class = filtersets.DeviceRoleFilterSet
-
-
-class DeviceTypeType(NetBoxObjectType):
-
-    class Meta:
-        model = models.DeviceType
-        fields = '__all__'
-        filterset_class = filtersets.DeviceTypeFilterSet
-
-    def resolve_subdevice_role(self, info):
-        return self.subdevice_role or None
-
-    def resolve_airflow(self, info):
-        return self.airflow or None
+    @strawberry_django.field
+    def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.virtual_machines.all()
 
 
-    def resolve_weight_unit(self, info):
-        return self.weight_unit or None
+    @strawberry_django.field
+    def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devices.all()
 
 
 
 
-class FrontPortType(ComponentObjectType, CabledObjectMixin):
-
-    class Meta:
-        model = models.FrontPort
-        fields = '__all__'
-        filterset_class = filtersets.FrontPortFilterSet
-
+@strawberry_django.type(
+    models.DeviceType,
+    fields='__all__',
+    filters=DeviceTypeFilter
+)
+class DeviceTypeType(NetBoxObjectType):
+    console_port_template_count: BigInt
+    console_server_port_template_count: BigInt
+    power_port_template_count: BigInt
+    power_outlet_template_count: BigInt
+    interface_template_count: BigInt
+    front_port_template_count: BigInt
+    rear_port_template_count: BigInt
+    device_bay_template_count: BigInt
+    module_bay_template_count: BigInt
+    inventory_item_template_count: BigInt
+    front_image: strawberry_django.fields.types.DjangoImageType | None
+    rear_image: strawberry_django.fields.types.DjangoImageType | None
+    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
+    default_platform: Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')] | None
+
+    @strawberry_django.field
+    def frontporttemplates(self) -> List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.frontporttemplates.all()
+
+    @strawberry_django.field
+    def modulebaytemplates(self) -> List[Annotated["ModuleBayTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.modulebaytemplates.all()
+
+    @strawberry_django.field
+    def instances(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.instances.all()
+
+    @strawberry_django.field
+    def poweroutlettemplates(self) -> List[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.poweroutlettemplates.all()
+
+    @strawberry_django.field
+    def powerporttemplates(self) -> List[Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.powerporttemplates.all()
+
+    @strawberry_django.field
+    def inventoryitemtemplates(self) -> List[Annotated["InventoryItemTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.inventoryitemtemplates.all()
+
+    @strawberry_django.field
+    def rearporttemplates(self) -> List[Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.rearporttemplates.all()
+
+    @strawberry_django.field
+    def consoleserverporttemplates(self) -> List[Annotated["ConsoleServerPortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.consoleserverporttemplates.all()
+
+    @strawberry_django.field
+    def interfacetemplates(self) -> List[Annotated["InterfaceTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfacetemplates.all()
+
+    @strawberry_django.field
+    def devicebaytemplates(self) -> List[Annotated["DeviceBayTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devicebaytemplates.all()
+
+    @strawberry_django.field
+    def consoleporttemplates(self) -> List[Annotated["ConsolePortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.consoleporttemplates.all()
+
+
+@strawberry_django.type(
+    models.FrontPort,
+    fields='__all__',
+    filters=FrontPortFilter
+)
+class FrontPortType(ModularComponentType, CabledObjectMixin):
+    color: str
+    rear_port: Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]
 
 
-class FrontPortTemplateType(ComponentTemplateObjectType):
 
 
-    class Meta:
-        model = models.FrontPortTemplate
-        fields = '__all__'
-        filterset_class = filtersets.FrontPortTemplateFilterSet
+@strawberry_django.type(
+    models.FrontPortTemplate,
+    fields='__all__',
+    filters=FrontPortTemplateFilter
+)
+class FrontPortTemplateType(ModularComponentTemplateType):
+    _name: str
+    color: str
+    rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
 
 
 
 
-class InterfaceType(IPAddressesMixin, ComponentObjectType, CabledObjectMixin, PathEndpointMixin):
+@strawberry_django.type(
+    models.Interface,
+    exclude=('_path',),
+    filters=InterfaceFilter
+)
+class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin):
+    mac_address: str | None
+    wwn: str | None
+    parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
+    bridge: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
+    lag: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
+    wireless_link: Annotated["WirelessLinkType", strawberry.lazy('wireless.graphql.types')] | None
+    untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
+    vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
+
+    @strawberry_django.field
+    def vdcs(self) -> List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.vdcs.all()
+
+    @strawberry_django.field
+    def tagged_vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.tagged_vlans.all()
+
+    @strawberry_django.field
+    def bridge_interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.bridge_interfaces.all()
+
+    @strawberry_django.field
+    def wireless_lans(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
+        return self.wireless_lans.all()
+
+    @strawberry_django.field
+    def member_interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.member_interfaces.all()
+
+    @strawberry_django.field
+    def child_interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.child_interfaces.all()
+
+
+@strawberry_django.type(
+    models.InterfaceTemplate,
+    fields='__all__',
+    filters=InterfaceTemplateFilter
+)
+class InterfaceTemplateType(ModularComponentTemplateType):
+    _name: str
+    bridge: Annotated["InterfaceTemplateType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-    class Meta:
-        model = models.Interface
-        exclude = ('_path',)
-        filterset_class = filtersets.InterfaceFilterSet
+    @strawberry_django.field
+    def bridge_interfaces(self) -> List[Annotated["InterfaceTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.bridge_interfaces.all()
 
 
-    def resolve_poe_mode(self, info):
-        return self.poe_mode or None
 
 
-    def resolve_poe_type(self, info):
-        return self.poe_type or None
+@strawberry_django.type(
+    models.InventoryItem,
+    exclude=('component_type', 'component_id', 'parent'),
+    filters=InventoryItemFilter
+)
+class InventoryItemType(ComponentType):
+    role: Annotated["InventoryItemRoleType", strawberry.lazy('dcim.graphql.types')] | None
+    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
+
+    @strawberry_django.field
+    def parent(self) -> Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')] | None:
+        return self.parent
+
+    @strawberry_django.field
+    def child_items(self) -> List[Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.child_items.all()
+
+    @strawberry_django.field
+    def component(self) -> List[Annotated[Union[
+        Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
+    ], strawberry.union("InventoryItemComponentType")]]:
+        return self.component
+
+
+@strawberry_django.type(
+    models.InventoryItemRole,
+    fields='__all__',
+    filters=InventoryItemRoleFilter
+)
+class InventoryItemRoleType(OrganizationalObjectType):
+    color: str
 
 
-    def resolve_mode(self, info):
-        return self.mode or None
+    @strawberry_django.field
+    def inventory_items(self) -> List[Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.inventory_items.all()
 
 
-    def resolve_rf_role(self, info):
-        return self.rf_role or None
+    @strawberry_django.field
+    def inventory_item_templates(self) -> List[Annotated["InventoryItemTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.inventory_item_templates.all()
 
 
-    def resolve_rf_channel(self, info):
-        return self.rf_channel or None
 
 
+@strawberry_django.type(
+    models.Location,
+    # fields='__all__',
+    exclude=('parent',),  # bug - temp
+    filters=LocationFilter
+)
+class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, OrganizationalObjectType):
+    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    parent: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-class InterfaceTemplateType(ComponentTemplateObjectType):
+    @strawberry_django.field
+    def powerpanel_set(self) -> List[Annotated["PowerPanelType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.powerpanel_set.all()
 
 
-    class Meta:
-        model = models.InterfaceTemplate
-        fields = '__all__'
-        filterset_class = filtersets.InterfaceTemplateFilterSet
+    @strawberry_django.field
+    def cabletermination_set(self) -> List[Annotated["CableTerminationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.cabletermination_set.all()
 
 
-    def resolve_poe_mode(self, info):
-        return self.poe_mode or None
+    @strawberry_django.field
+    def racks(self) -> List[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.racks.all()
 
 
-    def resolve_poe_type(self, info):
-        return self.poe_type or None
+    @strawberry_django.field
+    def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devices.all()
 
 
-    def resolve_rf_role(self, info):
-        return self.rf_role or None
+    @strawberry_django.field
+    def children(self) -> List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.children.all()
 
 
 
 
-class InventoryItemType(ComponentObjectType):
-    component = graphene.Field('dcim.graphql.gfk_mixins.InventoryItemComponentType')
+@strawberry_django.type(
+    models.Manufacturer,
+    fields='__all__',
+    filters=ManufacturerFilter
+)
+class ManufacturerType(OrganizationalObjectType, ContactsMixin):
 
 
-    class Meta:
-        model = models.InventoryItem
-        exclude = ('component_type', 'component_id')
-        filterset_class = filtersets.InventoryItemFilterSet
+    @strawberry_django.field
+    def platforms(self) -> List[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.platforms.all()
 
 
+    @strawberry_django.field
+    def device_types(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.device_types.all()
 
 
-class InventoryItemRoleType(OrganizationalObjectType):
+    @strawberry_django.field
+    def inventory_item_templates(self) -> List[Annotated["InventoryItemTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.inventory_item_templates.all()
 
 
-    class Meta:
-        model = models.InventoryItemRole
-        fields = '__all__'
-        filterset_class = filtersets.InventoryItemRoleFilterSet
+    @strawberry_django.field
+    def inventory_items(self) -> List[Annotated["InventoryItemType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.inventory_items.all()
 
 
+    @strawberry_django.field
+    def module_types(self) -> List[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.module_types.all()
 
 
-class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.Location
-        fields = '__all__'
-        filterset_class = filtersets.LocationFilterSet
+@strawberry_django.type(
+    models.Module,
+    fields='__all__',
+    filters=ModuleFilter
+)
+class ModuleType(NetBoxObjectType):
+    device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
+    module_bay: Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')]
+    module_type: Annotated["ModuleTypeType", strawberry.lazy('dcim.graphql.types')]
 
 
+    @strawberry_django.field
+    def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfaces.all()
 
 
-class ManufacturerType(OrganizationalObjectType, ContactsMixin):
+    @strawberry_django.field
+    def powerports(self) -> List[Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.powerports.all()
 
 
-    class Meta:
-        model = models.Manufacturer
-        fields = '__all__'
-        filterset_class = filtersets.ManufacturerFilterSet
+    @strawberry_django.field
+    def consoleserverports(self) -> List[Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.consoleserverports.all()
 
 
+    @strawberry_django.field
+    def consoleports(self) -> List[Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.consoleports.all()
 
 
-class ModuleType(ComponentObjectType):
+    @strawberry_django.field
+    def poweroutlets(self) -> List[Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.poweroutlets.all()
 
 
-    class Meta:
-        model = models.Module
-        fields = '__all__'
-        filterset_class = filtersets.ModuleFilterSet
+    @strawberry_django.field
+    def rearports(self) -> List[Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.rearports.all()
 
 
+    @strawberry_django.field
+    def frontports(self) -> List[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.frontports.all()
 
 
-class ModuleBayType(ComponentObjectType):
 
 
-    class Meta:
-        model = models.ModuleBay
-        fields = '__all__'
-        filterset_class = filtersets.ModuleBayFilterSet
+@strawberry_django.type(
+    models.ModuleBay,
+    fields='__all__',
+    filters=ModuleBayFilter
+)
+class ModuleBayType(ComponentType):
 
 
+    @strawberry_django.field
+    def installed_module(self) -> Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')] | None:
+        return self.installed_module if hasattr(self, 'installed_module') else None
 
 
-class ModuleBayTemplateType(ComponentTemplateObjectType):
 
 
-    class Meta:
-        model = models.ModuleBayTemplate
-        fields = '__all__'
-        filterset_class = filtersets.ModuleBayTemplateFilterSet
+@strawberry_django.type(
+    models.ModuleBayTemplate,
+    fields='__all__',
+    filters=ModuleBayTemplateFilter
+)
+class ModuleBayTemplateType(ComponentTemplateType):
+    _name: str
 
 
 
 
+@strawberry_django.type(
+    models.ModuleType,
+    fields='__all__',
+    filters=ModuleTypeFilter
+)
 class ModuleTypeType(NetBoxObjectType):
 class ModuleTypeType(NetBoxObjectType):
+    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
 
 
-    class Meta:
-        model = models.ModuleType
-        fields = '__all__'
-        filterset_class = filtersets.ModuleTypeFilterSet
+    @strawberry_django.field
+    def frontporttemplates(self) -> List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.frontporttemplates.all()
 
 
-    def resolve_weight_unit(self, info):
-        return self.weight_unit or None
+    @strawberry_django.field
+    def consoleserverporttemplates(self) -> List[Annotated["ConsoleServerPortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.consoleserverporttemplates.all()
 
 
+    @strawberry_django.field
+    def interfacetemplates(self) -> List[Annotated["InterfaceTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfacetemplates.all()
 
 
-class PlatformType(OrganizationalObjectType):
+    @strawberry_django.field
+    def powerporttemplates(self) -> List[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.powerporttemplates.all()
 
 
-    class Meta:
-        model = models.Platform
-        fields = '__all__'
-        filterset_class = filtersets.PlatformFilterSet
+    @strawberry_django.field
+    def poweroutlettemplates(self) -> List[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.poweroutlettemplates.all()
 
 
+    @strawberry_django.field
+    def rearporttemplates(self) -> List[Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.rearporttemplates.all()
 
 
-class PowerFeedType(NetBoxObjectType, CabledObjectMixin, PathEndpointMixin):
+    @strawberry_django.field
+    def instances(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.instances.all()
 
 
-    class Meta:
-        model = models.PowerFeed
-        exclude = ('_path',)
-        filterset_class = filtersets.PowerFeedFilterSet
+    @strawberry_django.field
+    def consoleporttemplates(self) -> List[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.consoleporttemplates.all()
 
 
 
 
-class PowerOutletType(ComponentObjectType, CabledObjectMixin, PathEndpointMixin):
+@strawberry_django.type(
+    models.Platform,
+    fields='__all__',
+    filters=PlatformFilter
+)
+class PlatformType(OrganizationalObjectType):
+    manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')] | None
+    config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None
 
 
-    class Meta:
-        model = models.PowerOutlet
-        exclude = ('_path',)
-        filterset_class = filtersets.PowerOutletFilterSet
+    @strawberry_django.field
+    def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.virtual_machines.all()
 
 
-    def resolve_feed_leg(self, info):
-        return self.feed_leg or None
+    @strawberry_django.field
+    def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devices.all()
 
 
-    def resolve_type(self, info):
-        return self.type or None
 
 
+@strawberry_django.type(
+    models.PowerFeed,
+    exclude=('_path',),
+    filters=PowerFeedFilter
+)
+class PowerFeedType(NetBoxObjectType, CabledObjectMixin, PathEndpointMixin):
+    power_panel: Annotated["PowerPanelType", strawberry.lazy('dcim.graphql.types')]
+    rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
-class PowerOutletTemplateType(ComponentTemplateObjectType):
 
 
-    class Meta:
-        model = models.PowerOutletTemplate
-        fields = '__all__'
-        filterset_class = filtersets.PowerOutletTemplateFilterSet
+@strawberry_django.type(
+    models.PowerOutlet,
+    exclude=('_path',),
+    filters=PowerOutletFilter
+)
+class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
+    power_port: Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-    def resolve_feed_leg(self, info):
-        return self.feed_leg or None
 
 
-    def resolve_type(self, info):
-        return self.type or None
+@strawberry_django.type(
+    models.PowerOutletTemplate,
+    fields='__all__',
+    filters=PowerOutletTemplateFilter
+)
+class PowerOutletTemplateType(ModularComponentTemplateType):
+    _name: str
+    power_port: Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')] | None
 
 
 
 
+@strawberry_django.type(
+    models.PowerPanel,
+    fields='__all__',
+    filters=PowerPanelFilter
+)
 class PowerPanelType(NetBoxObjectType, ContactsMixin):
 class PowerPanelType(NetBoxObjectType, ContactsMixin):
+    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
+    location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-    class Meta:
-        model = models.PowerPanel
-        fields = '__all__'
-        filterset_class = filtersets.PowerPanelFilterSet
-
-
-class PowerPortType(ComponentObjectType, CabledObjectMixin, PathEndpointMixin):
+    @strawberry_django.field
+    def powerfeeds(self) -> List[Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.powerfeeds.all()
 
 
-    class Meta:
-        model = models.PowerPort
-        exclude = ('_path',)
-        filterset_class = filtersets.PowerPortFilterSet
 
 
-    def resolve_type(self, info):
-        return self.type or None
+@strawberry_django.type(
+    models.PowerPort,
+    exclude=('_path',),
+    filters=PowerPortFilter
+)
+class PowerPortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
 
 
+    @strawberry_django.field
+    def poweroutlets(self) -> List[Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.poweroutlets.all()
 
 
-class PowerPortTemplateType(ComponentTemplateObjectType):
 
 
-    class Meta:
-        model = models.PowerPortTemplate
-        fields = '__all__'
-        filterset_class = filtersets.PowerPortTemplateFilterSet
+@strawberry_django.type(
+    models.PowerPortTemplate,
+    fields='__all__',
+    filters=PowerPortTemplateFilter
+)
+class PowerPortTemplateType(ModularComponentTemplateType):
+    _name: str
 
 
-    def resolve_type(self, info):
-        return self.type or None
+    @strawberry_django.field
+    def poweroutlet_templates(self) -> List[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.poweroutlet_templates.all()
 
 
 
 
+@strawberry_django.type(
+    models.Rack,
+    fields='__all__',
+    filters=RackFilter
+)
 class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
 class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
+    _name: str
+    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
+    location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    role: Annotated["RackRoleType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-    class Meta:
-        model = models.Rack
-        fields = '__all__'
-        filterset_class = filtersets.RackFilterSet
+    @strawberry_django.field
+    def reservations(self) -> List[Annotated["RackReservationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.reservations.all()
 
 
-    def resolve_type(self, info):
-        return self.type or None
+    @strawberry_django.field
+    def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devices.all()
 
 
-    def resolve_outer_unit(self, info):
-        return self.outer_unit or None
+    @strawberry_django.field
+    def powerfeeds(self) -> List[Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.powerfeeds.all()
 
 
-    def resolve_weight_unit(self, info):
-        return self.weight_unit or None
+    @strawberry_django.field
+    def cabletermination_set(self) -> List[Annotated["CableTerminationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.cabletermination_set.all()
 
 
 
 
+@strawberry_django.type(
+    models.RackReservation,
+    fields='__all__',
+    filters=RackReservationFilter
+)
 class RackReservationType(NetBoxObjectType):
 class RackReservationType(NetBoxObjectType):
-
-    class Meta:
-        model = models.RackReservation
-        fields = '__all__'
-        filterset_class = filtersets.RackReservationFilterSet
+    units: List[int]
+    rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')]
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    user: Annotated["UserType", strawberry.lazy('users.graphql.types')]
 
 
 
 
+@strawberry_django.type(
+    models.RackRole,
+    fields='__all__',
+    filters=RackRoleFilter
+)
 class RackRoleType(OrganizationalObjectType):
 class RackRoleType(OrganizationalObjectType):
+    color: str
 
 
-    class Meta:
-        model = models.RackRole
-        fields = '__all__'
-        filterset_class = filtersets.RackRoleFilterSet
+    @strawberry_django.field
+    def racks(self) -> List[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.racks.all()
 
 
 
 
-class RearPortType(ComponentObjectType, CabledObjectMixin):
+@strawberry_django.type(
+    models.RearPort,
+    fields='__all__',
+    filters=RearPortFilter
+)
+class RearPortType(ModularComponentType, CabledObjectMixin):
+    color: str
 
 
-    class Meta:
-        model = models.RearPort
-        fields = '__all__'
-        filterset_class = filtersets.RearPortFilterSet
+    @strawberry_django.field
+    def frontports(self) -> List[Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.frontports.all()
 
 
 
 
-class RearPortTemplateType(ComponentTemplateObjectType):
+@strawberry_django.type(
+    models.RearPortTemplate,
+    fields='__all__',
+    filters=RearPortTemplateFilter
+)
+class RearPortTemplateType(ModularComponentTemplateType):
+    _name: str
+    color: str
 
 
-    class Meta:
-        model = models.RearPortTemplate
-        fields = '__all__'
-        filterset_class = filtersets.RearPortTemplateFilterSet
+    @strawberry_django.field
+    def frontport_templates(self) -> List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.frontport_templates.all()
 
 
 
 
+@strawberry_django.type(
+    models.Region,
+    exclude=('parent',),
+    # fields='__all__',
+    filters=RegionFilter
+)
 class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
 class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.Region
-        fields = '__all__'
-        filterset_class = filtersets.RegionFilterSet
+    @strawberry_django.field
+    def sites(self) -> List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.sites.all()
 
 
+    @strawberry_django.field
+    def parent(self) -> Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None:
+        return self.parent
 
 
-class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
-    asn = graphene.Field(BigInt)
-
-    class Meta:
-        model = models.Site
-        fields = '__all__'
-        filterset_class = filtersets.SiteFilterSet
+    @strawberry_django.field
+    def children(self) -> List[Annotated["RegionType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.children.all()
 
 
 
 
+@strawberry_django.type(
+    models.Site,
+    fields='__all__',
+    filters=SiteFilter
+)
+class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
+    _name: str
+    time_zone: str | None
+    region: Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None
+    group: Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+
+    @strawberry_django.field
+    def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.prefixes.all()
+
+    @strawberry_django.field
+    def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.virtual_machines.all()
+
+    @strawberry_django.field
+    def racks(self) -> List[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.racks.all()
+
+    @strawberry_django.field
+    def cabletermination_set(self) -> List[Annotated["CableTerminationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.cabletermination_set.all()
+
+    @strawberry_django.field
+    def powerpanel_set(self) -> List[Annotated["PowerPanelType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.powerpanel_set.all()
+
+    @strawberry_django.field
+    def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devices.all()
+
+    @strawberry_django.field
+    def locations(self) -> List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.locations.all()
+
+    @strawberry_django.field
+    def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.asns.all()
+
+    @strawberry_django.field
+    def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
+        return self.circuit_terminations.all()
+
+    @strawberry_django.field
+    def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.clusters.all()
+
+    @strawberry_django.field
+    def vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.vlans.all()
+
+
+@strawberry_django.type(
+    models.SiteGroup,
+    # fields='__all__',
+    exclude=('parent',),  # bug - temp
+    filters=SiteGroupFilter
+)
 class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
 class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.SiteGroup
-        fields = '__all__'
-        filterset_class = filtersets.SiteGroupFilterSet
+    @strawberry_django.field
+    def sites(self) -> List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.sites.all()
+
+    @strawberry_django.field
+    def parent(self) -> Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None:
+        return self.parent
+
+    @strawberry_django.field
+    def children(self) -> List[Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.children.all()
 
 
 
 
+@strawberry_django.type(
+    models.VirtualChassis,
+    fields='__all__',
+    filters=VirtualChassisFilter
+)
 class VirtualChassisType(NetBoxObjectType):
 class VirtualChassisType(NetBoxObjectType):
+    member_count: BigInt
+    master: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-    class Meta:
-        model = models.VirtualChassis
-        fields = '__all__'
-        filterset_class = filtersets.VirtualChassisFilterSet
+    @strawberry_django.field
+    def members(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.members.all()
 
 
 
 
+@strawberry_django.type(
+    models.VirtualDeviceContext,
+    fields='__all__',
+    filters=VirtualDeviceContextFilter
+)
 class VirtualDeviceContextType(NetBoxObjectType):
 class VirtualDeviceContextType(NetBoxObjectType):
-
-    class Meta:
-        model = models.VirtualDeviceContext
-        fields = '__all__'
-        filterset_class = filtersets.VirtualDeviceContextFilterSet
+    device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
+    primary_ip4: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
+    primary_ip6: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+
+    @strawberry_django.field
+    def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfaces.all()

+ 98 - 0
netbox/extras/graphql/filters.py

@@ -0,0 +1,98 @@
+import strawberry_django
+
+from extras import filtersets, models
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+
+__all__ = (
+    'ConfigContextFilter',
+    'ConfigTemplateFilter',
+    'CustomFieldFilter',
+    'CustomFieldChoiceSetFilter',
+    'CustomLinkFilter',
+    'EventRuleFilter',
+    'ExportTemplateFilter',
+    'ImageAttachmentFilter',
+    'JournalEntryFilter',
+    'ObjectChangeFilter',
+    'SavedFilterFilter',
+    'TagFilter',
+    'WebhookFilter',
+)
+
+
+@strawberry_django.filter(models.ConfigContext, lookups=True)
+@autotype_decorator(filtersets.ConfigContextFilterSet)
+class ConfigContextFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ConfigTemplate, lookups=True)
+@autotype_decorator(filtersets.ConfigTemplateFilterSet)
+class ConfigTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.CustomField, lookups=True)
+@autotype_decorator(filtersets.CustomFieldFilterSet)
+class CustomFieldFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.CustomFieldChoiceSet, lookups=True)
+@autotype_decorator(filtersets.CustomFieldChoiceSetFilterSet)
+class CustomFieldChoiceSetFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.CustomLink, lookups=True)
+@autotype_decorator(filtersets.CustomLinkFilterSet)
+class CustomLinkFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ExportTemplate, lookups=True)
+@autotype_decorator(filtersets.ExportTemplateFilterSet)
+class ExportTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ImageAttachment, lookups=True)
+@autotype_decorator(filtersets.ImageAttachmentFilterSet)
+class ImageAttachmentFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.JournalEntry, lookups=True)
+@autotype_decorator(filtersets.JournalEntryFilterSet)
+class JournalEntryFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ObjectChange, lookups=True)
+@autotype_decorator(filtersets.ObjectChangeFilterSet)
+class ObjectChangeFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.SavedFilter, lookups=True)
+@autotype_decorator(filtersets.SavedFilterFilterSet)
+class SavedFilterFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Tag, lookups=True)
+@autotype_decorator(filtersets.TagFilterSet)
+class TagFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Webhook, lookups=True)
+@autotype_decorator(filtersets.WebhookFilterSet)
+class WebhookFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.EventRule, lookups=True)
+@autotype_decorator(filtersets.EventRuleFilterSet)
+class EventRuleFilter(BaseFilterMixin):
+    pass

+ 32 - 19
netbox/extras/graphql/mixins.py

@@ -1,6 +1,8 @@
-import graphene
+from typing import TYPE_CHECKING, Annotated, List
+
+import strawberry
+import strawberry_django
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
-from graphene.types.generic import GenericScalar
 
 
 from extras.models import ObjectChange
 from extras.models import ObjectChange
 
 
@@ -14,56 +16,67 @@ __all__ = (
     'TagsMixin',
     'TagsMixin',
 )
 )
 
 
+if TYPE_CHECKING:
+    from .types import ImageAttachmentType, JournalEntryType, ObjectChangeType, TagType
+    from tenancy.graphql.types import ContactAssignmentType
+
 
 
+@strawberry.type
 class ChangelogMixin:
 class ChangelogMixin:
-    changelog = graphene.List('extras.graphql.types.ObjectChangeType')
 
 
-    def resolve_changelog(self, info):
+    @strawberry_django.field
+    def changelog(self, info) -> List[Annotated["ObjectChangeType", strawberry.lazy('.types')]]:
         content_type = ContentType.objects.get_for_model(self)
         content_type = ContentType.objects.get_for_model(self)
         object_changes = ObjectChange.objects.filter(
         object_changes = ObjectChange.objects.filter(
             changed_object_type=content_type,
             changed_object_type=content_type,
             changed_object_id=self.pk
             changed_object_id=self.pk
         )
         )
-        return object_changes.restrict(info.context.user, 'view')
+        return object_changes.restrict(info.context.request.user, 'view')
 
 
 
 
+@strawberry.type
 class ConfigContextMixin:
 class ConfigContextMixin:
-    config_context = GenericScalar()
 
 
-    def resolve_config_context(self, info):
+    @strawberry_django.field
+    def config_context(self) -> strawberry.scalars.JSON:
         return self.get_config_context()
         return self.get_config_context()
 
 
 
 
+@strawberry.type
 class CustomFieldsMixin:
 class CustomFieldsMixin:
-    custom_fields = GenericScalar()
 
 
-    def resolve_custom_fields(self, info):
+    @strawberry_django.field
+    def custom_fields(self) -> strawberry.scalars.JSON:
         return self.custom_field_data
         return self.custom_field_data
 
 
 
 
+@strawberry.type
 class ImageAttachmentsMixin:
 class ImageAttachmentsMixin:
-    image_attachments = graphene.List('extras.graphql.types.ImageAttachmentType')
 
 
-    def resolve_image_attachments(self, info):
-        return self.images.restrict(info.context.user, 'view')
+    @strawberry_django.field
+    def image_attachments(self, info) -> List[Annotated["ImageAttachmentType", strawberry.lazy('.types')]]:
+        return self.images.restrict(info.context.request.user, 'view')
 
 
 
 
+@strawberry.type
 class JournalEntriesMixin:
 class JournalEntriesMixin:
-    journal_entries = graphene.List('extras.graphql.types.JournalEntryType')
 
 
-    def resolve_journal_entries(self, info):
-        return self.journal_entries.restrict(info.context.user, 'view')
+    @strawberry_django.field
+    def journal_entries(self, info) -> List[Annotated["JournalEntryType", strawberry.lazy('.types')]]:
+        return self.journal_entries.all()
 
 
 
 
+@strawberry.type
 class TagsMixin:
 class TagsMixin:
-    tags = graphene.List('extras.graphql.types.TagType')
 
 
-    def resolve_tags(self, info):
+    @strawberry_django.field
+    def tags(self) -> List[Annotated["TagType", strawberry.lazy('.types')]]:
         return self.tags.all()
         return self.tags.all()
 
 
 
 
+@strawberry.type
 class ContactsMixin:
 class ContactsMixin:
-    contacts = graphene.List('tenancy.graphql.types.ContactAssignmentType')
 
 
-    def resolve_contacts(self, info):
+    @strawberry_django.field
+    def contacts(self) -> List[Annotated["ContactAssignmentType", strawberry.lazy('tenancy.graphql.types')]]:
         return list(self.contacts.all())
         return list(self.contacts.all())

+ 65 - 75
netbox/extras/graphql/schema.py

@@ -1,80 +1,70 @@
-import graphene
+from typing import List
+
+import strawberry
+import strawberry_django
 
 
 from extras import models
 from extras import models
-from netbox.graphql.fields import ObjectField, ObjectListField
 from .types import *
 from .types import *
-from utilities.graphql_optimizer import gql_query_optimizer
-
-
-class ExtrasQuery(graphene.ObjectType):
-    config_context = ObjectField(ConfigContextType)
-    config_context_list = ObjectListField(ConfigContextType)
-
-    def resolve_config_context_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ConfigContext.objects.all(), info)
-
-    config_template = ObjectField(ConfigTemplateType)
-    config_template_list = ObjectListField(ConfigTemplateType)
-
-    def resolve_config_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ConfigTemplate.objects.all(), info)
-
-    custom_field = ObjectField(CustomFieldType)
-    custom_field_list = ObjectListField(CustomFieldType)
-
-    def resolve_custom_field_list(root, info, **kwargs):
-        return gql_query_optimizer(models.CustomField.objects.all(), info)
-
-    custom_field_choice_set = ObjectField(CustomFieldChoiceSetType)
-    custom_field_choice_set_list = ObjectListField(CustomFieldChoiceSetType)
-
-    def resolve_custom_field_choices_list(root, info, **kwargs):
-        return gql_query_optimizer(models.CustomFieldChoiceSet.objects.all(), info)
-
-    custom_link = ObjectField(CustomLinkType)
-    custom_link_list = ObjectListField(CustomLinkType)
-
-    def resolve_custom_link_list(root, info, **kwargs):
-        return gql_query_optimizer(models.CustomLink.objects.all(), info)
-
-    export_template = ObjectField(ExportTemplateType)
-    export_template_list = ObjectListField(ExportTemplateType)
-
-    def resolve_export_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ExportTemplate.objects.all(), info)
-
-    image_attachment = ObjectField(ImageAttachmentType)
-    image_attachment_list = ObjectListField(ImageAttachmentType)
-
-    def resolve_image_attachment_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ImageAttachment.objects.all(), info)
-
-    saved_filter = ObjectField(SavedFilterType)
-    saved_filter_list = ObjectListField(SavedFilterType)
-
-    def resolve_saved_filter_list(root, info, **kwargs):
-        return gql_query_optimizer(models.SavedFilter.objects.all(), info)
-
-    journal_entry = ObjectField(JournalEntryType)
-    journal_entry_list = ObjectListField(JournalEntryType)
-
-    def resolve_journal_entry_list(root, info, **kwargs):
-        return gql_query_optimizer(models.JournalEntry.objects.all(), info)
-
-    tag = ObjectField(TagType)
-    tag_list = ObjectListField(TagType)
-
-    def resolve_tag_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Tag.objects.all(), info)
-
-    webhook = ObjectField(WebhookType)
-    webhook_list = ObjectListField(WebhookType)
-
-    def resolve_webhook_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Webhook.objects.all(), info)
 
 
-    event_rule = ObjectField(EventRuleType)
-    event_rule_list = ObjectListField(EventRuleType)
 
 
-    def resolve_eventrule_list(root, info, **kwargs):
-        return gql_query_optimizer(models.EventRule.objects.all(), info)
+@strawberry.type
+class ExtrasQuery:
+    @strawberry.field
+    def config_context(self, id: int) -> ConfigContextType:
+        return models.ConfigContext.objects.get(pk=id)
+    config_context_list: List[ConfigContextType] = strawberry_django.field()
+
+    @strawberry.field
+    def config_template(self, id: int) -> ConfigTemplateType:
+        return models.ConfigTemplate.objects.get(pk=id)
+    config_template_list: List[ConfigTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def custom_field(self, id: int) -> CustomFieldType:
+        return models.CustomField.objects.get(pk=id)
+    custom_field_list: List[CustomFieldType] = strawberry_django.field()
+
+    @strawberry.field
+    def custom_field_choice_set(self, id: int) -> CustomFieldChoiceSetType:
+        return models.CustomFieldChoiceSet.objects.get(pk=id)
+    custom_field_choice_set_list: List[CustomFieldChoiceSetType] = strawberry_django.field()
+
+    @strawberry.field
+    def custom_link(self, id: int) -> CustomLinkType:
+        return models.CustomLink.objects.get(pk=id)
+    custom_link_list: List[CustomLinkType] = strawberry_django.field()
+
+    @strawberry.field
+    def export_template(self, id: int) -> ExportTemplateType:
+        return models.ExportTemplate.objects.get(pk=id)
+    export_template_list: List[ExportTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def image_attachment(self, id: int) -> ImageAttachmentType:
+        return models.ImageAttachment.objects.get(pk=id)
+    image_attachment_list: List[ImageAttachmentType] = strawberry_django.field()
+
+    @strawberry.field
+    def saved_filter(self, id: int) -> SavedFilterType:
+        return models.SavedFilter.objects.get(pk=id)
+    saved_filter_list: List[SavedFilterType] = strawberry_django.field()
+
+    @strawberry.field
+    def journal_entry(self, id: int) -> JournalEntryType:
+        return models.JournalEntry.objects.get(pk=id)
+    journal_entry_list: List[JournalEntryType] = strawberry_django.field()
+
+    @strawberry.field
+    def tag(self, id: int) -> TagType:
+        return models.Tag.objects.get(pk=id)
+    tag_list: List[TagType] = strawberry_django.field()
+
+    @strawberry.field
+    def webhook(self, id: int) -> WebhookType:
+        return models.Webhook.objects.get(pk=id)
+    webhook_list: List[WebhookType] = strawberry_django.field()
+
+    @strawberry.field
+    def event_rule(self, id: int) -> EventRuleType:
+        return models.EventRule.objects.get(pk=id)
+    event_rule_list: List[EventRuleType] = strawberry_django.field()

+ 167 - 63
netbox/extras/graphql/types.py

@@ -1,6 +1,12 @@
-from extras import filtersets, models
+from typing import Annotated, List
+
+import strawberry
+import strawberry_django
+
+from extras import models
 from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
 from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
-from netbox.graphql.types import BaseObjectType, ObjectType, OrganizationalObjectType
+from netbox.graphql.types import BaseObjectType, ContentTypeType, ObjectType, OrganizationalObjectType
+from .filters import *
 
 
 __all__ = (
 __all__ = (
     'ConfigContextType',
     'ConfigContextType',
@@ -19,104 +25,202 @@ __all__ = (
 )
 )
 
 
 
 
+@strawberry_django.type(
+    models.ConfigContext,
+    fields='__all__',
+    filters=ConfigContextFilter
+)
 class ConfigContextType(ObjectType):
 class ConfigContextType(ObjectType):
+    data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
+    data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
 
 
-    class Meta:
-        model = models.ConfigContext
-        fields = '__all__'
-        filterset_class = filtersets.ConfigContextFilterSet
+    @strawberry_django.field
+    def roles(self) -> List[Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.roles.all()
 
 
+    @strawberry_django.field
+    def device_types(self) -> List[Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.device_types.all()
 
 
-class ConfigTemplateType(TagsMixin, ObjectType):
+    @strawberry_django.field
+    def tags(self) -> List[Annotated["TagType", strawberry.lazy('extras.graphql.types')]]:
+        return self.tags.all()
 
 
-    class Meta:
-        model = models.ConfigTemplate
-        fields = '__all__'
-        filterset_class = filtersets.ConfigTemplateFilterSet
+    @strawberry_django.field
+    def platforms(self) -> List[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.platforms.all()
 
 
+    @strawberry_django.field
+    def regions(self) -> List[Annotated["RegionType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.regions.all()
 
 
-class CustomFieldType(ObjectType):
+    @strawberry_django.field
+    def cluster_groups(self) -> List[Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.cluster_groups.all()
 
 
-    class Meta:
-        model = models.CustomField
-        fields = '__all__'
-        filterset_class = filtersets.CustomFieldFilterSet
+    @strawberry_django.field
+    def tenant_groups(self) -> List[Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')]]:
+        return self.tenant_groups.all()
 
 
+    @strawberry_django.field
+    def cluster_types(self) -> List[Annotated["ClusterTypeType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.cluster_types.all()
 
 
-class CustomFieldChoiceSetType(ObjectType):
+    @strawberry_django.field
+    def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.clusters.all()
 
 
-    class Meta:
-        model = models.CustomFieldChoiceSet
-        fields = '__all__'
-        filterset_class = filtersets.CustomFieldChoiceSetFilterSet
+    @strawberry_django.field
+    def locations(self) -> List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.locations.all()
 
 
+    @strawberry_django.field
+    def sites(self) -> List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.sites.all()
 
 
-class CustomLinkType(ObjectType):
+    @strawberry_django.field
+    def tenants(self) -> List[Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')]]:
+        return self.tenants.all()
 
 
-    class Meta:
-        model = models.CustomLink
-        fields = '__all__'
-        filterset_class = filtersets.CustomLinkFilterSet
+    @strawberry_django.field
+    def site_groups(self) -> List[Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.site_groups.all()
 
 
 
 
-class EventRuleType(OrganizationalObjectType):
+@strawberry_django.type(
+    models.ConfigTemplate,
+    fields='__all__',
+    filters=ConfigTemplateFilter
+)
+class ConfigTemplateType(TagsMixin, ObjectType):
+    data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
+    data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
 
 
-    class Meta:
-        model = models.EventRule
-        fields = '__all__'
-        filterset_class = filtersets.EventRuleFilterSet
+    @strawberry_django.field
+    def virtualmachines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.virtualmachines.all()
 
 
+    @strawberry_django.field
+    def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devices.all()
 
 
-class ExportTemplateType(ObjectType):
+    @strawberry_django.field
+    def platforms(self) -> List[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.platforms.all()
 
 
-    class Meta:
-        model = models.ExportTemplate
-        fields = '__all__'
-        filterset_class = filtersets.ExportTemplateFilterSet
+    @strawberry_django.field
+    def device_roles(self) -> List[Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.device_roles.all()
 
 
 
 
-class ImageAttachmentType(BaseObjectType):
+@strawberry_django.type(
+    models.CustomField,
+    fields='__all__',
+    filters=CustomFieldFilter
+)
+class CustomFieldType(ObjectType):
+    related_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
+    choice_set: Annotated["CustomFieldChoiceSetType", strawberry.lazy('extras.graphql.types')] | None
 
 
-    class Meta:
-        model = models.ImageAttachment
-        fields = '__all__'
-        filterset_class = filtersets.ImageAttachmentFilterSet
 
 
+@strawberry_django.type(
+    models.CustomFieldChoiceSet,
+    exclude=('extra_choices', ),
+    filters=CustomFieldChoiceSetFilter
+)
+class CustomFieldChoiceSetType(ObjectType):
+
+    @strawberry_django.field
+    def choices_for(self) -> List[Annotated["CustomFieldType", strawberry.lazy('extras.graphql.types')]]:
+        return self.choices_for.all()
 
 
-class JournalEntryType(CustomFieldsMixin, TagsMixin, ObjectType):
+    @strawberry_django.field
+    def extra_choices(self) -> List[str] | None:
+        return list(self.extra_choices)
 
 
-    class Meta:
-        model = models.JournalEntry
-        fields = '__all__'
-        filterset_class = filtersets.JournalEntryFilterSet
 
 
+@strawberry_django.type(
+    models.CustomLink,
+    fields='__all__',
+    filters=CustomLinkFilter
+)
+class CustomLinkType(ObjectType):
+    pass
 
 
-class ObjectChangeType(BaseObjectType):
 
 
-    class Meta:
-        model = models.ObjectChange
-        fields = '__all__'
-        filterset_class = filtersets.ObjectChangeFilterSet
+@strawberry_django.type(
+    models.ExportTemplate,
+    fields='__all__',
+    filters=ExportTemplateFilter
+)
+class ExportTemplateType(ObjectType):
+    data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
+    data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
 
 
 
 
-class SavedFilterType(ObjectType):
+@strawberry_django.type(
+    models.ImageAttachment,
+    fields='__all__',
+    filters=ImageAttachmentFilter
+)
+class ImageAttachmentType(BaseObjectType):
+    object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
 
 
-    class Meta:
-        model = models.SavedFilter
-        fields = '__all__'
-        filterset_class = filtersets.SavedFilterFilterSet
 
 
+@strawberry_django.type(
+    models.JournalEntry,
+    fields='__all__',
+    filters=JournalEntryFilter
+)
+class JournalEntryType(CustomFieldsMixin, TagsMixin, ObjectType):
+    assigned_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
+    created_by: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
+
+
+@strawberry_django.type(
+    models.ObjectChange,
+    fields='__all__',
+    filters=ObjectChangeFilter
+)
+class ObjectChangeType(BaseObjectType):
+    pass
 
 
+
+@strawberry_django.type(
+    models.SavedFilter,
+    exclude=['content_types',],
+    filters=SavedFilterFilter
+)
+class SavedFilterType(ObjectType):
+    user: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
+
+
+@strawberry_django.type(
+    models.Tag,
+    exclude=['extras_taggeditem_items', ],
+    filters=TagFilter
+)
 class TagType(ObjectType):
 class TagType(ObjectType):
+    color: str
 
 
-    class Meta:
-        model = models.Tag
-        exclude = ('extras_taggeditem_items',)
-        filterset_class = filtersets.TagFilterSet
+    @strawberry_django.field
+    def object_types(self) -> List[ContentTypeType]:
+        return self.object_types.all()
 
 
 
 
+@strawberry_django.type(
+    models.Webhook,
+    exclude=['content_types',],
+    filters=WebhookFilter
+)
 class WebhookType(OrganizationalObjectType):
 class WebhookType(OrganizationalObjectType):
+    pass
+
 
 
-    class Meta:
-        model = models.Webhook
-        filterset_class = filtersets.WebhookFilterSet
+@strawberry_django.type(
+    models.EventRule,
+    exclude=['content_types',],
+    filters=EventRuleFilter
+)
+class EventRuleType(OrganizationalObjectType):
+    action_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None

+ 119 - 0
netbox/ipam/graphql/filters.py

@@ -0,0 +1,119 @@
+import strawberry_django
+
+from ipam import filtersets, models
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+
+__all__ = (
+    'ASNFilter',
+    'ASNRangeFilter',
+    'AggregateFilter',
+    'FHRPGroupFilter',
+    'FHRPGroupAssignmentFilter',
+    'IPAddressFilter',
+    'IPRangeFilter',
+    'PrefixFilter',
+    'RIRFilter',
+    'RoleFilter',
+    'RouteTargetFilter',
+    'ServiceFilter',
+    'ServiceTemplateFilter',
+    'VLANFilter',
+    'VLANGroupFilter',
+    'VRFFilter',
+)
+
+
+@strawberry_django.filter(models.ASN, lookups=True)
+@autotype_decorator(filtersets.ASNFilterSet)
+class ASNFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ASNRange, lookups=True)
+@autotype_decorator(filtersets.ASNRangeFilterSet)
+class ASNRangeFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Aggregate, lookups=True)
+@autotype_decorator(filtersets.AggregateFilterSet)
+class AggregateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.FHRPGroup, lookups=True)
+@autotype_decorator(filtersets.FHRPGroupFilterSet)
+class FHRPGroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.FHRPGroupAssignment, lookups=True)
+@autotype_decorator(filtersets.FHRPGroupAssignmentFilterSet)
+class FHRPGroupAssignmentFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.IPAddress, lookups=True)
+@autotype_decorator(filtersets.IPAddressFilterSet)
+class IPAddressFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.IPRange, lookups=True)
+@autotype_decorator(filtersets.IPRangeFilterSet)
+class IPRangeFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Prefix, lookups=True)
+@autotype_decorator(filtersets.PrefixFilterSet)
+class PrefixFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.RIR, lookups=True)
+@autotype_decorator(filtersets.RIRFilterSet)
+class RIRFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Role, lookups=True)
+@autotype_decorator(filtersets.RoleFilterSet)
+class RoleFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.RouteTarget, lookups=True)
+@autotype_decorator(filtersets.RouteTargetFilterSet)
+class RouteTargetFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Service, lookups=True)
+@autotype_decorator(filtersets.ServiceFilterSet)
+class ServiceFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ServiceTemplate, lookups=True)
+@autotype_decorator(filtersets.ServiceTemplateFilterSet)
+class ServiceTemplateFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.VLAN, lookups=True)
+@autotype_decorator(filtersets.VLANFilterSet)
+class VLANFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.VLANGroup, lookups=True)
+@autotype_decorator(filtersets.VLANGroupFilterSet)
+class VLANGroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.VRF, lookups=True)
+@autotype_decorator(filtersets.VRFFilterSet)
+class VRFFilter(BaseFilterMixin):
+    pass

+ 0 - 95
netbox/ipam/graphql/gfk_mixins.py

@@ -1,95 +0,0 @@
-import graphene
-from dcim.graphql.types import (
-    InterfaceType,
-    LocationType,
-    RackType,
-    RegionType,
-    SiteGroupType,
-    SiteType,
-)
-from dcim.models import Interface, Location, Rack, Region, Site, SiteGroup
-from ipam.graphql.types import FHRPGroupType, VLANType
-from ipam.models import VLAN, FHRPGroup
-from virtualization.graphql.types import ClusterGroupType, ClusterType, VMInterfaceType
-from virtualization.models import Cluster, ClusterGroup, VMInterface
-
-
-class IPAddressAssignmentType(graphene.Union):
-    class Meta:
-        types = (
-            InterfaceType,
-            FHRPGroupType,
-            VMInterfaceType,
-        )
-
-    @classmethod
-    def resolve_type(cls, instance, info):
-        if type(instance) is Interface:
-            return InterfaceType
-        if type(instance) is FHRPGroup:
-            return FHRPGroupType
-        if type(instance) is VMInterface:
-            return VMInterfaceType
-
-
-class L2VPNAssignmentType(graphene.Union):
-    class Meta:
-        types = (
-            InterfaceType,
-            VLANType,
-            VMInterfaceType,
-        )
-
-    @classmethod
-    def resolve_type(cls, instance, info):
-        if type(instance) is Interface:
-            return InterfaceType
-        if type(instance) is VLAN:
-            return VLANType
-        if type(instance) is VMInterface:
-            return VMInterfaceType
-
-
-class FHRPGroupInterfaceType(graphene.Union):
-    class Meta:
-        types = (
-            InterfaceType,
-            VMInterfaceType,
-        )
-
-    @classmethod
-    def resolve_type(cls, instance, info):
-        if type(instance) is Interface:
-            return InterfaceType
-        if type(instance) is VMInterface:
-            return VMInterfaceType
-
-
-class VLANGroupScopeType(graphene.Union):
-    class Meta:
-        types = (
-            ClusterType,
-            ClusterGroupType,
-            LocationType,
-            RackType,
-            RegionType,
-            SiteType,
-            SiteGroupType,
-        )
-
-    @classmethod
-    def resolve_type(cls, instance, info):
-        if type(instance) is Cluster:
-            return ClusterType
-        if type(instance) is ClusterGroup:
-            return ClusterGroupType
-        if type(instance) is Location:
-            return LocationType
-        if type(instance) is Rack:
-            return RackType
-        if type(instance) is Region:
-            return RegionType
-        if type(instance) is Site:
-            return SiteType
-        if type(instance) is SiteGroup:
-            return SiteGroupType

+ 12 - 9
netbox/ipam/graphql/mixins.py

@@ -1,4 +1,7 @@
-import graphene
+from typing import Annotated, List
+
+import strawberry
+import strawberry_django
 
 
 __all__ = (
 __all__ = (
     'IPAddressesMixin',
     'IPAddressesMixin',
@@ -6,15 +9,15 @@ __all__ = (
 )
 )
 
 
 
 
+@strawberry.type
 class IPAddressesMixin:
 class IPAddressesMixin:
-    ip_addresses = graphene.List('ipam.graphql.types.IPAddressType')
-
-    def resolve_ip_addresses(self, info):
-        return self.ip_addresses.restrict(info.context.user, 'view')
+    @strawberry_django.field
+    def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.ip_addresses.all()
 
 
 
 
+@strawberry.type
 class VLANGroupsMixin:
 class VLANGroupsMixin:
-    vlan_groups = graphene.List('ipam.graphql.types.VLANGroupType')
-
-    def resolve_vlan_groups(self, info):
-        return self.vlan_groups.restrict(info.context.user, 'view')
+    @strawberry_django.field
+    def vlan_groups(self) -> List[Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.vlan_groups.all()

+ 85 - 99
netbox/ipam/graphql/schema.py

@@ -1,104 +1,90 @@
-import graphene
+from typing import List
+
+import strawberry
+import strawberry_django
 
 
 from ipam import models
 from ipam import models
-from netbox.graphql.fields import ObjectField, ObjectListField
-from utilities.graphql_optimizer import gql_query_optimizer
 from .types import *
 from .types import *
 
 
 
 
-class IPAMQuery(graphene.ObjectType):
-    asn = ObjectField(ASNType)
-    asn_list = ObjectListField(ASNType)
-
-    def resolve_asn_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ASN.objects.all(), info)
-
-    asn_range = ObjectField(ASNRangeType)
-    asn_range_list = ObjectListField(ASNRangeType)
-
-    def resolve_asn_range_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ASNRange.objects.all(), info)
-
-    aggregate = ObjectField(AggregateType)
-    aggregate_list = ObjectListField(AggregateType)
-
-    def resolve_aggregate_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Aggregate.objects.all(), info)
-
-    ip_address = ObjectField(IPAddressType)
-    ip_address_list = ObjectListField(IPAddressType)
-
-    def resolve_ip_address_list(root, info, **kwargs):
-        return gql_query_optimizer(models.IPAddress.objects.all(), info)
-
-    ip_range = ObjectField(IPRangeType)
-    ip_range_list = ObjectListField(IPRangeType)
-
-    def resolve_ip_range_list(root, info, **kwargs):
-        return gql_query_optimizer(models.IPRange.objects.all(), info)
-
-    prefix = ObjectField(PrefixType)
-    prefix_list = ObjectListField(PrefixType)
-
-    def resolve_prefix_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Prefix.objects.all(), info)
-
-    rir = ObjectField(RIRType)
-    rir_list = ObjectListField(RIRType)
-
-    def resolve_rir_list(root, info, **kwargs):
-        return gql_query_optimizer(models.RIR.objects.all(), info)
-
-    role = ObjectField(RoleType)
-    role_list = ObjectListField(RoleType)
-
-    def resolve_role_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Role.objects.all(), info)
-
-    route_target = ObjectField(RouteTargetType)
-    route_target_list = ObjectListField(RouteTargetType)
-
-    def resolve_route_target_list(root, info, **kwargs):
-        return gql_query_optimizer(models.RouteTarget.objects.all(), info)
-
-    service = ObjectField(ServiceType)
-    service_list = ObjectListField(ServiceType)
-
-    def resolve_service_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Service.objects.all(), info)
-
-    service_template = ObjectField(ServiceTemplateType)
-    service_template_list = ObjectListField(ServiceTemplateType)
-
-    def resolve_service_template_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ServiceTemplate.objects.all(), info)
-
-    fhrp_group = ObjectField(FHRPGroupType)
-    fhrp_group_list = ObjectListField(FHRPGroupType)
-
-    def resolve_fhrp_group_list(root, info, **kwargs):
-        return gql_query_optimizer(models.FHRPGroup.objects.all(), info)
-
-    fhrp_group_assignment = ObjectField(FHRPGroupAssignmentType)
-    fhrp_group_assignment_list = ObjectListField(FHRPGroupAssignmentType)
-
-    def resolve_fhrp_group_assignment_list(root, info, **kwargs):
-        return gql_query_optimizer(models.FHRPGroupAssignment.objects.all(), info)
-
-    vlan = ObjectField(VLANType)
-    vlan_list = ObjectListField(VLANType)
-
-    def resolve_vlan_list(root, info, **kwargs):
-        return gql_query_optimizer(models.VLAN.objects.all(), info)
-
-    vlan_group = ObjectField(VLANGroupType)
-    vlan_group_list = ObjectListField(VLANGroupType)
-
-    def resolve_vlan_group_list(root, info, **kwargs):
-        return gql_query_optimizer(models.VLANGroup.objects.all(), info)
-
-    vrf = ObjectField(VRFType)
-    vrf_list = ObjectListField(VRFType)
-
-    def resolve_vrf_list(root, info, **kwargs):
-        return gql_query_optimizer(models.VRF.objects.all(), info)
+@strawberry.type
+class IPAMQuery:
+    @strawberry.field
+    def asn(self, id: int) -> ASNType:
+        return models.ASN.objects.get(pk=id)
+    asn_list: List[ASNType] = strawberry_django.field()
+
+    @strawberry.field
+    def asn_range(self, id: int) -> ASNRangeType:
+        return models.ASNRange.objects.get(pk=id)
+    asn_range_list: List[ASNRangeType] = strawberry_django.field()
+
+    @strawberry.field
+    def aggregate(self, id: int) -> AggregateType:
+        return models.Aggregate.objects.get(pk=id)
+    aggregate_list: List[AggregateType] = strawberry_django.field()
+
+    @strawberry.field
+    def ip_address(self, id: int) -> IPAddressType:
+        return models.IPAddress.objects.get(pk=id)
+    ip_address_list: List[IPAddressType] = strawberry_django.field()
+
+    @strawberry.field
+    def ip_range(self, id: int) -> IPRangeType:
+        return models.IPRange.objects.get(pk=id)
+    ip_range_list: List[IPRangeType] = strawberry_django.field()
+
+    @strawberry.field
+    def prefix(self, id: int) -> PrefixType:
+        return models.Prefix.objects.get(pk=id)
+    prefix_list: List[PrefixType] = strawberry_django.field()
+
+    @strawberry.field
+    def rir(self, id: int) -> RIRType:
+        return models.RIR.objects.get(pk=id)
+    rir_list: List[RIRType] = strawberry_django.field()
+
+    @strawberry.field
+    def role(self, id: int) -> RoleType:
+        return models.Role.objects.get(pk=id)
+    role_list: List[RoleType] = strawberry_django.field()
+
+    @strawberry.field
+    def route_target(self, id: int) -> RouteTargetType:
+        return models.RouteTarget.objects.get(pk=id)
+    route_target_list: List[RouteTargetType] = strawberry_django.field()
+
+    @strawberry.field
+    def service(self, id: int) -> ServiceType:
+        return models.Service.objects.get(pk=id)
+    service_list: List[ServiceType] = strawberry_django.field()
+
+    @strawberry.field
+    def service_template(self, id: int) -> ServiceTemplateType:
+        return models.ServiceTemplate.objects.get(pk=id)
+    service_template_list: List[ServiceTemplateType] = strawberry_django.field()
+
+    @strawberry.field
+    def fhrp_group(self, id: int) -> FHRPGroupType:
+        return models.FHRPGroup.objects.get(pk=id)
+    fhrp_group_list: List[FHRPGroupType] = strawberry_django.field()
+
+    @strawberry.field
+    def fhrp_group_assignment(self, id: int) -> FHRPGroupAssignmentType:
+        return models.FHRPGroupAssignment.objects.get(pk=id)
+    fhrp_group_assignment_list: List[FHRPGroupAssignmentType] = strawberry_django.field()
+
+    @strawberry.field
+    def vlan(self, id: int) -> VLANType:
+        return models.VLAN.objects.get(pk=id)
+    vlan_list: List[VLANType] = strawberry_django.field()
+
+    @strawberry.field
+    def vlan_group(self, id: int) -> VLANGroupType:
+        return models.VLANGroup.objects.get(pk=id)
+    vlan_group_list: List[VLANGroupType] = strawberry_django.field()
+
+    @strawberry.field
+    def vrf(self, id: int) -> VRFType:
+        return models.VRF.objects.get(pk=id)
+    vrf_list: List[VRFType] = strawberry_django.field()

+ 278 - 101
netbox/ipam/graphql/types.py

@@ -1,9 +1,15 @@
-import graphene
+from typing import Annotated, List, Union
 
 
-from ipam import filtersets, models
-from .mixins import IPAddressesMixin
+import strawberry
+import strawberry_django
+
+from circuits.graphql.types import ProviderType
+from dcim.graphql.types import SiteType
+from ipam import models
 from netbox.graphql.scalars import BigInt
 from netbox.graphql.scalars import BigInt
-from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
+from netbox.graphql.types import BaseObjectType, NetBoxObjectType, OrganizationalObjectType
+from .filters import *
+from .mixins import IPAddressesMixin
 
 
 __all__ = (
 __all__ = (
     'ASNType',
     'ASNType',
@@ -25,164 +31,335 @@ __all__ = (
 )
 )
 
 
 
 
-class IPAddressFamilyType(graphene.ObjectType):
-
-    value = graphene.Int()
-    label = graphene.String()
-
-    def __init__(self, value):
-        self.value = value
-        self.label = f'IPv{value}'
+@strawberry.type
+class IPAddressFamilyType:
+    value: int
+    label: str
 
 
 
 
+@strawberry.type
 class BaseIPAddressFamilyType:
 class BaseIPAddressFamilyType:
     """
     """
     Base type for models that need to expose their IPAddress family type.
     Base type for models that need to expose their IPAddress family type.
     """
     """
-    family = graphene.Field(IPAddressFamilyType)
 
 
-    def resolve_family(self, _):
+    @strawberry.field
+    def family(self) -> IPAddressFamilyType:
         # Note that self, is an instance of models.IPAddress
         # Note that self, is an instance of models.IPAddress
         # thus resolves to the address family value.
         # thus resolves to the address family value.
-        return IPAddressFamilyType(self.family)
+        return IPAddressFamilyType(value=self.family, label=f'IPv{self.family}')
 
 
 
 
+@strawberry_django.type(
+    models.ASN,
+    fields='__all__',
+    filters=ASNFilter
+)
 class ASNType(NetBoxObjectType):
 class ASNType(NetBoxObjectType):
-    asn = graphene.Field(BigInt)
+    asn: BigInt
+    rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
-    class Meta:
-        model = models.ASN
-        fields = '__all__'
-        filterset_class = filtersets.ASNFilterSet
+    @strawberry_django.field
+    def sites(self) -> List[SiteType]:
+        return self.sites.all()
 
 
+    @strawberry_django.field
+    def providers(self) -> List[ProviderType]:
+        return self.providers.all()
 
 
-class ASNRangeType(NetBoxObjectType):
 
 
-    class Meta:
-        model = models.ASNRange
-        fields = '__all__'
-        filterset_class = filtersets.ASNRangeFilterSet
+@strawberry_django.type(
+    models.ASNRange,
+    fields='__all__',
+    filters=ASNRangeFilter
+)
+class ASNRangeType(NetBoxObjectType):
+    start: BigInt
+    end: BigInt
+    rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
 
 
+@strawberry_django.type(
+    models.Aggregate,
+    fields='__all__',
+    filters=AggregateFilter
+)
 class AggregateType(NetBoxObjectType, BaseIPAddressFamilyType):
 class AggregateType(NetBoxObjectType, BaseIPAddressFamilyType):
-
-    class Meta:
-        model = models.Aggregate
-        fields = '__all__'
-        filterset_class = filtersets.AggregateFilterSet
+    prefix: str
+    rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
 
 
+@strawberry_django.type(
+    models.FHRPGroup,
+    fields='__all__',
+    filters=FHRPGroupFilter
+)
 class FHRPGroupType(NetBoxObjectType, IPAddressesMixin):
 class FHRPGroupType(NetBoxObjectType, IPAddressesMixin):
 
 
-    class Meta:
-        model = models.FHRPGroup
-        fields = '__all__'
-        filterset_class = filtersets.FHRPGroupFilterSet
-
-    def resolve_auth_type(self, info):
-        return self.auth_type or None
+    @strawberry_django.field
+    def fhrpgroupassignment_set(self) -> List[Annotated["FHRPGroupAssignmentType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.fhrpgroupassignment_set.all()
 
 
 
 
+@strawberry_django.type(
+    models.FHRPGroupAssignment,
+    exclude=('interface_type', 'interface_id'),
+    filters=FHRPGroupAssignmentFilter
+)
 class FHRPGroupAssignmentType(BaseObjectType):
 class FHRPGroupAssignmentType(BaseObjectType):
-    interface = graphene.Field('ipam.graphql.gfk_mixins.FHRPGroupInterfaceType')
+    group: Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')]
 
 
-    class Meta:
-        model = models.FHRPGroupAssignment
-        exclude = ('interface_type', 'interface_id')
-        filterset_class = filtersets.FHRPGroupAssignmentFilterSet
+    @strawberry_django.field
+    def interface(self) -> Annotated[Union[
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
+    ], strawberry.union("FHRPGroupInterfaceType")]:
+        return self.interface
 
 
 
 
+@strawberry_django.type(
+    models.IPAddress,
+    exclude=('assigned_object_type', 'assigned_object_id', 'address'),
+    filters=IPAddressFilter
+)
 class IPAddressType(NetBoxObjectType, BaseIPAddressFamilyType):
 class IPAddressType(NetBoxObjectType, BaseIPAddressFamilyType):
-    assigned_object = graphene.Field('ipam.graphql.gfk_mixins.IPAddressAssignmentType')
-
-    class Meta:
-        model = models.IPAddress
-        exclude = ('assigned_object_type', 'assigned_object_id')
-        filterset_class = filtersets.IPAddressFilterSet
-
-    def resolve_role(self, info):
-        return self.role or None
+    address: str
+    vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    nat_inside: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
+
+    @strawberry_django.field
+    def nat_outside(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.nat_outside.all()
+
+    @strawberry_django.field
+    def tunnel_terminations(self) -> List[Annotated["TunnelTerminationType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.tunnel_terminations.all()
+
+    @strawberry_django.field
+    def services(self) -> List[Annotated["ServiceType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.services.all()
+
+    @strawberry_django.field
+    def assigned_object(self) -> Annotated[Union[
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')],
+        Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
+    ], strawberry.union("IPAddressAssignmentType")]:
+        return self.assigned_object
+
+
+@strawberry_django.type(
+    models.IPRange,
+    fields='__all__',
+    filters=IPRangeFilter
+)
+class IPRangeType(NetBoxObjectType):
+    start_address: str
+    end_address: str
+    vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
 
 
 
 
-class IPRangeType(NetBoxObjectType):
+@strawberry_django.type(
+    models.Prefix,
+    fields='__all__',
+    filters=PrefixFilter
+)
+class PrefixType(NetBoxObjectType, BaseIPAddressFamilyType):
+    prefix: str
+    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
+    vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
+    role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
+
+
+@strawberry_django.type(
+    models.RIR,
+    fields='__all__',
+    filters=RIRFilter
+)
+class RIRType(OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.IPRange
-        fields = '__all__'
-        filterset_class = filtersets.IPRangeFilterSet
+    @strawberry_django.field
+    def asn_ranges(self) -> List[Annotated["ASNRangeType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.asn_ranges.all()
 
 
-    def resolve_role(self, info):
-        return self.role or None
+    @strawberry_django.field
+    def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.asns.all()
 
 
+    @strawberry_django.field
+    def aggregates(self) -> List[Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.aggregates.all()
 
 
-class PrefixType(NetBoxObjectType, BaseIPAddressFamilyType):
 
 
-    class Meta:
-        model = models.Prefix
-        fields = '__all__'
-        filterset_class = filtersets.PrefixFilterSet
+@strawberry_django.type(
+    models.Role,
+    fields='__all__',
+    filters=RoleFilter
+)
+class RoleType(OrganizationalObjectType):
 
 
+    @strawberry_django.field
+    def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.prefixes.all()
 
 
-class RIRType(OrganizationalObjectType):
+    @strawberry_django.field
+    def ip_ranges(self) -> List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.ip_ranges.all()
 
 
-    class Meta:
-        model = models.RIR
-        fields = '__all__'
-        filterset_class = filtersets.RIRFilterSet
+    @strawberry_django.field
+    def vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.vlans.all()
 
 
 
 
-class RoleType(OrganizationalObjectType):
+@strawberry_django.type(
+    models.RouteTarget,
+    fields='__all__',
+    filters=RouteTargetFilter
+)
+class RouteTargetType(NetBoxObjectType):
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
-    class Meta:
-        model = models.Role
-        fields = '__all__'
-        filterset_class = filtersets.RoleFilterSet
+    @strawberry_django.field
+    def exporting_l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.exporting_l2vpns.all()
 
 
+    @strawberry_django.field
+    def exporting_vrfs(self) -> List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.exporting_vrfs.all()
 
 
-class RouteTargetType(NetBoxObjectType):
+    @strawberry_django.field
+    def importing_vrfs(self) -> List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.importing_vrfs.all()
 
 
-    class Meta:
-        model = models.RouteTarget
-        fields = '__all__'
-        filterset_class = filtersets.RouteTargetFilterSet
+    @strawberry_django.field
+    def importing_l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.importing_l2vpns.all()
 
 
 
 
+@strawberry_django.type(
+    models.Service,
+    fields='__all__',
+    filters=ServiceFilter
+)
 class ServiceType(NetBoxObjectType):
 class ServiceType(NetBoxObjectType):
+    ports: List[int]
+    device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
+    virtual_machine: Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')] | None
 
 
-    class Meta:
-        model = models.Service
-        fields = '__all__'
-        filterset_class = filtersets.ServiceFilterSet
+    @strawberry_django.field
+    def ipaddresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.ipaddresses.all()
 
 
 
 
+@strawberry_django.type(
+    models.ServiceTemplate,
+    fields='__all__',
+    filters=ServiceTemplateFilter
+)
 class ServiceTemplateType(NetBoxObjectType):
 class ServiceTemplateType(NetBoxObjectType):
-
-    class Meta:
-        model = models.ServiceTemplate
-        fields = '__all__'
-        filterset_class = filtersets.ServiceTemplateFilterSet
+    ports: List[int]
 
 
 
 
+@strawberry_django.type(
+    models.VLAN,
+    fields='__all__',
+    filters=VLANFilter
+)
 class VLANType(NetBoxObjectType):
 class VLANType(NetBoxObjectType):
+    site: Annotated["SiteType", strawberry.lazy('ipam.graphql.types')] | None
+    group: Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
 
 
-    class Meta:
-        model = models.VLAN
-        fields = '__all__'
-        filterset_class = filtersets.VLANFilterSet
+    @strawberry_django.field
+    def interfaces_as_untagged(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfaces_as_untagged.all()
 
 
+    @strawberry_django.field
+    def vminterfaces_as_untagged(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.vminterfaces_as_untagged.all()
 
 
-class VLANGroupType(OrganizationalObjectType):
-    scope = graphene.Field('ipam.graphql.gfk_mixins.VLANGroupScopeType')
+    @strawberry_django.field
+    def wirelesslan_set(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
+        return self.wirelesslan_set.all()
 
 
-    class Meta:
-        model = models.VLANGroup
-        exclude = ('scope_type', 'scope_id')
-        filterset_class = filtersets.VLANGroupFilterSet
+    @strawberry_django.field
+    def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.prefixes.all()
 
 
+    @strawberry_django.field
+    def interfaces_as_tagged(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfaces_as_tagged.all()
 
 
+    @strawberry_django.field
+    def vminterfaces_as_tagged(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.vminterfaces_as_tagged.all()
+
+
+@strawberry_django.type(
+    models.VLANGroup,
+    exclude=('scope_type', 'scope_id'),
+    filters=VLANGroupFilter
+)
+class VLANGroupType(OrganizationalObjectType):
+
+    @strawberry_django.field
+    def vlans(self) -> List[VLANType]:
+        return self.vlans.all()
+
+    @strawberry_django.field
+    def scope(self) -> Annotated[Union[
+        Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')],
+        Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')],
+        Annotated["LocationType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["RackType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["RegionType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["SiteType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')],
+    ], strawberry.union("VLANGroupScopeType")]:
+        return self.scope
+
+
+@strawberry_django.type(
+    models.VRF,
+    fields='__all__',
+    filters=VRFFilter
+)
 class VRFType(NetBoxObjectType):
 class VRFType(NetBoxObjectType):
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+
+    @strawberry_django.field
+    def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfaces.all()
+
+    @strawberry_django.field
+    def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.ip_addresses.all()
+
+    @strawberry_django.field
+    def vminterfaces(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.vminterfaces.all()
+
+    @strawberry_django.field
+    def ip_ranges(self) -> List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.ip_ranges.all()
+
+    @strawberry_django.field
+    def export_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.export_targets.all()
+
+    @strawberry_django.field
+    def import_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.import_targets.all()
 
 
-    class Meta:
-        model = models.VRF
-        fields = '__all__'
-        filterset_class = filtersets.VRFFilterSet
+    @strawberry_django.field
+    def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.prefixes.all()

+ 0 - 69
netbox/netbox/graphql/__init__.py

@@ -1,69 +0,0 @@
-import graphene
-from dcim.fields import MACAddressField, WWNField
-from django.db import models
-from graphene import Dynamic
-from graphene_django.converter import convert_django_field, get_django_field_description
-from graphene_django.fields import DjangoConnectionField
-from ipam.fields import IPAddressField, IPNetworkField
-from taggit.managers import TaggableManager
-
-from .fields import ObjectListField
-
-
-@convert_django_field.register(TaggableManager)
-def convert_field_to_tags_list(field, registry=None):
-    """
-    Register conversion handler for django-taggit's TaggableManager
-    """
-    return graphene.List(graphene.String)
-
-
-@convert_django_field.register(IPAddressField)
-@convert_django_field.register(IPNetworkField)
-@convert_django_field.register(MACAddressField)
-@convert_django_field.register(WWNField)
-def convert_field_to_string(field, registry=None):
-    # TODO: Update to use get_django_field_description under django_graphene v3.0
-    return graphene.String(description=field.help_text, required=not field.null)
-
-
-@convert_django_field.register(models.ManyToManyField)
-@convert_django_field.register(models.ManyToManyRel)
-@convert_django_field.register(models.ManyToOneRel)
-def convert_field_to_list_or_connection(field, registry=None):
-    """
-    From graphene_django.converter.py we need to monkey-patch this to return
-    our ObjectListField with filtering support instead of DjangoListField
-    """
-    model = field.related_model
-
-    def dynamic_type():
-        _type = registry.get_type_for_model(model)
-        if not _type:
-            return
-
-        if isinstance(field, models.ManyToManyField):
-            description = get_django_field_description(field)
-        else:
-            description = get_django_field_description(field.field)
-
-        # If there is a connection, we should transform the field
-        # into a DjangoConnectionField
-        if _type._meta.connection:
-            # Use a DjangoFilterConnectionField if there are
-            # defined filter_fields or a filterset_class in the
-            # DjangoObjectType Meta
-            if _type._meta.filter_fields or _type._meta.filterset_class:
-                from .filter.fields import DjangoFilterConnectionField
-
-                return DjangoFilterConnectionField(_type, required=True, description=description)
-
-            return DjangoConnectionField(_type, required=True, description=description)
-
-        return ObjectListField(
-            _type,
-            required=True,  # A Set is always returned, never None.
-            description=description,
-        )
-
-    return Dynamic(dynamic_type)

+ 0 - 70
netbox/netbox/graphql/fields.py

@@ -1,70 +0,0 @@
-from functools import partial
-
-import graphene
-from graphene_django import DjangoListField
-from .utils import get_graphene_type
-
-__all__ = (
-    'ObjectField',
-    'ObjectListField',
-)
-
-
-class ObjectField(graphene.Field):
-    """
-    Retrieve a single object, identified by its numeric ID.
-    """
-    def __init__(self, *args, **kwargs):
-
-        if 'id' not in kwargs:
-            kwargs['id'] = graphene.Int(required=True)
-
-        super().__init__(*args, **kwargs)
-
-    @staticmethod
-    def object_resolver(django_object_type, root, info, **args):
-        """
-        Return an object given its numeric ID.
-        """
-        manager = django_object_type._meta.model._default_manager
-        queryset = django_object_type.get_queryset(manager, info)
-
-        return queryset.get(**args)
-
-    def get_resolver(self, parent_resolver):
-        return partial(self.object_resolver, self._type)
-
-
-class ObjectListField(DjangoListField):
-    """
-    Retrieve a list of objects, optionally filtered by one or more FilterSet filters.
-    """
-    def __init__(self, _type, *args, **kwargs):
-        filter_kwargs = {}
-
-        # Get FilterSet kwargs
-        filterset_class = getattr(_type._meta, 'filterset_class', None)
-        if filterset_class:
-            for filter_name, filter_field in filterset_class.get_filters().items():
-                field_type = get_graphene_type(type(filter_field))
-                filter_kwargs[filter_name] = graphene.Argument(field_type)
-
-        super().__init__(_type, args=filter_kwargs, *args, **kwargs)
-
-    @staticmethod
-    def list_resolver(django_object_type, resolver, default_manager, root, info, **args):
-        queryset = super(ObjectListField, ObjectListField).list_resolver(django_object_type, resolver, default_manager, root, info, **args)
-
-        # if there are no filter params then don't need to filter
-        if not args:
-            return queryset
-
-        filterset_class = django_object_type._meta.filterset_class
-        if filterset_class:
-            filterset = filterset_class(data=args if args else None, queryset=queryset, request=info.context)
-
-            if not filterset.is_valid():
-                return queryset.none()
-            return filterset.qs
-
-        return queryset

+ 198 - 0
netbox/netbox/graphql/filter_mixins.py

@@ -0,0 +1,198 @@
+from functools import partial, partialmethod, wraps
+from typing import List
+
+import django_filters
+import strawberry
+import strawberry_django
+from strawberry import auto
+from ipam.fields import ASNField
+from netbox.graphql.scalars import BigInt
+from utilities.fields import ColorField, CounterCacheField
+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):
+        should_create_function = True
+        attr_type = List[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
+    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
+    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):
+        should_create_function = True
+        attr_type = List[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__:
+                field = model._meta.get_field(fieldname)
+                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):
+        return self.filterset(data={key: getattr(self, key)}, queryset=queryset).qs

+ 8 - 21
netbox/netbox/graphql/scalars.py

@@ -1,23 +1,10 @@
-from graphene import Scalar
-from graphql.language import ast
-from graphene.types.scalars import MAX_INT, MIN_INT
+from typing import Union
 
 
+import strawberry
 
 
-class BigInt(Scalar):
-    """
-    Handle any BigInts
-    """
-    @staticmethod
-    def to_float(value):
-        num = int(value)
-        if num > MAX_INT or num < MIN_INT:
-            return float(num)
-        return num
-
-    serialize = to_float
-    parse_value = to_float
-
-    @staticmethod
-    def parse_literal(node):
-        if isinstance(node, ast.IntValue):
-            return BigInt.to_float(node.value)
+BigInt = strawberry.scalar(
+    Union[int, str],  # type: ignore
+    serialize=lambda v: int(v),
+    parse_value=lambda v: str(v),
+    description="BigInt field",
+)

+ 11 - 3
netbox/netbox/graphql/schema.py

@@ -1,4 +1,6 @@
-import graphene
+import strawberry
+from strawberry_django.optimizer import DjangoOptimizerExtension
+from strawberry.schema.config import StrawberryConfig
 
 
 from circuits.graphql.schema import CircuitsQuery
 from circuits.graphql.schema import CircuitsQuery
 from core.graphql.schema import CoreQuery
 from core.graphql.schema import CoreQuery
@@ -13,6 +15,7 @@ from vpn.graphql.schema import VPNQuery
 from wireless.graphql.schema import WirelessQuery
 from wireless.graphql.schema import WirelessQuery
 
 
 
 
+@strawberry.type
 class Query(
 class Query(
     UsersQuery,
     UsersQuery,
     CircuitsQuery,
     CircuitsQuery,
@@ -25,9 +28,14 @@ class Query(
     VPNQuery,
     VPNQuery,
     WirelessQuery,
     WirelessQuery,
     *registry['plugins']['graphql_schemas'],  # Append plugin schemas
     *registry['plugins']['graphql_schemas'],  # Append plugin schemas
-    graphene.ObjectType
 ):
 ):
     pass
     pass
 
 
 
 
-schema = graphene.Schema(query=Query, auto_camelcase=False)
+schema = strawberry.Schema(
+    query=Query,
+    config=StrawberryConfig(auto_camel_case=False),
+    extensions=[
+        DjangoOptimizerExtension,
+    ]
+)

+ 33 - 32
netbox/netbox/graphql/types.py

@@ -1,4 +1,8 @@
-import graphene
+from typing import Annotated, List
+
+import strawberry
+from strawberry import auto
+import strawberry_django
 
 
 from core.models import ObjectType as ObjectType_
 from core.models import ObjectType as ObjectType_
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
@@ -8,13 +12,10 @@ from extras.graphql.mixins import (
     JournalEntriesMixin,
     JournalEntriesMixin,
     TagsMixin,
     TagsMixin,
 )
 )
-from graphene_django import DjangoObjectType
 
 
 __all__ = (
 __all__ = (
     'BaseObjectType',
     'BaseObjectType',
-    'ContentTypeType',
     'ObjectType',
     'ObjectType',
-    'ObjectTypeType',
     'OrganizationalObjectType',
     'OrganizationalObjectType',
     'NetBoxObjectType',
     'NetBoxObjectType',
 )
 )
@@ -24,26 +25,27 @@ __all__ = (
 # Base types
 # Base types
 #
 #
 
 
-class BaseObjectType(DjangoObjectType):
+@strawberry.type
+class BaseObjectType:
     """
     """
     Base GraphQL object type for all NetBox objects. Restricts the model queryset to enforce object permissions.
     Base GraphQL object type for all NetBox objects. Restricts the model queryset to enforce object permissions.
     """
     """
-    display = graphene.String()
-    class_type = graphene.String()
-
-    class Meta:
-        abstract = True
 
 
     @classmethod
     @classmethod
-    def get_queryset(cls, queryset, info):
+    def get_queryset(cls, queryset, info, **kwargs):
         # Enforce object permissions on the queryset
         # Enforce object permissions on the queryset
-        return queryset.restrict(info.context.user, 'view')
+        if hasattr(queryset, 'restrict'):
+            return queryset.restrict(info.context.request.user, 'view')
+        else:
+            return queryset
 
 
-    def resolve_display(parent, info, **kwargs):
-        return str(parent)
+    @strawberry_django.field
+    def display(self) -> str:
+        return str(self)
 
 
-    def resolve_class_type(parent, info, **kwargs):
-        return parent.__class__.__name__
+    @strawberry_django.field
+    def class_type(self) -> str:
+        return self.__class__.__name__
 
 
 
 
 class ObjectType(
 class ObjectType(
@@ -53,8 +55,7 @@ class ObjectType(
     """
     """
     Base GraphQL object type for unclassified models which support change logging
     Base GraphQL object type for unclassified models which support change logging
     """
     """
-    class Meta:
-        abstract = True
+    pass
 
 
 
 
 class OrganizationalObjectType(
 class OrganizationalObjectType(
@@ -66,8 +67,7 @@ class OrganizationalObjectType(
     """
     """
     Base type for organizational models
     Base type for organizational models
     """
     """
-    class Meta:
-        abstract = True
+    pass
 
 
 
 
 class NetBoxObjectType(
 class NetBoxObjectType(
@@ -80,23 +80,24 @@ class NetBoxObjectType(
     """
     """
     GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags.
     GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags.
     """
     """
-    class Meta:
-        abstract = True
+    pass
 
 
 
 
 #
 #
 # Miscellaneous types
 # Miscellaneous types
 #
 #
 
 
-class ContentTypeType(DjangoObjectType):
-
-    class Meta:
-        model = ContentType
-        fields = ('id', 'app_label', 'model')
-
+@strawberry_django.type(
+    ContentType,
+    fields=['id', 'app_label', 'model'],
+)
+class ContentTypeType:
+    pass
 
 
-class ObjectTypeType(DjangoObjectType):
 
 
-    class Meta:
-        model = ObjectType_
-        fields = ('id', 'app_label', 'model')
+@strawberry_django.type(
+    ObjectType_,
+    fields=['id', 'app_label', 'model'],
+)
+class ObjectTypeType:
+    pass

+ 0 - 25
netbox/netbox/graphql/utils.py

@@ -1,25 +0,0 @@
-import graphene
-from django_filters import filters
-
-
-def get_graphene_type(filter_cls):
-    """
-    Return the appropriate Graphene scalar type for a django_filters Filter
-    """
-    if issubclass(filter_cls, filters.BooleanFilter):
-        field_type = graphene.Boolean
-    elif issubclass(filter_cls, filters.NumberFilter):
-        # TODO: Floats? BigInts?
-        field_type = graphene.Int
-    elif issubclass(filter_cls, filters.DateFilter):
-        field_type = graphene.Date
-    elif issubclass(filter_cls, filters.DateTimeFilter):
-        field_type = graphene.DateTime
-    else:
-        field_type = graphene.String
-
-    # Multi-value filters should be handled as lists
-    if issubclass(filter_cls, filters.MultipleChoiceFilter):
-        return graphene.List(field_type)
-
-    return field_type

+ 18 - 8
netbox/netbox/graphql/views.py

@@ -1,20 +1,26 @@
+import json
+
 from django.conf import settings
 from django.conf import settings
 from django.contrib.auth.views import redirect_to_login
 from django.contrib.auth.views import redirect_to_login
 from django.http import HttpResponseNotFound, HttpResponseForbidden
 from django.http import HttpResponseNotFound, HttpResponseForbidden
+from django.http import HttpResponse
+from django.template import loader
 from django.urls import reverse
 from django.urls import reverse
-from graphene_django.views import GraphQLView as GraphQLView_
+from django.views.decorators.csrf import csrf_exempt
 from rest_framework.exceptions import AuthenticationFailed
 from rest_framework.exceptions import AuthenticationFailed
+from strawberry.django.views import GraphQLView
 
 
 from netbox.api.authentication import TokenAuthentication
 from netbox.api.authentication import TokenAuthentication
 from netbox.config import get_config
 from netbox.config import get_config
 
 
 
 
-class GraphQLView(GraphQLView_):
+class NetBoxGraphQLView(GraphQLView):
     """
     """
-    Extends graphene_django's GraphQLView to support DRF's token-based authentication.
+    Extends strawberry's GraphQLView to support DRF's token-based authentication.
     """
     """
     graphiql_template = 'graphiql.html'
     graphiql_template = 'graphiql.html'
 
 
+    @csrf_exempt
     def dispatch(self, request, *args, **kwargs):
     def dispatch(self, request, *args, **kwargs):
         config = get_config()
         config = get_config()
 
 
@@ -34,11 +40,15 @@ class GraphQLView(GraphQLView_):
 
 
         # Enforce LOGIN_REQUIRED
         # Enforce LOGIN_REQUIRED
         if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
         if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
-
-            # If this is a human user, send a redirect to the login page
-            if self.request_wants_html(request):
+            if request.accepts("text/html"):
                 return redirect_to_login(reverse('graphql'))
                 return redirect_to_login(reverse('graphql'))
-
-            return HttpResponseForbidden("No credentials provided.")
+            else:
+                return HttpResponseForbidden("No credentials provided.")
 
 
         return super().dispatch(request, *args, **kwargs)
         return super().dispatch(request, *args, **kwargs)
+
+    def render_graphql_ide(self, request):
+        template = loader.get_template("graphiql.html")
+        context = {"SUBSCRIPTION_ENABLED": json.dumps(self.subscriptions_enabled)}
+
+        return HttpResponse(template.render(context, request))

+ 1 - 1
netbox/netbox/plugins/registration.py

@@ -73,7 +73,7 @@ def register_graphql_schema(graphql_schema):
     """
     """
     Register a GraphQL schema class for inclusion in NetBox's GraphQL API.
     Register a GraphQL schema class for inclusion in NetBox's GraphQL API.
     """
     """
-    registry['plugins']['graphql_schemas'].append(graphql_schema)
+    registry['plugins']['graphql_schemas'].extend(graphql_schema)
 
 
 
 
 def register_user_preferences(plugin_name, preferences):
 def register_user_preferences(plugin_name, preferences):

+ 9 - 14
netbox/netbox/settings.py

@@ -365,12 +365,11 @@ INSTALLED_APPS = [
     'django.forms',
     'django.forms',
     'corsheaders',
     'corsheaders',
     'debug_toolbar',
     'debug_toolbar',
-    'graphiql_debug_toolbar',
     'django_filters',
     'django_filters',
     'django_htmx',
     'django_htmx',
     'django_tables2',
     'django_tables2',
     'django_prometheus',
     'django_prometheus',
-    'graphene_django',
+    'strawberry_django',
     'mptt',
     'mptt',
     'rest_framework',
     'rest_framework',
     'social_django',
     'social_django',
@@ -398,7 +397,7 @@ if DJANGO_ADMIN_ENABLED:
 
 
 # Middleware
 # Middleware
 MIDDLEWARE = [
 MIDDLEWARE = [
-    'graphiql_debug_toolbar.middleware.DebugToolbarMiddleware',
+    "strawberry_django.middlewares.debug_toolbar.DebugToolbarMiddleware",
     'django_prometheus.middleware.PrometheusBeforeMiddleware',
     'django_prometheus.middleware.PrometheusBeforeMiddleware',
     'corsheaders.middleware.CorsMiddleware',
     'corsheaders.middleware.CorsMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
@@ -674,17 +673,6 @@ SPECTACULAR_SETTINGS = {
     'POSTPROCESSING_HOOKS': [],
     'POSTPROCESSING_HOOKS': [],
 }
 }
 
 
-#
-# Graphene
-#
-
-GRAPHENE = {
-    # Avoids naming collision on models with 'type' field; see
-    # https://github.com/graphql-python/graphene-django/issues/185
-    'DJANGO_CHOICE_FIELD_ENUM_V3_NAMING': True,
-}
-
-
 #
 #
 # Django RQ (events backend)
 # Django RQ (events backend)
 #
 #
@@ -749,6 +737,13 @@ if not ENABLE_LOCALIZATION:
     USE_I18N = False
     USE_I18N = False
     USE_L10N = False
     USE_L10N = False
 
 
+#
+# Strawberry (GraphQL)
+#
+STRAWBERRY_DJANGO = {
+    "TYPE_DESCRIPTION_FROM_MODEL_DOCSTRING": True,
+}
+
 #
 #
 # Plugins
 # Plugins
 #
 #

+ 18 - 13
netbox/netbox/tests/dummy_plugin/graphql.py

@@ -1,21 +1,26 @@
-import graphene
-from graphene_django import DjangoObjectType
-
-from netbox.graphql.fields import ObjectField, ObjectListField
+from typing import List
+import strawberry
+import strawberry_django
 
 
 from . import models
 from . import models
 
 
 
 
-class DummyModelType(DjangoObjectType):
-
-    class Meta:
-        model = models.DummyModel
-        fields = '__all__'
+@strawberry_django.type(
+    models.DummyModel,
+    fields='__all__',
+)
+class DummyModelType:
+    pass
 
 
 
 
-class DummyQuery(graphene.ObjectType):
-    dummymodel = ObjectField(DummyModelType)
-    dummymodel_list = ObjectListField(DummyModelType)
+@strawberry.type
+class DummyQuery:
+    @strawberry.field
+    def dummymodel(self, id: int) -> DummyModelType:
+        return None
+    dummymodel_list: List[DummyModelType] = strawberry_django.field()
 
 
 
 
-schema = DummyQuery
+schema = [
+    DummyQuery,
+]

+ 3 - 3
netbox/netbox/urls.py

@@ -1,16 +1,16 @@
 from django.conf import settings
 from django.conf import settings
 from django.conf.urls import include
 from django.conf.urls import include
 from django.urls import path
 from django.urls import path
-from django.views.decorators.csrf import csrf_exempt
 from django.views.static import serve
 from django.views.static import serve
 from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
 from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
 
 
 from account.views import LoginView, LogoutView
 from account.views import LoginView, LogoutView
 from netbox.api.views import APIRootView, StatusView
 from netbox.api.views import APIRootView, StatusView
 from netbox.graphql.schema import schema
 from netbox.graphql.schema import schema
-from netbox.graphql.views import GraphQLView
+from netbox.graphql.views import NetBoxGraphQLView
 from netbox.plugins.urls import plugin_patterns, plugin_api_patterns
 from netbox.plugins.urls import plugin_patterns, plugin_api_patterns
 from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx
 from netbox.views import HomeView, StaticMediaFailureView, SearchView, htmx
+from strawberry.django.views import GraphQLView
 
 
 _patterns = [
 _patterns = [
 
 
@@ -60,7 +60,7 @@ _patterns = [
     path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='api_redocs'),
     path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='api_redocs'),
 
 
     # GraphQL
     # GraphQL
-    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema)), name='graphql'),
+    path('graphql/', NetBoxGraphQLView.as_view(schema=schema), name='graphql'),
 
 
     # Serving static media in Django to pipe it through LoginRequiredMiddleware
     # Serving static media in Django to pipe it through LoginRequiredMiddleware
     path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
     path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),

+ 48 - 13
netbox/project-static/bundle.js

@@ -1,5 +1,8 @@
 const esbuild = require('esbuild');
 const esbuild = require('esbuild');
 const { sassPlugin } = require('esbuild-sass-plugin');
 const { sassPlugin } = require('esbuild-sass-plugin');
+const util = require('util');
+const fs = require('fs');
+const copyFilePromise = util.promisify(fs.copyFile);
 
 
 // Bundler options common to all bundle jobs.
 // Bundler options common to all bundle jobs.
 const options = {
 const options = {
@@ -14,24 +17,57 @@ const options = {
 // Get CLI arguments for optional overrides.
 // Get CLI arguments for optional overrides.
 const ARGS = process.argv.slice(2);
 const ARGS = process.argv.slice(2);
 
 
+function copyFiles(files) {
+    return Promise.all(files.map(f => {
+       return copyFilePromise(f.source, f.dest);
+    }));
+}
+
 async function bundleGraphIQL() {
 async function bundleGraphIQL() {
+  let fileMap = [
+    {
+      source: './node_modules/react/umd/react.production.min.js',
+      dest: './dist/graphiql/react.production.min.js'
+    },
+    {
+      source: './node_modules/react-dom/umd/react-dom.production.min.js',
+      dest: './dist/graphiql/react-dom.production.min.js'
+    },
+    {
+      source: './node_modules/js-cookie/dist/js.cookie.min.js',
+      dest: './dist/graphiql/js.cookie.min.js'
+    },
+    {
+      source: './node_modules/graphiql/graphiql.min.js',
+      dest: './dist/graphiql/graphiql.min.js'
+    },
+    {
+      source: './node_modules/@graphiql/plugin-explorer/dist/index.umd.js',
+      dest: './dist/graphiql/index.umd.js'
+    },
+    {
+      source: './node_modules/graphiql/graphiql.min.css',
+      dest: './dist/graphiql/graphiql.min.css'
+    },
+    {
+      source: './node_modules/@graphiql/plugin-explorer/dist/style.css',
+      dest: './dist/graphiql/plugin-explorer-style.css'
+    }
+  ];
+
   try {
   try {
-    const result = await esbuild.build({
-      ...options,
-      entryPoints: {
-        graphiql: 'netbox-graphiql/index.ts',
-      },
-      target: 'es2016',
-      define: {
-        global: 'window',
-      },
-    });
-    if (result.errors.length === 0) {
-      console.log(`✅ Bundled source file 'netbox-graphiql/index.ts' to 'graphiql.js'`);
+    if (!fs.existsSync('./dist/graphiql/')) {
+      fs.mkdirSync('./dist/graphiql/');
     }
     }
   } catch (err) {
   } catch (err) {
     console.error(err);
     console.error(err);
   }
   }
+
+  copyFiles(fileMap).then(() => {
+     console.log('✅ Copied graphiql files');
+  }).catch(err => {
+     console.error(err);
+  });
 }
 }
 
 
 /**
 /**
@@ -77,7 +113,6 @@ async function bundleStyles() {
       'netbox': 'styles/netbox.scss',
       'netbox': 'styles/netbox.scss',
       rack_elevation: 'styles/svg/rack_elevation.scss',
       rack_elevation: 'styles/svg/rack_elevation.scss',
       cable_trace: 'styles/svg/cable_trace.scss',
       cable_trace: 'styles/svg/cable_trace.scss',
-      graphiql: 'netbox-graphiql/graphiql.scss',
     };
     };
     const pluginOptions = { outputStyle: 'compressed' };
     const pluginOptions = { outputStyle: 'compressed' };
     // Allow cache disabling.
     // Allow cache disabling.

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
netbox/project-static/dist/graphiql.css


Fișier diff suprimat deoarece este prea mare
+ 8 - 0
netbox/project-static/dist/graphiql.min.css


Fișier diff suprimat deoarece este prea mare
+ 18 - 0
netbox/project-static/dist/graphiql.min.js


Fișier diff suprimat deoarece este prea mare
+ 0 - 0
netbox/project-static/dist/index.umd.js


+ 2 - 0
netbox/project-static/dist/js.cookie.min.js

@@ -0,0 +1,2 @@
+/*! js-cookie v3.0.5 | MIT */
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)e[o]=n[o]}return e}var t=function t(n,o){function r(t,r,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var c="";for(var u in i)i[u]&&(c+="; "+u,!0!==i[u]&&(c+="="+i[u].split(";")[0]));return document.cookie=t+"="+n.write(r,t)+c}}return Object.create({set:r,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var t=document.cookie?document.cookie.split("; "):[],o={},r=0;r<t.length;r++){var i=t[r].split("="),c=i.slice(1).join("=");try{var u=decodeURIComponent(i[0]);if(o[u]=n.read(c,u),e===u)break}catch(e){}}return e?o[e]:o}},remove:function(t,n){r(t,"",e({},n,{expires:-1}))},withAttributes:function(n){return t(this.converter,e({},this.attributes,n))},withConverter:function(n){return t(e({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(n)}})}({read:function(e){return'"'===e[0]&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}},{path:"/"});return t}));

+ 1 - 0
netbox/project-static/dist/plugin-explorer-style.css

@@ -0,0 +1 @@
+.docExplorerWrap{height:unset!important;min-width:unset!important;width:unset!important}.doc-explorer-title{font-size:var(--font-size-h2);font-weight:var(--font-weight-medium)}.doc-explorer-rhs{display:none}.graphiql-explorer-root{font-family:var(--font-family-mono)!important;font-size:var(--font-size-body)!important;padding:0!important}.graphiql-explorer-root>div:first-child{padding-left:var(--px-8);overflow:hidden!important}.graphiql-explorer-root input{background:hsl(var(--color-base))}.graphiql-explorer-root select{background-color:hsl(var(--color-base));border:1px solid hsla(var(--color-neutral),var(--alpha-secondary));border-radius:var(--border-radius-4);color:hsl(var(--color-neutral));margin:0 var(--px-4);padding:var(--px-4) var(--px-6)}

+ 267 - 0
netbox/project-static/dist/react-dom.production.min.js

@@ -0,0 +1,267 @@
+/**
+ * @license React
+ * react-dom.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+(function(){/*
+ Modernizr 3.0.0pre (Custom Build) | MIT
+*/
+'use strict';(function(Q,mb){"object"===typeof exports&&"undefined"!==typeof module?mb(exports,require("react")):"function"===typeof define&&define.amd?define(["exports","react"],mb):(Q=Q||self,mb(Q.ReactDOM={},Q.React))})(this,function(Q,mb){function n(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;c<arguments.length;c++)b+="&args[]="+encodeURIComponent(arguments[c]);return"Minified React error #"+a+"; visit "+b+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}
+function nb(a,b){Ab(a,b);Ab(a+"Capture",b)}function Ab(a,b){$b[a]=b;for(a=0;a<b.length;a++)cg.add(b[a])}function cj(a){if(Zd.call(dg,a))return!0;if(Zd.call(eg,a))return!1;if(dj.test(a))return dg[a]=!0;eg[a]=!0;return!1}function ej(a,b,c,d){if(null!==c&&0===c.type)return!1;switch(typeof b){case "function":case "symbol":return!0;case "boolean":if(d)return!1;if(null!==c)return!c.acceptsBooleans;a=a.toLowerCase().slice(0,5);return"data-"!==a&&"aria-"!==a;default:return!1}}function fj(a,b,c,d){if(null===
+b||"undefined"===typeof b||ej(a,b,c,d))return!0;if(d)return!1;if(null!==c)switch(c.type){case 3:return!b;case 4:return!1===b;case 5:return isNaN(b);case 6:return isNaN(b)||1>b}return!1}function Y(a,b,c,d,e,f,g){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f;this.removeEmptyString=g}function $d(a,b,c,d){var e=R.hasOwnProperty(b)?R[b]:null;if(null!==e?0!==e.type:d||!(2<b.length)||"o"!==
+b[0]&&"O"!==b[0]||"n"!==b[1]&&"N"!==b[1])fj(b,c,e,d)&&(c=null),d||null===e?cj(b)&&(null===c?a.removeAttribute(b):a.setAttribute(b,""+c)):e.mustUseProperty?a[e.propertyName]=null===c?3===e.type?!1:"":c:(b=e.attributeName,d=e.attributeNamespace,null===c?a.removeAttribute(b):(e=e.type,c=3===e||4===e&&!0===c?"":""+c,d?a.setAttributeNS(d,b,c):a.setAttribute(b,c)))}function ac(a){if(null===a||"object"!==typeof a)return null;a=fg&&a[fg]||a["@@iterator"];return"function"===typeof a?a:null}function bc(a,b,
+c){if(void 0===ae)try{throw Error();}catch(d){ae=(b=d.stack.trim().match(/\n( *(at )?)/))&&b[1]||""}return"\n"+ae+a}function be(a,b){if(!a||ce)return"";ce=!0;var c=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(b)if(b=function(){throw Error();},Object.defineProperty(b.prototype,"props",{set:function(){throw Error();}}),"object"===typeof Reflect&&Reflect.construct){try{Reflect.construct(b,[])}catch(m){var d=m}Reflect.construct(a,[],b)}else{try{b.call()}catch(m){d=m}a.call(b.prototype)}else{try{throw Error();
+}catch(m){d=m}a()}}catch(m){if(m&&d&&"string"===typeof m.stack){for(var e=m.stack.split("\n"),f=d.stack.split("\n"),g=e.length-1,h=f.length-1;1<=g&&0<=h&&e[g]!==f[h];)h--;for(;1<=g&&0<=h;g--,h--)if(e[g]!==f[h]){if(1!==g||1!==h){do if(g--,h--,0>h||e[g]!==f[h]){var k="\n"+e[g].replace(" at new "," at ");a.displayName&&k.includes("<anonymous>")&&(k=k.replace("<anonymous>",a.displayName));return k}while(1<=g&&0<=h)}break}}}finally{ce=!1,Error.prepareStackTrace=c}return(a=a?a.displayName||a.name:"")?bc(a):
+""}function gj(a){switch(a.tag){case 5:return bc(a.type);case 16:return bc("Lazy");case 13:return bc("Suspense");case 19:return bc("SuspenseList");case 0:case 2:case 15:return a=be(a.type,!1),a;case 11:return a=be(a.type.render,!1),a;case 1:return a=be(a.type,!0),a;default:return""}}function de(a){if(null==a)return null;if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case Bb:return"Fragment";case Cb:return"Portal";case ee:return"Profiler";case fe:return"StrictMode";
+case ge:return"Suspense";case he:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case gg:return(a.displayName||"Context")+".Consumer";case hg:return(a._context.displayName||"Context")+".Provider";case ie:var b=a.render;a=a.displayName;a||(a=b.displayName||b.name||"",a=""!==a?"ForwardRef("+a+")":"ForwardRef");return a;case je:return b=a.displayName||null,null!==b?b:de(a.type)||"Memo";case Ta:b=a._payload;a=a._init;try{return de(a(b))}catch(c){}}return null}function hj(a){var b=a.type;
+switch(a.tag){case 24:return"Cache";case 9:return(b.displayName||"Context")+".Consumer";case 10:return(b._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return a=b.render,a=a.displayName||a.name||"",b.displayName||(""!==a?"ForwardRef("+a+")":"ForwardRef");case 7:return"Fragment";case 5:return b;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return de(b);case 8:return b===fe?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";
+case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if("function"===typeof b)return b.displayName||b.name||null;if("string"===typeof b)return b}return null}function Ua(a){switch(typeof a){case "boolean":case "number":case "string":case "undefined":return a;case "object":return a;default:return""}}function ig(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===
+b)}function ij(a){var b=ig(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker=
+null;delete a[b]}}}}function Pc(a){a._valueTracker||(a._valueTracker=ij(a))}function jg(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=ig(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Qc(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}}function ke(a,b){var c=b.checked;return E({},b,{defaultChecked:void 0,defaultValue:void 0,
+value:void 0,checked:null!=c?c:a._wrapperState.initialChecked})}function kg(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=Ua(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function lg(a,b){b=b.checked;null!=b&&$d(a,"checked",b,!1)}function le(a,b){lg(a,b);var c=Ua(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=
+c)a.value=""+c}else a.value!==""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?me(a,b.type,c):b.hasOwnProperty("defaultValue")&&me(a,b.type,Ua(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)}function mg(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;
+c||b===a.value||(a.value=b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)}function me(a,b,c){if("number"!==b||Qc(a.ownerDocument)!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}function Db(a,b,c,d){a=a.options;if(b){b={};for(var e=0;e<c.length;e++)b["$"+c[e]]=!0;for(c=0;c<a.length;c++)e=b.hasOwnProperty("$"+a[c].value),a[c].selected!==e&&(a[c].selected=e),e&&d&&(a[c].defaultSelected=
+!0)}else{c=""+Ua(c);b=null;for(e=0;e<a.length;e++){if(a[e].value===c){a[e].selected=!0;d&&(a[e].defaultSelected=!0);return}null!==b||a[e].disabled||(b=a[e])}null!==b&&(b.selected=!0)}}function ne(a,b){if(null!=b.dangerouslySetInnerHTML)throw Error(n(91));return E({},b,{value:void 0,defaultValue:void 0,children:""+a._wrapperState.initialValue})}function ng(a,b){var c=b.value;if(null==c){c=b.children;b=b.defaultValue;if(null!=c){if(null!=b)throw Error(n(92));if(cc(c)){if(1<c.length)throw Error(n(93));
+c=c[0]}b=c}null==b&&(b="");c=b}a._wrapperState={initialValue:Ua(c)}}function og(a,b){var c=Ua(b.value),d=Ua(b.defaultValue);null!=c&&(c=""+c,c!==a.value&&(a.value=c),null==b.defaultValue&&a.defaultValue!==c&&(a.defaultValue=c));null!=d&&(a.defaultValue=""+d)}function pg(a,b){b=a.textContent;b===a._wrapperState.initialValue&&""!==b&&null!==b&&(a.value=b)}function qg(a){switch(a){case "svg":return"http://www.w3.org/2000/svg";case "math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}
+function oe(a,b){return null==a||"http://www.w3.org/1999/xhtml"===a?qg(b):"http://www.w3.org/2000/svg"===a&&"foreignObject"===b?"http://www.w3.org/1999/xhtml":a}function rg(a,b,c){return null==b||"boolean"===typeof b||""===b?"":c||"number"!==typeof b||0===b||dc.hasOwnProperty(a)&&dc[a]?(""+b).trim():b+"px"}function sg(a,b){a=a.style;for(var c in b)if(b.hasOwnProperty(c)){var d=0===c.indexOf("--"),e=rg(c,b[c],d);"float"===c&&(c="cssFloat");d?a.setProperty(c,e):a[c]=e}}function pe(a,b){if(b){if(jj[a]&&
+(null!=b.children||null!=b.dangerouslySetInnerHTML))throw Error(n(137,a));if(null!=b.dangerouslySetInnerHTML){if(null!=b.children)throw Error(n(60));if("object"!==typeof b.dangerouslySetInnerHTML||!("__html"in b.dangerouslySetInnerHTML))throw Error(n(61));}if(null!=b.style&&"object"!==typeof b.style)throw Error(n(62));}}function qe(a,b){if(-1===a.indexOf("-"))return"string"===typeof b.is;switch(a){case "annotation-xml":case "color-profile":case "font-face":case "font-face-src":case "font-face-uri":case "font-face-format":case "font-face-name":case "missing-glyph":return!1;
+default:return!0}}function re(a){a=a.target||a.srcElement||window;a.correspondingUseElement&&(a=a.correspondingUseElement);return 3===a.nodeType?a.parentNode:a}function tg(a){if(a=ec(a)){if("function"!==typeof se)throw Error(n(280));var b=a.stateNode;b&&(b=Rc(b),se(a.stateNode,a.type,b))}}function ug(a){Eb?Fb?Fb.push(a):Fb=[a]:Eb=a}function vg(){if(Eb){var a=Eb,b=Fb;Fb=Eb=null;tg(a);if(b)for(a=0;a<b.length;a++)tg(b[a])}}function wg(a,b,c){if(te)return a(b,c);te=!0;try{return xg(a,b,c)}finally{if(te=
+!1,null!==Eb||null!==Fb)yg(),vg()}}function fc(a,b){var c=a.stateNode;if(null===c)return null;var d=Rc(c);if(null===d)return null;c=d[b];a:switch(b){case "onClick":case "onClickCapture":case "onDoubleClick":case "onDoubleClickCapture":case "onMouseDown":case "onMouseDownCapture":case "onMouseMove":case "onMouseMoveCapture":case "onMouseUp":case "onMouseUpCapture":case "onMouseEnter":(d=!d.disabled)||(a=a.type,d=!("button"===a||"input"===a||"select"===a||"textarea"===a));a=!d;break a;default:a=!1}if(a)return null;
+if(c&&"function"!==typeof c)throw Error(n(231,b,typeof c));return c}function kj(a,b,c,d,e,f,g,h,k){gc=!1;Sc=null;lj.apply(mj,arguments)}function nj(a,b,c,d,e,f,g,h,k){kj.apply(this,arguments);if(gc){if(gc){var m=Sc;gc=!1;Sc=null}else throw Error(n(198));Tc||(Tc=!0,ue=m)}}function ob(a){var b=a,c=a;if(a.alternate)for(;b.return;)b=b.return;else{a=b;do b=a,0!==(b.flags&4098)&&(c=b.return),a=b.return;while(a)}return 3===b.tag?c:null}function zg(a){if(13===a.tag){var b=a.memoizedState;null===b&&(a=a.alternate,
+null!==a&&(b=a.memoizedState));if(null!==b)return b.dehydrated}return null}function Ag(a){if(ob(a)!==a)throw Error(n(188));}function oj(a){var b=a.alternate;if(!b){b=ob(a);if(null===b)throw Error(n(188));return b!==a?null:a}for(var c=a,d=b;;){var e=c.return;if(null===e)break;var f=e.alternate;if(null===f){d=e.return;if(null!==d){c=d;continue}break}if(e.child===f.child){for(f=e.child;f;){if(f===c)return Ag(e),a;if(f===d)return Ag(e),b;f=f.sibling}throw Error(n(188));}if(c.return!==d.return)c=e,d=f;
+else{for(var g=!1,h=e.child;h;){if(h===c){g=!0;c=e;d=f;break}if(h===d){g=!0;d=e;c=f;break}h=h.sibling}if(!g){for(h=f.child;h;){if(h===c){g=!0;c=f;d=e;break}if(h===d){g=!0;d=f;c=e;break}h=h.sibling}if(!g)throw Error(n(189));}}if(c.alternate!==d)throw Error(n(190));}if(3!==c.tag)throw Error(n(188));return c.stateNode.current===c?a:b}function Bg(a){a=oj(a);return null!==a?Cg(a):null}function Cg(a){if(5===a.tag||6===a.tag)return a;for(a=a.child;null!==a;){var b=Cg(a);if(null!==b)return b;a=a.sibling}return null}
+function pj(a,b){if(Ca&&"function"===typeof Ca.onCommitFiberRoot)try{Ca.onCommitFiberRoot(Uc,a,void 0,128===(a.current.flags&128))}catch(c){}}function qj(a){a>>>=0;return 0===a?32:31-(rj(a)/sj|0)|0}function hc(a){switch(a&-a){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a&
+4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return a&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return a}}function Vc(a,b){var c=a.pendingLanes;if(0===c)return 0;var d=0,e=a.suspendedLanes,f=a.pingedLanes,g=c&268435455;if(0!==g){var h=g&~e;0!==h?d=hc(h):(f&=g,0!==f&&(d=hc(f)))}else g=c&~e,0!==g?d=hc(g):0!==f&&(d=hc(f));if(0===d)return 0;if(0!==b&&b!==d&&0===(b&e)&&
+(e=d&-d,f=b&-b,e>=f||16===e&&0!==(f&4194240)))return b;0!==(d&4)&&(d|=c&16);b=a.entangledLanes;if(0!==b)for(a=a.entanglements,b&=d;0<b;)c=31-ta(b),e=1<<c,d|=a[c],b&=~e;return d}function tj(a,b){switch(a){case 1:case 2:case 4:return b+250;case 8:case 16:case 32:case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return b+5E3;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return-1;
+case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function uj(a,b){for(var c=a.suspendedLanes,d=a.pingedLanes,e=a.expirationTimes,f=a.pendingLanes;0<f;){var g=31-ta(f),h=1<<g,k=e[g];if(-1===k){if(0===(h&c)||0!==(h&d))e[g]=tj(h,b)}else k<=b&&(a.expiredLanes|=h);f&=~h}}function ve(a){a=a.pendingLanes&-1073741825;return 0!==a?a:a&1073741824?1073741824:0}function Dg(){var a=Wc;Wc<<=1;0===(Wc&4194240)&&(Wc=64);return a}function we(a){for(var b=[],c=0;31>c;c++)b.push(a);
+return b}function ic(a,b,c){a.pendingLanes|=b;536870912!==b&&(a.suspendedLanes=0,a.pingedLanes=0);a=a.eventTimes;b=31-ta(b);a[b]=c}function vj(a,b){var c=a.pendingLanes&~b;a.pendingLanes=b;a.suspendedLanes=0;a.pingedLanes=0;a.expiredLanes&=b;a.mutableReadLanes&=b;a.entangledLanes&=b;b=a.entanglements;var d=a.eventTimes;for(a=a.expirationTimes;0<c;){var e=31-ta(c),f=1<<e;b[e]=0;d[e]=-1;a[e]=-1;c&=~f}}function xe(a,b){var c=a.entangledLanes|=b;for(a=a.entanglements;c;){var d=31-ta(c),e=1<<d;e&b|a[d]&
+b&&(a[d]|=b);c&=~e}}function Eg(a){a&=-a;return 1<a?4<a?0!==(a&268435455)?16:536870912:4:1}function Fg(a,b){switch(a){case "focusin":case "focusout":Va=null;break;case "dragenter":case "dragleave":Wa=null;break;case "mouseover":case "mouseout":Xa=null;break;case "pointerover":case "pointerout":jc.delete(b.pointerId);break;case "gotpointercapture":case "lostpointercapture":kc.delete(b.pointerId)}}function lc(a,b,c,d,e,f){if(null===a||a.nativeEvent!==f)return a={blockedOn:b,domEventName:c,eventSystemFlags:d,
+nativeEvent:f,targetContainers:[e]},null!==b&&(b=ec(b),null!==b&&Gg(b)),a;a.eventSystemFlags|=d;b=a.targetContainers;null!==e&&-1===b.indexOf(e)&&b.push(e);return a}function wj(a,b,c,d,e){switch(b){case "focusin":return Va=lc(Va,a,b,c,d,e),!0;case "dragenter":return Wa=lc(Wa,a,b,c,d,e),!0;case "mouseover":return Xa=lc(Xa,a,b,c,d,e),!0;case "pointerover":var f=e.pointerId;jc.set(f,lc(jc.get(f)||null,a,b,c,d,e));return!0;case "gotpointercapture":return f=e.pointerId,kc.set(f,lc(kc.get(f)||null,a,b,
+c,d,e)),!0}return!1}function Hg(a){var b=pb(a.target);if(null!==b){var c=ob(b);if(null!==c)if(b=c.tag,13===b){if(b=zg(c),null!==b){a.blockedOn=b;xj(a.priority,function(){yj(c)});return}}else if(3===b&&c.stateNode.current.memoizedState.isDehydrated){a.blockedOn=3===c.tag?c.stateNode.containerInfo:null;return}}a.blockedOn=null}function Xc(a){if(null!==a.blockedOn)return!1;for(var b=a.targetContainers;0<b.length;){var c=ye(a.domEventName,a.eventSystemFlags,b[0],a.nativeEvent);if(null===c){c=a.nativeEvent;
+var d=new c.constructor(c.type,c);ze=d;c.target.dispatchEvent(d);ze=null}else return b=ec(c),null!==b&&Gg(b),a.blockedOn=c,!1;b.shift()}return!0}function Ig(a,b,c){Xc(a)&&c.delete(b)}function zj(){Ae=!1;null!==Va&&Xc(Va)&&(Va=null);null!==Wa&&Xc(Wa)&&(Wa=null);null!==Xa&&Xc(Xa)&&(Xa=null);jc.forEach(Ig);kc.forEach(Ig)}function mc(a,b){a.blockedOn===b&&(a.blockedOn=null,Ae||(Ae=!0,Jg(Kg,zj)))}function nc(a){if(0<Yc.length){mc(Yc[0],a);for(var b=1;b<Yc.length;b++){var c=Yc[b];c.blockedOn===a&&(c.blockedOn=
+null)}}null!==Va&&mc(Va,a);null!==Wa&&mc(Wa,a);null!==Xa&&mc(Xa,a);b=function(b){return mc(b,a)};jc.forEach(b);kc.forEach(b);for(b=0;b<Ya.length;b++)c=Ya[b],c.blockedOn===a&&(c.blockedOn=null);for(;0<Ya.length&&(b=Ya[0],null===b.blockedOn);)Hg(b),null===b.blockedOn&&Ya.shift()}function Aj(a,b,c,d){var e=z,f=Gb.transition;Gb.transition=null;try{z=1,Be(a,b,c,d)}finally{z=e,Gb.transition=f}}function Bj(a,b,c,d){var e=z,f=Gb.transition;Gb.transition=null;try{z=4,Be(a,b,c,d)}finally{z=e,Gb.transition=
+f}}function Be(a,b,c,d){if(Zc){var e=ye(a,b,c,d);if(null===e)Ce(a,b,d,$c,c),Fg(a,d);else if(wj(e,a,b,c,d))d.stopPropagation();else if(Fg(a,d),b&4&&-1<Cj.indexOf(a)){for(;null!==e;){var f=ec(e);null!==f&&Dj(f);f=ye(a,b,c,d);null===f&&Ce(a,b,d,$c,c);if(f===e)break;e=f}null!==e&&d.stopPropagation()}else Ce(a,b,d,null,c)}}function ye(a,b,c,d){$c=null;a=re(d);a=pb(a);if(null!==a)if(b=ob(a),null===b)a=null;else if(c=b.tag,13===c){a=zg(b);if(null!==a)return a;a=null}else if(3===c){if(b.stateNode.current.memoizedState.isDehydrated)return 3===
+b.tag?b.stateNode.containerInfo:null;a=null}else b!==a&&(a=null);$c=a;return null}function Lg(a){switch(a){case "cancel":case "click":case "close":case "contextmenu":case "copy":case "cut":case "auxclick":case "dblclick":case "dragend":case "dragstart":case "drop":case "focusin":case "focusout":case "input":case "invalid":case "keydown":case "keypress":case "keyup":case "mousedown":case "mouseup":case "paste":case "pause":case "play":case "pointercancel":case "pointerdown":case "pointerup":case "ratechange":case "reset":case "resize":case "seeked":case "submit":case "touchcancel":case "touchend":case "touchstart":case "volumechange":case "change":case "selectionchange":case "textInput":case "compositionstart":case "compositionend":case "compositionupdate":case "beforeblur":case "afterblur":case "beforeinput":case "blur":case "fullscreenchange":case "focus":case "hashchange":case "popstate":case "select":case "selectstart":return 1;
+case "drag":case "dragenter":case "dragexit":case "dragleave":case "dragover":case "mousemove":case "mouseout":case "mouseover":case "pointermove":case "pointerout":case "pointerover":case "scroll":case "toggle":case "touchmove":case "wheel":case "mouseenter":case "mouseleave":case "pointerenter":case "pointerleave":return 4;case "message":switch(Ej()){case De:return 1;case Mg:return 4;case ad:case Fj:return 16;case Ng:return 536870912;default:return 16}default:return 16}}function Og(){if(bd)return bd;
+var a,b=Ee,c=b.length,d,e="value"in Za?Za.value:Za.textContent,f=e.length;for(a=0;a<c&&b[a]===e[a];a++);var g=c-a;for(d=1;d<=g&&b[c-d]===e[f-d];d++);return bd=e.slice(a,1<d?1-d:void 0)}function cd(a){var b=a.keyCode;"charCode"in a?(a=a.charCode,0===a&&13===b&&(a=13)):a=b;10===a&&(a=13);return 32<=a||13===a?a:0}function dd(){return!0}function Pg(){return!1}function ka(a){function b(b,d,e,f,g){this._reactName=b;this._targetInst=e;this.type=d;this.nativeEvent=f;this.target=g;this.currentTarget=null;
+for(var c in a)a.hasOwnProperty(c)&&(b=a[c],this[c]=b?b(f):f[c]);this.isDefaultPrevented=(null!=f.defaultPrevented?f.defaultPrevented:!1===f.returnValue)?dd:Pg;this.isPropagationStopped=Pg;return this}E(b.prototype,{preventDefault:function(){this.defaultPrevented=!0;var a=this.nativeEvent;a&&(a.preventDefault?a.preventDefault():"unknown"!==typeof a.returnValue&&(a.returnValue=!1),this.isDefaultPrevented=dd)},stopPropagation:function(){var a=this.nativeEvent;a&&(a.stopPropagation?a.stopPropagation():
+"unknown"!==typeof a.cancelBubble&&(a.cancelBubble=!0),this.isPropagationStopped=dd)},persist:function(){},isPersistent:dd});return b}function Gj(a){var b=this.nativeEvent;return b.getModifierState?b.getModifierState(a):(a=Hj[a])?!!b[a]:!1}function Fe(a){return Gj}function Qg(a,b){switch(a){case "keyup":return-1!==Ij.indexOf(b.keyCode);case "keydown":return 229!==b.keyCode;case "keypress":case "mousedown":case "focusout":return!0;default:return!1}}function Rg(a){a=a.detail;return"object"===typeof a&&
+"data"in a?a.data:null}function Jj(a,b){switch(a){case "compositionend":return Rg(b);case "keypress":if(32!==b.which)return null;Sg=!0;return Tg;case "textInput":return a=b.data,a===Tg&&Sg?null:a;default:return null}}function Kj(a,b){if(Hb)return"compositionend"===a||!Ge&&Qg(a,b)?(a=Og(),bd=Ee=Za=null,Hb=!1,a):null;switch(a){case "paste":return null;case "keypress":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1<b.char.length)return b.char;if(b.which)return String.fromCharCode(b.which)}return null;
+case "compositionend":return Ug&&"ko"!==b.locale?null:b.data;default:return null}}function Vg(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return"input"===b?!!Lj[a.type]:"textarea"===b?!0:!1}function Mj(a){if(!Ia)return!1;a="on"+a;var b=a in document;b||(b=document.createElement("div"),b.setAttribute(a,"return;"),b="function"===typeof b[a]);return b}function Wg(a,b,c,d){ug(d);b=ed(b,"onChange");0<b.length&&(c=new He("onChange","change",null,c,d),a.push({event:c,listeners:b}))}function Nj(a){Xg(a,
+0)}function fd(a){var b=Ib(a);if(jg(b))return a}function Oj(a,b){if("change"===a)return b}function Yg(){oc&&(oc.detachEvent("onpropertychange",Zg),pc=oc=null)}function Zg(a){if("value"===a.propertyName&&fd(pc)){var b=[];Wg(b,pc,a,re(a));wg(Nj,b)}}function Pj(a,b,c){"focusin"===a?(Yg(),oc=b,pc=c,oc.attachEvent("onpropertychange",Zg)):"focusout"===a&&Yg()}function Qj(a,b){if("selectionchange"===a||"keyup"===a||"keydown"===a)return fd(pc)}function Rj(a,b){if("click"===a)return fd(b)}function Sj(a,b){if("input"===
+a||"change"===a)return fd(b)}function Tj(a,b){return a===b&&(0!==a||1/a===1/b)||a!==a&&b!==b}function qc(a,b){if(ua(a,b))return!0;if("object"!==typeof a||null===a||"object"!==typeof b||null===b)return!1;var c=Object.keys(a),d=Object.keys(b);if(c.length!==d.length)return!1;for(d=0;d<c.length;d++){var e=c[d];if(!Zd.call(b,e)||!ua(a[e],b[e]))return!1}return!0}function $g(a){for(;a&&a.firstChild;)a=a.firstChild;return a}function ah(a,b){var c=$g(a);a=0;for(var d;c;){if(3===c.nodeType){d=a+c.textContent.length;
+if(a<=b&&d>=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=$g(c)}}function bh(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?bh(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function ch(){for(var a=window,b=Qc();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;
+b=Qc(a.document)}return b}function Ie(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)}function Uj(a){var b=ch(),c=a.focusedElem,d=a.selectionRange;if(b!==c&&c&&c.ownerDocument&&bh(c.ownerDocument.documentElement,c)){if(null!==d&&Ie(c))if(b=d.start,a=d.end,void 0===a&&(a=b),"selectionStart"in c)c.selectionStart=b,c.selectionEnd=Math.min(a,c.value.length);
+else if(a=(b=c.ownerDocument||document)&&b.defaultView||window,a.getSelection){a=a.getSelection();var e=c.textContent.length,f=Math.min(d.start,e);d=void 0===d.end?f:Math.min(d.end,e);!a.extend&&f>d&&(e=d,d=f,f=e);e=ah(c,f);var g=ah(c,d);e&&g&&(1!==a.rangeCount||a.anchorNode!==e.node||a.anchorOffset!==e.offset||a.focusNode!==g.node||a.focusOffset!==g.offset)&&(b=b.createRange(),b.setStart(e.node,e.offset),a.removeAllRanges(),f>d?(a.addRange(b),a.extend(g.node,g.offset)):(b.setEnd(g.node,g.offset),
+a.addRange(b)))}b=[];for(a=c;a=a.parentNode;)1===a.nodeType&&b.push({element:a,left:a.scrollLeft,top:a.scrollTop});"function"===typeof c.focus&&c.focus();for(c=0;c<b.length;c++)a=b[c],a.element.scrollLeft=a.left,a.element.scrollTop=a.top}}function dh(a,b,c){var d=c.window===c?c.document:9===c.nodeType?c:c.ownerDocument;Je||null==Jb||Jb!==Qc(d)||(d=Jb,"selectionStart"in d&&Ie(d)?d={start:d.selectionStart,end:d.selectionEnd}:(d=(d.ownerDocument&&d.ownerDocument.defaultView||window).getSelection(),d=
+{anchorNode:d.anchorNode,anchorOffset:d.anchorOffset,focusNode:d.focusNode,focusOffset:d.focusOffset}),rc&&qc(rc,d)||(rc=d,d=ed(Ke,"onSelect"),0<d.length&&(b=new He("onSelect","select",null,b,c),a.push({event:b,listeners:d}),b.target=Jb)))}function gd(a,b){var c={};c[a.toLowerCase()]=b.toLowerCase();c["Webkit"+a]="webkit"+b;c["Moz"+a]="moz"+b;return c}function hd(a){if(Le[a])return Le[a];if(!Kb[a])return a;var b=Kb[a],c;for(c in b)if(b.hasOwnProperty(c)&&c in eh)return Le[a]=b[c];return a}function $a(a,
+b){fh.set(a,b);nb(b,[a])}function gh(a,b,c){var d=a.type||"unknown-event";a.currentTarget=c;nj(d,b,void 0,a);a.currentTarget=null}function Xg(a,b){b=0!==(b&4);for(var c=0;c<a.length;c++){var d=a[c],e=d.event;d=d.listeners;a:{var f=void 0;if(b)for(var g=d.length-1;0<=g;g--){var h=d[g],k=h.instance,m=h.currentTarget;h=h.listener;if(k!==f&&e.isPropagationStopped())break a;gh(e,h,m);f=k}else for(g=0;g<d.length;g++){h=d[g];k=h.instance;m=h.currentTarget;h=h.listener;if(k!==f&&e.isPropagationStopped())break a;
+gh(e,h,m);f=k}}}if(Tc)throw a=ue,Tc=!1,ue=null,a;}function B(a,b){var c=b[Me];void 0===c&&(c=b[Me]=new Set);var d=a+"__bubble";c.has(d)||(hh(b,a,2,!1),c.add(d))}function Ne(a,b,c){var d=0;b&&(d|=4);hh(c,a,d,b)}function sc(a){if(!a[id]){a[id]=!0;cg.forEach(function(b){"selectionchange"!==b&&(Vj.has(b)||Ne(b,!1,a),Ne(b,!0,a))});var b=9===a.nodeType?a:a.ownerDocument;null===b||b[id]||(b[id]=!0,Ne("selectionchange",!1,b))}}function hh(a,b,c,d,e){switch(Lg(b)){case 1:e=Aj;break;case 4:e=Bj;break;default:e=
+Be}c=e.bind(null,b,c,a);e=void 0;!Oe||"touchstart"!==b&&"touchmove"!==b&&"wheel"!==b||(e=!0);d?void 0!==e?a.addEventListener(b,c,{capture:!0,passive:e}):a.addEventListener(b,c,!0):void 0!==e?a.addEventListener(b,c,{passive:e}):a.addEventListener(b,c,!1)}function Ce(a,b,c,d,e){var f=d;if(0===(b&1)&&0===(b&2)&&null!==d)a:for(;;){if(null===d)return;var g=d.tag;if(3===g||4===g){var h=d.stateNode.containerInfo;if(h===e||8===h.nodeType&&h.parentNode===e)break;if(4===g)for(g=d.return;null!==g;){var k=g.tag;
+if(3===k||4===k)if(k=g.stateNode.containerInfo,k===e||8===k.nodeType&&k.parentNode===e)return;g=g.return}for(;null!==h;){g=pb(h);if(null===g)return;k=g.tag;if(5===k||6===k){d=f=g;continue a}h=h.parentNode}}d=d.return}wg(function(){var d=f,e=re(c),g=[];a:{var h=fh.get(a);if(void 0!==h){var k=He,n=a;switch(a){case "keypress":if(0===cd(c))break a;case "keydown":case "keyup":k=Wj;break;case "focusin":n="focus";k=Pe;break;case "focusout":n="blur";k=Pe;break;case "beforeblur":case "afterblur":k=Pe;break;
+case "click":if(2===c.button)break a;case "auxclick":case "dblclick":case "mousedown":case "mousemove":case "mouseup":case "mouseout":case "mouseover":case "contextmenu":k=ih;break;case "drag":case "dragend":case "dragenter":case "dragexit":case "dragleave":case "dragover":case "dragstart":case "drop":k=Xj;break;case "touchcancel":case "touchend":case "touchmove":case "touchstart":k=Yj;break;case jh:case kh:case lh:k=Zj;break;case mh:k=ak;break;case "scroll":k=bk;break;case "wheel":k=ck;break;case "copy":case "cut":case "paste":k=
+dk;break;case "gotpointercapture":case "lostpointercapture":case "pointercancel":case "pointerdown":case "pointermove":case "pointerout":case "pointerover":case "pointerup":k=nh}var l=0!==(b&4),p=!l&&"scroll"===a,A=l?null!==h?h+"Capture":null:h;l=[];for(var v=d,q;null!==v;){q=v;var M=q.stateNode;5===q.tag&&null!==M&&(q=M,null!==A&&(M=fc(v,A),null!=M&&l.push(tc(v,M,q))));if(p)break;v=v.return}0<l.length&&(h=new k(h,n,null,c,e),g.push({event:h,listeners:l}))}}if(0===(b&7)){a:{h="mouseover"===a||"pointerover"===
+a;k="mouseout"===a||"pointerout"===a;if(h&&c!==ze&&(n=c.relatedTarget||c.fromElement)&&(pb(n)||n[Ja]))break a;if(k||h){h=e.window===e?e:(h=e.ownerDocument)?h.defaultView||h.parentWindow:window;if(k){if(n=c.relatedTarget||c.toElement,k=d,n=n?pb(n):null,null!==n&&(p=ob(n),n!==p||5!==n.tag&&6!==n.tag))n=null}else k=null,n=d;if(k!==n){l=ih;M="onMouseLeave";A="onMouseEnter";v="mouse";if("pointerout"===a||"pointerover"===a)l=nh,M="onPointerLeave",A="onPointerEnter",v="pointer";p=null==k?h:Ib(k);q=null==
+n?h:Ib(n);h=new l(M,v+"leave",k,c,e);h.target=p;h.relatedTarget=q;M=null;pb(e)===d&&(l=new l(A,v+"enter",n,c,e),l.target=q,l.relatedTarget=p,M=l);p=M;if(k&&n)b:{l=k;A=n;v=0;for(q=l;q;q=Lb(q))v++;q=0;for(M=A;M;M=Lb(M))q++;for(;0<v-q;)l=Lb(l),v--;for(;0<q-v;)A=Lb(A),q--;for(;v--;){if(l===A||null!==A&&l===A.alternate)break b;l=Lb(l);A=Lb(A)}l=null}else l=null;null!==k&&oh(g,h,k,l,!1);null!==n&&null!==p&&oh(g,p,n,l,!0)}}}a:{h=d?Ib(d):window;k=h.nodeName&&h.nodeName.toLowerCase();if("select"===k||"input"===
+k&&"file"===h.type)var ma=Oj;else if(Vg(h))if(ph)ma=Sj;else{ma=Qj;var va=Pj}else(k=h.nodeName)&&"input"===k.toLowerCase()&&("checkbox"===h.type||"radio"===h.type)&&(ma=Rj);if(ma&&(ma=ma(a,d))){Wg(g,ma,c,e);break a}va&&va(a,h,d);"focusout"===a&&(va=h._wrapperState)&&va.controlled&&"number"===h.type&&me(h,"number",h.value)}va=d?Ib(d):window;switch(a){case "focusin":if(Vg(va)||"true"===va.contentEditable)Jb=va,Ke=d,rc=null;break;case "focusout":rc=Ke=Jb=null;break;case "mousedown":Je=!0;break;case "contextmenu":case "mouseup":case "dragend":Je=
+!1;dh(g,c,e);break;case "selectionchange":if(ek)break;case "keydown":case "keyup":dh(g,c,e)}var ab;if(Ge)b:{switch(a){case "compositionstart":var da="onCompositionStart";break b;case "compositionend":da="onCompositionEnd";break b;case "compositionupdate":da="onCompositionUpdate";break b}da=void 0}else Hb?Qg(a,c)&&(da="onCompositionEnd"):"keydown"===a&&229===c.keyCode&&(da="onCompositionStart");da&&(Ug&&"ko"!==c.locale&&(Hb||"onCompositionStart"!==da?"onCompositionEnd"===da&&Hb&&(ab=Og()):(Za=e,Ee=
+"value"in Za?Za.value:Za.textContent,Hb=!0)),va=ed(d,da),0<va.length&&(da=new qh(da,a,null,c,e),g.push({event:da,listeners:va}),ab?da.data=ab:(ab=Rg(c),null!==ab&&(da.data=ab))));if(ab=fk?Jj(a,c):Kj(a,c))d=ed(d,"onBeforeInput"),0<d.length&&(e=new gk("onBeforeInput","beforeinput",null,c,e),g.push({event:e,listeners:d}),e.data=ab)}Xg(g,b)})}function tc(a,b,c){return{instance:a,listener:b,currentTarget:c}}function ed(a,b){for(var c=b+"Capture",d=[];null!==a;){var e=a,f=e.stateNode;5===e.tag&&null!==
+f&&(e=f,f=fc(a,c),null!=f&&d.unshift(tc(a,f,e)),f=fc(a,b),null!=f&&d.push(tc(a,f,e)));a=a.return}return d}function Lb(a){if(null===a)return null;do a=a.return;while(a&&5!==a.tag);return a?a:null}function oh(a,b,c,d,e){for(var f=b._reactName,g=[];null!==c&&c!==d;){var h=c,k=h.alternate,m=h.stateNode;if(null!==k&&k===d)break;5===h.tag&&null!==m&&(h=m,e?(k=fc(c,f),null!=k&&g.unshift(tc(c,k,h))):e||(k=fc(c,f),null!=k&&g.push(tc(c,k,h))));c=c.return}0!==g.length&&a.push({event:b,listeners:g})}function rh(a){return("string"===
+typeof a?a:""+a).replace(hk,"\n").replace(ik,"")}function jd(a,b,c,d){b=rh(b);if(rh(a)!==b&&c)throw Error(n(425));}function kd(){}function Qe(a,b){return"textarea"===a||"noscript"===a||"string"===typeof b.children||"number"===typeof b.children||"object"===typeof b.dangerouslySetInnerHTML&&null!==b.dangerouslySetInnerHTML&&null!=b.dangerouslySetInnerHTML.__html}function jk(a){setTimeout(function(){throw a;})}function Re(a,b){var c=b,d=0;do{var e=c.nextSibling;a.removeChild(c);if(e&&8===e.nodeType)if(c=
+e.data,"/$"===c){if(0===d){a.removeChild(e);nc(b);return}d--}else"$"!==c&&"$?"!==c&&"$!"!==c||d++;c=e}while(c);nc(b)}function Ka(a){for(;null!=a;a=a.nextSibling){var b=a.nodeType;if(1===b||3===b)break;if(8===b){b=a.data;if("$"===b||"$!"===b||"$?"===b)break;if("/$"===b)return null}}return a}function sh(a){a=a.previousSibling;for(var b=0;a;){if(8===a.nodeType){var c=a.data;if("$"===c||"$!"===c||"$?"===c){if(0===b)return a;b--}else"/$"===c&&b++}a=a.previousSibling}return null}function pb(a){var b=a[Da];
+if(b)return b;for(var c=a.parentNode;c;){if(b=c[Ja]||c[Da]){c=b.alternate;if(null!==b.child||null!==c&&null!==c.child)for(a=sh(a);null!==a;){if(c=a[Da])return c;a=sh(a)}return b}a=c;c=a.parentNode}return null}function ec(a){a=a[Da]||a[Ja];return!a||5!==a.tag&&6!==a.tag&&13!==a.tag&&3!==a.tag?null:a}function Ib(a){if(5===a.tag||6===a.tag)return a.stateNode;throw Error(n(33));}function Rc(a){return a[uc]||null}function bb(a){return{current:a}}function w(a,b){0>Mb||(a.current=Se[Mb],Se[Mb]=null,Mb--)}
+function y(a,b,c){Mb++;Se[Mb]=a.current;a.current=b}function Nb(a,b){var c=a.type.contextTypes;if(!c)return cb;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function ea(a){a=a.childContextTypes;return null!==a&&void 0!==a}function th(a,b,c){if(J.current!==cb)throw Error(n(168));
+y(J,b);y(S,c)}function uh(a,b,c){var d=a.stateNode;b=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in b))throw Error(n(108,hj(a)||"Unknown",e));return E({},c,d)}function ld(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||cb;qb=J.current;y(J,a);y(S,S.current);return!0}function vh(a,b,c){var d=a.stateNode;if(!d)throw Error(n(169));c?(a=uh(a,b,qb),d.__reactInternalMemoizedMergedChildContext=a,w(S),w(J),y(J,a)):w(S);
+y(S,c)}function wh(a){null===La?La=[a]:La.push(a)}function kk(a){md=!0;wh(a)}function db(){if(!Te&&null!==La){Te=!0;var a=0,b=z;try{var c=La;for(z=1;a<c.length;a++){var d=c[a];do d=d(!0);while(null!==d)}La=null;md=!1}catch(e){throw null!==La&&(La=La.slice(a+1)),xh(De,db),e;}finally{z=b,Te=!1}}return null}function rb(a,b){Ob[Pb++]=nd;Ob[Pb++]=od;od=a;nd=b}function yh(a,b,c){na[oa++]=Ma;na[oa++]=Na;na[oa++]=sb;sb=a;var d=Ma;a=Na;var e=32-ta(d)-1;d&=~(1<<e);c+=1;var f=32-ta(b)+e;if(30<f){var g=e-e%5;
+f=(d&(1<<g)-1).toString(32);d>>=g;e-=g;Ma=1<<32-ta(b)+e|c<<e|d;Na=f+a}else Ma=1<<f|c<<e|d,Na=a}function Ue(a){null!==a.return&&(rb(a,1),yh(a,1,0))}function Ve(a){for(;a===od;)od=Ob[--Pb],Ob[Pb]=null,nd=Ob[--Pb],Ob[Pb]=null;for(;a===sb;)sb=na[--oa],na[oa]=null,Na=na[--oa],na[oa]=null,Ma=na[--oa],na[oa]=null}function zh(a,b){var c=pa(5,null,null,0);c.elementType="DELETED";c.stateNode=b;c.return=a;b=a.deletions;null===b?(a.deletions=[c],a.flags|=16):b.push(c)}function Ah(a,b){switch(a.tag){case 5:var c=
+a.type;b=1!==b.nodeType||c.toLowerCase()!==b.nodeName.toLowerCase()?null:b;return null!==b?(a.stateNode=b,la=a,fa=Ka(b.firstChild),!0):!1;case 6:return b=""===a.pendingProps||3!==b.nodeType?null:b,null!==b?(a.stateNode=b,la=a,fa=null,!0):!1;case 13:return b=8!==b.nodeType?null:b,null!==b?(c=null!==sb?{id:Ma,overflow:Na}:null,a.memoizedState={dehydrated:b,treeContext:c,retryLane:1073741824},c=pa(18,null,null,0),c.stateNode=b,c.return=a,a.child=c,la=a,fa=null,!0):!1;default:return!1}}function We(a){return 0!==
+(a.mode&1)&&0===(a.flags&128)}function Xe(a){if(D){var b=fa;if(b){var c=b;if(!Ah(a,b)){if(We(a))throw Error(n(418));b=Ka(c.nextSibling);var d=la;b&&Ah(a,b)?zh(d,c):(a.flags=a.flags&-4097|2,D=!1,la=a)}}else{if(We(a))throw Error(n(418));a.flags=a.flags&-4097|2;D=!1;la=a}}}function Bh(a){for(a=a.return;null!==a&&5!==a.tag&&3!==a.tag&&13!==a.tag;)a=a.return;la=a}function pd(a){if(a!==la)return!1;if(!D)return Bh(a),D=!0,!1;var b;(b=3!==a.tag)&&!(b=5!==a.tag)&&(b=a.type,b="head"!==b&&"body"!==b&&!Qe(a.type,
+a.memoizedProps));if(b&&(b=fa)){if(We(a)){for(a=fa;a;)a=Ka(a.nextSibling);throw Error(n(418));}for(;b;)zh(a,b),b=Ka(b.nextSibling)}Bh(a);if(13===a.tag){a=a.memoizedState;a=null!==a?a.dehydrated:null;if(!a)throw Error(n(317));a:{a=a.nextSibling;for(b=0;a;){if(8===a.nodeType){var c=a.data;if("/$"===c){if(0===b){fa=Ka(a.nextSibling);break a}b--}else"$"!==c&&"$!"!==c&&"$?"!==c||b++}a=a.nextSibling}fa=null}}else fa=la?Ka(a.stateNode.nextSibling):null;return!0}function Qb(){fa=la=null;D=!1}function Ye(a){null===
+wa?wa=[a]:wa.push(a)}function xa(a,b){if(a&&a.defaultProps){b=E({},b);a=a.defaultProps;for(var c in a)void 0===b[c]&&(b[c]=a[c]);return b}return b}function Ze(){$e=Rb=qd=null}function af(a,b){b=rd.current;w(rd);a._currentValue=b}function bf(a,b,c){for(;null!==a;){var d=a.alternate;(a.childLanes&b)!==b?(a.childLanes|=b,null!==d&&(d.childLanes|=b)):null!==d&&(d.childLanes&b)!==b&&(d.childLanes|=b);if(a===c)break;a=a.return}}function Sb(a,b){qd=a;$e=Rb=null;a=a.dependencies;null!==a&&null!==a.firstContext&&
+(0!==(a.lanes&b)&&(ha=!0),a.firstContext=null)}function qa(a){var b=a._currentValue;if($e!==a)if(a={context:a,memoizedValue:b,next:null},null===Rb){if(null===qd)throw Error(n(308));Rb=a;qd.dependencies={lanes:0,firstContext:a}}else Rb=Rb.next=a;return b}function cf(a){null===tb?tb=[a]:tb.push(a)}function Ch(a,b,c,d){var e=b.interleaved;null===e?(c.next=c,cf(b)):(c.next=e.next,e.next=c);b.interleaved=c;return Oa(a,d)}function Oa(a,b){a.lanes|=b;var c=a.alternate;null!==c&&(c.lanes|=b);c=a;for(a=a.return;null!==
+a;)a.childLanes|=b,c=a.alternate,null!==c&&(c.childLanes|=b),c=a,a=a.return;return 3===c.tag?c.stateNode:null}function df(a){a.updateQueue={baseState:a.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Dh(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue={baseState:a.baseState,firstBaseUpdate:a.firstBaseUpdate,lastBaseUpdate:a.lastBaseUpdate,shared:a.shared,effects:a.effects})}function Pa(a,b){return{eventTime:a,lane:b,
+tag:0,payload:null,callback:null,next:null}}function eb(a,b,c){var d=a.updateQueue;if(null===d)return null;d=d.shared;if(0!==(p&2)){var e=d.pending;null===e?b.next=b:(b.next=e.next,e.next=b);d.pending=b;return lk(a,c)}e=d.interleaved;null===e?(b.next=b,cf(d)):(b.next=e.next,e.next=b);d.interleaved=b;return Oa(a,c)}function sd(a,b,c){b=b.updateQueue;if(null!==b&&(b=b.shared,0!==(c&4194240))){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function Eh(a,b){var c=a.updateQueue,d=a.alternate;
+if(null!==d&&(d=d.updateQueue,c===d)){var e=null,f=null;c=c.firstBaseUpdate;if(null!==c){do{var g={eventTime:c.eventTime,lane:c.lane,tag:c.tag,payload:c.payload,callback:c.callback,next:null};null===f?e=f=g:f=f.next=g;c=c.next}while(null!==c);null===f?e=f=b:f=f.next=b}else e=f=b;c={baseState:d.baseState,firstBaseUpdate:e,lastBaseUpdate:f,shared:d.shared,effects:d.effects};a.updateQueue=c;return}a=c.lastBaseUpdate;null===a?c.firstBaseUpdate=b:a.next=b;c.lastBaseUpdate=b}function td(a,b,c,d){var e=
+a.updateQueue;fb=!1;var f=e.firstBaseUpdate,g=e.lastBaseUpdate,h=e.shared.pending;if(null!==h){e.shared.pending=null;var k=h,m=k.next;k.next=null;null===g?f=m:g.next=m;g=k;var n=a.alternate;null!==n&&(n=n.updateQueue,h=n.lastBaseUpdate,h!==g&&(null===h?n.firstBaseUpdate=m:h.next=m,n.lastBaseUpdate=k))}if(null!==f){var l=e.baseState;g=0;n=m=k=null;h=f;do{var r=h.lane,p=h.eventTime;if((d&r)===r){null!==n&&(n=n.next={eventTime:p,lane:0,tag:h.tag,payload:h.payload,callback:h.callback,next:null});a:{var x=
+a,F=h;r=b;p=c;switch(F.tag){case 1:x=F.payload;if("function"===typeof x){l=x.call(p,l,r);break a}l=x;break a;case 3:x.flags=x.flags&-65537|128;case 0:x=F.payload;r="function"===typeof x?x.call(p,l,r):x;if(null===r||void 0===r)break a;l=E({},l,r);break a;case 2:fb=!0}}null!==h.callback&&0!==h.lane&&(a.flags|=64,r=e.effects,null===r?e.effects=[h]:r.push(h))}else p={eventTime:p,lane:r,tag:h.tag,payload:h.payload,callback:h.callback,next:null},null===n?(m=n=p,k=l):n=n.next=p,g|=r;h=h.next;if(null===h)if(h=
+e.shared.pending,null===h)break;else r=h,h=r.next,r.next=null,e.lastBaseUpdate=r,e.shared.pending=null}while(1);null===n&&(k=l);e.baseState=k;e.firstBaseUpdate=m;e.lastBaseUpdate=n;b=e.shared.interleaved;if(null!==b){e=b;do g|=e.lane,e=e.next;while(e!==b)}else null===f&&(e.shared.lanes=0);ra|=g;a.lanes=g;a.memoizedState=l}}function Fh(a,b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;b<a.length;b++){var d=a[b],e=d.callback;if(null!==e){d.callback=null;d=c;if("function"!==typeof e)throw Error(n(191,
+e));e.call(d)}}}function ef(a,b,c,d){b=a.memoizedState;c=c(d,b);c=null===c||void 0===c?b:E({},b,c);a.memoizedState=c;0===a.lanes&&(a.updateQueue.baseState=c)}function Gh(a,b,c,d,e,f,g){a=a.stateNode;return"function"===typeof a.shouldComponentUpdate?a.shouldComponentUpdate(d,f,g):b.prototype&&b.prototype.isPureReactComponent?!qc(c,d)||!qc(e,f):!0}function Hh(a,b,c){var d=!1,e=cb;var f=b.contextType;"object"===typeof f&&null!==f?f=qa(f):(e=ea(b)?qb:J.current,d=b.contextTypes,f=(d=null!==d&&void 0!==
+d)?Nb(a,e):cb);b=new b(c,f);a.memoizedState=null!==b.state&&void 0!==b.state?b.state:null;b.updater=ud;a.stateNode=b;b._reactInternals=a;d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=e,a.__reactInternalMemoizedMaskedChildContext=f);return b}function Ih(a,b,c,d){a=b.state;"function"===typeof b.componentWillReceiveProps&&b.componentWillReceiveProps(c,d);"function"===typeof b.UNSAFE_componentWillReceiveProps&&b.UNSAFE_componentWillReceiveProps(c,d);b.state!==a&&ud.enqueueReplaceState(b,
+b.state,null)}function ff(a,b,c,d){var e=a.stateNode;e.props=c;e.state=a.memoizedState;e.refs=Jh;df(a);var f=b.contextType;"object"===typeof f&&null!==f?e.context=qa(f):(f=ea(b)?qb:J.current,e.context=Nb(a,f));e.state=a.memoizedState;f=b.getDerivedStateFromProps;"function"===typeof f&&(ef(a,b,f,c),e.state=a.memoizedState);"function"===typeof b.getDerivedStateFromProps||"function"===typeof e.getSnapshotBeforeUpdate||"function"!==typeof e.UNSAFE_componentWillMount&&"function"!==typeof e.componentWillMount||
+(b=e.state,"function"===typeof e.componentWillMount&&e.componentWillMount(),"function"===typeof e.UNSAFE_componentWillMount&&e.UNSAFE_componentWillMount(),b!==e.state&&ud.enqueueReplaceState(e,e.state,null),td(a,c,e,d),e.state=a.memoizedState);"function"===typeof e.componentDidMount&&(a.flags|=4194308)}function vc(a,b,c){a=c.ref;if(null!==a&&"function"!==typeof a&&"object"!==typeof a){if(c._owner){c=c._owner;if(c){if(1!==c.tag)throw Error(n(309));var d=c.stateNode}if(!d)throw Error(n(147,a));var e=
+d,f=""+a;if(null!==b&&null!==b.ref&&"function"===typeof b.ref&&b.ref._stringRef===f)return b.ref;b=function(a){var b=e.refs;b===Jh&&(b=e.refs={});null===a?delete b[f]:b[f]=a};b._stringRef=f;return b}if("string"!==typeof a)throw Error(n(284));if(!c._owner)throw Error(n(290,a));}return a}function vd(a,b){a=Object.prototype.toString.call(b);throw Error(n(31,"[object Object]"===a?"object with keys {"+Object.keys(b).join(", ")+"}":a));}function Kh(a){var b=a._init;return b(a._payload)}function Lh(a){function b(b,
+c){if(a){var d=b.deletions;null===d?(b.deletions=[c],b.flags|=16):d.push(c)}}function c(c,d){if(!a)return null;for(;null!==d;)b(c,d),d=d.sibling;return null}function d(a,b){for(a=new Map;null!==b;)null!==b.key?a.set(b.key,b):a.set(b.index,b),b=b.sibling;return a}function e(a,b){a=gb(a,b);a.index=0;a.sibling=null;return a}function f(b,c,d){b.index=d;if(!a)return b.flags|=1048576,c;d=b.alternate;if(null!==d)return d=d.index,d<c?(b.flags|=2,c):d;b.flags|=2;return c}function g(b){a&&null===b.alternate&&
+(b.flags|=2);return b}function h(a,b,c,d){if(null===b||6!==b.tag)return b=gf(c,a.mode,d),b.return=a,b;b=e(b,c);b.return=a;return b}function k(a,b,c,d){var f=c.type;if(f===Bb)return l(a,b,c.props.children,d,c.key);if(null!==b&&(b.elementType===f||"object"===typeof f&&null!==f&&f.$$typeof===Ta&&Kh(f)===b.type))return d=e(b,c.props),d.ref=vc(a,b,c),d.return=a,d;d=wd(c.type,c.key,c.props,null,a.mode,d);d.ref=vc(a,b,c);d.return=a;return d}function m(a,b,c,d){if(null===b||4!==b.tag||b.stateNode.containerInfo!==
+c.containerInfo||b.stateNode.implementation!==c.implementation)return b=hf(c,a.mode,d),b.return=a,b;b=e(b,c.children||[]);b.return=a;return b}function l(a,b,c,d,f){if(null===b||7!==b.tag)return b=ub(c,a.mode,d,f),b.return=a,b;b=e(b,c);b.return=a;return b}function u(a,b,c){if("string"===typeof b&&""!==b||"number"===typeof b)return b=gf(""+b,a.mode,c),b.return=a,b;if("object"===typeof b&&null!==b){switch(b.$$typeof){case xd:return c=wd(b.type,b.key,b.props,null,a.mode,c),c.ref=vc(a,null,b),c.return=
+a,c;case Cb:return b=hf(b,a.mode,c),b.return=a,b;case Ta:var d=b._init;return u(a,d(b._payload),c)}if(cc(b)||ac(b))return b=ub(b,a.mode,c,null),b.return=a,b;vd(a,b)}return null}function r(a,b,c,d){var e=null!==b?b.key:null;if("string"===typeof c&&""!==c||"number"===typeof c)return null!==e?null:h(a,b,""+c,d);if("object"===typeof c&&null!==c){switch(c.$$typeof){case xd:return c.key===e?k(a,b,c,d):null;case Cb:return c.key===e?m(a,b,c,d):null;case Ta:return e=c._init,r(a,b,e(c._payload),d)}if(cc(c)||
+ac(c))return null!==e?null:l(a,b,c,d,null);vd(a,c)}return null}function p(a,b,c,d,e){if("string"===typeof d&&""!==d||"number"===typeof d)return a=a.get(c)||null,h(b,a,""+d,e);if("object"===typeof d&&null!==d){switch(d.$$typeof){case xd:return a=a.get(null===d.key?c:d.key)||null,k(b,a,d,e);case Cb:return a=a.get(null===d.key?c:d.key)||null,m(b,a,d,e);case Ta:var f=d._init;return p(a,b,c,f(d._payload),e)}if(cc(d)||ac(d))return a=a.get(c)||null,l(b,a,d,e,null);vd(b,d)}return null}function x(e,g,h,k){for(var n=
+null,m=null,l=g,q=g=0,v=null;null!==l&&q<h.length;q++){l.index>q?(v=l,l=null):v=l.sibling;var A=r(e,l,h[q],k);if(null===A){null===l&&(l=v);break}a&&l&&null===A.alternate&&b(e,l);g=f(A,g,q);null===m?n=A:m.sibling=A;m=A;l=v}if(q===h.length)return c(e,l),D&&rb(e,q),n;if(null===l){for(;q<h.length;q++)l=u(e,h[q],k),null!==l&&(g=f(l,g,q),null===m?n=l:m.sibling=l,m=l);D&&rb(e,q);return n}for(l=d(e,l);q<h.length;q++)v=p(l,e,q,h[q],k),null!==v&&(a&&null!==v.alternate&&l.delete(null===v.key?q:v.key),g=f(v,
+g,q),null===m?n=v:m.sibling=v,m=v);a&&l.forEach(function(a){return b(e,a)});D&&rb(e,q);return n}function F(e,g,h,k){var m=ac(h);if("function"!==typeof m)throw Error(n(150));h=m.call(h);if(null==h)throw Error(n(151));for(var l=m=null,q=g,v=g=0,A=null,t=h.next();null!==q&&!t.done;v++,t=h.next()){q.index>v?(A=q,q=null):A=q.sibling;var x=r(e,q,t.value,k);if(null===x){null===q&&(q=A);break}a&&q&&null===x.alternate&&b(e,q);g=f(x,g,v);null===l?m=x:l.sibling=x;l=x;q=A}if(t.done)return c(e,q),D&&rb(e,v),m;
+if(null===q){for(;!t.done;v++,t=h.next())t=u(e,t.value,k),null!==t&&(g=f(t,g,v),null===l?m=t:l.sibling=t,l=t);D&&rb(e,v);return m}for(q=d(e,q);!t.done;v++,t=h.next())t=p(q,e,v,t.value,k),null!==t&&(a&&null!==t.alternate&&q.delete(null===t.key?v:t.key),g=f(t,g,v),null===l?m=t:l.sibling=t,l=t);a&&q.forEach(function(a){return b(e,a)});D&&rb(e,v);return m}function w(a,d,f,h){"object"===typeof f&&null!==f&&f.type===Bb&&null===f.key&&(f=f.props.children);if("object"===typeof f&&null!==f){switch(f.$$typeof){case xd:a:{for(var k=
+f.key,m=d;null!==m;){if(m.key===k){k=f.type;if(k===Bb){if(7===m.tag){c(a,m.sibling);d=e(m,f.props.children);d.return=a;a=d;break a}}else if(m.elementType===k||"object"===typeof k&&null!==k&&k.$$typeof===Ta&&Kh(k)===m.type){c(a,m.sibling);d=e(m,f.props);d.ref=vc(a,m,f);d.return=a;a=d;break a}c(a,m);break}else b(a,m);m=m.sibling}f.type===Bb?(d=ub(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=wd(f.type,f.key,f.props,null,a.mode,h),h.ref=vc(a,d,f),h.return=a,a=h)}return g(a);case Cb:a:{for(m=f.key;null!==
+d;){if(d.key===m)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=hf(f,a.mode,h);d.return=a;a=d}return g(a);case Ta:return m=f._init,w(a,d,m(f._payload),h)}if(cc(f))return x(a,d,f,h);if(ac(f))return F(a,d,f,h);vd(a,f)}return"string"===typeof f&&""!==f||"number"===typeof f?(f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):
+(c(a,d),d=gf(f,a.mode,h),d.return=a,a=d),g(a)):c(a,d)}return w}function vb(a){if(a===wc)throw Error(n(174));return a}function jf(a,b){y(xc,b);y(yc,a);y(Ea,wc);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:oe(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=oe(b,a)}w(Ea);y(Ea,b)}function Tb(a){w(Ea);w(yc);w(xc)}function Mh(a){vb(xc.current);var b=vb(Ea.current);var c=oe(b,a.type);b!==c&&(y(yc,a),y(Ea,c))}function kf(a){yc.current===a&&
+(w(Ea),w(yc))}function yd(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||"$?"===c.data||"$!"===c.data))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.flags&128))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}return null}function lf(){for(var a=0;a<mf.length;a++)mf[a]._workInProgressVersionPrimary=
+null;mf.length=0}function V(){throw Error(n(321));}function nf(a,b){if(null===b)return!1;for(var c=0;c<b.length&&c<a.length;c++)if(!ua(a[c],b[c]))return!1;return!0}function of(a,b,c,d,e,f){wb=f;C=b;b.memoizedState=null;b.updateQueue=null;b.lanes=0;zd.current=null===a||null===a.memoizedState?mk:nk;a=c(d,e);if(zc){f=0;do{zc=!1;Ac=0;if(25<=f)throw Error(n(301));f+=1;N=K=null;b.updateQueue=null;zd.current=ok;a=c(d,e)}while(zc)}zd.current=Ad;b=null!==K&&null!==K.next;wb=0;N=K=C=null;Bd=!1;if(b)throw Error(n(300));
+return a}function pf(){var a=0!==Ac;Ac=0;return a}function Fa(){var a={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};null===N?C.memoizedState=N=a:N=N.next=a;return N}function sa(){if(null===K){var a=C.alternate;a=null!==a?a.memoizedState:null}else a=K.next;var b=null===N?C.memoizedState:N.next;if(null!==b)N=b,K=a;else{if(null===a)throw Error(n(310));K=a;a={memoizedState:K.memoizedState,baseState:K.baseState,baseQueue:K.baseQueue,queue:K.queue,next:null};null===N?C.memoizedState=
+N=a:N=N.next=a}return N}function Bc(a,b){return"function"===typeof b?b(a):b}function qf(a,b,c){b=sa();c=b.queue;if(null===c)throw Error(n(311));c.lastRenderedReducer=a;var d=K,e=d.baseQueue,f=c.pending;if(null!==f){if(null!==e){var g=e.next;e.next=f.next;f.next=g}d.baseQueue=e=f;c.pending=null}if(null!==e){f=e.next;d=d.baseState;var h=g=null,k=null,m=f;do{var l=m.lane;if((wb&l)===l)null!==k&&(k=k.next={lane:0,action:m.action,hasEagerState:m.hasEagerState,eagerState:m.eagerState,next:null}),d=m.hasEagerState?
+m.eagerState:a(d,m.action);else{var u={lane:l,action:m.action,hasEagerState:m.hasEagerState,eagerState:m.eagerState,next:null};null===k?(h=k=u,g=d):k=k.next=u;C.lanes|=l;ra|=l}m=m.next}while(null!==m&&m!==f);null===k?g=d:k.next=h;ua(d,b.memoizedState)||(ha=!0);b.memoizedState=d;b.baseState=g;b.baseQueue=k;c.lastRenderedState=d}a=c.interleaved;if(null!==a){e=a;do f=e.lane,C.lanes|=f,ra|=f,e=e.next;while(e!==a)}else null===e&&(c.lanes=0);return[b.memoizedState,c.dispatch]}function rf(a,b,c){b=sa();
+c=b.queue;if(null===c)throw Error(n(311));c.lastRenderedReducer=a;var d=c.dispatch,e=c.pending,f=b.memoizedState;if(null!==e){c.pending=null;var g=e=e.next;do f=a(f,g.action),g=g.next;while(g!==e);ua(f,b.memoizedState)||(ha=!0);b.memoizedState=f;null===b.baseQueue&&(b.baseState=f);c.lastRenderedState=f}return[f,d]}function Nh(a,b,c){}function Oh(a,b,c){c=C;var d=sa(),e=b(),f=!ua(d.memoizedState,e);f&&(d.memoizedState=e,ha=!0);d=d.queue;sf(Ph.bind(null,c,d,a),[a]);if(d.getSnapshot!==b||f||null!==N&&
+N.memoizedState.tag&1){c.flags|=2048;Cc(9,Qh.bind(null,c,d,e,b),void 0,null);if(null===O)throw Error(n(349));0!==(wb&30)||Rh(c,b,e)}return e}function Rh(a,b,c){a.flags|=16384;a={getSnapshot:b,value:c};b=C.updateQueue;null===b?(b={lastEffect:null,stores:null},C.updateQueue=b,b.stores=[a]):(c=b.stores,null===c?b.stores=[a]:c.push(a))}function Qh(a,b,c,d){b.value=c;b.getSnapshot=d;Sh(b)&&Th(a)}function Ph(a,b,c){return c(function(){Sh(b)&&Th(a)})}function Sh(a){var b=a.getSnapshot;a=a.value;try{var c=
+b();return!ua(a,c)}catch(d){return!0}}function Th(a){var b=Oa(a,1);null!==b&&ya(b,a,1,-1)}function Uh(a){var b=Fa();"function"===typeof a&&(a=a());b.memoizedState=b.baseState=a;a={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:Bc,lastRenderedState:a};b.queue=a;a=a.dispatch=pk.bind(null,C,a);return[b.memoizedState,a]}function Cc(a,b,c,d){a={tag:a,create:b,destroy:c,deps:d,next:null};b=C.updateQueue;null===b?(b={lastEffect:null,stores:null},C.updateQueue=b,b.lastEffect=a.next=
+a):(c=b.lastEffect,null===c?b.lastEffect=a.next=a:(d=c.next,c.next=a,a.next=d,b.lastEffect=a));return a}function Vh(a){return sa().memoizedState}function Cd(a,b,c,d){var e=Fa();C.flags|=a;e.memoizedState=Cc(1|b,c,void 0,void 0===d?null:d)}function Dd(a,b,c,d){var e=sa();d=void 0===d?null:d;var f=void 0;if(null!==K){var g=K.memoizedState;f=g.destroy;if(null!==d&&nf(d,g.deps)){e.memoizedState=Cc(b,c,f,d);return}}C.flags|=a;e.memoizedState=Cc(1|b,c,f,d)}function Wh(a,b){return Cd(8390656,8,a,b)}function sf(a,
+b){return Dd(2048,8,a,b)}function Xh(a,b){return Dd(4,2,a,b)}function Yh(a,b){return Dd(4,4,a,b)}function Zh(a,b){if("function"===typeof b)return a=a(),b(a),function(){b(null)};if(null!==b&&void 0!==b)return a=a(),b.current=a,function(){b.current=null}}function $h(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return Dd(4,4,Zh.bind(null,b,a),c)}function tf(a,b){}function ai(a,b){var c=sa();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&nf(b,d[1]))return d[0];c.memoizedState=[a,
+b];return a}function bi(a,b){var c=sa();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&nf(b,d[1]))return d[0];a=a();c.memoizedState=[a,b];return a}function ci(a,b,c){if(0===(wb&21))return a.baseState&&(a.baseState=!1,ha=!0),a.memoizedState=c;ua(c,b)||(c=Dg(),C.lanes|=c,ra|=c,a.baseState=!0);return b}function qk(a,b,c){c=z;z=0!==c&&4>c?c:4;a(!0);var d=uf.transition;uf.transition={};try{a(!1),b()}finally{z=c,uf.transition=d}}function di(){return sa().memoizedState}function rk(a,b,
+c){var d=hb(a);c={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ei(a))fi(b,c);else if(c=Ch(a,b,c,d),null!==c){var e=Z();ya(c,a,d,e);gi(c,b,d)}}function pk(a,b,c){var d=hb(a),e={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(ei(a))fi(b,e);else{var f=a.alternate;if(0===a.lanes&&(null===f||0===f.lanes)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState,h=f(g,c);e.hasEagerState=!0;e.eagerState=h;if(ua(h,g)){var k=b.interleaved;null===k?(e.next=e,cf(b)):
+(e.next=k.next,k.next=e);b.interleaved=e;return}}catch(m){}finally{}c=Ch(a,b,e,d);null!==c&&(e=Z(),ya(c,a,d,e),gi(c,b,d))}}function ei(a){var b=a.alternate;return a===C||null!==b&&b===C}function fi(a,b){zc=Bd=!0;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}function gi(a,b,c){if(0!==(c&4194240)){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;xe(a,c)}}function Ub(a,b){try{var c="",d=b;do c+=gj(d),d=d.return;while(d);var e=c}catch(f){e="\nError generating stack: "+f.message+
+"\n"+f.stack}return{value:a,source:b,stack:e,digest:null}}function vf(a,b,c){return{value:a,source:null,stack:null!=c?c:null,digest:null!=b?b:null}}function wf(a,b){try{console.error(b.value)}catch(c){setTimeout(function(){throw c;})}}function hi(a,b,c){c=Pa(-1,c);c.tag=3;c.payload={element:null};var d=b.value;c.callback=function(){Ed||(Ed=!0,xf=d);wf(a,b)};return c}function ii(a,b,c){c=Pa(-1,c);c.tag=3;var d=a.type.getDerivedStateFromError;if("function"===typeof d){var e=b.value;c.payload=function(){return d(e)};
+c.callback=function(){wf(a,b)}}var f=a.stateNode;null!==f&&"function"===typeof f.componentDidCatch&&(c.callback=function(){wf(a,b);"function"!==typeof d&&(null===ib?ib=new Set([this]):ib.add(this));var c=b.stack;this.componentDidCatch(b.value,{componentStack:null!==c?c:""})});return c}function ji(a,b,c){var d=a.pingCache;if(null===d){d=a.pingCache=new sk;var e=new Set;d.set(b,e)}else e=d.get(b),void 0===e&&(e=new Set,d.set(b,e));e.has(c)||(e.add(c),a=tk.bind(null,a,b,c),b.then(a,a))}function ki(a){do{var b;
+if(b=13===a.tag)b=a.memoizedState,b=null!==b?null!==b.dehydrated?!0:!1:!0;if(b)return a;a=a.return}while(null!==a);return null}function li(a,b,c,d,e){if(0===(a.mode&1))return a===b?a.flags|=65536:(a.flags|=128,c.flags|=131072,c.flags&=-52805,1===c.tag&&(null===c.alternate?c.tag=17:(b=Pa(-1,1),b.tag=2,eb(c,b,1))),c.lanes|=1),a;a.flags|=65536;a.lanes=e;return a}function aa(a,b,c,d){b.child=null===a?mi(b,null,c,d):Vb(b,a.child,c,d)}function ni(a,b,c,d,e){c=c.render;var f=b.ref;Sb(b,e);d=of(a,b,c,d,f,
+e);c=pf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&c&&Ue(b);b.flags|=1;aa(a,b,d,e);return b.child}function oi(a,b,c,d,e){if(null===a){var f=c.type;if("function"===typeof f&&!yf(f)&&void 0===f.defaultProps&&null===c.compare&&void 0===c.defaultProps)return b.tag=15,b.type=f,pi(a,b,f,d,e);a=wd(c.type,null,d,b,b.mode,e);a.ref=b.ref;a.return=b;return b.child=a}f=a.child;if(0===(a.lanes&e)){var g=f.memoizedProps;c=c.compare;c=null!==c?c:qc;if(c(g,d)&&a.ref===
+b.ref)return Qa(a,b,e)}b.flags|=1;a=gb(f,d);a.ref=b.ref;a.return=b;return b.child=a}function pi(a,b,c,d,e){if(null!==a){var f=a.memoizedProps;if(qc(f,d)&&a.ref===b.ref)if(ha=!1,b.pendingProps=d=f,0!==(a.lanes&e))0!==(a.flags&131072)&&(ha=!0);else return b.lanes=a.lanes,Qa(a,b,e)}return zf(a,b,c,d,e)}function qi(a,b,c){var d=b.pendingProps,e=d.children,f=null!==a?a.memoizedState:null;if("hidden"===d.mode)if(0===(b.mode&1))b.memoizedState={baseLanes:0,cachePool:null,transitions:null},y(Ga,ba),ba|=c;
+else{if(0===(c&1073741824))return a=null!==f?f.baseLanes|c:c,b.lanes=b.childLanes=1073741824,b.memoizedState={baseLanes:a,cachePool:null,transitions:null},b.updateQueue=null,y(Ga,ba),ba|=a,null;b.memoizedState={baseLanes:0,cachePool:null,transitions:null};d=null!==f?f.baseLanes:c;y(Ga,ba);ba|=d}else null!==f?(d=f.baseLanes|c,b.memoizedState=null):d=c,y(Ga,ba),ba|=d;aa(a,b,e,c);return b.child}function ri(a,b){var c=b.ref;if(null===a&&null!==c||null!==a&&a.ref!==c)b.flags|=512,b.flags|=2097152}function zf(a,
+b,c,d,e){var f=ea(c)?qb:J.current;f=Nb(b,f);Sb(b,e);c=of(a,b,c,d,f,e);d=pf();if(null!==a&&!ha)return b.updateQueue=a.updateQueue,b.flags&=-2053,a.lanes&=~e,Qa(a,b,e);D&&d&&Ue(b);b.flags|=1;aa(a,b,c,e);return b.child}function si(a,b,c,d,e){if(ea(c)){var f=!0;ld(b)}else f=!1;Sb(b,e);if(null===b.stateNode)Fd(a,b),Hh(b,c,d),ff(b,c,d,e),d=!0;else if(null===a){var g=b.stateNode,h=b.memoizedProps;g.props=h;var k=g.context,m=c.contextType;"object"===typeof m&&null!==m?m=qa(m):(m=ea(c)?qb:J.current,m=Nb(b,
+m));var l=c.getDerivedStateFromProps,n="function"===typeof l||"function"===typeof g.getSnapshotBeforeUpdate;n||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==d||k!==m)&&Ih(b,g,d,m);fb=!1;var r=b.memoizedState;g.state=r;td(b,d,g,e);k=b.memoizedState;h!==d||r!==k||S.current||fb?("function"===typeof l&&(ef(b,c,l,d),k=b.memoizedState),(h=fb||Gh(b,c,h,d,r,k,m))?(n||"function"!==typeof g.UNSAFE_componentWillMount&&"function"!==typeof g.componentWillMount||
+("function"===typeof g.componentWillMount&&g.componentWillMount(),"function"===typeof g.UNSAFE_componentWillMount&&g.UNSAFE_componentWillMount()),"function"===typeof g.componentDidMount&&(b.flags|=4194308)):("function"===typeof g.componentDidMount&&(b.flags|=4194308),b.memoizedProps=d,b.memoizedState=k),g.props=d,g.state=k,g.context=m,d=h):("function"===typeof g.componentDidMount&&(b.flags|=4194308),d=!1)}else{g=b.stateNode;Dh(a,b);h=b.memoizedProps;m=b.type===b.elementType?h:xa(b.type,h);g.props=
+m;n=b.pendingProps;r=g.context;k=c.contextType;"object"===typeof k&&null!==k?k=qa(k):(k=ea(c)?qb:J.current,k=Nb(b,k));var p=c.getDerivedStateFromProps;(l="function"===typeof p||"function"===typeof g.getSnapshotBeforeUpdate)||"function"!==typeof g.UNSAFE_componentWillReceiveProps&&"function"!==typeof g.componentWillReceiveProps||(h!==n||r!==k)&&Ih(b,g,d,k);fb=!1;r=b.memoizedState;g.state=r;td(b,d,g,e);var x=b.memoizedState;h!==n||r!==x||S.current||fb?("function"===typeof p&&(ef(b,c,p,d),x=b.memoizedState),
+(m=fb||Gh(b,c,m,d,r,x,k)||!1)?(l||"function"!==typeof g.UNSAFE_componentWillUpdate&&"function"!==typeof g.componentWillUpdate||("function"===typeof g.componentWillUpdate&&g.componentWillUpdate(d,x,k),"function"===typeof g.UNSAFE_componentWillUpdate&&g.UNSAFE_componentWillUpdate(d,x,k)),"function"===typeof g.componentDidUpdate&&(b.flags|=4),"function"===typeof g.getSnapshotBeforeUpdate&&(b.flags|=1024)):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=
+4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),b.memoizedProps=d,b.memoizedState=x),g.props=d,g.state=x,g.context=k,d=m):("function"!==typeof g.componentDidUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=4),"function"!==typeof g.getSnapshotBeforeUpdate||h===a.memoizedProps&&r===a.memoizedState||(b.flags|=1024),d=!1)}return Af(a,b,c,d,f,e)}function Af(a,b,c,d,e,f){ri(a,b);var g=0!==(b.flags&128);if(!d&&!g)return e&&vh(b,c,!1),
+Qa(a,b,f);d=b.stateNode;uk.current=b;var h=g&&"function"!==typeof c.getDerivedStateFromError?null:d.render();b.flags|=1;null!==a&&g?(b.child=Vb(b,a.child,null,f),b.child=Vb(b,null,h,f)):aa(a,b,h,f);b.memoizedState=d.state;e&&vh(b,c,!0);return b.child}function ti(a){var b=a.stateNode;b.pendingContext?th(a,b.pendingContext,b.pendingContext!==b.context):b.context&&th(a,b.context,!1);jf(a,b.containerInfo)}function ui(a,b,c,d,e){Qb();Ye(e);b.flags|=256;aa(a,b,c,d);return b.child}function Bf(a){return{baseLanes:a,
+cachePool:null,transitions:null}}function vi(a,b,c){var d=b.pendingProps,e=G.current,f=!1,g=0!==(b.flags&128),h;(h=g)||(h=null!==a&&null===a.memoizedState?!1:0!==(e&2));if(h)f=!0,b.flags&=-129;else if(null===a||null!==a.memoizedState)e|=1;y(G,e&1);if(null===a){Xe(b);a=b.memoizedState;if(null!==a&&(a=a.dehydrated,null!==a))return 0===(b.mode&1)?b.lanes=1:"$!"===a.data?b.lanes=8:b.lanes=1073741824,null;g=d.children;a=d.fallback;return f?(d=b.mode,f=b.child,g={mode:"hidden",children:g},0===(d&1)&&null!==
+f?(f.childLanes=0,f.pendingProps=g):f=Gd(g,d,0,null),a=ub(a,d,c,null),f.return=b,a.return=b,f.sibling=a,b.child=f,b.child.memoizedState=Bf(c),b.memoizedState=Cf,a):Df(b,g)}e=a.memoizedState;if(null!==e&&(h=e.dehydrated,null!==h))return vk(a,b,g,d,h,e,c);if(f){f=d.fallback;g=b.mode;e=a.child;h=e.sibling;var k={mode:"hidden",children:d.children};0===(g&1)&&b.child!==e?(d=b.child,d.childLanes=0,d.pendingProps=k,b.deletions=null):(d=gb(e,k),d.subtreeFlags=e.subtreeFlags&14680064);null!==h?f=gb(h,f):(f=
+ub(f,g,c,null),f.flags|=2);f.return=b;d.return=b;d.sibling=f;b.child=d;d=f;f=b.child;g=a.child.memoizedState;g=null===g?Bf(c):{baseLanes:g.baseLanes|c,cachePool:null,transitions:g.transitions};f.memoizedState=g;f.childLanes=a.childLanes&~c;b.memoizedState=Cf;return d}f=a.child;a=f.sibling;d=gb(f,{mode:"visible",children:d.children});0===(b.mode&1)&&(d.lanes=c);d.return=b;d.sibling=null;null!==a&&(c=b.deletions,null===c?(b.deletions=[a],b.flags|=16):c.push(a));b.child=d;b.memoizedState=null;return d}
+function Df(a,b,c){b=Gd({mode:"visible",children:b},a.mode,0,null);b.return=a;return a.child=b}function Hd(a,b,c,d){null!==d&&Ye(d);Vb(b,a.child,null,c);a=Df(b,b.pendingProps.children);a.flags|=2;b.memoizedState=null;return a}function vk(a,b,c,d,e,f,g){if(c){if(b.flags&256)return b.flags&=-257,d=vf(Error(n(422))),Hd(a,b,g,d);if(null!==b.memoizedState)return b.child=a.child,b.flags|=128,null;f=d.fallback;e=b.mode;d=Gd({mode:"visible",children:d.children},e,0,null);f=ub(f,e,g,null);f.flags|=2;d.return=
+b;f.return=b;d.sibling=f;b.child=d;0!==(b.mode&1)&&Vb(b,a.child,null,g);b.child.memoizedState=Bf(g);b.memoizedState=Cf;return f}if(0===(b.mode&1))return Hd(a,b,g,null);if("$!"===e.data){d=e.nextSibling&&e.nextSibling.dataset;if(d)var h=d.dgst;d=h;f=Error(n(419));d=vf(f,d,void 0);return Hd(a,b,g,d)}h=0!==(g&a.childLanes);if(ha||h){d=O;if(null!==d){switch(g&-g){case 4:e=2;break;case 16:e=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:e=
+32;break;case 536870912:e=268435456;break;default:e=0}e=0!==(e&(d.suspendedLanes|g))?0:e;0!==e&&e!==f.retryLane&&(f.retryLane=e,Oa(a,e),ya(d,a,e,-1))}Ef();d=vf(Error(n(421)));return Hd(a,b,g,d)}if("$?"===e.data)return b.flags|=128,b.child=a.child,b=wk.bind(null,a),e._reactRetry=b,null;a=f.treeContext;fa=Ka(e.nextSibling);la=b;D=!0;wa=null;null!==a&&(na[oa++]=Ma,na[oa++]=Na,na[oa++]=sb,Ma=a.id,Na=a.overflow,sb=b);b=Df(b,d.children);b.flags|=4096;return b}function wi(a,b,c){a.lanes|=b;var d=a.alternate;
+null!==d&&(d.lanes|=b);bf(a.return,b,c)}function Ff(a,b,c,d,e){var f=a.memoizedState;null===f?a.memoizedState={isBackwards:b,rendering:null,renderingStartTime:0,last:d,tail:c,tailMode:e}:(f.isBackwards=b,f.rendering=null,f.renderingStartTime=0,f.last=d,f.tail=c,f.tailMode=e)}function xi(a,b,c){var d=b.pendingProps,e=d.revealOrder,f=d.tail;aa(a,b,d.children,c);d=G.current;if(0!==(d&2))d=d&1|2,b.flags|=128;else{if(null!==a&&0!==(a.flags&128))a:for(a=b.child;null!==a;){if(13===a.tag)null!==a.memoizedState&&
+wi(a,c,b);else if(19===a.tag)wi(a,c,b);else if(null!==a.child){a.child.return=a;a=a.child;continue}if(a===b)break a;for(;null===a.sibling;){if(null===a.return||a.return===b)break a;a=a.return}a.sibling.return=a.return;a=a.sibling}d&=1}y(G,d);if(0===(b.mode&1))b.memoizedState=null;else switch(e){case "forwards":c=b.child;for(e=null;null!==c;)a=c.alternate,null!==a&&null===yd(a)&&(e=c),c=c.sibling;c=e;null===c?(e=b.child,b.child=null):(e=c.sibling,c.sibling=null);Ff(b,!1,e,c,f);break;case "backwards":c=
+null;e=b.child;for(b.child=null;null!==e;){a=e.alternate;if(null!==a&&null===yd(a)){b.child=e;break}a=e.sibling;e.sibling=c;c=e;e=a}Ff(b,!0,c,null,f);break;case "together":Ff(b,!1,null,null,void 0);break;default:b.memoizedState=null}return b.child}function Fd(a,b){0===(b.mode&1)&&null!==a&&(a.alternate=null,b.alternate=null,b.flags|=2)}function Qa(a,b,c){null!==a&&(b.dependencies=a.dependencies);ra|=b.lanes;if(0===(c&b.childLanes))return null;if(null!==a&&b.child!==a.child)throw Error(n(153));if(null!==
+b.child){a=b.child;c=gb(a,a.pendingProps);b.child=c;for(c.return=b;null!==a.sibling;)a=a.sibling,c=c.sibling=gb(a,a.pendingProps),c.return=b;c.sibling=null}return b.child}function xk(a,b,c){switch(b.tag){case 3:ti(b);Qb();break;case 5:Mh(b);break;case 1:ea(b.type)&&ld(b);break;case 4:jf(b,b.stateNode.containerInfo);break;case 10:var d=b.type._context,e=b.memoizedProps.value;y(rd,d._currentValue);d._currentValue=e;break;case 13:d=b.memoizedState;if(null!==d){if(null!==d.dehydrated)return y(G,G.current&
+1),b.flags|=128,null;if(0!==(c&b.child.childLanes))return vi(a,b,c);y(G,G.current&1);a=Qa(a,b,c);return null!==a?a.sibling:null}y(G,G.current&1);break;case 19:d=0!==(c&b.childLanes);if(0!==(a.flags&128)){if(d)return xi(a,b,c);b.flags|=128}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null,e.lastEffect=null);y(G,G.current);if(d)break;else return null;case 22:case 23:return b.lanes=0,qi(a,b,c)}return Qa(a,b,c)}function Dc(a,b){if(!D)switch(a.tailMode){case "hidden":b=a.tail;for(var c=null;null!==
+b;)null!==b.alternate&&(c=b),b=b.sibling;null===c?a.tail=null:c.sibling=null;break;case "collapsed":c=a.tail;for(var d=null;null!==c;)null!==c.alternate&&(d=c),c=c.sibling;null===d?b||null===a.tail?a.tail=null:a.tail.sibling=null:d.sibling=null}}function W(a){var b=null!==a.alternate&&a.alternate.child===a.child,c=0,d=0;if(b)for(var e=a.child;null!==e;)c|=e.lanes|e.childLanes,d|=e.subtreeFlags&14680064,d|=e.flags&14680064,e.return=a,e=e.sibling;else for(e=a.child;null!==e;)c|=e.lanes|e.childLanes,
+d|=e.subtreeFlags,d|=e.flags,e.return=a,e=e.sibling;a.subtreeFlags|=d;a.childLanes=c;return b}function yk(a,b,c){var d=b.pendingProps;Ve(b);switch(b.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return W(b),null;case 1:return ea(b.type)&&(w(S),w(J)),W(b),null;case 3:d=b.stateNode;Tb();w(S);w(J);lf();d.pendingContext&&(d.context=d.pendingContext,d.pendingContext=null);if(null===a||null===a.child)pd(b)?b.flags|=4:null===a||a.memoizedState.isDehydrated&&0===(b.flags&
+256)||(b.flags|=1024,null!==wa&&(Gf(wa),wa=null));yi(a,b);W(b);return null;case 5:kf(b);var e=vb(xc.current);c=b.type;if(null!==a&&null!=b.stateNode)zk(a,b,c,d,e),a.ref!==b.ref&&(b.flags|=512,b.flags|=2097152);else{if(!d){if(null===b.stateNode)throw Error(n(166));W(b);return null}a=vb(Ea.current);if(pd(b)){d=b.stateNode;c=b.type;var f=b.memoizedProps;d[Da]=b;d[uc]=f;a=0!==(b.mode&1);switch(c){case "dialog":B("cancel",d);B("close",d);break;case "iframe":case "object":case "embed":B("load",d);break;
+case "video":case "audio":for(e=0;e<Ec.length;e++)B(Ec[e],d);break;case "source":B("error",d);break;case "img":case "image":case "link":B("error",d);B("load",d);break;case "details":B("toggle",d);break;case "input":kg(d,f);B("invalid",d);break;case "select":d._wrapperState={wasMultiple:!!f.multiple};B("invalid",d);break;case "textarea":ng(d,f),B("invalid",d)}pe(c,f);e=null;for(var g in f)if(f.hasOwnProperty(g)){var h=f[g];"children"===g?"string"===typeof h?d.textContent!==h&&(!0!==f.suppressHydrationWarning&&
+jd(d.textContent,h,a),e=["children",h]):"number"===typeof h&&d.textContent!==""+h&&(!0!==f.suppressHydrationWarning&&jd(d.textContent,h,a),e=["children",""+h]):$b.hasOwnProperty(g)&&null!=h&&"onScroll"===g&&B("scroll",d)}switch(c){case "input":Pc(d);mg(d,f,!0);break;case "textarea":Pc(d);pg(d);break;case "select":case "option":break;default:"function"===typeof f.onClick&&(d.onclick=kd)}d=e;b.updateQueue=d;null!==d&&(b.flags|=4)}else{g=9===e.nodeType?e:e.ownerDocument;"http://www.w3.org/1999/xhtml"===
+a&&(a=qg(c));"http://www.w3.org/1999/xhtml"===a?"script"===c?(a=g.createElement("div"),a.innerHTML="<script>\x3c/script>",a=a.removeChild(a.firstChild)):"string"===typeof d.is?a=g.createElement(c,{is:d.is}):(a=g.createElement(c),"select"===c&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,c);a[Da]=b;a[uc]=d;Ak(a,b,!1,!1);b.stateNode=a;a:{g=qe(c,d);switch(c){case "dialog":B("cancel",a);B("close",a);e=d;break;case "iframe":case "object":case "embed":B("load",a);e=d;break;
+case "video":case "audio":for(e=0;e<Ec.length;e++)B(Ec[e],a);e=d;break;case "source":B("error",a);e=d;break;case "img":case "image":case "link":B("error",a);B("load",a);e=d;break;case "details":B("toggle",a);e=d;break;case "input":kg(a,d);e=ke(a,d);B("invalid",a);break;case "option":e=d;break;case "select":a._wrapperState={wasMultiple:!!d.multiple};e=E({},d,{value:void 0});B("invalid",a);break;case "textarea":ng(a,d);e=ne(a,d);B("invalid",a);break;default:e=d}pe(c,e);h=e;for(f in h)if(h.hasOwnProperty(f)){var k=
+h[f];"style"===f?sg(a,k):"dangerouslySetInnerHTML"===f?(k=k?k.__html:void 0,null!=k&&zi(a,k)):"children"===f?"string"===typeof k?("textarea"!==c||""!==k)&&Fc(a,k):"number"===typeof k&&Fc(a,""+k):"suppressContentEditableWarning"!==f&&"suppressHydrationWarning"!==f&&"autoFocus"!==f&&($b.hasOwnProperty(f)?null!=k&&"onScroll"===f&&B("scroll",a):null!=k&&$d(a,f,k,g))}switch(c){case "input":Pc(a);mg(a,d,!1);break;case "textarea":Pc(a);pg(a);break;case "option":null!=d.value&&a.setAttribute("value",""+Ua(d.value));
+break;case "select":a.multiple=!!d.multiple;f=d.value;null!=f?Db(a,!!d.multiple,f,!1):null!=d.defaultValue&&Db(a,!!d.multiple,d.defaultValue,!0);break;default:"function"===typeof e.onClick&&(a.onclick=kd)}switch(c){case "button":case "input":case "select":case "textarea":d=!!d.autoFocus;break a;case "img":d=!0;break a;default:d=!1}}d&&(b.flags|=4)}null!==b.ref&&(b.flags|=512,b.flags|=2097152)}W(b);return null;case 6:if(a&&null!=b.stateNode)Bk(a,b,a.memoizedProps,d);else{if("string"!==typeof d&&null===
+b.stateNode)throw Error(n(166));c=vb(xc.current);vb(Ea.current);if(pd(b)){d=b.stateNode;c=b.memoizedProps;d[Da]=b;if(f=d.nodeValue!==c)if(a=la,null!==a)switch(a.tag){case 3:jd(d.nodeValue,c,0!==(a.mode&1));break;case 5:!0!==a.memoizedProps.suppressHydrationWarning&&jd(d.nodeValue,c,0!==(a.mode&1))}f&&(b.flags|=4)}else d=(9===c.nodeType?c:c.ownerDocument).createTextNode(d),d[Da]=b,b.stateNode=d}W(b);return null;case 13:w(G);d=b.memoizedState;if(null===a||null!==a.memoizedState&&null!==a.memoizedState.dehydrated){if(D&&
+null!==fa&&0!==(b.mode&1)&&0===(b.flags&128)){for(f=fa;f;)f=Ka(f.nextSibling);Qb();b.flags|=98560;f=!1}else if(f=pd(b),null!==d&&null!==d.dehydrated){if(null===a){if(!f)throw Error(n(318));f=b.memoizedState;f=null!==f?f.dehydrated:null;if(!f)throw Error(n(317));f[Da]=b}else Qb(),0===(b.flags&128)&&(b.memoizedState=null),b.flags|=4;W(b);f=!1}else null!==wa&&(Gf(wa),wa=null),f=!0;if(!f)return b.flags&65536?b:null}if(0!==(b.flags&128))return b.lanes=c,b;d=null!==d;d!==(null!==a&&null!==a.memoizedState)&&
+d&&(b.child.flags|=8192,0!==(b.mode&1)&&(null===a||0!==(G.current&1)?0===L&&(L=3):Ef()));null!==b.updateQueue&&(b.flags|=4);W(b);return null;case 4:return Tb(),yi(a,b),null===a&&sc(b.stateNode.containerInfo),W(b),null;case 10:return af(b.type._context),W(b),null;case 17:return ea(b.type)&&(w(S),w(J)),W(b),null;case 19:w(G);f=b.memoizedState;if(null===f)return W(b),null;d=0!==(b.flags&128);g=f.rendering;if(null===g)if(d)Dc(f,!1);else{if(0!==L||null!==a&&0!==(a.flags&128))for(a=b.child;null!==a;){g=
+yd(a);if(null!==g){b.flags|=128;Dc(f,!1);d=g.updateQueue;null!==d&&(b.updateQueue=d,b.flags|=4);b.subtreeFlags=0;d=c;for(c=b.child;null!==c;)f=c,a=d,f.flags&=14680066,g=f.alternate,null===g?(f.childLanes=0,f.lanes=a,f.child=null,f.subtreeFlags=0,f.memoizedProps=null,f.memoizedState=null,f.updateQueue=null,f.dependencies=null,f.stateNode=null):(f.childLanes=g.childLanes,f.lanes=g.lanes,f.child=g.child,f.subtreeFlags=0,f.deletions=null,f.memoizedProps=g.memoizedProps,f.memoizedState=g.memoizedState,
+f.updateQueue=g.updateQueue,f.type=g.type,a=g.dependencies,f.dependencies=null===a?null:{lanes:a.lanes,firstContext:a.firstContext}),c=c.sibling;y(G,G.current&1|2);return b.child}a=a.sibling}null!==f.tail&&P()>Hf&&(b.flags|=128,d=!0,Dc(f,!1),b.lanes=4194304)}else{if(!d)if(a=yd(g),null!==a){if(b.flags|=128,d=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.flags|=4),Dc(f,!0),null===f.tail&&"hidden"===f.tailMode&&!g.alternate&&!D)return W(b),null}else 2*P()-f.renderingStartTime>Hf&&1073741824!==c&&(b.flags|=
+128,d=!0,Dc(f,!1),b.lanes=4194304);f.isBackwards?(g.sibling=b.child,b.child=g):(c=f.last,null!==c?c.sibling=g:b.child=g,f.last=g)}if(null!==f.tail)return b=f.tail,f.rendering=b,f.tail=b.sibling,f.renderingStartTime=P(),b.sibling=null,c=G.current,y(G,d?c&1|2:c&1),b;W(b);return null;case 22:case 23:return ba=Ga.current,w(Ga),d=null!==b.memoizedState,null!==a&&null!==a.memoizedState!==d&&(b.flags|=8192),d&&0!==(b.mode&1)?0!==(ba&1073741824)&&(W(b),b.subtreeFlags&6&&(b.flags|=8192)):W(b),null;case 24:return null;
+case 25:return null}throw Error(n(156,b.tag));}function Ck(a,b,c){Ve(b);switch(b.tag){case 1:return ea(b.type)&&(w(S),w(J)),a=b.flags,a&65536?(b.flags=a&-65537|128,b):null;case 3:return Tb(),w(S),w(J),lf(),a=b.flags,0!==(a&65536)&&0===(a&128)?(b.flags=a&-65537|128,b):null;case 5:return kf(b),null;case 13:w(G);a=b.memoizedState;if(null!==a&&null!==a.dehydrated){if(null===b.alternate)throw Error(n(340));Qb()}a=b.flags;return a&65536?(b.flags=a&-65537|128,b):null;case 19:return w(G),null;case 4:return Tb(),
+null;case 10:return af(b.type._context),null;case 22:case 23:return ba=Ga.current,w(Ga),null;case 24:return null;default:return null}}function Wb(a,b){var c=a.ref;if(null!==c)if("function"===typeof c)try{c(null)}catch(d){H(a,b,d)}else c.current=null}function If(a,b,c){try{c()}catch(d){H(a,b,d)}}function Dk(a,b){Jf=Zc;a=ch();if(Ie(a)){if("selectionStart"in a)var c={start:a.selectionStart,end:a.selectionEnd};else a:{c=(c=a.ownerDocument)&&c.defaultView||window;var d=c.getSelection&&c.getSelection();
+if(d&&0!==d.rangeCount){c=d.anchorNode;var e=d.anchorOffset,f=d.focusNode;d=d.focusOffset;try{c.nodeType,f.nodeType}catch(M){c=null;break a}var g=0,h=-1,k=-1,m=0,t=0,u=a,r=null;b:for(;;){for(var p;;){u!==c||0!==e&&3!==u.nodeType||(h=g+e);u!==f||0!==d&&3!==u.nodeType||(k=g+d);3===u.nodeType&&(g+=u.nodeValue.length);if(null===(p=u.firstChild))break;r=u;u=p}for(;;){if(u===a)break b;r===c&&++m===e&&(h=g);r===f&&++t===d&&(k=g);if(null!==(p=u.nextSibling))break;u=r;r=u.parentNode}u=p}c=-1===h||-1===k?null:
+{start:h,end:k}}else c=null}c=c||{start:0,end:0}}else c=null;Kf={focusedElem:a,selectionRange:c};Zc=!1;for(l=b;null!==l;)if(b=l,a=b.child,0!==(b.subtreeFlags&1028)&&null!==a)a.return=b,l=a;else for(;null!==l;){b=l;try{var x=b.alternate;if(0!==(b.flags&1024))switch(b.tag){case 0:case 11:case 15:break;case 1:if(null!==x){var w=x.memoizedProps,z=x.memoizedState,A=b.stateNode,v=A.getSnapshotBeforeUpdate(b.elementType===b.type?w:xa(b.type,w),z);A.__reactInternalSnapshotBeforeUpdate=v}break;case 3:var q=
+b.stateNode.containerInfo;1===q.nodeType?q.textContent="":9===q.nodeType&&q.documentElement&&q.removeChild(q.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(n(163));}}catch(M){H(b,b.return,M)}a=b.sibling;if(null!==a){a.return=b.return;l=a;break}l=b.return}x=Ai;Ai=!1;return x}function Gc(a,b,c){var d=b.updateQueue;d=null!==d?d.lastEffect:null;if(null!==d){var e=d=d.next;do{if((e.tag&a)===a){var f=e.destroy;e.destroy=void 0;void 0!==f&&If(b,c,f)}e=e.next}while(e!==d)}}
+function Id(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function Lf(a){var b=a.ref;if(null!==b){var c=a.stateNode;switch(a.tag){case 5:a=c;break;default:a=c}"function"===typeof b?b(a):b.current=a}}function Bi(a){var b=a.alternate;null!==b&&(a.alternate=null,Bi(b));a.child=null;a.deletions=null;a.sibling=null;5===a.tag&&(b=a.stateNode,null!==b&&(delete b[Da],delete b[uc],delete b[Me],delete b[Ek],
+delete b[Fk]));a.stateNode=null;a.return=null;a.dependencies=null;a.memoizedProps=null;a.memoizedState=null;a.pendingProps=null;a.stateNode=null;a.updateQueue=null}function Ci(a){return 5===a.tag||3===a.tag||4===a.tag}function Di(a){a:for(;;){for(;null===a.sibling;){if(null===a.return||Ci(a.return))return null;a=a.return}a.sibling.return=a.return;for(a=a.sibling;5!==a.tag&&6!==a.tag&&18!==a.tag;){if(a.flags&2)continue a;if(null===a.child||4===a.tag)continue a;else a.child.return=a,a=a.child}if(!(a.flags&
+2))return a.stateNode}}function Mf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=kd));else if(4!==d&&(a=a.child,null!==a))for(Mf(a,b,c),a=a.sibling;null!==a;)Mf(a,b,c),a=a.sibling}function Nf(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?c.insertBefore(a,b):c.appendChild(a);
+else if(4!==d&&(a=a.child,null!==a))for(Nf(a,b,c),a=a.sibling;null!==a;)Nf(a,b,c),a=a.sibling}function jb(a,b,c){for(c=c.child;null!==c;)Ei(a,b,c),c=c.sibling}function Ei(a,b,c){if(Ca&&"function"===typeof Ca.onCommitFiberUnmount)try{Ca.onCommitFiberUnmount(Uc,c)}catch(h){}switch(c.tag){case 5:X||Wb(c,b);case 6:var d=T,e=za;T=null;jb(a,b,c);T=d;za=e;null!==T&&(za?(a=T,c=c.stateNode,8===a.nodeType?a.parentNode.removeChild(c):a.removeChild(c)):T.removeChild(c.stateNode));break;case 18:null!==T&&(za?
+(a=T,c=c.stateNode,8===a.nodeType?Re(a.parentNode,c):1===a.nodeType&&Re(a,c),nc(a)):Re(T,c.stateNode));break;case 4:d=T;e=za;T=c.stateNode.containerInfo;za=!0;jb(a,b,c);T=d;za=e;break;case 0:case 11:case 14:case 15:if(!X&&(d=c.updateQueue,null!==d&&(d=d.lastEffect,null!==d))){e=d=d.next;do{var f=e,g=f.destroy;f=f.tag;void 0!==g&&(0!==(f&2)?If(c,b,g):0!==(f&4)&&If(c,b,g));e=e.next}while(e!==d)}jb(a,b,c);break;case 1:if(!X&&(Wb(c,b),d=c.stateNode,"function"===typeof d.componentWillUnmount))try{d.props=
+c.memoizedProps,d.state=c.memoizedState,d.componentWillUnmount()}catch(h){H(c,b,h)}jb(a,b,c);break;case 21:jb(a,b,c);break;case 22:c.mode&1?(X=(d=X)||null!==c.memoizedState,jb(a,b,c),X=d):jb(a,b,c);break;default:jb(a,b,c)}}function Fi(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=new Gk);b.forEach(function(b){var d=Hk.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}}function Aa(a,b,c){c=b.deletions;if(null!==c)for(var d=0;d<c.length;d++){var e=
+c[d];try{var f=a,g=b,h=g;a:for(;null!==h;){switch(h.tag){case 5:T=h.stateNode;za=!1;break a;case 3:T=h.stateNode.containerInfo;za=!0;break a;case 4:T=h.stateNode.containerInfo;za=!0;break a}h=h.return}if(null===T)throw Error(n(160));Ei(f,g,e);T=null;za=!1;var k=e.alternate;null!==k&&(k.return=null);e.return=null}catch(m){H(e,b,m)}}if(b.subtreeFlags&12854)for(b=b.child;null!==b;)Gi(b,a),b=b.sibling}function Gi(a,b,c){var d=a.alternate;c=a.flags;switch(a.tag){case 0:case 11:case 14:case 15:Aa(b,a);
+Ha(a);if(c&4){try{Gc(3,a,a.return),Id(3,a)}catch(F){H(a,a.return,F)}try{Gc(5,a,a.return)}catch(F){H(a,a.return,F)}}break;case 1:Aa(b,a);Ha(a);c&512&&null!==d&&Wb(d,d.return);break;case 5:Aa(b,a);Ha(a);c&512&&null!==d&&Wb(d,d.return);if(a.flags&32){var e=a.stateNode;try{Fc(e,"")}catch(F){H(a,a.return,F)}}if(c&4&&(e=a.stateNode,null!=e)){var f=a.memoizedProps,g=null!==d?d.memoizedProps:f,h=a.type,k=a.updateQueue;a.updateQueue=null;if(null!==k)try{"input"===h&&"radio"===f.type&&null!=f.name&&lg(e,f);
+qe(h,g);var m=qe(h,f);for(g=0;g<k.length;g+=2){var t=k[g],u=k[g+1];"style"===t?sg(e,u):"dangerouslySetInnerHTML"===t?zi(e,u):"children"===t?Fc(e,u):$d(e,t,u,m)}switch(h){case "input":le(e,f);break;case "textarea":og(e,f);break;case "select":var r=e._wrapperState.wasMultiple;e._wrapperState.wasMultiple=!!f.multiple;var p=f.value;null!=p?Db(e,!!f.multiple,p,!1):r!==!!f.multiple&&(null!=f.defaultValue?Db(e,!!f.multiple,f.defaultValue,!0):Db(e,!!f.multiple,f.multiple?[]:"",!1))}e[uc]=f}catch(F){H(a,a.return,
+F)}}break;case 6:Aa(b,a);Ha(a);if(c&4){if(null===a.stateNode)throw Error(n(162));e=a.stateNode;f=a.memoizedProps;try{e.nodeValue=f}catch(F){H(a,a.return,F)}}break;case 3:Aa(b,a);Ha(a);if(c&4&&null!==d&&d.memoizedState.isDehydrated)try{nc(b.containerInfo)}catch(F){H(a,a.return,F)}break;case 4:Aa(b,a);Ha(a);break;case 13:Aa(b,a);Ha(a);e=a.child;e.flags&8192&&(f=null!==e.memoizedState,e.stateNode.isHidden=f,!f||null!==e.alternate&&null!==e.alternate.memoizedState||(Of=P()));c&4&&Fi(a);break;case 22:t=
+null!==d&&null!==d.memoizedState;a.mode&1?(X=(m=X)||t,Aa(b,a),X=m):Aa(b,a);Ha(a);if(c&8192){m=null!==a.memoizedState;if((a.stateNode.isHidden=m)&&!t&&0!==(a.mode&1))for(l=a,t=a.child;null!==t;){for(u=l=t;null!==l;){r=l;p=r.child;switch(r.tag){case 0:case 11:case 14:case 15:Gc(4,r,r.return);break;case 1:Wb(r,r.return);var x=r.stateNode;if("function"===typeof x.componentWillUnmount){c=r;b=r.return;try{d=c,x.props=d.memoizedProps,x.state=d.memoizedState,x.componentWillUnmount()}catch(F){H(c,b,F)}}break;
+case 5:Wb(r,r.return);break;case 22:if(null!==r.memoizedState){Hi(u);continue}}null!==p?(p.return=r,l=p):Hi(u)}t=t.sibling}a:for(t=null,u=a;;){if(5===u.tag){if(null===t){t=u;try{e=u.stateNode,m?(f=e.style,"function"===typeof f.setProperty?f.setProperty("display","none","important"):f.display="none"):(h=u.stateNode,k=u.memoizedProps.style,g=void 0!==k&&null!==k&&k.hasOwnProperty("display")?k.display:null,h.style.display=rg("display",g))}catch(F){H(a,a.return,F)}}}else if(6===u.tag){if(null===t)try{u.stateNode.nodeValue=
+m?"":u.memoizedProps}catch(F){H(a,a.return,F)}}else if((22!==u.tag&&23!==u.tag||null===u.memoizedState||u===a)&&null!==u.child){u.child.return=u;u=u.child;continue}if(u===a)break a;for(;null===u.sibling;){if(null===u.return||u.return===a)break a;t===u&&(t=null);u=u.return}t===u&&(t=null);u.sibling.return=u.return;u=u.sibling}}break;case 19:Aa(b,a);Ha(a);c&4&&Fi(a);break;case 21:break;default:Aa(b,a),Ha(a)}}function Ha(a){var b=a.flags;if(b&2){try{a:{for(var c=a.return;null!==c;){if(Ci(c)){var d=c;
+break a}c=c.return}throw Error(n(160));}switch(d.tag){case 5:var e=d.stateNode;d.flags&32&&(Fc(e,""),d.flags&=-33);var f=Di(a);Nf(a,f,e);break;case 3:case 4:var g=d.stateNode.containerInfo,h=Di(a);Mf(a,h,g);break;default:throw Error(n(161));}}catch(k){H(a,a.return,k)}a.flags&=-3}b&4096&&(a.flags&=-4097)}function Ik(a,b,c){l=a;Ii(a,b,c)}function Ii(a,b,c){for(var d=0!==(a.mode&1);null!==l;){var e=l,f=e.child;if(22===e.tag&&d){var g=null!==e.memoizedState||Jd;if(!g){var h=e.alternate,k=null!==h&&null!==
+h.memoizedState||X;h=Jd;var m=X;Jd=g;if((X=k)&&!m)for(l=e;null!==l;)g=l,k=g.child,22===g.tag&&null!==g.memoizedState?Ji(e):null!==k?(k.return=g,l=k):Ji(e);for(;null!==f;)l=f,Ii(f,b,c),f=f.sibling;l=e;Jd=h;X=m}Ki(a,b,c)}else 0!==(e.subtreeFlags&8772)&&null!==f?(f.return=e,l=f):Ki(a,b,c)}}function Ki(a,b,c){for(;null!==l;){b=l;if(0!==(b.flags&8772)){c=b.alternate;try{if(0!==(b.flags&8772))switch(b.tag){case 0:case 11:case 15:X||Id(5,b);break;case 1:var d=b.stateNode;if(b.flags&4&&!X)if(null===c)d.componentDidMount();
+else{var e=b.elementType===b.type?c.memoizedProps:xa(b.type,c.memoizedProps);d.componentDidUpdate(e,c.memoizedState,d.__reactInternalSnapshotBeforeUpdate)}var f=b.updateQueue;null!==f&&Fh(b,f,d);break;case 3:var g=b.updateQueue;if(null!==g){c=null;if(null!==b.child)switch(b.child.tag){case 5:c=b.child.stateNode;break;case 1:c=b.child.stateNode}Fh(b,g,c)}break;case 5:var h=b.stateNode;if(null===c&&b.flags&4){c=h;var k=b.memoizedProps;switch(b.type){case "button":case "input":case "select":case "textarea":k.autoFocus&&
+c.focus();break;case "img":k.src&&(c.src=k.src)}}break;case 6:break;case 4:break;case 12:break;case 13:if(null===b.memoizedState){var m=b.alternate;if(null!==m){var t=m.memoizedState;if(null!==t){var p=t.dehydrated;null!==p&&nc(p)}}}break;case 19:case 17:case 21:case 22:case 23:case 25:break;default:throw Error(n(163));}X||b.flags&512&&Lf(b)}catch(r){H(b,b.return,r)}}if(b===a){l=null;break}c=b.sibling;if(null!==c){c.return=b.return;l=c;break}l=b.return}}function Hi(a){for(;null!==l;){var b=l;if(b===
+a){l=null;break}var c=b.sibling;if(null!==c){c.return=b.return;l=c;break}l=b.return}}function Ji(a){for(;null!==l;){var b=l;try{switch(b.tag){case 0:case 11:case 15:var c=b.return;try{Id(4,b)}catch(k){H(b,c,k)}break;case 1:var d=b.stateNode;if("function"===typeof d.componentDidMount){var e=b.return;try{d.componentDidMount()}catch(k){H(b,e,k)}}var f=b.return;try{Lf(b)}catch(k){H(b,f,k)}break;case 5:var g=b.return;try{Lf(b)}catch(k){H(b,g,k)}}}catch(k){H(b,b.return,k)}if(b===a){l=null;break}var h=b.sibling;
+if(null!==h){h.return=b.return;l=h;break}l=b.return}}function Hc(){Hf=P()+500}function Z(){return 0!==(p&6)?P():-1!==Kd?Kd:Kd=P()}function hb(a){if(0===(a.mode&1))return 1;if(0!==(p&2)&&0!==U)return U&-U;if(null!==Jk.transition)return 0===Ld&&(Ld=Dg()),Ld;a=z;if(0!==a)return a;a=window.event;a=void 0===a?16:Lg(a.type);return a}function ya(a,b,c,d){if(50<Ic)throw Ic=0,Pf=null,Error(n(185));ic(a,c,d);if(0===(p&2)||a!==O)a===O&&(0===(p&2)&&(Md|=c),4===L&&kb(a,U)),ia(a,d),1===c&&0===p&&0===(b.mode&1)&&
+(Hc(),md&&db())}function ia(a,b){var c=a.callbackNode;uj(a,b);var d=Vc(a,a===O?U:0);if(0===d)null!==c&&Li(c),a.callbackNode=null,a.callbackPriority=0;else if(b=d&-d,a.callbackPriority!==b){null!=c&&Li(c);if(1===b)0===a.tag?kk(Mi.bind(null,a)):wh(Mi.bind(null,a)),Kk(function(){0===(p&6)&&db()}),c=null;else{switch(Eg(d)){case 1:c=De;break;case 4:c=Mg;break;case 16:c=ad;break;case 536870912:c=Ng;break;default:c=ad}c=Ni(c,Oi.bind(null,a))}a.callbackPriority=b;a.callbackNode=c}}function Oi(a,b){Kd=-1;
+Ld=0;if(0!==(p&6))throw Error(n(327));var c=a.callbackNode;if(Xb()&&a.callbackNode!==c)return null;var d=Vc(a,a===O?U:0);if(0===d)return null;if(0!==(d&30)||0!==(d&a.expiredLanes)||b)b=Nd(a,d);else{b=d;var e=p;p|=2;var f=Pi();if(O!==a||U!==b)Ra=null,Hc(),xb(a,b);do try{Lk();break}catch(h){Qi(a,h)}while(1);Ze();Od.current=f;p=e;null!==I?b=0:(O=null,U=0,b=L)}if(0!==b){2===b&&(e=ve(a),0!==e&&(d=e,b=Qf(a,e)));if(1===b)throw c=Jc,xb(a,0),kb(a,d),ia(a,P()),c;if(6===b)kb(a,d);else{e=a.current.alternate;
+if(0===(d&30)&&!Mk(e)&&(b=Nd(a,d),2===b&&(f=ve(a),0!==f&&(d=f,b=Qf(a,f))),1===b))throw c=Jc,xb(a,0),kb(a,d),ia(a,P()),c;a.finishedWork=e;a.finishedLanes=d;switch(b){case 0:case 1:throw Error(n(345));case 2:yb(a,ja,Ra);break;case 3:kb(a,d);if((d&130023424)===d&&(b=Of+500-P(),10<b)){if(0!==Vc(a,0))break;e=a.suspendedLanes;if((e&d)!==d){Z();a.pingedLanes|=a.suspendedLanes&e;break}a.timeoutHandle=Rf(yb.bind(null,a,ja,Ra),b);break}yb(a,ja,Ra);break;case 4:kb(a,d);if((d&4194240)===d)break;b=a.eventTimes;
+for(e=-1;0<d;){var g=31-ta(d);f=1<<g;g=b[g];g>e&&(e=g);d&=~f}d=e;d=P()-d;d=(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*Nk(d/1960))-d;if(10<d){a.timeoutHandle=Rf(yb.bind(null,a,ja,Ra),d);break}yb(a,ja,Ra);break;case 5:yb(a,ja,Ra);break;default:throw Error(n(329));}}}ia(a,P());return a.callbackNode===c?Oi.bind(null,a):null}function Qf(a,b){var c=Kc;a.current.memoizedState.isDehydrated&&(xb(a,b).flags|=256);a=Nd(a,b);2!==a&&(b=ja,ja=c,null!==b&&Gf(b));return a}function Gf(a){null===
+ja?ja=a:ja.push.apply(ja,a)}function Mk(a){for(var b=a;;){if(b.flags&16384){var c=b.updateQueue;if(null!==c&&(c=c.stores,null!==c))for(var d=0;d<c.length;d++){var e=c[d],f=e.getSnapshot;e=e.value;try{if(!ua(f(),e))return!1}catch(g){return!1}}}c=b.child;if(b.subtreeFlags&16384&&null!==c)c.return=b,b=c;else{if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return!0;b=b.return}b.sibling.return=b.return;b=b.sibling}}return!0}function kb(a,b){b&=~Sf;b&=~Md;a.suspendedLanes|=b;a.pingedLanes&=
+~b;for(a=a.expirationTimes;0<b;){var c=31-ta(b),d=1<<c;a[c]=-1;b&=~d}}function Mi(a){if(0!==(p&6))throw Error(n(327));Xb();var b=Vc(a,0);if(0===(b&1))return ia(a,P()),null;var c=Nd(a,b);if(0!==a.tag&&2===c){var d=ve(a);0!==d&&(b=d,c=Qf(a,d))}if(1===c)throw c=Jc,xb(a,0),kb(a,b),ia(a,P()),c;if(6===c)throw Error(n(345));a.finishedWork=a.current.alternate;a.finishedLanes=b;yb(a,ja,Ra);ia(a,P());return null}function Tf(a,b){var c=p;p|=1;try{return a(b)}finally{p=c,0===p&&(Hc(),md&&db())}}function zb(a){null!==
+lb&&0===lb.tag&&0===(p&6)&&Xb();var b=p;p|=1;var c=ca.transition,d=z;try{if(ca.transition=null,z=1,a)return a()}finally{z=d,ca.transition=c,p=b,0===(p&6)&&db()}}function xb(a,b){a.finishedWork=null;a.finishedLanes=0;var c=a.timeoutHandle;-1!==c&&(a.timeoutHandle=-1,Ok(c));if(null!==I)for(c=I.return;null!==c;){var d=c;Ve(d);switch(d.tag){case 1:d=d.type.childContextTypes;null!==d&&void 0!==d&&(w(S),w(J));break;case 3:Tb();w(S);w(J);lf();break;case 5:kf(d);break;case 4:Tb();break;case 13:w(G);break;
+case 19:w(G);break;case 10:af(d.type._context);break;case 22:case 23:ba=Ga.current,w(Ga)}c=c.return}O=a;I=a=gb(a.current,null);U=ba=b;L=0;Jc=null;Sf=Md=ra=0;ja=Kc=null;if(null!==tb){for(b=0;b<tb.length;b++)if(c=tb[b],d=c.interleaved,null!==d){c.interleaved=null;var e=d.next,f=c.pending;if(null!==f){var g=f.next;f.next=e;d.next=g}c.pending=d}tb=null}return a}function Qi(a,b){do{var c=I;try{Ze();zd.current=Ad;if(Bd){for(var d=C.memoizedState;null!==d;){var e=d.queue;null!==e&&(e.pending=null);d=d.next}Bd=
+!1}wb=0;N=K=C=null;zc=!1;Ac=0;Uf.current=null;if(null===c||null===c.return){L=1;Jc=b;I=null;break}a:{var f=a,g=c.return,h=c,k=b;b=U;h.flags|=32768;if(null!==k&&"object"===typeof k&&"function"===typeof k.then){var m=k,l=h,p=l.tag;if(0===(l.mode&1)&&(0===p||11===p||15===p)){var r=l.alternate;r?(l.updateQueue=r.updateQueue,l.memoizedState=r.memoizedState,l.lanes=r.lanes):(l.updateQueue=null,l.memoizedState=null)}var w=ki(g);if(null!==w){w.flags&=-257;li(w,g,h,f,b);w.mode&1&&ji(f,m,b);b=w;k=m;var x=b.updateQueue;
+if(null===x){var z=new Set;z.add(k);b.updateQueue=z}else x.add(k);break a}else{if(0===(b&1)){ji(f,m,b);Ef();break a}k=Error(n(426))}}else if(D&&h.mode&1){var y=ki(g);if(null!==y){0===(y.flags&65536)&&(y.flags|=256);li(y,g,h,f,b);Ye(Ub(k,h));break a}}f=k=Ub(k,h);4!==L&&(L=2);null===Kc?Kc=[f]:Kc.push(f);f=g;do{switch(f.tag){case 3:f.flags|=65536;b&=-b;f.lanes|=b;var A=hi(f,k,b);Eh(f,A);break a;case 1:h=k;var v=f.type,q=f.stateNode;if(0===(f.flags&128)&&("function"===typeof v.getDerivedStateFromError||
+null!==q&&"function"===typeof q.componentDidCatch&&(null===ib||!ib.has(q)))){f.flags|=65536;b&=-b;f.lanes|=b;var B=ii(f,h,b);Eh(f,B);break a}}f=f.return}while(null!==f)}Ri(c)}catch(ma){b=ma;I===c&&null!==c&&(I=c=c.return);continue}break}while(1)}function Pi(){var a=Od.current;Od.current=Ad;return null===a?Ad:a}function Ef(){if(0===L||3===L||2===L)L=4;null===O||0===(ra&268435455)&&0===(Md&268435455)||kb(O,U)}function Nd(a,b){var c=p;p|=2;var d=Pi();if(O!==a||U!==b)Ra=null,xb(a,b);do try{Pk();break}catch(e){Qi(a,
+e)}while(1);Ze();p=c;Od.current=d;if(null!==I)throw Error(n(261));O=null;U=0;return L}function Pk(){for(;null!==I;)Si(I)}function Lk(){for(;null!==I&&!Qk();)Si(I)}function Si(a){var b=Rk(a.alternate,a,ba);a.memoizedProps=a.pendingProps;null===b?Ri(a):I=b;Uf.current=null}function Ri(a){var b=a;do{var c=b.alternate;a=b.return;if(0===(b.flags&32768)){if(c=yk(c,b,ba),null!==c){I=c;return}}else{c=Ck(c,b);if(null!==c){c.flags&=32767;I=c;return}if(null!==a)a.flags|=32768,a.subtreeFlags=0,a.deletions=null;
+else{L=6;I=null;return}}b=b.sibling;if(null!==b){I=b;return}I=b=a}while(null!==b);0===L&&(L=5)}function yb(a,b,c){var d=z,e=ca.transition;try{ca.transition=null,z=1,Sk(a,b,c,d)}finally{ca.transition=e,z=d}return null}function Sk(a,b,c,d){do Xb();while(null!==lb);if(0!==(p&6))throw Error(n(327));c=a.finishedWork;var e=a.finishedLanes;if(null===c)return null;a.finishedWork=null;a.finishedLanes=0;if(c===a.current)throw Error(n(177));a.callbackNode=null;a.callbackPriority=0;var f=c.lanes|c.childLanes;
+vj(a,f);a===O&&(I=O=null,U=0);0===(c.subtreeFlags&2064)&&0===(c.flags&2064)||Pd||(Pd=!0,Ni(ad,function(){Xb();return null}));f=0!==(c.flags&15990);if(0!==(c.subtreeFlags&15990)||f){f=ca.transition;ca.transition=null;var g=z;z=1;var h=p;p|=4;Uf.current=null;Dk(a,c);Gi(c,a);Uj(Kf);Zc=!!Jf;Kf=Jf=null;a.current=c;Ik(c,a,e);Tk();p=h;z=g;ca.transition=f}else a.current=c;Pd&&(Pd=!1,lb=a,Qd=e);f=a.pendingLanes;0===f&&(ib=null);pj(c.stateNode,d);ia(a,P());if(null!==b)for(d=a.onRecoverableError,c=0;c<b.length;c++)e=
+b[c],d(e.value,{componentStack:e.stack,digest:e.digest});if(Ed)throw Ed=!1,a=xf,xf=null,a;0!==(Qd&1)&&0!==a.tag&&Xb();f=a.pendingLanes;0!==(f&1)?a===Pf?Ic++:(Ic=0,Pf=a):Ic=0;db();return null}function Xb(){if(null!==lb){var a=Eg(Qd),b=ca.transition,c=z;try{ca.transition=null;z=16>a?16:a;if(null===lb)var d=!1;else{a=lb;lb=null;Qd=0;if(0!==(p&6))throw Error(n(331));var e=p;p|=4;for(l=a.current;null!==l;){var f=l,g=f.child;if(0!==(l.flags&16)){var h=f.deletions;if(null!==h){for(var k=0;k<h.length;k++){var m=
+h[k];for(l=m;null!==l;){var t=l;switch(t.tag){case 0:case 11:case 15:Gc(8,t,f)}var u=t.child;if(null!==u)u.return=t,l=u;else for(;null!==l;){t=l;var r=t.sibling,w=t.return;Bi(t);if(t===m){l=null;break}if(null!==r){r.return=w;l=r;break}l=w}}}var x=f.alternate;if(null!==x){var y=x.child;if(null!==y){x.child=null;do{var C=y.sibling;y.sibling=null;y=C}while(null!==y)}}l=f}}if(0!==(f.subtreeFlags&2064)&&null!==g)g.return=f,l=g;else b:for(;null!==l;){f=l;if(0!==(f.flags&2048))switch(f.tag){case 0:case 11:case 15:Gc(9,
+f,f.return)}var A=f.sibling;if(null!==A){A.return=f.return;l=A;break b}l=f.return}}var v=a.current;for(l=v;null!==l;){g=l;var q=g.child;if(0!==(g.subtreeFlags&2064)&&null!==q)q.return=g,l=q;else b:for(g=v;null!==l;){h=l;if(0!==(h.flags&2048))try{switch(h.tag){case 0:case 11:case 15:Id(9,h)}}catch(ma){H(h,h.return,ma)}if(h===g){l=null;break b}var B=h.sibling;if(null!==B){B.return=h.return;l=B;break b}l=h.return}}p=e;db();if(Ca&&"function"===typeof Ca.onPostCommitFiberRoot)try{Ca.onPostCommitFiberRoot(Uc,
+a)}catch(ma){}d=!0}return d}finally{z=c,ca.transition=b}}return!1}function Ti(a,b,c){b=Ub(c,b);b=hi(a,b,1);a=eb(a,b,1);b=Z();null!==a&&(ic(a,1,b),ia(a,b))}function H(a,b,c){if(3===a.tag)Ti(a,a,c);else for(;null!==b;){if(3===b.tag){Ti(b,a,c);break}else if(1===b.tag){var d=b.stateNode;if("function"===typeof b.type.getDerivedStateFromError||"function"===typeof d.componentDidCatch&&(null===ib||!ib.has(d))){a=Ub(c,a);a=ii(b,a,1);b=eb(b,a,1);a=Z();null!==b&&(ic(b,1,a),ia(b,a));break}}b=b.return}}function tk(a,
+b,c){var d=a.pingCache;null!==d&&d.delete(b);b=Z();a.pingedLanes|=a.suspendedLanes&c;O===a&&(U&c)===c&&(4===L||3===L&&(U&130023424)===U&&500>P()-Of?xb(a,0):Sf|=c);ia(a,b)}function Ui(a,b){0===b&&(0===(a.mode&1)?b=1:(b=Rd,Rd<<=1,0===(Rd&130023424)&&(Rd=4194304)));var c=Z();a=Oa(a,b);null!==a&&(ic(a,b,c),ia(a,c))}function wk(a){var b=a.memoizedState,c=0;null!==b&&(c=b.retryLane);Ui(a,c)}function Hk(a,b){var c=0;switch(a.tag){case 13:var d=a.stateNode;var e=a.memoizedState;null!==e&&(c=e.retryLane);
+break;case 19:d=a.stateNode;break;default:throw Error(n(314));}null!==d&&d.delete(b);Ui(a,c)}function Ni(a,b){return xh(a,b)}function Uk(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.subtreeFlags=this.flags=0;this.deletions=null;this.childLanes=this.lanes=0;this.alternate=null}function yf(a){a=
+a.prototype;return!(!a||!a.isReactComponent)}function Vk(a){if("function"===typeof a)return yf(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===ie)return 11;if(a===je)return 14}return 2}function gb(a,b){var c=a.alternate;null===c?(c=pa(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.type=a.type,c.flags=0,c.subtreeFlags=0,c.deletions=null);c.flags=a.flags&14680064;c.childLanes=a.childLanes;c.lanes=a.lanes;c.child=
+a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{lanes:b.lanes,firstContext:b.firstContext};c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c}function wd(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)yf(a)&&(g=1);else if("string"===typeof a)g=5;else a:switch(a){case Bb:return ub(c.children,e,f,b);case fe:g=8;e|=8;break;case ee:return a=pa(12,c,b,e|2),a.elementType=ee,a.lanes=f,a;case ge:return a=
+pa(13,c,b,e),a.elementType=ge,a.lanes=f,a;case he:return a=pa(19,c,b,e),a.elementType=he,a.lanes=f,a;case Vi:return Gd(c,e,f,b);default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case hg:g=10;break a;case gg:g=9;break a;case ie:g=11;break a;case je:g=14;break a;case Ta:g=16;d=null;break a}throw Error(n(130,null==a?a:typeof a,""));}b=pa(g,c,b,e);b.elementType=a;b.type=d;b.lanes=f;return b}function ub(a,b,c,d){a=pa(7,a,d,b);a.lanes=c;return a}function Gd(a,b,c,d){a=pa(22,a,d,b);a.elementType=
+Vi;a.lanes=c;a.stateNode={isHidden:!1};return a}function gf(a,b,c){a=pa(6,a,null,b);a.lanes=c;return a}function hf(a,b,c){b=pa(4,null!==a.children?a.children:[],a.key,b);b.lanes=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b}function Wk(a,b,c,d,e){this.tag=b;this.containerInfo=a;this.finishedWork=this.pingCache=this.current=this.pendingChildren=null;this.timeoutHandle=-1;this.callbackNode=this.pendingContext=this.context=null;this.callbackPriority=
+0;this.eventTimes=we(0);this.expirationTimes=we(-1);this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0;this.entanglements=we(0);this.identifierPrefix=d;this.onRecoverableError=e;this.mutableSourceEagerHydrationData=null}function Vf(a,b,c,d,e,f,g,h,k,m){a=new Wk(a,b,c,h,k);1===b?(b=1,!0===f&&(b|=8)):b=0;f=pa(3,null,null,b);a.current=f;f.stateNode=a;f.memoizedState={element:d,isDehydrated:c,cache:null,transitions:null,
+pendingSuspenseBoundaries:null};df(f);return a}function Xk(a,b,c){var d=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:Cb,key:null==d?null:""+d,children:a,containerInfo:b,implementation:c}}function Wi(a){if(!a)return cb;a=a._reactInternals;a:{if(ob(a)!==a||1!==a.tag)throw Error(n(170));var b=a;do{switch(b.tag){case 3:b=b.stateNode.context;break a;case 1:if(ea(b.type)){b=b.stateNode.__reactInternalMemoizedMergedChildContext;break a}}b=b.return}while(null!==b);throw Error(n(171));
+}if(1===a.tag){var c=a.type;if(ea(c))return uh(a,c,b)}return b}function Xi(a,b,c,d,e,f,g,h,k,m){a=Vf(c,d,!0,a,e,f,g,h,k);a.context=Wi(null);c=a.current;d=Z();e=hb(c);f=Pa(d,e);f.callback=void 0!==b&&null!==b?b:null;eb(c,f,e);a.current.lanes=e;ic(a,e,d);ia(a,d);return a}function Sd(a,b,c,d){var e=b.current,f=Z(),g=hb(e);c=Wi(c);null===b.context?b.context=c:b.pendingContext=c;b=Pa(f,g);b.payload={element:a};d=void 0===d?null:d;null!==d&&(b.callback=d);a=eb(e,b,g);null!==a&&(ya(a,e,g,f),sd(a,e,g));return g}
+function Td(a){a=a.current;if(!a.child)return null;switch(a.child.tag){case 5:return a.child.stateNode;default:return a.child.stateNode}}function Yi(a,b){a=a.memoizedState;if(null!==a&&null!==a.dehydrated){var c=a.retryLane;a.retryLane=0!==c&&c<b?c:b}}function Wf(a,b){Yi(a,b);(a=a.alternate)&&Yi(a,b)}function Yk(a){a=Bg(a);return null===a?null:a.stateNode}function Zk(a){return null}function Xf(a){this._internalRoot=a}function Ud(a){this._internalRoot=a}function Yf(a){return!(!a||1!==a.nodeType&&9!==
+a.nodeType&&11!==a.nodeType)}function Vd(a){return!(!a||1!==a.nodeType&&9!==a.nodeType&&11!==a.nodeType&&(8!==a.nodeType||" react-mount-point-unstable "!==a.nodeValue))}function Zi(){}function $k(a,b,c,d,e){if(e){if("function"===typeof d){var f=d;d=function(){var a=Td(g);f.call(a)}}var g=Xi(b,d,a,0,null,!1,!1,"",Zi);a._reactRootContainer=g;a[Ja]=g.current;sc(8===a.nodeType?a.parentNode:a);zb();return g}for(;e=a.lastChild;)a.removeChild(e);if("function"===typeof d){var h=d;d=function(){var a=Td(k);
+h.call(a)}}var k=Vf(a,0,!1,null,null,!1,!1,"",Zi);a._reactRootContainer=k;a[Ja]=k.current;sc(8===a.nodeType?a.parentNode:a);zb(function(){Sd(b,k,c,d)});return k}function Wd(a,b,c,d,e){var f=c._reactRootContainer;if(f){var g=f;if("function"===typeof e){var h=e;e=function(){var a=Td(g);h.call(a)}}Sd(b,g,a,e)}else g=$k(c,b,a,e,d);return Td(g)}var cg=new Set,$b={},Ia=!("undefined"===typeof window||"undefined"===typeof window.document||"undefined"===typeof window.document.createElement),Zd=Object.prototype.hasOwnProperty,
+dj=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,eg={},dg={},R={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(a){R[a]=
+new Y(a,0,!1,a,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(a){var b=a[0];R[b]=new Y(b,1,!1,a[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(a){R[a]=new Y(a,2,!1,a.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(a){R[a]=new Y(a,2,!1,a,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(a){R[a]=
+new Y(a,3,!1,a.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(a){R[a]=new Y(a,3,!0,a,null,!1,!1)});["capture","download"].forEach(function(a){R[a]=new Y(a,4,!1,a,null,!1,!1)});["cols","rows","size","span"].forEach(function(a){R[a]=new Y(a,6,!1,a,null,!1,!1)});["rowSpan","start"].forEach(function(a){R[a]=new Y(a,5,!1,a.toLowerCase(),null,!1,!1)});var Zf=/[\-:]([a-z])/g,$f=function(a){return a[1].toUpperCase()};"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(a){var b=
+a.replace(Zf,$f);R[b]=new Y(b,1,!1,a,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(a){var b=a.replace(Zf,$f);R[b]=new Y(b,1,!1,a,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(a){var b=a.replace(Zf,$f);R[b]=new Y(b,1,!1,a,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(a){R[a]=new Y(a,1,!1,a.toLowerCase(),null,!1,!1)});R.xlinkHref=new Y("xlinkHref",
+1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(a){R[a]=new Y(a,1,!1,a.toLowerCase(),null,!0,!0)});var Sa=mb.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,xd=Symbol.for("react.element"),Cb=Symbol.for("react.portal"),Bb=Symbol.for("react.fragment"),fe=Symbol.for("react.strict_mode"),ee=Symbol.for("react.profiler"),hg=Symbol.for("react.provider"),gg=Symbol.for("react.context"),ie=Symbol.for("react.forward_ref"),ge=Symbol.for("react.suspense"),
+he=Symbol.for("react.suspense_list"),je=Symbol.for("react.memo"),Ta=Symbol.for("react.lazy");Symbol.for("react.scope");Symbol.for("react.debug_trace_mode");var Vi=Symbol.for("react.offscreen");Symbol.for("react.legacy_hidden");Symbol.for("react.cache");Symbol.for("react.tracing_marker");var fg=Symbol.iterator,E=Object.assign,ae,ce=!1,cc=Array.isArray,Xd,zi=function(a){return"undefined"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(b,c,d,e){MSApp.execUnsafeLocalFunction(function(){return a(b,
+c,d,e)})}:a}(function(a,b){if("http://www.w3.org/2000/svg"!==a.namespaceURI||"innerHTML"in a)a.innerHTML=b;else{Xd=Xd||document.createElement("div");Xd.innerHTML="<svg>"+b.valueOf().toString()+"</svg>";for(b=Xd.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}}),Fc=function(a,b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b},dc={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,
+borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,
+strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},al=["Webkit","ms","Moz","O"];Object.keys(dc).forEach(function(a){al.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);dc[b]=dc[a]})});var jj=E({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}),ze=null,se=null,Eb=null,Fb=null,xg=function(a,b){return a(b)},yg=function(){},te=!1,Oe=!1;if(Ia)try{var Lc={};Object.defineProperty(Lc,
+"passive",{get:function(){Oe=!0}});window.addEventListener("test",Lc,Lc);window.removeEventListener("test",Lc,Lc)}catch(a){Oe=!1}var lj=function(a,b,c,d,e,f,g,h,k){var m=Array.prototype.slice.call(arguments,3);try{b.apply(c,m)}catch(t){this.onError(t)}},gc=!1,Sc=null,Tc=!1,ue=null,mj={onError:function(a){gc=!0;Sc=a}},Ba=mb.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler,Jg=Ba.unstable_scheduleCallback,Kg=Ba.unstable_NormalPriority,xh=Jg,Li=Ba.unstable_cancelCallback,Qk=Ba.unstable_shouldYield,
+Tk=Ba.unstable_requestPaint,P=Ba.unstable_now,Ej=Ba.unstable_getCurrentPriorityLevel,De=Ba.unstable_ImmediatePriority,Mg=Ba.unstable_UserBlockingPriority,ad=Kg,Fj=Ba.unstable_LowPriority,Ng=Ba.unstable_IdlePriority,Uc=null,Ca=null,ta=Math.clz32?Math.clz32:qj,rj=Math.log,sj=Math.LN2,Wc=64,Rd=4194304,z=0,Ae=!1,Yc=[],Va=null,Wa=null,Xa=null,jc=new Map,kc=new Map,Ya=[],Cj="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" "),
+Gb=Sa.ReactCurrentBatchConfig,Zc=!0,$c=null,Za=null,Ee=null,bd=null,Yb={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(a){return a.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},He=ka(Yb),Mc=E({},Yb,{view:0,detail:0}),bk=ka(Mc),ag,bg,Nc,Yd=E({},Mc,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Fe,button:0,buttons:0,relatedTarget:function(a){return void 0===a.relatedTarget?a.fromElement===a.srcElement?a.toElement:a.fromElement:
+a.relatedTarget},movementX:function(a){if("movementX"in a)return a.movementX;a!==Nc&&(Nc&&"mousemove"===a.type?(ag=a.screenX-Nc.screenX,bg=a.screenY-Nc.screenY):bg=ag=0,Nc=a);return ag},movementY:function(a){return"movementY"in a?a.movementY:bg}}),ih=ka(Yd),bl=E({},Yd,{dataTransfer:0}),Xj=ka(bl),cl=E({},Mc,{relatedTarget:0}),Pe=ka(cl),dl=E({},Yb,{animationName:0,elapsedTime:0,pseudoElement:0}),Zj=ka(dl),el=E({},Yb,{clipboardData:function(a){return"clipboardData"in a?a.clipboardData:window.clipboardData}}),
+dk=ka(el),fl=E({},Yb,{data:0}),qh=ka(fl),gk=qh,gl={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},hl={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",
+112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Hj={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"},il=E({},Mc,{key:function(a){if(a.key){var b=gl[a.key]||a.key;if("Unidentified"!==b)return b}return"keypress"===a.type?(a=cd(a),13===a?"Enter":String.fromCharCode(a)):"keydown"===a.type||"keyup"===a.type?hl[a.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,
+metaKey:0,repeat:0,locale:0,getModifierState:Fe,charCode:function(a){return"keypress"===a.type?cd(a):0},keyCode:function(a){return"keydown"===a.type||"keyup"===a.type?a.keyCode:0},which:function(a){return"keypress"===a.type?cd(a):"keydown"===a.type||"keyup"===a.type?a.keyCode:0}}),Wj=ka(il),jl=E({},Yd,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),nh=ka(jl),kl=E({},Mc,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,
+ctrlKey:0,shiftKey:0,getModifierState:Fe}),Yj=ka(kl),ll=E({},Yb,{propertyName:0,elapsedTime:0,pseudoElement:0}),ak=ka(ll),ml=E({},Yd,{deltaX:function(a){return"deltaX"in a?a.deltaX:"wheelDeltaX"in a?-a.wheelDeltaX:0},deltaY:function(a){return"deltaY"in a?a.deltaY:"wheelDeltaY"in a?-a.wheelDeltaY:"wheelDelta"in a?-a.wheelDelta:0},deltaZ:0,deltaMode:0}),ck=ka(ml),Ij=[9,13,27,32],Ge=Ia&&"CompositionEvent"in window,Oc=null;Ia&&"documentMode"in document&&(Oc=document.documentMode);var fk=Ia&&"TextEvent"in
+window&&!Oc,Ug=Ia&&(!Ge||Oc&&8<Oc&&11>=Oc),Tg=String.fromCharCode(32),Sg=!1,Hb=!1,Lj={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0},oc=null,pc=null,ph=!1;Ia&&(ph=Mj("input")&&(!document.documentMode||9<document.documentMode));var ua="function"===typeof Object.is?Object.is:Tj,ek=Ia&&"documentMode"in document&&11>=document.documentMode,Jb=null,Ke=null,rc=null,Je=!1,Kb={animationend:gd("Animation","AnimationEnd"),
+animationiteration:gd("Animation","AnimationIteration"),animationstart:gd("Animation","AnimationStart"),transitionend:gd("Transition","TransitionEnd")},Le={},eh={};Ia&&(eh=document.createElement("div").style,"AnimationEvent"in window||(delete Kb.animationend.animation,delete Kb.animationiteration.animation,delete Kb.animationstart.animation),"TransitionEvent"in window||delete Kb.transitionend.transition);var jh=hd("animationend"),kh=hd("animationiteration"),lh=hd("animationstart"),mh=hd("transitionend"),
+fh=new Map,$i="abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");
+(function(){for(var a=0;a<$i.length;a++){var b=$i[a],c=b.toLowerCase();b=b[0].toUpperCase()+b.slice(1);$a(c,"on"+b)}$a(jh,"onAnimationEnd");$a(kh,"onAnimationIteration");$a(lh,"onAnimationStart");$a("dblclick","onDoubleClick");$a("focusin","onFocus");$a("focusout","onBlur");$a(mh,"onTransitionEnd")})();Ab("onMouseEnter",["mouseout","mouseover"]);Ab("onMouseLeave",["mouseout","mouseover"]);Ab("onPointerEnter",["pointerout","pointerover"]);Ab("onPointerLeave",["pointerout","pointerover"]);nb("onChange",
+"change click focusin focusout input keydown keyup selectionchange".split(" "));nb("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" "));nb("onBeforeInput",["compositionend","keypress","textInput","paste"]);nb("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" "));nb("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" "));nb("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));
+var Ec="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Vj=new Set("cancel close invalid load scroll toggle".split(" ").concat(Ec)),id="_reactListening"+Math.random().toString(36).slice(2),hk=/\r\n?/g,ik=/\u0000|\uFFFD/g,Jf=null,Kf=null,Rf="function"===typeof setTimeout?setTimeout:void 0,Ok="function"===typeof clearTimeout?
+clearTimeout:void 0,aj="function"===typeof Promise?Promise:void 0,Kk="function"===typeof queueMicrotask?queueMicrotask:"undefined"!==typeof aj?function(a){return aj.resolve(null).then(a).catch(jk)}:Rf,Zb=Math.random().toString(36).slice(2),Da="__reactFiber$"+Zb,uc="__reactProps$"+Zb,Ja="__reactContainer$"+Zb,Me="__reactEvents$"+Zb,Ek="__reactListeners$"+Zb,Fk="__reactHandles$"+Zb,Se=[],Mb=-1,cb={},J=bb(cb),S=bb(!1),qb=cb,La=null,md=!1,Te=!1,Ob=[],Pb=0,od=null,nd=0,na=[],oa=0,sb=null,Ma=1,Na="",la=
+null,fa=null,D=!1,wa=null,Jk=Sa.ReactCurrentBatchConfig,rd=bb(null),qd=null,Rb=null,$e=null,tb=null,lk=Oa,fb=!1,Jh=(new mb.Component).refs,ud={isMounted:function(a){return(a=a._reactInternals)?ob(a)===a:!1},enqueueSetState:function(a,b,c){a=a._reactInternals;var d=Z(),e=hb(a),f=Pa(d,e);f.payload=b;void 0!==c&&null!==c&&(f.callback=c);b=eb(a,f,e);null!==b&&(ya(b,a,e,d),sd(b,a,e))},enqueueReplaceState:function(a,b,c){a=a._reactInternals;var d=Z(),e=hb(a),f=Pa(d,e);f.tag=1;f.payload=b;void 0!==c&&null!==
+c&&(f.callback=c);b=eb(a,f,e);null!==b&&(ya(b,a,e,d),sd(b,a,e))},enqueueForceUpdate:function(a,b){a=a._reactInternals;var c=Z(),d=hb(a),e=Pa(c,d);e.tag=2;void 0!==b&&null!==b&&(e.callback=b);b=eb(a,e,d);null!==b&&(ya(b,a,d,c),sd(b,a,d))}},Vb=Lh(!0),mi=Lh(!1),wc={},Ea=bb(wc),yc=bb(wc),xc=bb(wc),G=bb(0),mf=[],zd=Sa.ReactCurrentDispatcher,uf=Sa.ReactCurrentBatchConfig,wb=0,C=null,K=null,N=null,Bd=!1,zc=!1,Ac=0,nl=0,Ad={readContext:qa,useCallback:V,useContext:V,useEffect:V,useImperativeHandle:V,useInsertionEffect:V,
+useLayoutEffect:V,useMemo:V,useReducer:V,useRef:V,useState:V,useDebugValue:V,useDeferredValue:V,useTransition:V,useMutableSource:V,useSyncExternalStore:V,useId:V,unstable_isNewReconciler:!1},mk={readContext:qa,useCallback:function(a,b){Fa().memoizedState=[a,void 0===b?null:b];return a},useContext:qa,useEffect:Wh,useImperativeHandle:function(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return Cd(4194308,4,Zh.bind(null,b,a),c)},useLayoutEffect:function(a,b){return Cd(4194308,4,a,b)},useInsertionEffect:function(a,
+b){return Cd(4,2,a,b)},useMemo:function(a,b){var c=Fa();b=void 0===b?null:b;a=a();c.memoizedState=[a,b];return a},useReducer:function(a,b,c){var d=Fa();b=void 0!==c?c(b):b;d.memoizedState=d.baseState=b;a={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:a,lastRenderedState:b};d.queue=a;a=a.dispatch=rk.bind(null,C,a);return[d.memoizedState,a]},useRef:function(a){var b=Fa();a={current:a};return b.memoizedState=a},useState:Uh,useDebugValue:tf,useDeferredValue:function(a){return Fa().memoizedState=
+a},useTransition:function(){var a=Uh(!1),b=a[0];a=qk.bind(null,a[1]);Fa().memoizedState=a;return[b,a]},useMutableSource:function(a,b,c){},useSyncExternalStore:function(a,b,c){var d=C,e=Fa();if(D){if(void 0===c)throw Error(n(407));c=c()}else{c=b();if(null===O)throw Error(n(349));0!==(wb&30)||Rh(d,b,c)}e.memoizedState=c;var f={value:c,getSnapshot:b};e.queue=f;Wh(Ph.bind(null,d,f,a),[a]);d.flags|=2048;Cc(9,Qh.bind(null,d,f,c,b),void 0,null);return c},useId:function(){var a=Fa(),b=O.identifierPrefix;
+if(D){var c=Na;var d=Ma;c=(d&~(1<<32-ta(d)-1)).toString(32)+c;b=":"+b+"R"+c;c=Ac++;0<c&&(b+="H"+c.toString(32));b+=":"}else c=nl++,b=":"+b+"r"+c.toString(32)+":";return a.memoizedState=b},unstable_isNewReconciler:!1},nk={readContext:qa,useCallback:ai,useContext:qa,useEffect:sf,useImperativeHandle:$h,useInsertionEffect:Xh,useLayoutEffect:Yh,useMemo:bi,useReducer:qf,useRef:Vh,useState:function(a){return qf(Bc)},useDebugValue:tf,useDeferredValue:function(a){var b=sa();return ci(b,K.memoizedState,a)},
+useTransition:function(){var a=qf(Bc)[0],b=sa().memoizedState;return[a,b]},useMutableSource:Nh,useSyncExternalStore:Oh,useId:di,unstable_isNewReconciler:!1},ok={readContext:qa,useCallback:ai,useContext:qa,useEffect:sf,useImperativeHandle:$h,useInsertionEffect:Xh,useLayoutEffect:Yh,useMemo:bi,useReducer:rf,useRef:Vh,useState:function(a){return rf(Bc)},useDebugValue:tf,useDeferredValue:function(a){var b=sa();return null===K?b.memoizedState=a:ci(b,K.memoizedState,a)},useTransition:function(){var a=rf(Bc)[0],
+b=sa().memoizedState;return[a,b]},useMutableSource:Nh,useSyncExternalStore:Oh,useId:di,unstable_isNewReconciler:!1},sk="function"===typeof WeakMap?WeakMap:Map,uk=Sa.ReactCurrentOwner,ha=!1,Cf={dehydrated:null,treeContext:null,retryLane:0};var Ak=function(a,b,c,d){for(c=b.child;null!==c;){if(5===c.tag||6===c.tag)a.appendChild(c.stateNode);else if(4!==c.tag&&null!==c.child){c.child.return=c;c=c.child;continue}if(c===b)break;for(;null===c.sibling;){if(null===c.return||c.return===b)return;c=c.return}c.sibling.return=
+c.return;c=c.sibling}};var yi=function(a,b){};var zk=function(a,b,c,d,e){var f=a.memoizedProps;if(f!==d){a=b.stateNode;vb(Ea.current);e=null;switch(c){case "input":f=ke(a,f);d=ke(a,d);e=[];break;case "select":f=E({},f,{value:void 0});d=E({},d,{value:void 0});e=[];break;case "textarea":f=ne(a,f);d=ne(a,d);e=[];break;default:"function"!==typeof f.onClick&&"function"===typeof d.onClick&&(a.onclick=kd)}pe(c,d);var g;c=null;for(m in f)if(!d.hasOwnProperty(m)&&f.hasOwnProperty(m)&&null!=f[m])if("style"===
+m){var h=f[m];for(g in h)h.hasOwnProperty(g)&&(c||(c={}),c[g]="")}else"dangerouslySetInnerHTML"!==m&&"children"!==m&&"suppressContentEditableWarning"!==m&&"suppressHydrationWarning"!==m&&"autoFocus"!==m&&($b.hasOwnProperty(m)?e||(e=[]):(e=e||[]).push(m,null));for(m in d){var k=d[m];h=null!=f?f[m]:void 0;if(d.hasOwnProperty(m)&&k!==h&&(null!=k||null!=h))if("style"===m)if(h){for(g in h)!h.hasOwnProperty(g)||k&&k.hasOwnProperty(g)||(c||(c={}),c[g]="");for(g in k)k.hasOwnProperty(g)&&h[g]!==k[g]&&(c||
+(c={}),c[g]=k[g])}else c||(e||(e=[]),e.push(m,c)),c=k;else"dangerouslySetInnerHTML"===m?(k=k?k.__html:void 0,h=h?h.__html:void 0,null!=k&&h!==k&&(e=e||[]).push(m,k)):"children"===m?"string"!==typeof k&&"number"!==typeof k||(e=e||[]).push(m,""+k):"suppressContentEditableWarning"!==m&&"suppressHydrationWarning"!==m&&($b.hasOwnProperty(m)?(null!=k&&"onScroll"===m&&B("scroll",a),e||h===k||(e=[])):(e=e||[]).push(m,k))}c&&(e=e||[]).push("style",c);var m=e;if(b.updateQueue=m)b.flags|=4}};var Bk=function(a,
+b,c,d){c!==d&&(b.flags|=4)};var Jd=!1,X=!1,Gk="function"===typeof WeakSet?WeakSet:Set,l=null,Ai=!1,T=null,za=!1,Nk=Math.ceil,Od=Sa.ReactCurrentDispatcher,Uf=Sa.ReactCurrentOwner,ca=Sa.ReactCurrentBatchConfig,p=0,O=null,I=null,U=0,ba=0,Ga=bb(0),L=0,Jc=null,ra=0,Md=0,Sf=0,Kc=null,ja=null,Of=0,Hf=Infinity,Ra=null,Ed=!1,xf=null,ib=null,Pd=!1,lb=null,Qd=0,Ic=0,Pf=null,Kd=-1,Ld=0;var Rk=function(a,b,c){if(null!==a)if(a.memoizedProps!==b.pendingProps||S.current)ha=!0;else{if(0===(a.lanes&c)&&0===(b.flags&
+128))return ha=!1,xk(a,b,c);ha=0!==(a.flags&131072)?!0:!1}else ha=!1,D&&0!==(b.flags&1048576)&&yh(b,nd,b.index);b.lanes=0;switch(b.tag){case 2:var d=b.type;Fd(a,b);a=b.pendingProps;var e=Nb(b,J.current);Sb(b,c);e=of(null,b,d,a,e,c);var f=pf();b.flags|=1;"object"===typeof e&&null!==e&&"function"===typeof e.render&&void 0===e.$$typeof?(b.tag=1,b.memoizedState=null,b.updateQueue=null,ea(d)?(f=!0,ld(b)):f=!1,b.memoizedState=null!==e.state&&void 0!==e.state?e.state:null,df(b),e.updater=ud,b.stateNode=
+e,e._reactInternals=b,ff(b,d,a,c),b=Af(null,b,d,!0,f,c)):(b.tag=0,D&&f&&Ue(b),aa(null,b,e,c),b=b.child);return b;case 16:d=b.elementType;a:{Fd(a,b);a=b.pendingProps;e=d._init;d=e(d._payload);b.type=d;e=b.tag=Vk(d);a=xa(d,a);switch(e){case 0:b=zf(null,b,d,a,c);break a;case 1:b=si(null,b,d,a,c);break a;case 11:b=ni(null,b,d,a,c);break a;case 14:b=oi(null,b,d,xa(d.type,a),c);break a}throw Error(n(306,d,""));}return b;case 0:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:xa(d,e),zf(a,b,d,e,c);
+case 1:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:xa(d,e),si(a,b,d,e,c);case 3:a:{ti(b);if(null===a)throw Error(n(387));d=b.pendingProps;f=b.memoizedState;e=f.element;Dh(a,b);td(b,d,null,c);var g=b.memoizedState;d=g.element;if(f.isDehydrated)if(f={element:d,isDehydrated:!1,cache:g.cache,pendingSuspenseBoundaries:g.pendingSuspenseBoundaries,transitions:g.transitions},b.updateQueue.baseState=f,b.memoizedState=f,b.flags&256){e=Ub(Error(n(423)),b);b=ui(a,b,d,c,e);break a}else if(d!==e){e=
+Ub(Error(n(424)),b);b=ui(a,b,d,c,e);break a}else for(fa=Ka(b.stateNode.containerInfo.firstChild),la=b,D=!0,wa=null,c=mi(b,null,d,c),b.child=c;c;)c.flags=c.flags&-3|4096,c=c.sibling;else{Qb();if(d===e){b=Qa(a,b,c);break a}aa(a,b,d,c)}b=b.child}return b;case 5:return Mh(b),null===a&&Xe(b),d=b.type,e=b.pendingProps,f=null!==a?a.memoizedProps:null,g=e.children,Qe(d,e)?g=null:null!==f&&Qe(d,f)&&(b.flags|=32),ri(a,b),aa(a,b,g,c),b.child;case 6:return null===a&&Xe(b),null;case 13:return vi(a,b,c);case 4:return jf(b,
+b.stateNode.containerInfo),d=b.pendingProps,null===a?b.child=Vb(b,null,d,c):aa(a,b,d,c),b.child;case 11:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:xa(d,e),ni(a,b,d,e,c);case 7:return aa(a,b,b.pendingProps,c),b.child;case 8:return aa(a,b,b.pendingProps.children,c),b.child;case 12:return aa(a,b,b.pendingProps.children,c),b.child;case 10:a:{d=b.type._context;e=b.pendingProps;f=b.memoizedProps;g=e.value;y(rd,d._currentValue);d._currentValue=g;if(null!==f)if(ua(f.value,g)){if(f.children===
+e.children&&!S.current){b=Qa(a,b,c);break a}}else for(f=b.child,null!==f&&(f.return=b);null!==f;){var h=f.dependencies;if(null!==h){g=f.child;for(var k=h.firstContext;null!==k;){if(k.context===d){if(1===f.tag){k=Pa(-1,c&-c);k.tag=2;var m=f.updateQueue;if(null!==m){m=m.shared;var l=m.pending;null===l?k.next=k:(k.next=l.next,l.next=k);m.pending=k}}f.lanes|=c;k=f.alternate;null!==k&&(k.lanes|=c);bf(f.return,c,b);h.lanes|=c;break}k=k.next}}else if(10===f.tag)g=f.type===b.type?null:f.child;else if(18===
+f.tag){g=f.return;if(null===g)throw Error(n(341));g.lanes|=c;h=g.alternate;null!==h&&(h.lanes|=c);bf(g,c,b);g=f.sibling}else g=f.child;if(null!==g)g.return=f;else for(g=f;null!==g;){if(g===b){g=null;break}f=g.sibling;if(null!==f){f.return=g.return;g=f;break}g=g.return}f=g}aa(a,b,e.children,c);b=b.child}return b;case 9:return e=b.type,d=b.pendingProps.children,Sb(b,c),e=qa(e),d=d(e),b.flags|=1,aa(a,b,d,c),b.child;case 14:return d=b.type,e=xa(d,b.pendingProps),e=xa(d.type,e),oi(a,b,d,e,c);case 15:return pi(a,
+b,b.type,b.pendingProps,c);case 17:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:xa(d,e),Fd(a,b),b.tag=1,ea(d)?(a=!0,ld(b)):a=!1,Sb(b,c),Hh(b,d,e),ff(b,d,e,c),Af(null,b,d,!0,a,c);case 19:return xi(a,b,c);case 22:return qi(a,b,c)}throw Error(n(156,b.tag));};var pa=function(a,b,c,d){return new Uk(a,b,c,d)},bj="function"===typeof reportError?reportError:function(a){console.error(a)};Ud.prototype.render=Xf.prototype.render=function(a){var b=this._internalRoot;if(null===b)throw Error(n(409));
+Sd(a,b,null,null)};Ud.prototype.unmount=Xf.prototype.unmount=function(){var a=this._internalRoot;if(null!==a){this._internalRoot=null;var b=a.containerInfo;zb(function(){Sd(null,a,null,null)});b[Ja]=null}};Ud.prototype.unstable_scheduleHydration=function(a){if(a){var b=ol();a={blockedOn:null,target:a,priority:b};for(var c=0;c<Ya.length&&0!==b&&b<Ya[c].priority;c++);Ya.splice(c,0,a);0===c&&Hg(a)}};var Dj=function(a){switch(a.tag){case 3:var b=a.stateNode;if(b.current.memoizedState.isDehydrated){var c=
+hc(b.pendingLanes);0!==c&&(xe(b,c|1),ia(b,P()),0===(p&6)&&(Hc(),db()))}break;case 13:zb(function(){var b=Oa(a,1);if(null!==b){var c=Z();ya(b,a,1,c)}}),Wf(a,1)}};var Gg=function(a){if(13===a.tag){var b=Oa(a,134217728);if(null!==b){var c=Z();ya(b,a,134217728,c)}Wf(a,134217728)}};var yj=function(a){if(13===a.tag){var b=hb(a),c=Oa(a,b);if(null!==c){var d=Z();ya(c,a,b,d)}Wf(a,b)}};var ol=function(){return z};var xj=function(a,b){var c=z;try{return z=a,b()}finally{z=c}};se=function(a,b,c){switch(b){case "input":le(a,
+c);b=c.name;if("radio"===c.type&&null!=b){for(c=a;c.parentNode;)c=c.parentNode;c=c.querySelectorAll("input[name="+JSON.stringify(""+b)+'][type="radio"]');for(b=0;b<c.length;b++){var d=c[b];if(d!==a&&d.form===a.form){var e=Rc(d);if(!e)throw Error(n(90));jg(d);le(d,e)}}}break;case "textarea":og(a,c);break;case "select":b=c.value,null!=b&&Db(a,!!c.multiple,b,!1)}};(function(a,b,c){xg=a;yg=c})(Tf,function(a,b,c,d,e){var f=z,g=ca.transition;try{return ca.transition=null,z=1,a(b,c,d,e)}finally{z=f,ca.transition=
+g,0===p&&Hc()}},zb);var pl={usingClientEntryPoint:!1,Events:[ec,Ib,Rc,ug,vg,Tf]};(function(a){a={bundleType:a.bundleType,version:a.version,rendererPackageName:a.rendererPackageName,rendererConfig:a.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setErrorHandler:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:Sa.ReactCurrentDispatcher,findHostInstanceByFiber:Yk,
+findFiberByHostInstance:a.findFiberByHostInstance||Zk,findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null,reconcilerVersion:"18.2.0"};if("undefined"===typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)a=!1;else{var b=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(b.isDisabled||!b.supportsFiber)a=!0;else{try{Uc=b.inject(a),Ca=b}catch(c){}a=b.checkDCE?!0:!1}}return a})({findFiberByHostInstance:pb,bundleType:0,version:"18.2.0-next-9e3b772b8-20220608",
+rendererPackageName:"react-dom"});Q.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=pl;Q.createPortal=function(a,b){var c=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!Yf(b))throw Error(n(200));return Xk(a,b,null,c)};Q.createRoot=function(a,b){if(!Yf(a))throw Error(n(299));var c=!1,d="",e=bj;null!==b&&void 0!==b&&(!0===b.unstable_strictMode&&(c=!0),void 0!==b.identifierPrefix&&(d=b.identifierPrefix),void 0!==b.onRecoverableError&&(e=b.onRecoverableError));b=Vf(a,1,!1,null,null,
+c,!1,d,e);a[Ja]=b.current;sc(8===a.nodeType?a.parentNode:a);return new Xf(b)};Q.findDOMNode=function(a){if(null==a)return null;if(1===a.nodeType)return a;var b=a._reactInternals;if(void 0===b){if("function"===typeof a.render)throw Error(n(188));a=Object.keys(a).join(",");throw Error(n(268,a));}a=Bg(b);a=null===a?null:a.stateNode;return a};Q.flushSync=function(a){return zb(a)};Q.hydrate=function(a,b,c){if(!Vd(b))throw Error(n(200));return Wd(null,a,b,!0,c)};Q.hydrateRoot=function(a,b,c){if(!Yf(a))throw Error(n(405));
+var d=null!=c&&c.hydratedSources||null,e=!1,f="",g=bj;null!==c&&void 0!==c&&(!0===c.unstable_strictMode&&(e=!0),void 0!==c.identifierPrefix&&(f=c.identifierPrefix),void 0!==c.onRecoverableError&&(g=c.onRecoverableError));b=Xi(b,null,a,1,null!=c?c:null,e,!1,f,g);a[Ja]=b.current;sc(a);if(d)for(a=0;a<d.length;a++)c=d[a],e=c._getVersion,e=e(c._source),null==b.mutableSourceEagerHydrationData?b.mutableSourceEagerHydrationData=[c,e]:b.mutableSourceEagerHydrationData.push(c,e);return new Ud(b)};Q.render=
+function(a,b,c){if(!Vd(b))throw Error(n(200));return Wd(null,a,b,!1,c)};Q.unmountComponentAtNode=function(a){if(!Vd(a))throw Error(n(40));return a._reactRootContainer?(zb(function(){Wd(null,null,a,!1,function(){a._reactRootContainer=null;a[Ja]=null})}),!0):!1};Q.unstable_batchedUpdates=Tf;Q.unstable_renderSubtreeIntoContainer=function(a,b,c,d){if(!Vd(c))throw Error(n(200));if(null==a||void 0===a._reactInternals)throw Error(n(38));return Wd(a,b,c,!1,d)};Q.version="18.2.0-next-9e3b772b8-20220608"});
+})();

+ 31 - 0
netbox/project-static/dist/react.production.min.js

@@ -0,0 +1,31 @@
+/**
+ * @license React
+ * react.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+(function(){'use strict';(function(c,x){"object"===typeof exports&&"undefined"!==typeof module?x(exports):"function"===typeof define&&define.amd?define(["exports"],x):(c=c||self,x(c.React={}))})(this,function(c){function x(a){if(null===a||"object"!==typeof a)return null;a=V&&a[V]||a["@@iterator"];return"function"===typeof a?a:null}function w(a,b,e){this.props=a;this.context=b;this.refs=W;this.updater=e||X}function Y(){}function K(a,b,e){this.props=a;this.context=b;this.refs=W;this.updater=e||X}function Z(a,b,
+e){var m,d={},c=null,h=null;if(null!=b)for(m in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(c=""+b.key),b)aa.call(b,m)&&!ba.hasOwnProperty(m)&&(d[m]=b[m]);var l=arguments.length-2;if(1===l)d.children=e;else if(1<l){for(var f=Array(l),k=0;k<l;k++)f[k]=arguments[k+2];d.children=f}if(a&&a.defaultProps)for(m in l=a.defaultProps,l)void 0===d[m]&&(d[m]=l[m]);return{$$typeof:y,type:a,key:c,ref:h,props:d,_owner:L.current}}function na(a,b){return{$$typeof:y,type:a.type,key:b,ref:a.ref,props:a.props,_owner:a._owner}}
+function M(a){return"object"===typeof a&&null!==a&&a.$$typeof===y}function oa(a){var b={"=":"=0",":":"=2"};return"$"+a.replace(/[=:]/g,function(a){return b[a]})}function N(a,b){return"object"===typeof a&&null!==a&&null!=a.key?oa(""+a.key):b.toString(36)}function B(a,b,e,m,d){var c=typeof a;if("undefined"===c||"boolean"===c)a=null;var h=!1;if(null===a)h=!0;else switch(c){case "string":case "number":h=!0;break;case "object":switch(a.$$typeof){case y:case pa:h=!0}}if(h)return h=a,d=d(h),a=""===m?"."+
+N(h,0):m,ca(d)?(e="",null!=a&&(e=a.replace(da,"$&/")+"/"),B(d,b,e,"",function(a){return a})):null!=d&&(M(d)&&(d=na(d,e+(!d.key||h&&h.key===d.key?"":(""+d.key).replace(da,"$&/")+"/")+a)),b.push(d)),1;h=0;m=""===m?".":m+":";if(ca(a))for(var l=0;l<a.length;l++){c=a[l];var f=m+N(c,l);h+=B(c,b,e,f,d)}else if(f=x(a),"function"===typeof f)for(a=f.call(a),l=0;!(c=a.next()).done;)c=c.value,f=m+N(c,l++),h+=B(c,b,e,f,d);else if("object"===c)throw b=String(a),Error("Objects are not valid as a React child (found: "+
+("[object Object]"===b?"object with keys {"+Object.keys(a).join(", ")+"}":b)+"). If you meant to render a collection of children, use an array instead.");return h}function C(a,b,e){if(null==a)return a;var c=[],d=0;B(a,c,"","",function(a){return b.call(e,a,d++)});return c}function qa(a){if(-1===a._status){var b=a._result;b=b();b.then(function(b){if(0===a._status||-1===a._status)a._status=1,a._result=b},function(b){if(0===a._status||-1===a._status)a._status=2,a._result=b});-1===a._status&&(a._status=
+0,a._result=b)}if(1===a._status)return a._result.default;throw a._result;}function O(a,b){var e=a.length;a.push(b);a:for(;0<e;){var c=e-1>>>1,d=a[c];if(0<D(d,b))a[c]=b,a[e]=d,e=c;else break a}}function p(a){return 0===a.length?null:a[0]}function E(a){if(0===a.length)return null;var b=a[0],e=a.pop();if(e!==b){a[0]=e;a:for(var c=0,d=a.length,k=d>>>1;c<k;){var h=2*(c+1)-1,l=a[h],f=h+1,g=a[f];if(0>D(l,e))f<d&&0>D(g,l)?(a[c]=g,a[f]=e,c=f):(a[c]=l,a[h]=e,c=h);else if(f<d&&0>D(g,e))a[c]=g,a[f]=e,c=f;else break a}}return b}
+function D(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function P(a){for(var b=p(r);null!==b;){if(null===b.callback)E(r);else if(b.startTime<=a)E(r),b.sortIndex=b.expirationTime,O(q,b);else break;b=p(r)}}function Q(a){z=!1;P(a);if(!u)if(null!==p(q))u=!0,R(S);else{var b=p(r);null!==b&&T(Q,b.startTime-a)}}function S(a,b){u=!1;z&&(z=!1,ea(A),A=-1);F=!0;var c=k;try{P(b);for(n=p(q);null!==n&&(!(n.expirationTime>b)||a&&!fa());){var m=n.callback;if("function"===typeof m){n.callback=null;
+k=n.priorityLevel;var d=m(n.expirationTime<=b);b=v();"function"===typeof d?n.callback=d:n===p(q)&&E(q);P(b)}else E(q);n=p(q)}if(null!==n)var g=!0;else{var h=p(r);null!==h&&T(Q,h.startTime-b);g=!1}return g}finally{n=null,k=c,F=!1}}function fa(){return v()-ha<ia?!1:!0}function R(a){G=a;H||(H=!0,I())}function T(a,b){A=ja(function(){a(v())},b)}var y=Symbol.for("react.element"),pa=Symbol.for("react.portal"),ra=Symbol.for("react.fragment"),sa=Symbol.for("react.strict_mode"),ta=Symbol.for("react.profiler"),
+ua=Symbol.for("react.provider"),va=Symbol.for("react.context"),wa=Symbol.for("react.forward_ref"),xa=Symbol.for("react.suspense"),ya=Symbol.for("react.memo"),za=Symbol.for("react.lazy"),V=Symbol.iterator,X={isMounted:function(a){return!1},enqueueForceUpdate:function(a,b,c){},enqueueReplaceState:function(a,b,c,m){},enqueueSetState:function(a,b,c,m){}},ka=Object.assign,W={};w.prototype.isReactComponent={};w.prototype.setState=function(a,b){if("object"!==typeof a&&"function"!==typeof a&&null!=a)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");
+this.updater.enqueueSetState(this,a,b,"setState")};w.prototype.forceUpdate=function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate")};Y.prototype=w.prototype;var t=K.prototype=new Y;t.constructor=K;ka(t,w.prototype);t.isPureReactComponent=!0;var ca=Array.isArray,aa=Object.prototype.hasOwnProperty,L={current:null},ba={key:!0,ref:!0,__self:!0,__source:!0},da=/\/+/g,g={current:null},J={transition:null};if("object"===typeof performance&&"function"===typeof performance.now){var Aa=performance;
+var v=function(){return Aa.now()}}else{var la=Date,Ba=la.now();v=function(){return la.now()-Ba}}var q=[],r=[],Ca=1,n=null,k=3,F=!1,u=!1,z=!1,ja="function"===typeof setTimeout?setTimeout:null,ea="function"===typeof clearTimeout?clearTimeout:null,ma="undefined"!==typeof setImmediate?setImmediate:null;"undefined"!==typeof navigator&&void 0!==navigator.scheduling&&void 0!==navigator.scheduling.isInputPending&&navigator.scheduling.isInputPending.bind(navigator.scheduling);var H=!1,G=null,A=-1,ia=5,ha=
+-1,U=function(){if(null!==G){var a=v();ha=a;var b=!0;try{b=G(!0,a)}finally{b?I():(H=!1,G=null)}}else H=!1};if("function"===typeof ma)var I=function(){ma(U)};else if("undefined"!==typeof MessageChannel){t=new MessageChannel;var Da=t.port2;t.port1.onmessage=U;I=function(){Da.postMessage(null)}}else I=function(){ja(U,0)};t={ReactCurrentDispatcher:g,ReactCurrentOwner:L,ReactCurrentBatchConfig:J,Scheduler:{__proto__:null,unstable_ImmediatePriority:1,unstable_UserBlockingPriority:2,unstable_NormalPriority:3,
+unstable_IdlePriority:5,unstable_LowPriority:4,unstable_runWithPriority:function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=k;k=a;try{return b()}finally{k=c}},unstable_next:function(a){switch(k){case 1:case 2:case 3:var b=3;break;default:b=k}var c=k;k=b;try{return a()}finally{k=c}},unstable_scheduleCallback:function(a,b,c){var e=v();"object"===typeof c&&null!==c?(c=c.delay,c="number"===typeof c&&0<c?e+c:e):c=e;switch(a){case 1:var d=-1;break;case 2:d=250;break;case 5:d=
+1073741823;break;case 4:d=1E4;break;default:d=5E3}d=c+d;a={id:Ca++,callback:b,priorityLevel:a,startTime:c,expirationTime:d,sortIndex:-1};c>e?(a.sortIndex=c,O(r,a),null===p(q)&&a===p(r)&&(z?(ea(A),A=-1):z=!0,T(Q,c-e))):(a.sortIndex=d,O(q,a),u||F||(u=!0,R(S)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=k;return function(){var c=k;k=b;try{return a.apply(this,arguments)}finally{k=c}}},unstable_getCurrentPriorityLevel:function(){return k},unstable_shouldYield:fa,
+unstable_requestPaint:function(){},unstable_continueExecution:function(){u||F||(u=!0,R(S))},unstable_pauseExecution:function(){},unstable_getFirstCallbackNode:function(){return p(q)},get unstable_now(){return v},unstable_forceFrameRate:function(a){0>a||125<a?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):ia=0<a?Math.floor(1E3/a):5},unstable_Profiling:null}};c.Children={map:C,forEach:function(a,b,c){C(a,function(){b.apply(this,
+arguments)},c)},count:function(a){var b=0;C(a,function(){b++});return b},toArray:function(a){return C(a,function(a){return a})||[]},only:function(a){if(!M(a))throw Error("React.Children.only expected to receive a single React element child.");return a}};c.Component=w;c.Fragment=ra;c.Profiler=ta;c.PureComponent=K;c.StrictMode=sa;c.Suspense=xa;c.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=t;c.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+
+a+".");var e=ka({},a.props),d=a.key,k=a.ref,h=a._owner;if(null!=b){void 0!==b.ref&&(k=b.ref,h=L.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var l=a.type.defaultProps;for(f in b)aa.call(b,f)&&!ba.hasOwnProperty(f)&&(e[f]=void 0===b[f]&&void 0!==l?l[f]:b[f])}var f=arguments.length-2;if(1===f)e.children=c;else if(1<f){l=Array(f);for(var g=0;g<f;g++)l[g]=arguments[g+2];e.children=l}return{$$typeof:y,type:a.type,key:d,ref:k,props:e,_owner:h}};c.createContext=function(a){a={$$typeof:va,
+_currentValue:a,_currentValue2:a,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null};a.Provider={$$typeof:ua,_context:a};return a.Consumer=a};c.createElement=Z;c.createFactory=function(a){var b=Z.bind(null,a);b.type=a;return b};c.createRef=function(){return{current:null}};c.forwardRef=function(a){return{$$typeof:wa,render:a}};c.isValidElement=M;c.lazy=function(a){return{$$typeof:za,_payload:{_status:-1,_result:a},_init:qa}};c.memo=function(a,b){return{$$typeof:ya,type:a,
+compare:void 0===b?null:b}};c.startTransition=function(a,b){b=J.transition;J.transition={};try{a()}finally{J.transition=b}};c.unstable_act=function(a){throw Error("act(...) is not supported in production builds of React.");};c.useCallback=function(a,b){return g.current.useCallback(a,b)};c.useContext=function(a){return g.current.useContext(a)};c.useDebugValue=function(a,b){};c.useDeferredValue=function(a){return g.current.useDeferredValue(a)};c.useEffect=function(a,b){return g.current.useEffect(a,
+b)};c.useId=function(){return g.current.useId()};c.useImperativeHandle=function(a,b,c){return g.current.useImperativeHandle(a,b,c)};c.useInsertionEffect=function(a,b){return g.current.useInsertionEffect(a,b)};c.useLayoutEffect=function(a,b){return g.current.useLayoutEffect(a,b)};c.useMemo=function(a,b){return g.current.useMemo(a,b)};c.useReducer=function(a,b,c){return g.current.useReducer(a,b,c)};c.useRef=function(a){return g.current.useRef(a)};c.useState=function(a){return g.current.useState(a)};
+c.useSyncExternalStore=function(a,b,c){return g.current.useSyncExternalStore(a,b,c)};c.useTransition=function(){return g.current.useTransition()};c.version="18.2.0"});
+})();

+ 0 - 3
netbox/project-static/netbox-graphiql/graphiql.scss

@@ -1,3 +0,0 @@
-// Rather than use CDNs to include GraphiQL dependencies, import and bundle the dependencies so
-// they can be locally served.
-@import '../node_modules/graphiql/graphiql.css';

+ 0 - 17
netbox/project-static/netbox-graphiql/index.ts

@@ -1,17 +0,0 @@
-/**
- * Rather than use CDNs to include GraphiQL dependencies, import and bundle the dependencies so
- * they can be locally served.
- */
-
-import * as React from 'react';
-import * as ReactDOM from 'react-dom';
-import 'graphql';
-import GraphiQL from 'graphiql';
-import SubscriptionsTransportWs from 'subscriptions-transport-ws';
-
-window.React = React;
-window.ReactDOM = ReactDOM;
-// @ts-expect-error Assigning to window is required for graphene-django
-window.SubscriptionsTransportWs = SubscriptionsTransportWs;
-// @ts-expect-error Assigning to window is required for graphene-django
-window.GraphiQL = GraphiQL;

+ 8 - 7
netbox/project-static/netbox-graphiql/package.json

@@ -1,16 +1,17 @@
 {
 {
   "name": "netbox-graphiql",
   "name": "netbox-graphiql",
-  "version": "0.1.0",
+  "version": "0.2.0",
   "description": "NetBox GraphiQL Custom Front End",
   "description": "NetBox GraphiQL Custom Front End",
   "main": "dist/graphiql.js",
   "main": "dist/graphiql.js",
   "license": "Apache-2.0",
   "license": "Apache-2.0",
   "private": true,
   "private": true,
   "dependencies": {
   "dependencies": {
-    "graphiql": "1.8.9",
-    "graphql": ">= v14.5.0 <= 15.5.0",
-    "react": "17.0.2",
-    "react-dom": "17.0.2",
-    "subscriptions-transport-ws": "0.9.18",
-    "whatwg-fetch": "3.6.2"
+    "graphiql": "3.0.9",
+    "graphql": "16.8.1",
+    "react": "18.2.0",
+    "react-dom": "18.2.0",
+    "react-scripts": "5.0.1",
+    "js-cookie": "3.0.5",
+    "@graphiql/plugin-explorer": "1.0.2"
   }
   }
 }
 }

Fișier diff suprimat deoarece este prea mare
+ 1286 - 0
netbox/project-static/yarn.lock


+ 117 - 23
netbox/templates/graphiql.html

@@ -1,6 +1,6 @@
 {% comment %}
 {% comment %}
-  This template derives from the graphene-django project:
-  https://github.com/graphql-python/graphene-django/blob/main/graphene_django/templates/graphene/graphiql.html
+  This template derives from the strawberry-graphql project:
+  https://github.com/strawberry-graphql/strawberry/blob/main/strawberry/static/graphiql.html
 {% endcomment %}
 {% endcomment %}
 <!--
 <!--
 The request to this GraphQL server provided the header "Accept: text/html"
 The request to this GraphQL server provided the header "Accept: text/html"
@@ -11,36 +11,130 @@ add "&raw" to the end of the URL within a browser.
 -->
 -->
 {% load static %}
 {% load static %}
 <!DOCTYPE html>
 <!DOCTYPE html>
-<html lang="en">
+<html>
   <head>
   <head>
+    <title>GraphiQL | NetBox</title>
+    <link
+      rel="icon"
+      href="data:image/svg+xml,
+        <svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22>
+            <!-- Strawberry Emoji as a HTML Entity (hex)  -->
+            <text y=%22.9em%22 font-size=%2280%22>&#x1f353;</text>
+        </svg>"
+    />
     <style>
     <style>
-      html, body, #editor {
+      body {
         height: 100%;
         height: 100%;
         margin: 0;
         margin: 0;
-        overflow: hidden;
         width: 100%;
         width: 100%;
+        overflow: hidden;
+      }
+
+      #graphiql {
+        height: 100vh;
+        display: flex;
+      }
+
+      .docExplorerHide {
+        display: none;
+      }
+
+      .doc-explorer-contents {
+        overflow-y: hidden !important;
+      }
+
+      .docExplorerWrap {
+        width: unset !important;
+        min-width: unset !important;
+      }
+
+      .graphiql-explorer-actions select {
+        margin-left: 4px;
       }
       }
     </style>
     </style>
-    <link href="{% static 'graphiql.css'%}" rel="stylesheet" />
-    <link rel="icon" type="image/png" href="{% static 'graphql.ico' %}" />
-    <title>GraphiQL | NetBox</title>
+
+    <script src="{% static 'graphiql/react.production.min.js' %}"></script>
+    <script src="{% static 'graphiql/react-dom.production.min.js' %}"></script>
+    <script src="{% static 'graphiql/js.cookie.min.js' %}"></script>
+
+    <link rel="stylesheet" href="{% static 'graphiql/graphiql.min.css' %}"/>
+    <link rel="stylesheet" href="{% static 'graphiql/plugin-explorer-style.css' %}"/>
   </head>
   </head>
+
   <body>
   <body>
-    <div id="editor"></div>
-    {% csrf_token %}
-    <script type="application/javascript">
-      window.GRAPHENE_SETTINGS = {
-      {% if subscription_path %}
-        subscriptionPath: "{{subscription_path}}",
-      {% endif %}
-        graphiqlHeaderEditorEnabled: {{ graphiql_header_editor_enabled|yesno:"true,false" }},
-      };
-    </script>
-    <script
-      type="text/javascript"
-      src="{% static 'graphiql.js' %}"
-      onerror="window.location='{% url 'media_failure' %}?filename=graphiql.js'">
+    <div id="graphiql" class="graphiql-container">Loading...</div>
+    <script src="{% static 'graphiql/graphiql.min.js' %}"></script>
+    <script src="{% static 'graphiql/index.umd.js' %}"></script>
+
+    <script>
+      const EXAMPLE_QUERY = `# Welcome to GraphiQL 🍓
+#
+# GraphiQL is an in-browser tool for writing, validating, and
+# testing GraphQL queries.
+#
+# Type queries into this side of the screen, and you will see intelligent
+# typeaheads aware of the current GraphQL type schema and live syntax and
+# validation errors highlighted within the text.
+#
+# GraphQL queries typically start with a "{" character. Lines that starts
+# with a # are ignored.
+#
+# An example GraphQL query might look like:
+#
+#     {
+#       field(arg: "value") {
+#         subField
+#       }
+#     }
+#
+# Keyboard shortcuts:
+#
+#       Run Query:  Ctrl-Enter (or press the play button above)
+#
+#   Auto Complete:  Ctrl-Space (or just start typing)
+#
+`;
+
+      const fetchURL = window.location.href;
+
+      function httpUrlToWebSockeUrl(url) {
+        const parsedURL = new URL(url);
+        const protocol = parsedURL.protocol === "http:" ? "ws:" : "wss:";
+        parsedURL.protocol = protocol;
+        parsedURL.hash = "";
+        return parsedURL.toString();
+      }
+
+      const headers = {};
+      const csrfToken = Cookies.get("csrftoken");
+
+      if (csrfToken) {
+        headers["x-csrftoken"] = csrfToken;
+      }
+
+      const subscriptionsEnabled = JSON.parse("{{ SUBSCRIPTION_ENABLED }}");
+      const subscriptionUrl = subscriptionsEnabled
+        ? httpUrlToWebSockeUrl(fetchURL)
+        : null;
+
+      const fetcher = GraphiQL.createFetcher({
+        url: fetchURL,
+        headers: headers,
+        subscriptionUrl,
+      });
+
+      const explorerPlugin = GraphiQLPluginExplorer.explorerPlugin();
+
+      const root = ReactDOM.createRoot(document.getElementById("graphiql"));
+
+      root.render(
+        React.createElement(GraphiQL, {
+          fetcher: fetcher,
+          defaultEditorToolsVisibility: true,
+          plugins: [explorerPlugin],
+          inputValueDeprecation: true,
+        }),
+      );
     </script>
     </script>
-    <script src="{% static 'graphene_django/graphiql.js' %}"></script>
   </body>
   </body>
 </html>
 </html>

+ 1 - 2
netbox/tenancy/filtersets.py

@@ -127,11 +127,10 @@ class ContactAssignmentFilterSet(NetBoxModelFilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label=_('Contact role (slug)'),
         label=_('Contact role (slug)'),
     )
     )
-    tag = TagFilter()
 
 
     class Meta:
     class Meta:
         model = ContactAssignment
         model = ContactAssignment
-        fields = ('id', 'object_type_id', 'object_id', 'priority', 'tag')
+        fields = ('id', 'object_type_id', 'object_id', 'priority')
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):
         if not value.strip():
         if not value.strip():

+ 49 - 0
netbox/tenancy/graphql/filters.py

@@ -0,0 +1,49 @@
+import strawberry_django
+
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+from tenancy import filtersets, models
+
+__all__ = (
+    'TenantFilter',
+    'TenantGroupFilter',
+    'ContactFilter',
+    'ContactRoleFilter',
+    'ContactGroupFilter',
+    'ContactAssignmentFilter',
+)
+
+
+@strawberry_django.filter(models.Tenant, lookups=True)
+@autotype_decorator(filtersets.TenantFilterSet)
+class TenantFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.TenantGroup, lookups=True)
+@autotype_decorator(filtersets.TenantGroupFilterSet)
+class TenantGroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Contact, lookups=True)
+@autotype_decorator(filtersets.ContactFilterSet)
+class ContactFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ContactRole, lookups=True)
+@autotype_decorator(filtersets.ContactRoleFilterSet)
+class ContactRoleFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ContactGroup, lookups=True)
+@autotype_decorator(filtersets.ContactGroupFilterSet)
+class ContactGroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ContactAssignment, lookups=True)
+@autotype_decorator(filtersets.ContactAssignmentFilterSet)
+class ContactAssignmentFilter(BaseFilterMixin):
+    pass

+ 17 - 0
netbox/tenancy/graphql/mixins.py

@@ -0,0 +1,17 @@
+from typing import Annotated, List
+
+import strawberry
+import strawberry_django
+
+
+__all__ = (
+    'ContactAssignmentsMixin',
+)
+
+
+@strawberry.type
+class ContactAssignmentsMixin:
+
+    @strawberry_django.field
+    def assignments(self) -> List[Annotated["ContactAssignmentType", strawberry.lazy('tenancy.graphql.types')]]:
+        return self.assignments.all()

+ 35 - 39
netbox/tenancy/graphql/schema.py

@@ -1,44 +1,40 @@
-import graphene
+from typing import List
+
+import strawberry
+import strawberry_django
 
 
-from netbox.graphql.fields import ObjectField, ObjectListField
 from tenancy import models
 from tenancy import models
 from .types import *
 from .types import *
-from utilities.graphql_optimizer import gql_query_optimizer
-
-
-class TenancyQuery(graphene.ObjectType):
-    tenant = ObjectField(TenantType)
-    tenant_list = ObjectListField(TenantType)
-
-    def resolve_tenant_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Tenant.objects.all(), info)
-
-    tenant_group = ObjectField(TenantGroupType)
-    tenant_group_list = ObjectListField(TenantGroupType)
-
-    def resolve_tenant_group_list(root, info, **kwargs):
-        return gql_query_optimizer(models.TenantGroup.objects.all(), info)
-
-    contact = ObjectField(ContactType)
-    contact_list = ObjectListField(ContactType)
-
-    def resolve_contact_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Contact.objects.all(), info)
-
-    contact_role = ObjectField(ContactRoleType)
-    contact_role_list = ObjectListField(ContactRoleType)
-
-    def resolve_contact_role_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ContactRole.objects.all(), info)
-
-    contact_group = ObjectField(ContactGroupType)
-    contact_group_list = ObjectListField(ContactGroupType)
-
-    def resolve_contact_group_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ContactGroup.objects.all(), info)
 
 
-    contact_assignment = ObjectField(ContactAssignmentType)
-    contact_assignment_list = ObjectListField(ContactAssignmentType)
 
 
-    def resolve_contact_assignment_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ContactAssignment.objects.all(), info)
+@strawberry.type
+class TenancyQuery:
+    @strawberry.field
+    def tenant(self, id: int) -> TenantType:
+        return models.Tenant.objects.get(pk=id)
+    tenant_list: List[TenantType] = strawberry_django.field()
+
+    @strawberry.field
+    def tenant_group(self, id: int) -> TenantGroupType:
+        return models.TenantGroup.objects.get(pk=id)
+    tenant_group_list: List[TenantGroupType] = strawberry_django.field()
+
+    @strawberry.field
+    def contact(self, id: int) -> ContactType:
+        return models.Contact.objects.get(pk=id)
+    contact_list: List[ContactType] = strawberry_django.field()
+
+    @strawberry.field
+    def contact_role(self, id: int) -> ContactRoleType:
+        return models.ContactRole.objects.get(pk=id)
+    contact_role_list: List[ContactRoleType] = strawberry_django.field()
+
+    @strawberry.field
+    def contact_group(self, id: int) -> ContactGroupType:
+        return models.ContactGroup.objects.get(pk=id)
+    contact_group_list: List[ContactGroupType] = strawberry_django.field()
+
+    @strawberry.field
+    def contact_assignment(self, id: int) -> ContactAssignmentType:
+        return models.ContactAssignment.objects.get(pk=id)
+    contact_assignment_list: List[ContactAssignmentType] = strawberry_django.field()

+ 146 - 36
netbox/tenancy/graphql/types.py

@@ -1,8 +1,13 @@
-import graphene
+from typing import Annotated, List
+
+import strawberry
+import strawberry_django
 
 
 from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
 from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
-from tenancy import filtersets, models
 from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
 from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
+from tenancy import models
+from .mixins import ContactAssignmentsMixin
+from .filters import *
 
 
 __all__ = (
 __all__ = (
     'ContactAssignmentType',
     'ContactAssignmentType',
@@ -14,64 +19,169 @@ __all__ = (
 )
 )
 
 
 
 
-class ContactAssignmentsMixin:
-    assignments = graphene.List('tenancy.graphql.types.ContactAssignmentType')
-
-    def resolve_assignments(self, info):
-        return self.assignments.restrict(info.context.user, 'view')
-
-
 #
 #
 # Tenants
 # Tenants
 #
 #
 
 
+@strawberry_django.type(
+    models.Tenant,
+    fields='__all__',
+    filters=TenantFilter
+)
 class TenantType(NetBoxObjectType):
 class TenantType(NetBoxObjectType):
+    group: Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')] | None
+
+    @strawberry_django.field
+    def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.asns.all()
+
+    @strawberry_django.field
+    def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
+        return self.circuits.all()
+
+    @strawberry_django.field
+    def sites(self) -> List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.sites.all()
+
+    @strawberry_django.field
+    def vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.vlans.all()
+
+    @strawberry_django.field
+    def wireless_lans(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
+        return self.wireless_lans.all()
+
+    @strawberry_django.field
+    def route_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.route_targets.all()
+
+    @strawberry_django.field
+    def locations(self) -> List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.locations.all()
+
+    @strawberry_django.field
+    def ip_ranges(self) -> List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.ip_ranges.all()
+
+    @strawberry_django.field
+    def rackreservations(self) -> List[Annotated["RackReservationType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.rackreservations.all()
+
+    @strawberry_django.field
+    def racks(self) -> List[Annotated["RackType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.racks.all()
+
+    @strawberry_django.field
+    def vdcs(self) -> List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.vdcs.all()
+
+    @strawberry_django.field
+    def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.prefixes.all()
+
+    @strawberry_django.field
+    def cables(self) -> List[Annotated["CableType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.cables.all()
 
 
-    class Meta:
-        model = models.Tenant
-        fields = '__all__'
-        filterset_class = filtersets.TenantFilterSet
+    @strawberry_django.field
+    def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.virtual_machines.all()
 
 
+    @strawberry_django.field
+    def vrfs(self) -> List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.vrfs.all()
 
 
+    @strawberry_django.field
+    def asn_ranges(self) -> List[Annotated["ASNRangeType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.asn_ranges.all()
+
+    @strawberry_django.field
+    def wireless_links(self) -> List[Annotated["WirelessLinkType", strawberry.lazy('wireless.graphql.types')]]:
+        return self.wireless_links.all()
+
+    @strawberry_django.field
+    def aggregates(self) -> List[Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.aggregates.all()
+
+    @strawberry_django.field
+    def power_feeds(self) -> List[Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.power_feeds.all()
+
+    @strawberry_django.field
+    def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devices.all()
+
+    @strawberry_django.field
+    def tunnels(self) -> List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.tunnels.all()
+
+    @strawberry_django.field
+    def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.ip_addresses.all()
+
+    @strawberry_django.field
+    def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.clusters.all()
+
+    @strawberry_django.field
+    def l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.l2vpns.all()
+
+
+@strawberry_django.type(
+    models.TenantGroup,
+    fields='__all__',
+    filters=TenantGroupFilter
+)
 class TenantGroupType(OrganizationalObjectType):
 class TenantGroupType(OrganizationalObjectType):
+    parent: Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
-    class Meta:
-        model = models.TenantGroup
-        fields = '__all__'
-        filterset_class = filtersets.TenantGroupFilterSet
+    @strawberry_django.field
+    def tenants(self) -> List[TenantType]:
+        return self.tenants.all()
 
 
 
 
 #
 #
 # Contacts
 # Contacts
 #
 #
 
 
+@strawberry_django.type(
+    models.Contact,
+    fields='__all__',
+    filters=ContactFilter
+)
 class ContactType(ContactAssignmentsMixin, NetBoxObjectType):
 class ContactType(ContactAssignmentsMixin, NetBoxObjectType):
-
-    class Meta:
-        model = models.Contact
-        fields = '__all__'
-        filterset_class = filtersets.ContactFilterSet
+    group: Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
 
 
+@strawberry_django.type(
+    models.ContactRole,
+    fields='__all__',
+    filters=ContactRoleFilter
+)
 class ContactRoleType(ContactAssignmentsMixin, OrganizationalObjectType):
 class ContactRoleType(ContactAssignmentsMixin, OrganizationalObjectType):
-
-    class Meta:
-        model = models.ContactRole
-        fields = '__all__'
-        filterset_class = filtersets.ContactRoleFilterSet
+    pass
 
 
 
 
+@strawberry_django.type(
+    models.ContactGroup,
+    fields='__all__',
+    filters=ContactGroupFilter
+)
 class ContactGroupType(OrganizationalObjectType):
 class ContactGroupType(OrganizationalObjectType):
+    parent: Annotated["ContactGroupType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
-    class Meta:
-        model = models.ContactGroup
-        fields = '__all__'
-        filterset_class = filtersets.ContactGroupFilterSet
+    @strawberry_django.field
+    def contacts(self) -> List[ContactType]:
+        return self.contacts.all()
 
 
 
 
+@strawberry_django.type(
+    models.ContactAssignment,
+    fields='__all__',
+    filters=ContactAssignmentFilter
+)
 class ContactAssignmentType(CustomFieldsMixin, TagsMixin, BaseObjectType):
 class ContactAssignmentType(CustomFieldsMixin, TagsMixin, BaseObjectType):
-
-    class Meta:
-        model = models.ContactAssignment
-        fields = '__all__'
-        filterset_class = filtersets.ContactAssignmentFilterSet
+    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

+ 1 - 0
netbox/users/filtersets.py

@@ -1,4 +1,5 @@
 import django_filters
 import django_filters
+
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.db.models import Q
 from django.db.models import Q
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _

+ 23 - 0
netbox/users/graphql/filters.py

@@ -0,0 +1,23 @@
+import strawberry
+import strawberry_django
+from django.contrib.auth import get_user_model
+from users import filtersets, models
+
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+
+__all__ = (
+    'GroupFilter',
+    'UserFilter',
+)
+
+
+@strawberry_django.filter(models.Group, lookups=True)
+@autotype_decorator(filtersets.GroupFilterSet)
+class GroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(get_user_model(), lookups=True)
+@autotype_decorator(filtersets.UserFilterSet)
+class UserFilter(BaseFilterMixin):
+    pass

+ 16 - 16
netbox/users/graphql/schema.py

@@ -1,21 +1,21 @@
-import graphene
-from django.contrib.auth import get_user_model
+from typing import List
+import strawberry
+import strawberry_django
 
 
-from netbox.graphql.fields import ObjectField, ObjectListField
-from users.models import Group
-from utilities.graphql_optimizer import gql_query_optimizer
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
+from users import models
 from .types import *
 from .types import *
 
 
 
 
-class UsersQuery(graphene.ObjectType):
-    group = ObjectField(GroupType)
-    group_list = ObjectListField(GroupType)
-
-    def resolve_group_list(root, info, **kwargs):
-        return gql_query_optimizer(Group.objects.all(), info)
-
-    user = ObjectField(UserType)
-    user_list = ObjectListField(UserType)
+@strawberry.type
+class UsersQuery:
+    @strawberry.field
+    def group(self, id: int) -> GroupType:
+        return models.Group.objects.get(pk=id)
+    group_list: List[GroupType] = strawberry_django.field()
 
 
-    def resolve_user_list(root, info, **kwargs):
-        return gql_query_optimizer(get_user_model().objects.all(), info)
+    @strawberry.field
+    def user(self, id: int) -> UserType:
+        return models.User.objects.get(pk=id)
+    user_list: List[UserType] = strawberry_django.field()

+ 26 - 25
netbox/users/graphql/types.py

@@ -1,9 +1,14 @@
-from django.contrib.auth import get_user_model
-from graphene_django import DjangoObjectType
+from typing import List
 
 
+import strawberry
+import strawberry_django
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
+from strawberry import auto
 from users import filtersets
 from users import filtersets
 from users.models import Group
 from users.models import Group
 from utilities.querysets import RestrictedQuerySet
 from utilities.querysets import RestrictedQuerySet
+from .filters import *
 
 
 __all__ = (
 __all__ = (
     'GroupType',
     'GroupType',
@@ -11,28 +16,24 @@ __all__ = (
 )
 )
 
 
 
 
-class GroupType(DjangoObjectType):
-
-    class Meta:
-        model = Group
-        fields = ('id', 'name')
-        filterset_class = filtersets.GroupFilterSet
-
-    @classmethod
-    def get_queryset(cls, queryset, info):
-        return RestrictedQuerySet(model=Group).restrict(info.context.user, 'view')
-
-
-class UserType(DjangoObjectType):
+@strawberry_django.type(
+    Group,
+    fields=['id', 'name'],
+    filters=GroupFilter
+)
+class GroupType:
+    pass
 
 
-    class Meta:
-        model = get_user_model()
-        fields = (
-            'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'date_joined',
-            'groups',
-        )
-        filterset_class = filtersets.UserFilterSet
 
 
-    @classmethod
-    def get_queryset(cls, queryset, info):
-        return RestrictedQuerySet(model=get_user_model()).restrict(info.context.user, 'view')
+@strawberry_django.type(
+    get_user_model(),
+    fields=[
+        'id', 'username', 'password', 'first_name', 'last_name', 'email', 'is_staff',
+        'is_active', 'date_joined', 'groups',
+    ],
+    filters=UserFilter
+)
+class UserType:
+    @strawberry_django.field
+    def groups(self) -> List[GroupType]:
+        return self.groups.all()

+ 0 - 252
netbox/utilities/graphql_optimizer.py

@@ -1,252 +0,0 @@
-import functools
-
-from django.core.exceptions import FieldDoesNotExist
-from django.db.models import ForeignKey
-from django.db.models.constants import LOOKUP_SEP
-from django.db.models.fields.reverse_related import ManyToOneRel
-from graphene import InputObjectType
-from graphene.types.generic import GenericScalar
-from graphene.types.resolver import default_resolver
-from graphene_django import DjangoObjectType
-from graphql import GraphQLResolveInfo, GraphQLSchema
-from graphql.execution.execute import get_field_def
-from graphql.language.ast import FragmentSpreadNode, InlineFragmentNode, VariableNode
-from graphql.pyutils import Path
-from graphql.type.definition import GraphQLInterfaceType, GraphQLUnionType
-
-__all__ = (
-    'gql_query_optimizer',
-)
-
-
-def gql_query_optimizer(queryset, info, **options):
-    return QueryOptimizer(info).optimize(queryset)
-
-
-class QueryOptimizer(object):
-    def __init__(self, info, **options):
-        self.root_info = info
-
-    def optimize(self, queryset):
-        info = self.root_info
-        field_def = get_field_def(info.schema, info.parent_type, info.field_nodes[0])
-
-        field_names = self._optimize_gql_selections(
-            self._get_type(field_def),
-            info.field_nodes[0],
-        )
-
-        qs = queryset.prefetch_related(*field_names)
-        return qs
-
-    def _get_type(self, field_def):
-        a_type = field_def.type
-        while hasattr(a_type, "of_type"):
-            a_type = a_type.of_type
-        return a_type
-
-    def _get_graphql_schema(self, schema):
-        if isinstance(schema, GraphQLSchema):
-            return schema
-        else:
-            return schema.graphql_schema
-
-    def _get_possible_types(self, graphql_type):
-        if isinstance(graphql_type, (GraphQLInterfaceType, GraphQLUnionType)):
-            graphql_schema = self._get_graphql_schema(self.root_info.schema)
-            return graphql_schema.get_possible_types(graphql_type)
-        else:
-            return (graphql_type,)
-
-    def _get_base_model(self, graphql_types):
-        models = tuple(t.graphene_type._meta.model for t in graphql_types)
-        for model in models:
-            if all(issubclass(m, model) for m in models):
-                return model
-        return None
-
-    def handle_inline_fragment(self, selection, schema, possible_types, field_names):
-        fragment_type_name = selection.type_condition.name.value
-        graphql_schema = self._get_graphql_schema(schema)
-        fragment_type = graphql_schema.get_type(fragment_type_name)
-        fragment_possible_types = self._get_possible_types(fragment_type)
-        for fragment_possible_type in fragment_possible_types:
-            fragment_model = fragment_possible_type.graphene_type._meta.model
-            parent_model = self._get_base_model(possible_types)
-            if not parent_model:
-                continue
-            path_from_parent = fragment_model._meta.get_path_from_parent(parent_model)
-            select_related_name = LOOKUP_SEP.join(p.join_field.name for p in path_from_parent)
-            if not select_related_name:
-                continue
-            sub_field_names = self._optimize_gql_selections(
-                fragment_possible_type,
-                selection,
-            )
-            field_names.append(select_related_name)
-        return
-
-    def handle_fragment_spread(self, field_names, name, field_type):
-        fragment = self.root_info.fragments[name]
-        sub_field_names = self._optimize_gql_selections(
-            field_type,
-            fragment,
-        )
-
-    def _optimize_gql_selections(self, field_type, field_ast):
-        field_names = []
-        selection_set = field_ast.selection_set
-        if not selection_set:
-            return field_names
-        optimized_fields_by_model = {}
-        schema = self.root_info.schema
-        graphql_schema = self._get_graphql_schema(schema)
-        graphql_type = graphql_schema.get_type(field_type.name)
-
-        possible_types = self._get_possible_types(graphql_type)
-        for selection in selection_set.selections:
-            if isinstance(selection, InlineFragmentNode):
-                self.handle_inline_fragment(selection, schema, possible_types, field_names)
-            else:
-                name = selection.name.value
-                if isinstance(selection, FragmentSpreadNode):
-                    self.handle_fragment_spread(field_names, name, field_type)
-                else:
-                    for possible_type in possible_types:
-                        selection_field_def = possible_type.fields.get(name)
-                        if not selection_field_def:
-                            continue
-
-                        graphene_type = possible_type.graphene_type
-                        model = getattr(graphene_type._meta, "model", None)
-                        if model and name not in optimized_fields_by_model:
-                            field_model = optimized_fields_by_model[name] = model
-                            if field_model == model:
-                                self._optimize_field(
-                                    field_names,
-                                    model,
-                                    selection,
-                                    selection_field_def,
-                                    possible_type,
-                                )
-        return field_names
-
-    def _get_field_info(self, field_names, model, selection, field_def):
-        name = None
-        model_field = None
-        name = self._get_name_from_resolver(field_def.resolve)
-        if not name and callable(field_def.resolve) and not isinstance(field_def.resolve, functools.partial):
-            name = selection.name.value
-        if name:
-            model_field = self._get_model_field_from_name(model, name)
-
-        return (name, model_field)
-
-    def _optimize_field(self, field_names, model, selection, field_def, parent_type):
-        name, model_field = self._get_field_info(field_names, model, selection, field_def)
-        if model_field:
-            self._optimize_field_by_name(field_names, model, selection, field_def, name, model_field)
-
-        return
-
-    def _optimize_field_by_name(self, field_names, model, selection, field_def, name, model_field):
-        if model_field.many_to_one or model_field.one_to_one:
-            sub_field_names = self._optimize_gql_selections(
-                self._get_type(field_def),
-                selection,
-            )
-            if name not in field_names:
-                field_names.append(name)
-
-            for field in sub_field_names:
-                prefetch_key = f"{name}__{field}"
-                if prefetch_key not in field_names:
-                    field_names.append(prefetch_key)
-
-        if model_field.one_to_many or model_field.many_to_many:
-            sub_field_names = self._optimize_gql_selections(
-                self._get_type(field_def),
-                selection,
-            )
-
-            if isinstance(model_field, ManyToOneRel):
-                sub_field_names.append(model_field.field.name)
-
-            field_names.append(name)
-            for field in sub_field_names:
-                prefetch_key = f"{name}__{field}"
-                if prefetch_key not in field_names:
-                    field_names.append(prefetch_key)
-
-        return
-
-    def _get_optimization_hints(self, resolver):
-        return getattr(resolver, "optimization_hints", None)
-
-    def _get_value(self, info, value):
-        if isinstance(value, VariableNode):
-            var_name = value.name.value
-            value = info.variable_values.get(var_name)
-            return value
-        elif isinstance(value, InputObjectType):
-            return value.__dict__
-        else:
-            return GenericScalar.parse_literal(value)
-
-    def _get_name_from_resolver(self, resolver):
-        optimization_hints = self._get_optimization_hints(resolver)
-        if optimization_hints:
-            name_fn = optimization_hints.model_field
-            if name_fn:
-                return name_fn()
-        if self._is_resolver_for_id_field(resolver):
-            return "id"
-        elif isinstance(resolver, functools.partial):
-            resolver_fn = resolver
-            if resolver_fn.func != default_resolver:
-                # Some resolvers have the partial function as the second
-                # argument.
-                for arg in resolver_fn.args:
-                    if isinstance(arg, (str, functools.partial)):
-                        break
-                else:
-                    # No suitable instances found, default to first arg
-                    arg = resolver_fn.args[0]
-                resolver_fn = arg
-            if isinstance(resolver_fn, functools.partial) and resolver_fn.func == default_resolver:
-                return resolver_fn.args[0]
-            if self._is_resolver_for_id_field(resolver_fn):
-                return "id"
-            return resolver_fn
-
-    def _is_resolver_for_id_field(self, resolver):
-        resolve_id = DjangoObjectType.resolve_id
-        return resolver == resolve_id
-
-    def _get_model_field_from_name(self, model, name):
-        try:
-            return model._meta.get_field(name)
-        except FieldDoesNotExist:
-            descriptor = model.__dict__.get(name)
-            if not descriptor:
-                return None
-            return getattr(descriptor, "rel", None) or getattr(descriptor, "related", None)  # Django < 1.9
-
-    def _is_foreign_key_id(self, model_field, name):
-        return isinstance(model_field, ForeignKey) and model_field.name != name and model_field.get_attname() == name
-
-    def _create_resolve_info(self, field_name, field_asts, return_type, parent_type):
-        return GraphQLResolveInfo(
-            field_name,
-            field_asts,
-            return_type,
-            parent_type,
-            Path(None, 0, None),
-            schema=self.root_info.schema,
-            fragments=self.root_info.fragments,
-            root_value=self.root_info.root_value,
-            operation=self.root_info.operation,
-            variable_values=self.root_info.variable_values,
-            context=self.root_info.context,
-            is_awaitable=self.root_info.is_awaitable,
-        )

+ 39 - 30
netbox/utilities/testing/api.py

@@ -1,12 +1,12 @@
 import inspect
 import inspect
 import json
 import json
+import strawberry_django
 
 
 from django.conf import settings
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.urls import reverse
 from django.urls import reverse
 from django.test import override_settings
 from django.test import override_settings
-from graphene.types import Dynamic as GQLDynamic, List as GQLList, Union as GQLUnion, String as GQLString, NonNull as GQLNonNull
 from rest_framework import status
 from rest_framework import status
 from rest_framework.test import APIClient
 from rest_framework.test import APIClient
 
 
@@ -19,7 +19,10 @@ from .base import ModelTestCase
 from .utils import disable_warnings
 from .utils import disable_warnings
 
 
 from ipam.graphql.types import IPAddressFamilyType
 from ipam.graphql.types import IPAddressFamilyType
-
+from strawberry.field import StrawberryField
+from strawberry.lazy_type import LazyType
+from strawberry.type import StrawberryList, StrawberryOptional
+from strawberry.union import StrawberryUnion
 
 
 __all__ = (
 __all__ = (
     'APITestCase',
     'APITestCase',
@@ -447,34 +450,34 @@ class APIViewTestCases:
 
 
             # Compile list of fields to include
             # Compile list of fields to include
             fields_string = ''
             fields_string = ''
-            for field_name, field in type_class._meta.fields.items():
-                is_string_array = False
-                if type(field.type) is GQLList:
-                    if field.type.of_type is GQLString:
-                        is_string_array = True
-                    elif type(field.type.of_type) is GQLNonNull and field.type.of_type.of_type is GQLString:
-                        is_string_array = True
-
-                if type(field) is GQLDynamic:
-                    # Dynamic fields must specify a subselection
-                    fields_string += f'{field_name} {{ id }}\n'
-                # TODO: Improve field detection logic to avoid nested ArrayFields
-                elif field_name == 'extra_choices':
-                    continue
-                elif inspect.isclass(field.type) and issubclass(field.type, GQLUnion):
-                    # Union types dont' have an id or consistent values
+
+            file_fields = (strawberry_django.fields.types.DjangoFileType, strawberry_django.fields.types.DjangoImageType)
+            for field in type_class.__strawberry_definition__.fields:
+                if (
+                    field.type in file_fields or (
+                        type(field.type) is StrawberryOptional and field.type.of_type in file_fields
+                    )
+                ):
+                    # image / file fields nullable or not...
+                    fields_string += f'{field.name} {{ name }}\n'
+                elif type(field.type) is StrawberryList and type(field.type.of_type) is LazyType:
+                    # List of related objects (queryset)
+                    fields_string += f'{field.name} {{ id }}\n'
+                elif type(field.type) is StrawberryList and type(field.type.of_type) is StrawberryUnion:
+                    # this would require a fragment query
                     continue
                     continue
-                elif type(field.type) is GQLList and inspect.isclass(field.type.of_type) and issubclass(field.type.of_type, GQLUnion):
-                    # Union types dont' have an id or consistent values
+                elif type(field.type) is StrawberryUnion:
+                    # this would require a fragment query
                     continue
                     continue
-                elif type(field.type) is GQLList and not is_string_array:
-                    # TODO: Come up with something more elegant
-                    # Temporary hack to support automated testing of reverse generic relations
-                    fields_string += f'{field_name} {{ id }}\n'
+                elif type(field.type) is StrawberryOptional and type(field.type.of_type) is LazyType:
+                    fields_string += f'{field.name} {{ id }}\n'
+                elif hasattr(field, 'is_relation') and field.is_relation:
+                    # Note: StrawberryField types do not have is_relation
+                    fields_string += f'{field.name} {{ id }}\n'
                 elif inspect.isclass(field.type) and issubclass(field.type, IPAddressFamilyType):
                 elif inspect.isclass(field.type) and issubclass(field.type, IPAddressFamilyType):
-                    fields_string += f'{field_name} {{ value, label }}\n'
+                    fields_string += f'{field.name} {{ value, label }}\n'
                 else:
                 else:
-                    fields_string += f'{field_name}\n'
+                    fields_string += f'{field.name}\n'
 
 
             query = f"""
             query = f"""
             {{
             {{
@@ -496,7 +499,10 @@ class APIViewTestCases:
 
 
             # Non-authenticated requests should fail
             # Non-authenticated requests should fail
             with disable_warnings('django.request'):
             with disable_warnings('django.request'):
-                self.assertHttpStatus(self.client.post(url, data={'query': query}), status.HTTP_403_FORBIDDEN)
+                header = {
+                    'HTTP_ACCEPT': 'application/json',
+                }
+                self.assertHttpStatus(self.client.post(url, data={'query': query}, format="json", **header), status.HTTP_403_FORBIDDEN)
 
 
             # Add object-level permission
             # Add object-level permission
             obj_perm = ObjectPermission(
             obj_perm = ObjectPermission(
@@ -507,7 +513,7 @@ class APIViewTestCases:
             obj_perm.users.add(self.user)
             obj_perm.users.add(self.user)
             obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
             obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
 
 
-            response = self.client.post(url, data={'query': query}, **self.header)
+            response = self.client.post(url, data={'query': query}, format="json", **self.header)
             self.assertHttpStatus(response, status.HTTP_200_OK)
             self.assertHttpStatus(response, status.HTTP_200_OK)
             data = json.loads(response.content)
             data = json.loads(response.content)
             self.assertNotIn('errors', data)
             self.assertNotIn('errors', data)
@@ -521,7 +527,10 @@ class APIViewTestCases:
 
 
             # Non-authenticated requests should fail
             # Non-authenticated requests should fail
             with disable_warnings('django.request'):
             with disable_warnings('django.request'):
-                self.assertHttpStatus(self.client.post(url, data={'query': query}), status.HTTP_403_FORBIDDEN)
+                header = {
+                    'HTTP_ACCEPT': 'application/json',
+                }
+                self.assertHttpStatus(self.client.post(url, data={'query': query}, format="json", **header), status.HTTP_403_FORBIDDEN)
 
 
             # Add object-level permission
             # Add object-level permission
             obj_perm = ObjectPermission(
             obj_perm = ObjectPermission(
@@ -532,7 +541,7 @@ class APIViewTestCases:
             obj_perm.users.add(self.user)
             obj_perm.users.add(self.user)
             obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
             obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
 
 
-            response = self.client.post(url, data={'query': query}, **self.header)
+            response = self.client.post(url, data={'query': query}, format="json", **self.header)
             self.assertHttpStatus(response, status.HTTP_200_OK)
             self.assertHttpStatus(response, status.HTTP_200_OK)
             data = json.loads(response.content)
             data = json.loads(response.content)
             self.assertNotIn('errors', data)
             self.assertNotIn('errors', data)

+ 49 - 0
netbox/virtualization/graphql/filters.py

@@ -0,0 +1,49 @@
+import strawberry_django
+
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+from virtualization import filtersets, models
+
+__all__ = (
+    'ClusterFilter',
+    'ClusterGroupFilter',
+    'ClusterTypeFilter',
+    'VirtualMachineFilter',
+    'VMInterfaceFilter',
+    'VirtualDiskFilter',
+)
+
+
+@strawberry_django.filter(models.Cluster, lookups=True)
+@autotype_decorator(filtersets.ClusterFilterSet)
+class ClusterFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ClusterGroup, lookups=True)
+@autotype_decorator(filtersets.ClusterGroupFilterSet)
+class ClusterGroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.ClusterType, lookups=True)
+@autotype_decorator(filtersets.ClusterTypeFilterSet)
+class ClusterTypeFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.VirtualMachine, lookups=True)
+@autotype_decorator(filtersets.VirtualMachineFilterSet)
+class VirtualMachineFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.VMInterface, lookups=True)
+@autotype_decorator(filtersets.VMInterfaceFilterSet)
+class VMInterfaceFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.VirtualDisk, lookups=True)
+@autotype_decorator(filtersets.VirtualDiskFilterSet)
+class VirtualDiskFilter(BaseFilterMixin):
+    pass

+ 36 - 40
netbox/virtualization/graphql/schema.py

@@ -1,44 +1,40 @@
-import graphene
+from typing import List
 
 
-from netbox.graphql.fields import ObjectField, ObjectListField
-from .types import *
-from utilities.graphql_optimizer import gql_query_optimizer
-from virtualization import models
-
-
-class VirtualizationQuery(graphene.ObjectType):
-    cluster = ObjectField(ClusterType)
-    cluster_list = ObjectListField(ClusterType)
-
-    def resolve_cluster_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Cluster.objects.all(), info)
-
-    cluster_group = ObjectField(ClusterGroupType)
-    cluster_group_list = ObjectListField(ClusterGroupType)
-
-    def resolve_cluster_group_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ClusterGroup.objects.all(), info)
+import strawberry
+import strawberry_django
 
 
-    cluster_type = ObjectField(ClusterTypeType)
-    cluster_type_list = ObjectListField(ClusterTypeType)
-
-    def resolve_cluster_type_list(root, info, **kwargs):
-        return gql_query_optimizer(models.ClusterType.objects.all(), info)
-
-    virtual_machine = ObjectField(VirtualMachineType)
-    virtual_machine_list = ObjectListField(VirtualMachineType)
-
-    def resolve_virtual_machine_list(root, info, **kwargs):
-        return gql_query_optimizer(models.VirtualMachine.objects.all(), info)
-
-    vm_interface = ObjectField(VMInterfaceType)
-    vm_interface_list = ObjectListField(VMInterfaceType)
-
-    def resolve_vm_interface_list(root, info, **kwargs):
-        return gql_query_optimizer(models.VMInterface.objects.all(), info)
+from virtualization import models
+from .types import *
 
 
-    virtual_disk = ObjectField(VirtualDiskType)
-    virtual_disk_list = ObjectListField(VirtualDiskType)
 
 
-    def resolve_virtual_disk_list(root, info, **kwargs):
-        return gql_query_optimizer(models.VirtualDisk.objects.all(), info)
+@strawberry.type
+class VirtualizationQuery:
+    @strawberry.field
+    def cluster(self, id: int) -> ClusterType:
+        return models.Cluster.objects.get(pk=id)
+    cluster_list: List[ClusterType] = strawberry_django.field()
+
+    @strawberry.field
+    def cluster_group(self, id: int) -> ClusterGroupType:
+        return models.ClusterGroup.objects.get(pk=id)
+    cluster_group_list: List[ClusterGroupType] = strawberry_django.field()
+
+    @strawberry.field
+    def cluster_type(self, id: int) -> ClusterTypeType:
+        return models.ClusterType.objects.get(pk=id)
+    cluster_type_list: List[ClusterTypeType] = strawberry_django.field()
+
+    @strawberry.field
+    def virtual_machine(self, id: int) -> VirtualMachineType:
+        return models.VirtualMachine.objects.get(pk=id)
+    virtual_machine_list: List[VirtualMachineType] = strawberry_django.field()
+
+    @strawberry.field
+    def vm_interface(self, id: int) -> VMInterfaceType:
+        return models.VMInterface.objects.get(pk=id)
+    vm_interface_list: List[VMInterfaceType] = strawberry_django.field()
+
+    @strawberry.field
+    def virtual_disk(self, id: int) -> VirtualDiskType:
+        return models.VirtualDisk.objects.get(pk=id)
+    virtual_disk_list: List[VirtualDiskType] = strawberry_django.field()

+ 113 - 41
netbox/virtualization/graphql/types.py

@@ -1,8 +1,14 @@
-from dcim.graphql.types import ComponentObjectType
+from typing import Annotated, List
+
+import strawberry
+import strawberry_django
+
 from extras.graphql.mixins import ConfigContextMixin, ContactsMixin
 from extras.graphql.mixins import ConfigContextMixin, ContactsMixin
 from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
 from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
+from netbox.graphql.scalars import BigInt
 from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
 from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
-from virtualization import filtersets, models
+from virtualization import models
+from .filters import *
 
 
 __all__ = (
 __all__ = (
     'ClusterType',
     'ClusterType',
@@ -14,55 +20,121 @@ __all__ = (
 )
 )
 
 
 
 
+@strawberry.type
+class ComponentType(NetBoxObjectType):
+    """
+    Base type for device/VM components
+    """
+    _name: str
+    virtual_machine: Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]
+
+
+@strawberry_django.type(
+    models.Cluster,
+    fields='__all__',
+    filters=ClusterFilter
+)
 class ClusterType(VLANGroupsMixin, NetBoxObjectType):
 class ClusterType(VLANGroupsMixin, NetBoxObjectType):
+    type: Annotated["ClusterTypeType", strawberry.lazy('virtualization.graphql.types')] | None
+    group: Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
 
 
-    class Meta:
-        model = models.Cluster
-        fields = '__all__'
-        filterset_class = filtersets.ClusterFilterSet
+    @strawberry_django.field
+    def virtual_machines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.virtual_machines.all()
 
 
+    @strawberry_django.field
+    def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.devices.all()
 
 
+
+@strawberry_django.type(
+    models.ClusterGroup,
+    fields='__all__',
+    filters=ClusterGroupFilter
+)
 class ClusterGroupType(VLANGroupsMixin, OrganizationalObjectType):
 class ClusterGroupType(VLANGroupsMixin, OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.ClusterGroup
-        fields = '__all__'
-        filterset_class = filtersets.ClusterGroupFilterSet
+    @strawberry_django.field
+    def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.clusters.all()
 
 
 
 
+@strawberry_django.type(
+    models.ClusterType,
+    fields='__all__',
+    filters=ClusterTypeFilter
+)
 class ClusterTypeType(OrganizationalObjectType):
 class ClusterTypeType(OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.ClusterType
-        fields = '__all__'
-        filterset_class = filtersets.ClusterTypeFilterSet
+    @strawberry_django.field
+    def clusters(self) -> List[ClusterType]:
+        return self.clusters.all()
 
 
 
 
+@strawberry_django.type(
+    models.VirtualMachine,
+    fields='__all__',
+    filters=VirtualMachineFilter
+)
 class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType):
 class VirtualMachineType(ConfigContextMixin, ContactsMixin, NetBoxObjectType):
-
-    class Meta:
-        model = models.VirtualMachine
-        fields = '__all__'
-        filterset_class = filtersets.VirtualMachineFilterSet
-
-
-class VMInterfaceType(IPAddressesMixin, ComponentObjectType):
-
-    class Meta:
-        model = models.VMInterface
-        fields = '__all__'
-        filterset_class = filtersets.VMInterfaceFilterSet
-
-    def resolve_mode(self, info):
-        return self.mode or None
-
-
-class VirtualDiskType(ComponentObjectType):
-
-    class Meta:
-        model = models.VirtualDisk
-        fields = '__all__'
-        filterset_class = filtersets.VirtualDiskFilterSet
-
-    def resolve_mode(self, info):
-        return self.mode or None
+    _name: str
+    interface_count: BigInt
+    virtual_disk_count: BigInt
+    interface_count: BigInt
+    config_template: Annotated["ConfigTemplateType", strawberry.lazy('extras.graphql.types')] | None
+    site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
+    cluster: Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')] | None
+    device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    platform: Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')] | None
+    role: Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')] | None
+    primary_ip4: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
+    primary_ip6: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
+
+    @strawberry_django.field
+    def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfaces.all()
+
+    @strawberry_django.field
+    def services(self) -> List[Annotated["ServiceType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.services.all()
+
+    @strawberry_django.field
+    def virtualdisks(self) -> List[Annotated["VirtualDiskType", strawberry.lazy('virtualization.graphql.types')]]:
+        return self.virtualdisks.all()
+
+
+@strawberry_django.type(
+    models.VMInterface,
+    fields='__all__',
+    filters=VMInterfaceFilter
+)
+class VMInterfaceType(IPAddressesMixin, ComponentType):
+    mac_address: str | None
+    parent: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None
+    bridge: Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')] | None
+    untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
+    vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
+
+    @strawberry_django.field
+    def tagged_vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.tagged_vlans.all()
+
+    @strawberry_django.field
+    def bridge_interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.bridge_interfaces.all()
+
+    @strawberry_django.field
+    def child_interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.child_interfaces.all()
+
+
+@strawberry_django.type(
+    models.VirtualDisk,
+    fields='__all__',
+    filters=VirtualDiskFilter
+)
+class VirtualDiskType(ComponentType):
+    pass

+ 77 - 0
netbox/vpn/graphql/filters.py

@@ -0,0 +1,77 @@
+import strawberry_django
+
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+from vpn import filtersets, models
+
+__all__ = (
+    'TunnelGroupFilter',
+    'TunnelTerminationFilter',
+    'TunnelFilter',
+    'IKEProposalFilter',
+    'IKEPolicyFilter',
+    'IPSecProposalFilter',
+    'IPSecPolicyFilter',
+    'IPSecProfileFilter',
+    'L2VPNFilter',
+    'L2VPNTerminationFilter',
+)
+
+
+@strawberry_django.filter(models.TunnelGroup, lookups=True)
+@autotype_decorator(filtersets.TunnelGroupFilterSet)
+class TunnelGroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.TunnelTermination, lookups=True)
+@autotype_decorator(filtersets.TunnelTerminationFilterSet)
+class TunnelTerminationFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.Tunnel, lookups=True)
+@autotype_decorator(filtersets.TunnelFilterSet)
+class TunnelFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.IKEProposal, lookups=True)
+@autotype_decorator(filtersets.IKEProposalFilterSet)
+class IKEProposalFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.IKEPolicy, lookups=True)
+@autotype_decorator(filtersets.IKEPolicyFilterSet)
+class IKEPolicyFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.IPSecProposal, lookups=True)
+@autotype_decorator(filtersets.IPSecProposalFilterSet)
+class IPSecProposalFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.IPSecPolicy, lookups=True)
+@autotype_decorator(filtersets.IPSecPolicyFilterSet)
+class IPSecPolicyFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.IPSecProfile, lookups=True)
+@autotype_decorator(filtersets.IPSecProfileFilterSet)
+class IPSecProfileFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.L2VPN, lookups=True)
+@autotype_decorator(filtersets.L2VPNFilterSet)
+class L2VPNFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.L2VPNTermination, lookups=True)
+@autotype_decorator(filtersets.L2VPNTerminationFilterSet)
+class L2VPNTerminationFilter(BaseFilterMixin):
+    pass

+ 0 - 30
netbox/vpn/graphql/gfk_mixins.py

@@ -1,30 +0,0 @@
-import graphene
-
-from dcim.graphql.types import InterfaceType
-from dcim.models import Interface
-from ipam.graphql.types import VLANType
-from ipam.models import VLAN
-from virtualization.graphql.types import VMInterfaceType
-from virtualization.models import VMInterface
-
-__all__ = (
-    'L2VPNAssignmentType',
-)
-
-
-class L2VPNAssignmentType(graphene.Union):
-    class Meta:
-        types = (
-            InterfaceType,
-            VLANType,
-            VMInterfaceType,
-        )
-
-    @classmethod
-    def resolve_type(cls, instance, info):
-        if type(instance) is Interface:
-            return InterfaceType
-        if type(instance) is VLAN:
-            return VLANType
-        if type(instance) is VMInterface:
-            return VMInterfaceType

+ 55 - 64
netbox/vpn/graphql/schema.py

@@ -1,69 +1,60 @@
-import graphene
+from typing import List
+
+import strawberry
+import strawberry_django
 
 
-from netbox.graphql.fields import ObjectField, ObjectListField
-from utilities.graphql_optimizer import gql_query_optimizer
 from vpn import models
 from vpn import models
 from .types import *
 from .types import *
 
 
 
 
-class VPNQuery(graphene.ObjectType):
-
-    ike_policy = ObjectField(IKEPolicyType)
-    ike_policy_list = ObjectListField(IKEPolicyType)
-
-    def resolve_ike_policy_list(root, info, **kwargs):
-        return gql_query_optimizer(models.IKEPolicy.objects.all(), info)
-
-    ike_proposal = ObjectField(IKEProposalType)
-    ike_proposal_list = ObjectListField(IKEProposalType)
-
-    def resolve_ike_proposal_list(root, info, **kwargs):
-        return gql_query_optimizer(models.IKEProposal.objects.all(), info)
-
-    ipsec_policy = ObjectField(IPSecPolicyType)
-    ipsec_policy_list = ObjectListField(IPSecPolicyType)
-
-    def resolve_ipsec_policy_list(root, info, **kwargs):
-        return gql_query_optimizer(models.IPSecPolicy.objects.all(), info)
-
-    ipsec_profile = ObjectField(IPSecProfileType)
-    ipsec_profile_list = ObjectListField(IPSecProfileType)
-
-    def resolve_ipsec_profile_list(root, info, **kwargs):
-        return gql_query_optimizer(models.IPSecProfile.objects.all(), info)
-
-    ipsec_proposal = ObjectField(IPSecProposalType)
-    ipsec_proposal_list = ObjectListField(IPSecProposalType)
-
-    def resolve_ipsec_proposal_list(root, info, **kwargs):
-        return gql_query_optimizer(models.IPSecProposal.objects.all(), info)
-
-    l2vpn = ObjectField(L2VPNType)
-    l2vpn_list = ObjectListField(L2VPNType)
-
-    def resolve_l2vpn_list(root, info, **kwargs):
-        return gql_query_optimizer(models.L2VPN.objects.all(), info)
-
-    l2vpn_termination = ObjectField(L2VPNTerminationType)
-    l2vpn_termination_list = ObjectListField(L2VPNTerminationType)
-
-    def resolve_l2vpn_termination_list(root, info, **kwargs):
-        return gql_query_optimizer(models.L2VPNTermination.objects.all(), info)
-
-    tunnel = ObjectField(TunnelType)
-    tunnel_list = ObjectListField(TunnelType)
-
-    def resolve_tunnel_list(root, info, **kwargs):
-        return gql_query_optimizer(models.Tunnel.objects.all(), info)
-
-    tunnel_group = ObjectField(TunnelGroupType)
-    tunnel_group_list = ObjectListField(TunnelGroupType)
-
-    def resolve_tunnel_group_list(root, info, **kwargs):
-        return gql_query_optimizer(models.TunnelGroup.objects.all(), info)
-
-    tunnel_termination = ObjectField(TunnelTerminationType)
-    tunnel_termination_list = ObjectListField(TunnelTerminationType)
-
-    def resolve_tunnel_termination_list(root, info, **kwargs):
-        return gql_query_optimizer(models.TunnelTermination.objects.all(), info)
+@strawberry.type
+class VPNQuery:
+    @strawberry.field
+    def ike_policy(self, id: int) -> IKEPolicyType:
+        return models.IKEPolicy.objects.get(pk=id)
+    ike_policy_list: List[IKEPolicyType] = strawberry_django.field()
+
+    @strawberry.field
+    def ike_proposal(self, id: int) -> IKEProposalType:
+        return models.IKEProposal.objects.get(pk=id)
+    ike_proposal_list: List[IKEProposalType] = strawberry_django.field()
+
+    @strawberry.field
+    def ipsec_policy(self, id: int) -> IPSecPolicyType:
+        return models.IPSecPolicy.objects.get(pk=id)
+    ipsec_policy_list: List[IPSecPolicyType] = strawberry_django.field()
+
+    @strawberry.field
+    def ipsec_profile(self, id: int) -> IPSecProfileType:
+        return models.IPSecProfile.objects.get(pk=id)
+    ipsec_profile_list: List[IPSecProfileType] = strawberry_django.field()
+
+    @strawberry.field
+    def ipsec_proposal(self, id: int) -> IPSecProposalType:
+        return models.IPSecProposal.objects.get(pk=id)
+    ipsec_proposal_list: List[IPSecProposalType] = strawberry_django.field()
+
+    @strawberry.field
+    def l2vpn(self, id: int) -> L2VPNType:
+        return models.L2VPN.objects.get(pk=id)
+    l2vpn_list: List[L2VPNType] = strawberry_django.field()
+
+    @strawberry.field
+    def l2vpn_termination(self, id: int) -> L2VPNTerminationType:
+        return models.L2VPNTermination.objects.get(pk=id)
+    l2vpn_termination_list: List[L2VPNTerminationType] = strawberry_django.field()
+
+    @strawberry.field
+    def tunnel(self, id: int) -> TunnelType:
+        return models.Tunnel.objects.get(pk=id)
+    tunnel_list: List[TunnelType] = strawberry_django.field()
+
+    @strawberry.field
+    def tunnel_group(self, id: int) -> TunnelGroupType:
+        return models.TunnelGroup.objects.get(pk=id)
+    tunnel_group_list: List[TunnelGroupType] = strawberry_django.field()
+
+    @strawberry.field
+    def tunnel_termination(self, id: int) -> TunnelTerminationType:
+        return models.TunnelTermination.objects.get(pk=id)
+    tunnel_termination_list: List[TunnelTerminationType] = strawberry_django.field()

+ 115 - 45
netbox/vpn/graphql/types.py

@@ -1,8 +1,12 @@
-import graphene
+from typing import Annotated, List, Union
+
+import strawberry
+import strawberry_django
 
 
 from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
 from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
 from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
 from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
-from vpn import filtersets, models
+from vpn import models
+from .filters import *
 
 
 __all__ = (
 __all__ = (
     'IKEPolicyType',
     'IKEPolicyType',
@@ -18,81 +22,147 @@ __all__ = (
 )
 )
 
 
 
 
+@strawberry_django.type(
+    models.TunnelGroup,
+    fields='__all__',
+    filters=TunnelGroupFilter
+)
 class TunnelGroupType(OrganizationalObjectType):
 class TunnelGroupType(OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.TunnelGroup
-        fields = '__all__'
-        filterset_class = filtersets.TunnelGroupFilterSet
+    @strawberry_django.field
+    def tunnels(self) -> List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.tunnels.all()
 
 
 
 
+@strawberry_django.type(
+    models.TunnelTermination,
+    fields='__all__',
+    filters=TunnelTerminationFilter
+)
 class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
 class TunnelTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
-
-    class Meta:
-        model = models.TunnelTermination
-        fields = '__all__'
-        filterset_class = filtersets.TunnelTerminationFilterSet
+    tunnel: Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]
+    termination_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
+    outside_ip: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
 
 
 
 
+@strawberry_django.type(
+    models.Tunnel,
+    fields='__all__',
+    filters=TunnelFilter
+)
 class TunnelType(NetBoxObjectType):
 class TunnelType(NetBoxObjectType):
+    group: Annotated["TunnelGroupType", strawberry.lazy('vpn.graphql.types')] | None
+    ipsec_profile: Annotated["IPSecProfileType", strawberry.lazy('vpn.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
-    class Meta:
-        model = models.Tunnel
-        fields = '__all__'
-        filterset_class = filtersets.TunnelFilterSet
+    @strawberry_django.field
+    def terminations(self) -> List[Annotated["TunnelTerminationType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.terminations.all()
 
 
 
 
+@strawberry_django.type(
+    models.IKEProposal,
+    fields='__all__',
+    filters=IKEProposalFilter
+)
 class IKEProposalType(OrganizationalObjectType):
 class IKEProposalType(OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.IKEProposal
-        fields = '__all__'
-        filterset_class = filtersets.IKEProposalFilterSet
+    @strawberry_django.field
+    def ike_policies(self) -> List[Annotated["IKEPolicyType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.ike_policies.all()
 
 
 
 
+@strawberry_django.type(
+    models.IKEPolicy,
+    fields='__all__',
+    filters=IKEPolicyFilter
+)
 class IKEPolicyType(OrganizationalObjectType):
 class IKEPolicyType(OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.IKEPolicy
-        fields = '__all__'
-        filterset_class = filtersets.IKEPolicyFilterSet
+    @strawberry_django.field
+    def proposals(self) -> List[Annotated["IKEProposalType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.proposals.all()
 
 
+    @strawberry_django.field
+    def ipsec_profiles(self) -> List[Annotated["IPSecProposalType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.ipsec_profiles.all()
 
 
+
+@strawberry_django.type(
+    models.IPSecProposal,
+    fields='__all__',
+    filters=IPSecProposalFilter
+)
 class IPSecProposalType(OrganizationalObjectType):
 class IPSecProposalType(OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.IPSecProposal
-        fields = '__all__'
-        filterset_class = filtersets.IPSecProposalFilterSet
+    @strawberry_django.field
+    def ipsec_policies(self) -> List[Annotated["IPSecPolicyType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.ipsec_policies.all()
 
 
 
 
+@strawberry_django.type(
+    models.IPSecPolicy,
+    fields='__all__',
+    filters=IPSecPolicyFilter
+)
 class IPSecPolicyType(OrganizationalObjectType):
 class IPSecPolicyType(OrganizationalObjectType):
 
 
-    class Meta:
-        model = models.IPSecPolicy
-        fields = '__all__'
-        filterset_class = filtersets.IPSecPolicyFilterSet
+    @strawberry_django.field
+    def proposals(self) -> List[Annotated["IPSecProposalType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.proposals.all()
+
+    @strawberry_django.field
+    def ipsec_profiles(self) -> List[Annotated["IPSecProfileType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.ipsec_profiles.all()
 
 
 
 
+@strawberry_django.type(
+    models.IPSecProfile,
+    fields='__all__',
+    filters=IPSecProfileFilter
+)
 class IPSecProfileType(OrganizationalObjectType):
 class IPSecProfileType(OrganizationalObjectType):
+    ike_policy: Annotated["IKEPolicyType", strawberry.lazy('vpn.graphql.types')]
+    ipsec_policy: Annotated["IPSecPolicyType", strawberry.lazy('vpn.graphql.types')]
 
 
-    class Meta:
-        model = models.IPSecProfile
-        fields = '__all__'
-        filterset_class = filtersets.IPSecProfileFilterSet
+    @strawberry_django.field
+    def tunnels(self) -> List[Annotated["TunnelType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.tunnels.all()
 
 
 
 
+@strawberry_django.type(
+    models.L2VPN,
+    fields='__all__',
+    filters=L2VPNFilter
+)
 class L2VPNType(ContactsMixin, NetBoxObjectType):
 class L2VPNType(ContactsMixin, NetBoxObjectType):
-    class Meta:
-        model = models.L2VPN
-        fields = '__all__'
-        filtersets_class = filtersets.L2VPNFilterSet
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
+    @strawberry_django.field
+    def export_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.export_targets.all()
 
 
-class L2VPNTerminationType(NetBoxObjectType):
-    assigned_object = graphene.Field('vpn.graphql.gfk_mixins.L2VPNAssignmentType')
+    @strawberry_django.field
+    def terminations(self) -> List[Annotated["L2VPNTerminationType", strawberry.lazy('vpn.graphql.types')]]:
+        return self.terminations.all()
 
 
-    class Meta:
-        model = models.L2VPNTermination
-        exclude = ('assigned_object_type', 'assigned_object_id')
-        filtersets_class = filtersets.L2VPNTerminationFilterSet
+    @strawberry_django.field
+    def import_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
+        return self.import_targets.all()
+
+
+@strawberry_django.type(
+    models.L2VPNTermination,
+    exclude=('assigned_object_type', 'assigned_object_id'),
+    filters=L2VPNTerminationFilter
+)
+class L2VPNTerminationType(NetBoxObjectType):
+    l2vpn: Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]
+
+    @strawberry_django.field
+    def assigned_object(self) -> Annotated[Union[
+        Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
+        Annotated["VLANType", strawberry.lazy('ipam.graphql.types')],
+        Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
+    ], strawberry.union("L2VPNAssignmentType")]:
+        return self.assigned_object

+ 28 - 0
netbox/wireless/graphql/filters.py

@@ -0,0 +1,28 @@
+import strawberry_django
+
+from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
+from wireless import filtersets, models
+
+__all__ = (
+    'WirelessLANGroupFilter',
+    'WirelessLANFilter',
+    'WirelessLinkFilter',
+)
+
+
+@strawberry_django.filter(models.WirelessLANGroup, lookups=True)
+@autotype_decorator(filtersets.WirelessLANGroupFilterSet)
+class WirelessLANGroupFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.WirelessLAN, lookups=True)
+@autotype_decorator(filtersets.WirelessLANFilterSet)
+class WirelessLANFilter(BaseFilterMixin):
+    pass
+
+
+@strawberry_django.filter(models.WirelessLink, lookups=True)
+@autotype_decorator(filtersets.WirelessLinkFilterSet)
+class WirelessLinkFilter(BaseFilterMixin):
+    pass

+ 19 - 20
netbox/wireless/graphql/schema.py

@@ -1,26 +1,25 @@
-import graphene
-
-from netbox.graphql.fields import ObjectField, ObjectListField
-from .types import *
-from utilities.graphql_optimizer import gql_query_optimizer
-from wireless import models
+from typing import List
 
 
+import strawberry
+import strawberry_django
 
 
-class WirelessQuery(graphene.ObjectType):
-    wireless_lan = ObjectField(WirelessLANType)
-    wireless_lan_list = ObjectListField(WirelessLANType)
-
-    def resolve_wireless_lan_list(root, info, **kwargs):
-        return gql_query_optimizer(models.WirelessLAN.objects.all(), info)
+from wireless import models
+from .types import *
 
 
-    wireless_lan_group = ObjectField(WirelessLANGroupType)
-    wireless_lan_group_list = ObjectListField(WirelessLANGroupType)
 
 
-    def resolve_wireless_lan_group_list(root, info, **kwargs):
-        return gql_query_optimizer(models.WirelessLANGroup.objects.all(), info)
+@strawberry.type
+class WirelessQuery:
+    @strawberry.field
+    def wireless_lan(self, id: int) -> WirelessLANType:
+        return models.WirelessLAN.objects.get(pk=id)
+    wireless_lan_list: List[WirelessLANType] = strawberry_django.field()
 
 
-    wireless_link = ObjectField(WirelessLinkType)
-    wireless_link_list = ObjectListField(WirelessLinkType)
+    @strawberry.field
+    def wireless_lan_group(self, id: int) -> WirelessLANGroupType:
+        return models.WirelessLANGroup.objects.get(pk=id)
+    wireless_lan_group_list: List[WirelessLANGroupType] = strawberry_django.field()
 
 
-    def resolve_wireless_link_list(root, info, **kwargs):
-        return gql_query_optimizer(models.WirelessLink.objects.all(), info)
+    @strawberry.field
+    def wireless_link(self, id: int) -> WirelessLinkType:
+        return models.WirelessLink.objects.get(pk=id)
+    wireless_link_list: List[WirelessLinkType] = strawberry_django.field()

+ 37 - 26
netbox/wireless/graphql/types.py

@@ -1,5 +1,11 @@
-from wireless import filtersets, models
+from typing import Annotated, List
+
+import strawberry
+import strawberry_django
+
 from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
 from netbox.graphql.types import OrganizationalObjectType, NetBoxObjectType
+from wireless import models
+from .filters import *
 
 
 __all__ = (
 __all__ = (
     'WirelessLANType',
     'WirelessLANType',
@@ -8,37 +14,42 @@ __all__ = (
 )
 )
 
 
 
 
+@strawberry_django.type(
+    models.WirelessLANGroup,
+    fields='__all__',
+    filters=WirelessLANGroupFilter
+)
 class WirelessLANGroupType(OrganizationalObjectType):
 class WirelessLANGroupType(OrganizationalObjectType):
+    parent: Annotated["WirelessLANGroupType", strawberry.lazy('wireless.graphql.types')] | None
 
 
-    class Meta:
-        model = models.WirelessLANGroup
-        fields = '__all__'
-        filterset_class = filtersets.WirelessLANGroupFilterSet
+    @strawberry_django.field
+    def wireless_lans(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
+        return self.wireless_lans.all()
 
 
 
 
+@strawberry_django.type(
+    models.WirelessLAN,
+    fields='__all__',
+    filters=WirelessLANFilter
+)
 class WirelessLANType(NetBoxObjectType):
 class WirelessLANType(NetBoxObjectType):
+    group: Annotated["WirelessLANGroupType", strawberry.lazy('wireless.graphql.types')] | None
+    vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
 
 
-    class Meta:
-        model = models.WirelessLAN
-        fields = '__all__'
-        filterset_class = filtersets.WirelessLANFilterSet
-
-    def resolve_auth_type(self, info):
-        return self.auth_type or None
-
-    def resolve_auth_cipher(self, info):
-        return self.auth_cipher or None
+    @strawberry_django.field
+    def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
+        return self.interfaces.all()
 
 
 
 
+@strawberry_django.type(
+    models.WirelessLink,
+    fields='__all__',
+    filters=WirelessLinkFilter
+)
 class WirelessLinkType(NetBoxObjectType):
 class WirelessLinkType(NetBoxObjectType):
-
-    class Meta:
-        model = models.WirelessLink
-        fields = '__all__'
-        filterset_class = filtersets.WirelessLinkFilterSet
-
-    def resolve_auth_type(self, info):
-        return self.auth_type or None
-
-    def resolve_auth_cipher(self, info):
-        return self.auth_cipher or None
+    interface_a: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
+    interface_b: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]
+    tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
+    _interface_a_device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
+    _interface_b_device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None

+ 2 - 2
requirements.txt

@@ -2,7 +2,6 @@ Django==5.0.3
 django-cors-headers==4.3.1
 django-cors-headers==4.3.1
 django-debug-toolbar==4.3.0
 django-debug-toolbar==4.3.0
 django-filter==24.1
 django-filter==24.1
-django-graphiql-debug-toolbar==0.2.0
 django-htmx==1.17.3
 django-htmx==1.17.3
 django-mptt==0.14.0
 django-mptt==0.14.0
 django-pglocks==1.0.4
 django-pglocks==1.0.4
@@ -17,7 +16,6 @@ djangorestframework==3.14.0
 drf-spectacular==0.27.1
 drf-spectacular==0.27.1
 drf-spectacular-sidecar==2024.3.4
 drf-spectacular-sidecar==2024.3.4
 feedparser==6.0.11
 feedparser==6.0.11
-graphene-django==3.0.0
 gunicorn==21.2.0
 gunicorn==21.2.0
 Jinja2==3.1.3
 Jinja2==3.1.3
 Markdown==3.5.2
 Markdown==3.5.2
@@ -31,6 +29,8 @@ PyYAML==6.0.1
 requests==2.31.0
 requests==2.31.0
 social-auth-app-django==5.4.0
 social-auth-app-django==5.4.0
 social-auth-core[openidconnect]==4.5.3
 social-auth-core[openidconnect]==4.5.3
+strawberry-graphql==0.221.1
+strawberry-graphql-django==0.35.1
 svgwrite==1.4.3
 svgwrite==1.4.3
 tablib==3.5.0
 tablib==3.5.0
 tzdata==2024.1
 tzdata==2024.1

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff