django-cfg 1.5.8__py3-none-any.whl → 1.5.20__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/api/commands/serializers.py +152 -0
- django_cfg/apps/api/commands/views.py +32 -0
- django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
- django_cfg/apps/business/accounts/serializers/profile.py +42 -0
- django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
- django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
- django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
- django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
- django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
- django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
- django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
- django_cfg/apps/business/support/serializers.py +3 -2
- django_cfg/apps/integrations/centrifugo/apps.py +2 -1
- django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +6 -6
- django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
- django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
- django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
- django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
- django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
- django_cfg/apps/integrations/centrifugo/urls.py +8 -0
- django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
- django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
- django_cfg/apps/integrations/grpc/admin/config.py +113 -9
- django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
- django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
- django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
- django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
- django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
- django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
- django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
- django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
- django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
- django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
- django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
- django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
- django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
- django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
- django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
- django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
- django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
- django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
- django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
- django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
- django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
- django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
- django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
- django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
- django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
- django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
- django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
- django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
- django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
- django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
- django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
- django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
- django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
- django_cfg/apps/integrations/grpc/urls.py +8 -0
- django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
- django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +378 -0
- django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
- django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
- django_cfg/apps/integrations/grpc/views/charts.py +21 -14
- django_cfg/apps/integrations/grpc/views/config.py +8 -6
- django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
- django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
- django_cfg/apps/integrations/grpc/views/services.py +30 -21
- django_cfg/apps/integrations/grpc/views/testing.py +45 -43
- django_cfg/apps/integrations/rq/views/jobs.py +19 -9
- django_cfg/apps/integrations/rq/views/schedule.py +7 -3
- django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
- django_cfg/apps/system/dashboard/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
- django_cfg/apps/system/frontend/views.py +87 -6
- django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
- django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
- django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
- django_cfg/config.py +33 -0
- django_cfg/core/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
- django_cfg/management/commands/check_endpoints.py +2 -2
- django_cfg/management/commands/check_settings.py +3 -10
- django_cfg/management/commands/clear_constance.py +3 -10
- django_cfg/management/commands/create_token.py +4 -11
- django_cfg/management/commands/list_urls.py +4 -10
- django_cfg/management/commands/migrate_all.py +18 -12
- django_cfg/management/commands/migrator.py +4 -11
- django_cfg/management/commands/script.py +4 -10
- django_cfg/management/commands/show_config.py +8 -16
- django_cfg/management/commands/show_urls.py +5 -11
- django_cfg/management/commands/superuser.py +4 -11
- django_cfg/management/commands/tree.py +5 -10
- django_cfg/management/utils/README.md +402 -0
- django_cfg/management/utils/__init__.py +29 -0
- django_cfg/management/utils/mixins.py +176 -0
- django_cfg/middleware/pagination.py +53 -54
- django_cfg/models/api/grpc/__init__.py +15 -21
- django_cfg/models/api/grpc/config.py +155 -73
- django_cfg/models/ngrok/config.py +7 -6
- django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
- django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +126 -30
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
- django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
- django_cfg/modules/django_email/management/commands/test_email.py +4 -10
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
- django_cfg/modules/django_unfold/navigation.py +6 -18
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/modules.py +1 -4
- django_cfg/requirements.txt +52 -0
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/RECORD +158 -121
- django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -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(
|
|
76
|
-
|
|
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(
|
|
98
|
-
|
|
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(
|
|
120
|
-
|
|
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(
|
|
142
|
-
|
|
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(
|
|
164
|
-
|
|
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(
|
|
186
|
-
|
|
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(
|
|
208
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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(
|
|
90
|
-
|
|
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(
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
id
|
|
179
|
-
request_id
|
|
180
|
-
service_name
|
|
181
|
-
method_name
|
|
182
|
-
status
|
|
183
|
-
duration_ms
|
|
184
|
-
grpc_status_code
|
|
185
|
-
error_message
|
|
186
|
-
created_at
|
|
187
|
-
client_ip
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
196
|
-
id
|
|
197
|
-
request_id
|
|
198
|
-
service_name
|
|
199
|
-
method_name
|
|
200
|
-
status
|
|
201
|
-
duration_ms
|
|
202
|
-
grpc_status_code
|
|
203
|
-
error_message
|
|
204
|
-
created_at
|
|
205
|
-
client_ip
|
|
206
|
-
|
|
207
|
-
|
|
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(
|
|
312
|
-
|
|
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}")
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC Proto Files ViewSet.
|
|
3
|
+
|
|
4
|
+
Provides REST API endpoints for downloading proto files generated from Django models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from django.http import FileResponse, HttpResponse
|
|
8
|
+
from django_cfg.mixins import AdminAPIMixin
|
|
9
|
+
from django_cfg.modules.django_logging import get_logger
|
|
10
|
+
from drf_spectacular.types import OpenApiTypes
|
|
11
|
+
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
|
12
|
+
from rest_framework import status, viewsets
|
|
13
|
+
from rest_framework.decorators import action
|
|
14
|
+
from rest_framework.response import Response
|
|
15
|
+
|
|
16
|
+
from ..serializers.proto_files import (
|
|
17
|
+
ProtoFileListSerializer,
|
|
18
|
+
ProtoGenerateRequestSerializer,
|
|
19
|
+
ProtoGenerateResponseSerializer,
|
|
20
|
+
)
|
|
21
|
+
from ..services import ProtoFilesManager
|
|
22
|
+
|
|
23
|
+
logger = get_logger("grpc.proto_files")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GRPCProtoFilesViewSet(AdminAPIMixin, viewsets.ViewSet):
|
|
27
|
+
"""
|
|
28
|
+
ViewSet for gRPC proto files management.
|
|
29
|
+
|
|
30
|
+
Provides endpoints for:
|
|
31
|
+
- List all available proto files
|
|
32
|
+
- Download specific proto file
|
|
33
|
+
- Download all proto files as .zip
|
|
34
|
+
- Trigger proto generation
|
|
35
|
+
|
|
36
|
+
Requires admin authentication (JWT, Session, or Basic Auth).
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
lookup_value_regex = r'[^/]+'
|
|
40
|
+
|
|
41
|
+
def __init__(self, **kwargs):
|
|
42
|
+
super().__init__(**kwargs)
|
|
43
|
+
self.manager = ProtoFilesManager()
|
|
44
|
+
|
|
45
|
+
@extend_schema(
|
|
46
|
+
tags=["gRPC Proto Files"],
|
|
47
|
+
summary="List all proto files",
|
|
48
|
+
description="Returns list of all available proto files with metadata.",
|
|
49
|
+
responses={
|
|
50
|
+
200: ProtoFileListSerializer,
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
def list(self, request):
|
|
54
|
+
"""List all available proto files."""
|
|
55
|
+
try:
|
|
56
|
+
from django.urls import reverse
|
|
57
|
+
from django_cfg.core.state import get_current_config
|
|
58
|
+
|
|
59
|
+
proto_files = self.manager.scan_proto_files(request=request)
|
|
60
|
+
|
|
61
|
+
config = get_current_config()
|
|
62
|
+
|
|
63
|
+
# Build download-all URL
|
|
64
|
+
# Use api_url from config (respects HTTPS behind reverse proxy)
|
|
65
|
+
# Falls back to request.build_absolute_uri if config not available
|
|
66
|
+
if config and hasattr(config, 'api_url'):
|
|
67
|
+
path = reverse('django_cfg_grpc:proto-files-download-all')
|
|
68
|
+
download_all_url = f"{config.api_url}{path}"
|
|
69
|
+
else:
|
|
70
|
+
download_all_url = request.build_absolute_uri(
|
|
71
|
+
reverse('django_cfg_grpc:proto-files-download-all')
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
response_data = {
|
|
75
|
+
"files": proto_files,
|
|
76
|
+
"total_files": len(proto_files),
|
|
77
|
+
"proto_dir": str(self.manager.get_proto_dir()),
|
|
78
|
+
"download_all_url": download_all_url,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
serializer = ProtoFileListSerializer(data=response_data)
|
|
82
|
+
serializer.is_valid(raise_exception=True)
|
|
83
|
+
return Response(serializer.data)
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.error(f"Proto files list error: {e}", exc_info=True)
|
|
87
|
+
return Response(
|
|
88
|
+
{"error": "Internal server error"},
|
|
89
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
@extend_schema(
|
|
93
|
+
tags=["gRPC Proto Files"],
|
|
94
|
+
summary="Download proto file",
|
|
95
|
+
description="Download specific proto file by app label.",
|
|
96
|
+
parameters=[
|
|
97
|
+
OpenApiParameter(
|
|
98
|
+
name="pk",
|
|
99
|
+
type=OpenApiTypes.STR,
|
|
100
|
+
location=OpenApiParameter.PATH,
|
|
101
|
+
description="App label (e.g., 'crypto')",
|
|
102
|
+
required=True,
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
responses={
|
|
106
|
+
200: {
|
|
107
|
+
"description": "Proto file content",
|
|
108
|
+
"content": {"text/plain": {}},
|
|
109
|
+
},
|
|
110
|
+
404: {"description": "Proto file not found"},
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
def retrieve(self, request, pk=None):
|
|
114
|
+
"""Download specific proto file."""
|
|
115
|
+
try:
|
|
116
|
+
app_label = pk
|
|
117
|
+
proto_file = self.manager.get_proto_file(app_label)
|
|
118
|
+
|
|
119
|
+
if not proto_file:
|
|
120
|
+
return Response(
|
|
121
|
+
{"error": f"Proto file for app '{app_label}' not found"},
|
|
122
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Return proto file content
|
|
126
|
+
response = FileResponse(
|
|
127
|
+
open(proto_file, "rb"),
|
|
128
|
+
content_type="text/plain; charset=utf-8",
|
|
129
|
+
)
|
|
130
|
+
response["Content-Disposition"] = f'attachment; filename="{proto_file.name}"'
|
|
131
|
+
return response
|
|
132
|
+
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.error(f"Proto file download error: {e}", exc_info=True)
|
|
135
|
+
return Response(
|
|
136
|
+
{"error": "Internal server error"},
|
|
137
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@extend_schema(
|
|
141
|
+
tags=["gRPC Proto Files"],
|
|
142
|
+
summary="Download all proto files",
|
|
143
|
+
description="Download all proto files as a .zip archive.",
|
|
144
|
+
responses={
|
|
145
|
+
200: {
|
|
146
|
+
"description": "Zip archive with all proto files",
|
|
147
|
+
"content": {"application/zip": {}},
|
|
148
|
+
},
|
|
149
|
+
404: {"description": "No proto files found"},
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
@action(detail=False, methods=["get"], url_path="download-all")
|
|
153
|
+
def download_all(self, request):
|
|
154
|
+
"""Download all proto files as .zip archive."""
|
|
155
|
+
try:
|
|
156
|
+
zip_data = self.manager.create_zip_archive()
|
|
157
|
+
|
|
158
|
+
if not zip_data:
|
|
159
|
+
return Response(
|
|
160
|
+
{"error": "No proto files found"},
|
|
161
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Return zip file
|
|
165
|
+
response = HttpResponse(zip_data, content_type="application/zip")
|
|
166
|
+
response["Content-Disposition"] = 'attachment; filename="protos.zip"'
|
|
167
|
+
return response
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"Proto files zip error: {e}", exc_info=True)
|
|
171
|
+
return Response(
|
|
172
|
+
{"error": "Internal server error"},
|
|
173
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@extend_schema(
|
|
177
|
+
tags=["gRPC Proto Files"],
|
|
178
|
+
summary="Generate proto files",
|
|
179
|
+
description="Trigger proto file generation for specified apps.",
|
|
180
|
+
request=ProtoGenerateRequestSerializer,
|
|
181
|
+
responses={
|
|
182
|
+
200: ProtoGenerateResponseSerializer,
|
|
183
|
+
400: {"description": "Bad request"},
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
@action(detail=False, methods=["post"], url_path="generate")
|
|
187
|
+
def generate(self, request):
|
|
188
|
+
"""Trigger proto generation for specified apps."""
|
|
189
|
+
try:
|
|
190
|
+
serializer = ProtoGenerateRequestSerializer(data=request.data)
|
|
191
|
+
serializer.is_valid(raise_exception=True)
|
|
192
|
+
|
|
193
|
+
apps = serializer.validated_data.get("apps")
|
|
194
|
+
force = serializer.validated_data.get("force", False)
|
|
195
|
+
|
|
196
|
+
# Generate protos via service
|
|
197
|
+
result = self.manager.generate_protos(apps=apps, force=force)
|
|
198
|
+
|
|
199
|
+
# Add proto_dir to response
|
|
200
|
+
result["proto_dir"] = str(self.manager.get_proto_dir())
|
|
201
|
+
|
|
202
|
+
response_serializer = ProtoGenerateResponseSerializer(data=result)
|
|
203
|
+
response_serializer.is_valid(raise_exception=True)
|
|
204
|
+
return Response(response_serializer.data)
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
logger.error(f"Proto generation error: {e}", exc_info=True)
|
|
208
|
+
return Response(
|
|
209
|
+
{"error": "Internal server error"},
|
|
210
|
+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
__all__ = ["GRPCProtoFilesViewSet"]
|
|
@@ -6,6 +6,7 @@ Provides REST API endpoints for viewing registered gRPC services and their metho
|
|
|
6
6
|
|
|
7
7
|
from django.db import models
|
|
8
8
|
from django.db.models import Avg, Count, Max, Min
|
|
9
|
+
from django_cfg.middleware.pagination import DefaultPagination
|
|
9
10
|
from django_cfg.mixins import AdminAPIMixin
|
|
10
11
|
from django_cfg.modules.django_logging import get_logger
|
|
11
12
|
from drf_spectacular.types import OpenApiTypes
|
|
@@ -20,13 +21,14 @@ from ..serializers.service_registry import (
|
|
|
20
21
|
ServiceDetailSerializer,
|
|
21
22
|
ServiceListSerializer,
|
|
22
23
|
ServiceMethodsSerializer,
|
|
24
|
+
ServiceSummarySerializer,
|
|
23
25
|
)
|
|
24
26
|
from ..services import ServiceRegistryManager
|
|
25
27
|
|
|
26
28
|
logger = get_logger("grpc.services")
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
class GRPCServiceViewSet(AdminAPIMixin, viewsets.
|
|
31
|
+
class GRPCServiceViewSet(AdminAPIMixin, viewsets.GenericViewSet):
|
|
30
32
|
"""
|
|
31
33
|
ViewSet for gRPC service registry and management.
|
|
32
34
|
|
|
@@ -39,13 +41,20 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
|
|
|
39
41
|
Requires admin authentication (JWT, Session, or Basic Auth).
|
|
40
42
|
"""
|
|
41
43
|
|
|
44
|
+
# Pagination for list endpoint
|
|
45
|
+
pagination_class = DefaultPagination
|
|
46
|
+
|
|
47
|
+
# Required for GenericViewSet
|
|
48
|
+
queryset = GRPCRequestLog.objects.none() # Placeholder
|
|
49
|
+
serializer_class = ServiceSummarySerializer
|
|
50
|
+
|
|
42
51
|
# Allow dots in service names (e.g., apps.CryptoService)
|
|
43
52
|
lookup_value_regex = r'[^/]+'
|
|
44
53
|
|
|
45
54
|
@extend_schema(
|
|
46
55
|
tags=["gRPC Services"],
|
|
47
56
|
summary="List all services",
|
|
48
|
-
description="Returns list of all registered gRPC services with basic statistics.",
|
|
57
|
+
description="Returns paginated list of all registered gRPC services with basic statistics.",
|
|
49
58
|
parameters=[
|
|
50
59
|
OpenApiParameter(
|
|
51
60
|
name="hours",
|
|
@@ -54,13 +63,14 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
|
|
|
54
63
|
description="Statistics period in hours (default: 24)",
|
|
55
64
|
required=False,
|
|
56
65
|
),
|
|
66
|
+
# Note: page, page_size added automatically by pagination_class
|
|
57
67
|
],
|
|
58
68
|
responses={
|
|
59
|
-
200:
|
|
69
|
+
200: ServiceSummarySerializer(many=True),
|
|
60
70
|
},
|
|
61
71
|
)
|
|
62
72
|
def list(self, request):
|
|
63
|
-
"""List all registered gRPC services."""
|
|
73
|
+
"""List all registered gRPC services with pagination."""
|
|
64
74
|
try:
|
|
65
75
|
hours = int(request.GET.get("hours", 24))
|
|
66
76
|
hours = min(max(hours, 1), 168)
|
|
@@ -68,23 +78,20 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
|
|
|
68
78
|
# Use service registry manager
|
|
69
79
|
registry = ServiceRegistryManager()
|
|
70
80
|
|
|
71
|
-
if not registry.is_server_running():
|
|
72
|
-
# No running server - return empty list
|
|
73
|
-
return Response({
|
|
74
|
-
"services": [],
|
|
75
|
-
"total_services": 0,
|
|
76
|
-
})
|
|
77
|
-
|
|
78
81
|
# Get services with stats from service layer
|
|
82
|
+
# Note: This will return empty list if server is not running,
|
|
83
|
+
# but that's expected - services are only known when server is started
|
|
79
84
|
services_list = registry.get_all_services_with_stats(hours=hours)
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
# Paginate the services list (works with empty list too)
|
|
87
|
+
page = self.paginate_queryset(services_list)
|
|
88
|
+
if page is not None:
|
|
89
|
+
serializer = ServiceSummarySerializer(page, many=True)
|
|
90
|
+
return self.get_paginated_response(serializer.data)
|
|
85
91
|
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
# Fallback (no pagination)
|
|
93
|
+
serializer = ServiceSummarySerializer(services_list, many=True)
|
|
94
|
+
return Response(serializer.data)
|
|
88
95
|
|
|
89
96
|
except Exception as e:
|
|
90
97
|
logger.error(f"Service list error: {e}", exc_info=True)
|
|
@@ -216,8 +223,9 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
|
|
|
216
223
|
],
|
|
217
224
|
}
|
|
218
225
|
|
|
219
|
-
serializer = ServiceDetailSerializer(
|
|
220
|
-
|
|
226
|
+
serializer = ServiceDetailSerializer(data=service_detail)
|
|
227
|
+
serializer.is_valid(raise_exception=True)
|
|
228
|
+
return Response(serializer.data)
|
|
221
229
|
|
|
222
230
|
except Exception as e:
|
|
223
231
|
logger.error(f"Service detail error: {e}", exc_info=True)
|
|
@@ -275,8 +283,9 @@ class GRPCServiceViewSet(AdminAPIMixin, viewsets.ViewSet):
|
|
|
275
283
|
"total_methods": len(methods_list),
|
|
276
284
|
}
|
|
277
285
|
|
|
278
|
-
serializer = ServiceMethodsSerializer(
|
|
279
|
-
|
|
286
|
+
serializer = ServiceMethodsSerializer(data=response_data)
|
|
287
|
+
serializer.is_valid(raise_exception=True)
|
|
288
|
+
return Response(serializer.data)
|
|
280
289
|
|
|
281
290
|
except Exception as e:
|
|
282
291
|
logger.error(f"Service methods error: {e}", exc_info=True)
|