django-cfg 1.5.8__py3-none-any.whl → 1.5.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (119) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/commands/serializers.py +152 -0
  3. django_cfg/apps/api/commands/views.py +32 -0
  4. django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
  5. django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
  6. django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
  7. django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
  8. django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
  9. django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
  10. django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
  11. django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
  12. django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
  13. django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
  14. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +5 -5
  15. django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
  16. django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
  17. django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
  18. django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
  19. django_cfg/apps/integrations/grpc/admin/config.py +113 -9
  20. django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
  21. django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
  22. django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
  23. django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
  24. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
  25. django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
  26. django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
  27. django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
  28. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +130 -0
  29. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +171 -96
  30. django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
  31. django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
  32. django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
  33. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
  34. django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
  35. django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
  36. django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
  37. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
  38. django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
  39. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
  40. django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
  41. django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
  42. django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
  43. django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
  44. django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
  45. django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
  46. django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
  47. django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
  48. django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
  49. django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
  50. django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
  51. django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
  52. django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
  53. django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
  54. django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
  55. django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
  56. django_cfg/apps/integrations/grpc/urls.py +8 -0
  57. django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
  58. django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
  59. django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
  60. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +177 -0
  61. django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
  62. django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
  63. django_cfg/apps/integrations/grpc/views/charts.py +21 -14
  64. django_cfg/apps/integrations/grpc/views/config.py +8 -6
  65. django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
  66. django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
  67. django_cfg/apps/integrations/grpc/views/services.py +30 -21
  68. django_cfg/apps/integrations/grpc/views/testing.py +45 -43
  69. django_cfg/apps/integrations/rq/views/jobs.py +19 -9
  70. django_cfg/apps/integrations/rq/views/schedule.py +7 -3
  71. django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
  72. django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
  73. django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
  74. django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
  75. django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
  76. django_cfg/config.py +33 -0
  77. django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
  78. django_cfg/management/commands/check_endpoints.py +2 -2
  79. django_cfg/management/commands/check_settings.py +3 -10
  80. django_cfg/management/commands/clear_constance.py +3 -10
  81. django_cfg/management/commands/create_token.py +4 -11
  82. django_cfg/management/commands/list_urls.py +4 -10
  83. django_cfg/management/commands/migrate_all.py +18 -12
  84. django_cfg/management/commands/migrator.py +4 -11
  85. django_cfg/management/commands/script.py +4 -10
  86. django_cfg/management/commands/show_config.py +8 -16
  87. django_cfg/management/commands/show_urls.py +5 -11
  88. django_cfg/management/commands/superuser.py +4 -11
  89. django_cfg/management/commands/tree.py +5 -10
  90. django_cfg/management/utils/README.md +402 -0
  91. django_cfg/management/utils/__init__.py +29 -0
  92. django_cfg/management/utils/mixins.py +176 -0
  93. django_cfg/middleware/pagination.py +53 -54
  94. django_cfg/models/api/grpc/__init__.py +15 -21
  95. django_cfg/models/api/grpc/config.py +155 -73
  96. django_cfg/models/ngrok/config.py +7 -6
  97. django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
  98. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
  99. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
  100. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
  101. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
  102. django_cfg/modules/django_client/core/parser/base.py +114 -30
  103. django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
  104. django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
  105. django_cfg/modules/django_email/management/commands/test_email.py +4 -10
  106. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
  107. django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
  108. django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
  109. django_cfg/modules/django_unfold/navigation.py +6 -18
  110. django_cfg/pyproject.toml +1 -1
  111. django_cfg/registry/modules.py +1 -4
  112. django_cfg/requirements.txt +52 -0
  113. django_cfg/static/frontend/admin.zip +0 -0
  114. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/METADATA +1 -1
  115. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/RECORD +118 -97
  116. django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
  117. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/WHEEL +0 -0
  118. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/entry_points.txt +0 -0
  119. {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,255 @@
1
+ """
2
+ gRPC API Keys ViewSet.
3
+
4
+ Provides REST API endpoints for viewing API keys.
5
+ Create/Update/Delete operations handled through Django Admin.
6
+ """
7
+
8
+ from django.db.models import Count, Q, Sum
9
+ from django.utils import timezone
10
+ from django_cfg.mixins import AdminAPIMixin
11
+ from django_cfg.modules.django_logging import get_logger
12
+ from drf_spectacular.types import OpenApiTypes
13
+ from drf_spectacular.utils import OpenApiParameter, extend_schema
14
+ from rest_framework import status, viewsets
15
+ from rest_framework.decorators import action
16
+ from rest_framework.response import Response
17
+
18
+ from ..models import GrpcApiKey
19
+ from ..serializers.api_keys import (
20
+ ApiKeyListSerializer,
21
+ ApiKeySerializer,
22
+ ApiKeyStatsSerializer,
23
+ )
24
+
25
+ logger = get_logger("grpc.api_keys")
26
+
27
+
28
+ class GRPCApiKeyViewSet(AdminAPIMixin, viewsets.GenericViewSet):
29
+ """
30
+ ViewSet for gRPC API Keys (Read-Only).
31
+
32
+ Provides listing and statistics for API keys.
33
+ Create/Update/Delete operations are handled through Django Admin.
34
+
35
+ Requires admin authentication (JWT, Session, or Basic Auth).
36
+ """
37
+
38
+ queryset = GrpcApiKey.objects.none()
39
+ serializer_class = ApiKeySerializer
40
+
41
+ @extend_schema(
42
+ tags=["gRPC API Keys"],
43
+ summary="List API keys",
44
+ description="Returns a list of all API keys with their details. Uses standard DRF pagination.",
45
+ parameters=[
46
+ OpenApiParameter(
47
+ name="is_active",
48
+ type=OpenApiTypes.BOOL,
49
+ location=OpenApiParameter.QUERY,
50
+ description="Filter by active status",
51
+ required=False,
52
+ ),
53
+ OpenApiParameter(
54
+ name="key_type",
55
+ type=OpenApiTypes.STR,
56
+ location=OpenApiParameter.QUERY,
57
+ description="Filter by key type (service, cli, webhook, internal, development)",
58
+ required=False,
59
+ ),
60
+ OpenApiParameter(
61
+ name="user_id",
62
+ type=OpenApiTypes.INT,
63
+ location=OpenApiParameter.QUERY,
64
+ description="Filter by user ID",
65
+ required=False,
66
+ ),
67
+ ],
68
+ responses={
69
+ 200: ApiKeySerializer(many=True),
70
+ 400: {"description": "Invalid parameters"},
71
+ },
72
+ )
73
+ def list(self, request):
74
+ """List all API keys."""
75
+ try:
76
+ # Build queryset with filters
77
+ queryset = GrpcApiKey.objects.select_related("user", "created_by").all()
78
+
79
+ # Apply filters
80
+ is_active = request.GET.get("is_active")
81
+ if is_active is not None:
82
+ is_active_bool = is_active.lower() in ("true", "1", "yes")
83
+ queryset = queryset.filter(is_active=is_active_bool)
84
+
85
+ key_type = request.GET.get("key_type")
86
+ if key_type:
87
+ queryset = queryset.filter(key_type=key_type)
88
+
89
+ user_id = request.GET.get("user_id")
90
+ if user_id:
91
+ queryset = queryset.filter(user_id=user_id)
92
+
93
+ # Order by most recently created
94
+ queryset = queryset.order_by("-created_at")
95
+
96
+ # Paginate
97
+ page = self.paginate_queryset(queryset)
98
+ if page is not None:
99
+ # Serialize paginated data
100
+ results = []
101
+ for key in page:
102
+ results.append({
103
+ "id": key.id,
104
+ "name": key.name,
105
+ "key_type": key.key_type,
106
+ "masked_key": key.masked_key,
107
+ "is_active": key.is_active,
108
+ "is_valid": key.is_valid,
109
+ "user_id": key.user.id,
110
+ "username": key.user.username,
111
+ "user_email": key.user.email,
112
+ "request_count": key.request_count,
113
+ "last_used_at": key.last_used_at,
114
+ "expires_at": key.expires_at,
115
+ "created_at": key.created_at,
116
+ "created_by": key.created_by.username if key.created_by else None,
117
+ })
118
+ return self.get_paginated_response(results)
119
+
120
+ # No pagination fallback
121
+ results = []
122
+ for key in queryset[:100]:
123
+ results.append({
124
+ "id": key.id,
125
+ "name": key.name,
126
+ "key_type": key.key_type,
127
+ "masked_key": key.masked_key,
128
+ "is_active": key.is_active,
129
+ "is_valid": key.is_valid,
130
+ "user_id": key.user.id,
131
+ "username": key.user.username,
132
+ "user_email": key.user.email,
133
+ "request_count": key.request_count,
134
+ "last_used_at": key.last_used_at,
135
+ "expires_at": key.expires_at,
136
+ "created_at": key.created_at,
137
+ "created_by": key.created_by.username if key.created_by else None,
138
+ })
139
+
140
+ return Response({"results": results, "count": len(results)})
141
+
142
+ except ValueError as e:
143
+ logger.warning(f"API keys list validation error: {e}")
144
+ return Response(
145
+ {"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
146
+ )
147
+ except Exception as e:
148
+ logger.error(f"API keys list error: {e}", exc_info=True)
149
+ return Response(
150
+ {"error": "Internal server error"},
151
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
152
+ )
153
+
154
+ @extend_schema(
155
+ tags=["gRPC API Keys"],
156
+ summary="Get API key details",
157
+ description="Returns detailed information about a specific API key.",
158
+ responses={
159
+ 200: ApiKeySerializer,
160
+ 404: {"description": "API key not found"},
161
+ },
162
+ )
163
+ def retrieve(self, request, pk=None):
164
+ """Get details of a specific API key."""
165
+ try:
166
+ key = GrpcApiKey.objects.select_related("user", "created_by").get(pk=pk)
167
+
168
+ data = {
169
+ "id": key.id,
170
+ "name": key.name,
171
+ "key_type": key.key_type,
172
+ "masked_key": key.masked_key,
173
+ "is_active": key.is_active,
174
+ "is_valid": key.is_valid,
175
+ "user_id": key.user.id,
176
+ "username": key.user.username,
177
+ "user_email": key.user.email,
178
+ "request_count": key.request_count,
179
+ "last_used_at": key.last_used_at,
180
+ "expires_at": key.expires_at,
181
+ "created_at": key.created_at,
182
+ "created_by": key.created_by.username if key.created_by else None,
183
+ }
184
+
185
+ serializer = ApiKeySerializer(data=data)
186
+ serializer.is_valid(raise_exception=True)
187
+ return Response(serializer.data)
188
+
189
+ except GrpcApiKey.DoesNotExist:
190
+ return Response(
191
+ {"error": "API key not found"},
192
+ status=status.HTTP_404_NOT_FOUND,
193
+ )
194
+ except Exception as e:
195
+ logger.error(f"API key retrieve error: {e}", exc_info=True)
196
+ return Response(
197
+ {"error": "Internal server error"},
198
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
199
+ )
200
+
201
+ @extend_schema(
202
+ tags=["gRPC API Keys"],
203
+ summary="Get API keys statistics",
204
+ description="Returns overall statistics about API keys usage.",
205
+ responses={
206
+ 200: ApiKeyStatsSerializer,
207
+ },
208
+ )
209
+ @action(detail=False, methods=["get"], url_path="stats")
210
+ def stats(self, request):
211
+ """Get API keys statistics."""
212
+ try:
213
+ # Total keys
214
+ total_keys = GrpcApiKey.objects.count()
215
+
216
+ # Active keys
217
+ active_keys = GrpcApiKey.objects.filter(is_active=True).count()
218
+
219
+ # Expired keys
220
+ expired_keys = GrpcApiKey.objects.filter(
221
+ Q(is_active=True) & Q(expires_at__lt=timezone.now())
222
+ ).count()
223
+
224
+ # Total requests across all keys
225
+ total_requests = GrpcApiKey.objects.aggregate(
226
+ total=Sum("request_count")
227
+ )["total"] or 0
228
+
229
+ # Keys by type
230
+ keys_by_type_qs = GrpcApiKey.objects.values("key_type").annotate(
231
+ count=Count("id")
232
+ )
233
+ keys_by_type = {item["key_type"]: item["count"] for item in keys_by_type_qs}
234
+
235
+ stats_data = {
236
+ "total_keys": total_keys,
237
+ "active_keys": active_keys,
238
+ "expired_keys": expired_keys,
239
+ "total_requests": total_requests,
240
+ "keys_by_type": keys_by_type,
241
+ }
242
+
243
+ serializer = ApiKeyStatsSerializer(data=stats_data)
244
+ serializer.is_valid(raise_exception=True)
245
+ return Response(serializer.data)
246
+
247
+ except Exception as e:
248
+ logger.error(f"API keys stats error: {e}", exc_info=True)
249
+ return Response(
250
+ {"error": "Internal server error"},
251
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
252
+ )
253
+
254
+
255
+ __all__ = ["GRPCApiKeyViewSet"]
@@ -72,8 +72,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
72
72
  try:
73
73
  hours = self._validate_hours(request.GET.get("hours", "24"))
74
74
  data = ChartGeneratorService.generate_server_uptime_data(hours)
75
- serializer = ServerUptimeChartSerializer(**data)
76
- return Response(serializer.model_dump())
75
+ serializer = ServerUptimeChartSerializer(data=data)
76
+ serializer.is_valid(raise_exception=True)
77
+ return Response(serializer.data)
77
78
  except Exception as e:
78
79
  logger.error(f"Server uptime chart error: {e}", exc_info=True)
79
80
  return Response(
@@ -94,8 +95,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
94
95
  try:
95
96
  hours = self._validate_hours(request.GET.get("hours", "24"))
96
97
  data = ChartGeneratorService.generate_request_volume_data(hours)
97
- serializer = RequestVolumeChartSerializer(**data)
98
- return Response(serializer.model_dump())
98
+ serializer = RequestVolumeChartSerializer(data=data)
99
+ serializer.is_valid(raise_exception=True)
100
+ return Response(serializer.data)
99
101
  except Exception as e:
100
102
  logger.error(f"Request volume chart error: {e}", exc_info=True)
101
103
  return Response(
@@ -116,8 +118,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
116
118
  try:
117
119
  hours = self._validate_hours(request.GET.get("hours", "24"))
118
120
  data = ChartGeneratorService.generate_response_time_data(hours)
119
- serializer = ResponseTimeChartSerializer(**data)
120
- return Response(serializer.model_dump())
121
+ serializer = ResponseTimeChartSerializer(data=data)
122
+ serializer.is_valid(raise_exception=True)
123
+ return Response(serializer.data)
121
124
  except Exception as e:
122
125
  logger.error(f"Response time chart error: {e}", exc_info=True)
123
126
  return Response(
@@ -138,8 +141,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
138
141
  try:
139
142
  hours = self._validate_hours(request.GET.get("hours", "24"))
140
143
  data = ChartGeneratorService.generate_service_activity_data(hours)
141
- serializer = ServiceActivityChartSerializer(**data)
142
- return Response(serializer.model_dump())
144
+ serializer = ServiceActivityChartSerializer(data=data)
145
+ serializer.is_valid(raise_exception=True)
146
+ return Response(serializer.data)
143
147
  except Exception as e:
144
148
  logger.error(f"Service activity chart error: {e}", exc_info=True)
145
149
  return Response(
@@ -160,8 +164,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
160
164
  try:
161
165
  hours = self._validate_hours(request.GET.get("hours", "24"))
162
166
  data = ChartGeneratorService.generate_server_lifecycle_data(hours)
163
- serializer = ServerLifecycleChartSerializer(**data)
164
- return Response(serializer.model_dump())
167
+ serializer = ServerLifecycleChartSerializer(data=data)
168
+ serializer.is_valid(raise_exception=True)
169
+ return Response(serializer.data)
165
170
  except Exception as e:
166
171
  logger.error(f"Server lifecycle chart error: {e}", exc_info=True)
167
172
  return Response(
@@ -182,8 +187,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
182
187
  try:
183
188
  hours = self._validate_hours(request.GET.get("hours", "24"))
184
189
  data = ChartGeneratorService.generate_error_distribution_data(hours)
185
- serializer = ErrorDistributionChartSerializer(**data)
186
- return Response(serializer.model_dump())
190
+ serializer = ErrorDistributionChartSerializer(data=data)
191
+ serializer.is_valid(raise_exception=True)
192
+ return Response(serializer.data)
187
193
  except Exception as e:
188
194
  logger.error(f"Error distribution chart error: {e}", exc_info=True)
189
195
  return Response(
@@ -204,8 +210,9 @@ class GRPCChartsViewSet(AdminAPIMixin, viewsets.ViewSet):
204
210
  try:
205
211
  hours = self._validate_hours(request.GET.get("hours", "24"))
206
212
  data = ChartGeneratorService.generate_dashboard_data(hours)
207
- serializer = DashboardChartsSerializer(**data)
208
- return Response(serializer.model_dump())
213
+ serializer = DashboardChartsSerializer(data=data)
214
+ serializer.is_valid(raise_exception=True)
215
+ return Response(serializer.data)
209
216
  except Exception as e:
210
217
  logger.error(f"Dashboard charts error: {e}", exc_info=True)
211
218
  return Response(
@@ -67,7 +67,7 @@ class GRPCConfigViewSet(AdminAPIMixin, viewsets.ViewSet):
67
67
  "host": grpc_config.server.host,
68
68
  "port": grpc_config.server.port,
69
69
  "enabled": grpc_config.server.enabled,
70
- "max_workers": grpc_config.server.max_workers,
70
+ "max_concurrent_streams": grpc_config.server.max_concurrent_streams,
71
71
  "max_concurrent_rpcs": None, # Not in current config
72
72
  },
73
73
  "framework": {
@@ -77,7 +77,7 @@ class GRPCConfigViewSet(AdminAPIMixin, viewsets.ViewSet):
77
77
  "interceptors": grpc_config.server.interceptors,
78
78
  },
79
79
  "features": {
80
- "jwt_auth": grpc_config.auth.enabled,
80
+ "api_key_auth": grpc_config.auth.enabled,
81
81
  "request_logging": True, # Always on
82
82
  "metrics": True, # Always on
83
83
  "reflection": grpc_config.server.enable_reflection,
@@ -86,8 +86,9 @@ class GRPCConfigViewSet(AdminAPIMixin, viewsets.ViewSet):
86
86
  "total_methods": methods_count,
87
87
  }
88
88
 
89
- serializer = GRPCConfigSerializer(**config_data)
90
- return Response(serializer.model_dump())
89
+ serializer = GRPCConfigSerializer(data=config_data)
90
+ serializer.is_valid(raise_exception=True)
91
+ return Response(serializer.data)
91
92
 
92
93
  except Exception as e:
93
94
  logger.error(f"Config fetch error: {e}", exc_info=True)
@@ -177,8 +178,9 @@ class GRPCConfigViewSet(AdminAPIMixin, viewsets.ViewSet):
177
178
  },
178
179
  }
179
180
 
180
- serializer = GRPCServerInfoSerializer(**server_info_data)
181
- return Response(serializer.model_dump())
181
+ serializer = GRPCServerInfoSerializer(data=server_info_data)
182
+ serializer.is_valid(raise_exception=True)
183
+ return Response(serializer.data)
182
184
 
183
185
  except Exception as e:
184
186
  logger.error(f"Server info error: {e}", exc_info=True)
@@ -11,6 +11,7 @@ from django.db import models
11
11
  from django.db.models import Avg, Count, Max
12
12
  from django.db.models.functions import TruncDay, TruncHour
13
13
  from django_cfg.mixins import AdminAPIMixin
14
+ from django_cfg.middleware.pagination import DefaultPagination
14
15
  from django_cfg.modules.django_logging import get_logger
15
16
  from drf_spectacular.types import OpenApiTypes
16
17
  from drf_spectacular.utils import OpenApiParameter, extend_schema
@@ -24,8 +25,6 @@ from ..serializers import (
24
25
  GRPCOverviewStatsSerializer,
25
26
  MethodListSerializer,
26
27
  MethodStatsSerializer,
27
- MonitoringServiceStatsSerializer,
28
- ServiceListSerializer,
29
28
  )
30
29
  from ..serializers.service_registry import RecentRequestSerializer
31
30
  from ..services import MonitoringService
@@ -48,6 +47,8 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
48
47
  Requires admin authentication (JWT, Session, or Basic Auth).
49
48
  """
50
49
 
50
+ pagination_class = DefaultPagination
51
+
51
52
  # Required for GenericViewSet
52
53
  queryset = GRPCRequestLog.objects.none() # Placeholder, actual queries in actions
53
54
  serializer_class = RecentRequestSerializer # Default serializer for schema
@@ -67,7 +68,9 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
67
68
  try:
68
69
  service = MonitoringService()
69
70
  health_data = service.get_health_status()
70
- return Response(health_data)
71
+ serializer = GRPCHealthCheckSerializer(data=health_data)
72
+ serializer.is_valid(raise_exception=True)
73
+ return Response(serializer.data)
71
74
 
72
75
  except ValueError as e:
73
76
  return Response(
@@ -107,7 +110,9 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
107
110
 
108
111
  service = MonitoringService()
109
112
  stats = service.get_overview_statistics(hours=hours)
110
- return Response(stats)
113
+ serializer = GRPCOverviewStatsSerializer(data=stats)
114
+ serializer.is_valid(raise_exception=True)
115
+ return Response(serializer.data)
111
116
 
112
117
  except ValueError as e:
113
118
  logger.warning(f"Overview stats validation error: {e}")
@@ -149,7 +154,7 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
149
154
  ),
150
155
  ],
151
156
  responses={
152
- 200: RecentRequestSerializer,
157
+ 200: RecentRequestSerializer(many=True), # Use many=True for paginated list
153
158
  400: {"description": "Invalid parameters"},
154
159
  },
155
160
  )
@@ -174,37 +179,49 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
174
179
  # Serialize paginated data
175
180
  requests_list = []
176
181
  for req in page:
177
- req_serializer = RecentRequestSerializer(
178
- id=req.id,
179
- request_id=req.request_id,
180
- service_name=req.service_name,
181
- method_name=req.method_name,
182
- status=req.status,
183
- duration_ms=req.duration_ms or 0,
184
- grpc_status_code=req.grpc_status_code or "",
185
- error_message=req.error_message or "",
186
- created_at=req.created_at.isoformat(),
187
- client_ip=req.client_ip or "",
188
- )
189
- requests_list.append(req_serializer.model_dump())
182
+ requests_list.append({
183
+ "id": req.id,
184
+ "request_id": req.request_id,
185
+ "service_name": req.service_name,
186
+ "method_name": req.method_name,
187
+ "status": req.status,
188
+ "duration_ms": req.duration_ms or 0,
189
+ "grpc_status_code": req.grpc_status_code or "",
190
+ "error_message": req.error_message or "",
191
+ "created_at": req.created_at.isoformat(),
192
+ "client_ip": req.client_ip or "",
193
+ # User information
194
+ "user_id": req.user.id if req.user else None,
195
+ "username": req.user.username if req.user else "",
196
+ "is_authenticated": req.is_authenticated,
197
+ # API Key information
198
+ "api_key_id": req.api_key.id if req.api_key else None,
199
+ "api_key_name": req.api_key.name if req.api_key else "",
200
+ })
190
201
  return self.get_paginated_response(requests_list)
191
202
 
192
203
  # No pagination fallback
193
204
  requests_list = []
194
205
  for req in queryset[:100]:
195
- req_serializer = RecentRequestSerializer(
196
- id=req.id,
197
- request_id=req.request_id,
198
- service_name=req.service_name,
199
- method_name=req.method_name,
200
- status=req.status,
201
- duration_ms=req.duration_ms or 0,
202
- grpc_status_code=req.grpc_status_code or "",
203
- error_message=req.error_message or "",
204
- created_at=req.created_at.isoformat(),
205
- client_ip=req.client_ip or "",
206
- )
207
- requests_list.append(req_serializer.model_dump())
206
+ requests_list.append({
207
+ "id": req.id,
208
+ "request_id": req.request_id,
209
+ "service_name": req.service_name,
210
+ "method_name": req.method_name,
211
+ "status": req.status,
212
+ "duration_ms": req.duration_ms or 0,
213
+ "grpc_status_code": req.grpc_status_code or "",
214
+ "error_message": req.error_message or "",
215
+ "created_at": req.created_at.isoformat(),
216
+ "client_ip": req.client_ip or "",
217
+ # User information
218
+ "user_id": req.user.id if req.user else None,
219
+ "username": req.user.username if req.user else "",
220
+ "is_authenticated": req.is_authenticated,
221
+ # API Key information
222
+ "api_key_id": req.api_key.id if req.api_key else None,
223
+ "api_key_name": req.api_key.name if req.api_key else "",
224
+ })
208
225
  return Response({"requests": requests_list, "count": len(requests_list)})
209
226
 
210
227
  except ValueError as e:
@@ -219,52 +236,6 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
219
236
  status=status.HTTP_500_INTERNAL_SERVER_ERROR,
220
237
  )
221
238
 
222
- @extend_schema(
223
- tags=["gRPC Monitoring"],
224
- summary="Get service statistics",
225
- description="Returns statistics grouped by service.",
226
- parameters=[
227
- OpenApiParameter(
228
- name="hours",
229
- type=OpenApiTypes.INT,
230
- location=OpenApiParameter.QUERY,
231
- description="Statistics period in hours (default: 24)",
232
- required=False,
233
- ),
234
- ],
235
- responses={
236
- 200: ServiceListSerializer,
237
- 400: {"description": "Invalid parameters"},
238
- },
239
- )
240
- @action(detail=False, methods=["get"], url_path="services", pagination_class=None)
241
- def services(self, request):
242
- """Get statistics per service."""
243
- try:
244
- hours = int(request.GET.get("hours", 24))
245
-
246
- service = MonitoringService()
247
- services_list = service.get_service_statistics(hours=hours)
248
-
249
- response_data = {
250
- "services": services_list,
251
- "total_services": len(services_list),
252
- }
253
-
254
- return Response(response_data)
255
-
256
- except ValueError as e:
257
- logger.warning(f"Service stats validation error: {e}")
258
- return Response(
259
- {"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
260
- )
261
- except Exception as e:
262
- logger.error(f"Service stats error: {e}", exc_info=True)
263
- return Response(
264
- {"error": "Internal server error"},
265
- status=status.HTTP_500_INTERNAL_SERVER_ERROR,
266
- )
267
-
268
239
  @extend_schema(
269
240
  tags=["gRPC Monitoring"],
270
241
  summary="Get method statistics",
@@ -308,8 +279,9 @@ class GRPCMonitorViewSet(AdminAPIMixin, viewsets.GenericViewSet):
308
279
  "total_methods": len(methods_list),
309
280
  }
310
281
 
311
- serializer = MethodListSerializer(**response_data)
312
- return Response(serializer.model_dump())
282
+ serializer = MethodListSerializer(data=response_data)
283
+ serializer.is_valid(raise_exception=True)
284
+ return Response(serializer.data)
313
285
 
314
286
  except ValueError as e:
315
287
  logger.warning(f"Method stats validation error: {e}")