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.

Files changed (159) 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/accounts/serializers/profile.py +42 -0
  6. django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
  7. django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
  8. django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
  9. django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
  10. django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
  11. django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
  12. django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
  13. django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
  14. django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
  15. django_cfg/apps/business/support/serializers.py +3 -2
  16. django_cfg/apps/integrations/centrifugo/apps.py +2 -1
  17. django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
  18. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +6 -6
  19. django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
  20. django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
  21. django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
  22. django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
  23. django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
  24. django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
  25. django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
  26. django_cfg/apps/integrations/centrifugo/urls.py +8 -0
  27. django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
  28. django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
  29. django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
  30. django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
  31. django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
  32. django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
  33. django_cfg/apps/integrations/grpc/admin/config.py +113 -9
  34. django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
  35. django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
  36. django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
  37. django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
  38. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
  39. django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
  40. django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
  41. django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
  42. django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
  43. django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
  44. django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
  45. django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
  46. django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
  47. django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
  48. django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
  49. django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
  50. django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
  51. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
  52. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
  53. django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
  54. django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
  55. django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
  56. django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
  57. django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
  58. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
  59. django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
  60. django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
  61. django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
  62. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
  63. django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
  64. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
  65. django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
  66. django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
  67. django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
  68. django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
  69. django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
  70. django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
  71. django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
  72. django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
  73. django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
  74. django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
  75. django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
  76. django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
  77. django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
  78. django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
  79. django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
  80. django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
  81. django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
  82. django_cfg/apps/integrations/grpc/urls.py +8 -0
  83. django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
  84. django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
  85. django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
  86. django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
  87. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +378 -0
  88. django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
  89. django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
  90. django_cfg/apps/integrations/grpc/views/charts.py +21 -14
  91. django_cfg/apps/integrations/grpc/views/config.py +8 -6
  92. django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
  93. django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
  94. django_cfg/apps/integrations/grpc/views/services.py +30 -21
  95. django_cfg/apps/integrations/grpc/views/testing.py +45 -43
  96. django_cfg/apps/integrations/rq/views/jobs.py +19 -9
  97. django_cfg/apps/integrations/rq/views/schedule.py +7 -3
  98. django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
  99. django_cfg/apps/system/dashboard/serializers/config.py +95 -9
  100. django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
  101. django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
  102. django_cfg/apps/system/frontend/views.py +87 -6
  103. django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
  104. django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
  105. django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
  106. django_cfg/config.py +33 -0
  107. django_cfg/core/builders/security_builder.py +1 -0
  108. django_cfg/core/generation/integration_generators/api.py +2 -0
  109. django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
  110. django_cfg/management/commands/check_endpoints.py +2 -2
  111. django_cfg/management/commands/check_settings.py +3 -10
  112. django_cfg/management/commands/clear_constance.py +3 -10
  113. django_cfg/management/commands/create_token.py +4 -11
  114. django_cfg/management/commands/list_urls.py +4 -10
  115. django_cfg/management/commands/migrate_all.py +18 -12
  116. django_cfg/management/commands/migrator.py +4 -11
  117. django_cfg/management/commands/script.py +4 -10
  118. django_cfg/management/commands/show_config.py +8 -16
  119. django_cfg/management/commands/show_urls.py +5 -11
  120. django_cfg/management/commands/superuser.py +4 -11
  121. django_cfg/management/commands/tree.py +5 -10
  122. django_cfg/management/utils/README.md +402 -0
  123. django_cfg/management/utils/__init__.py +29 -0
  124. django_cfg/management/utils/mixins.py +176 -0
  125. django_cfg/middleware/pagination.py +53 -54
  126. django_cfg/models/api/grpc/__init__.py +15 -21
  127. django_cfg/models/api/grpc/config.py +155 -73
  128. django_cfg/models/ngrok/config.py +7 -6
  129. django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
  130. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
  131. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
  132. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
  133. django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
  134. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
  135. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
  136. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
  137. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
  138. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
  139. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
  140. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
  141. django_cfg/modules/django_client/core/ir/schema.py +15 -1
  142. django_cfg/modules/django_client/core/parser/base.py +126 -30
  143. django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
  144. django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
  145. django_cfg/modules/django_email/management/commands/test_email.py +4 -10
  146. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
  147. django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
  148. django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
  149. django_cfg/modules/django_unfold/navigation.py +6 -18
  150. django_cfg/pyproject.toml +1 -1
  151. django_cfg/registry/modules.py +1 -4
  152. django_cfg/requirements.txt +52 -0
  153. django_cfg/static/frontend/admin.zip +0 -0
  154. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
  155. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/RECORD +158 -121
  156. django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
  157. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
  158. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
  159. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,129 @@
1
+ """
2
+ gRPC API Key Admin.
3
+
4
+ PydanticAdmin for GrpcApiKey model with enhanced UI and status tracking.
5
+ """
6
+
7
+ from django.contrib import admin
8
+ from django_cfg.modules.django_admin import Icons, computed_field
9
+ from django_cfg.modules.django_admin.base import PydanticAdmin
10
+
11
+ from ..models import GrpcApiKey
12
+ from .config import grpcapikey_config
13
+
14
+
15
+ @admin.register(GrpcApiKey)
16
+ class GrpcApiKeyAdmin(PydanticAdmin):
17
+ """
18
+ Admin interface for gRPC API keys.
19
+
20
+ Features:
21
+ - List view with status indicators
22
+ - Search by name, description, user
23
+ - Filter by status, type, user
24
+ - Read-only key display (masked)
25
+ - Actions for revoking keys
26
+ - Type-safe configuration via AdminConfig
27
+ """
28
+
29
+ config = grpcapikey_config
30
+
31
+ # Permissions
32
+ def save_model(self, request, obj, form, change):
33
+ """Save model with created_by tracking."""
34
+ if not change: # New object
35
+ obj.created_by = request.user
36
+ super().save_model(request, obj, form, change)
37
+
38
+ # Display methods
39
+ @computed_field("Status", ordering="is_active")
40
+ def status_indicator(self, obj):
41
+ """Display status indicator with expiration check."""
42
+ if obj.is_valid:
43
+ return self.html.badge(
44
+ "Active",
45
+ variant="success",
46
+ icon=Icons.CHECK_CIRCLE
47
+ )
48
+ elif obj.is_expired:
49
+ return self.html.badge(
50
+ "Expired",
51
+ variant="warning",
52
+ icon=Icons.SCHEDULE
53
+ )
54
+ else:
55
+ return self.html.badge(
56
+ "Revoked",
57
+ variant="danger",
58
+ icon=Icons.CANCEL
59
+ )
60
+
61
+ @computed_field("Masked Key")
62
+ def masked_key_display(self, obj):
63
+ """Display masked API key."""
64
+ return self.html.code(obj.masked_key)
65
+
66
+ def key_display(self, obj):
67
+ """Display full API key (read-only, for copying)."""
68
+ if obj.pk:
69
+ return self.html.code(obj.key)
70
+ return self.html.empty()
71
+
72
+ key_display.short_description = "Full API Key"
73
+
74
+ @computed_field("Requests", ordering="request_count")
75
+ def request_count_display(self, obj):
76
+ """Display request count with badge."""
77
+ if obj.request_count == 0:
78
+ return self.html.empty("0")
79
+
80
+ # Color based on usage
81
+ if obj.request_count > 1000:
82
+ variant = "success"
83
+ elif obj.request_count > 100:
84
+ variant = "info"
85
+ else:
86
+ variant = "secondary"
87
+
88
+ return self.html.badge(
89
+ str(obj.request_count),
90
+ variant=variant,
91
+ icon=Icons.ANALYTICS
92
+ )
93
+
94
+ @computed_field("Expires", ordering="expires_at")
95
+ def expires_display(self, obj):
96
+ """Display expiration date with status."""
97
+ if not obj.expires_at:
98
+ return self.html.badge(
99
+ "Never",
100
+ variant="success",
101
+ icon=Icons.ALL_INCLUSIVE
102
+ )
103
+
104
+ from django.utils import timezone
105
+ if obj.expires_at < timezone.now():
106
+ return self.html.badge(
107
+ obj.expires_at.strftime("%Y-%m-%d"),
108
+ variant="danger",
109
+ icon=Icons.ERROR
110
+ )
111
+
112
+ return self.html.text(
113
+ obj.expires_at.strftime("%Y-%m-%d"),
114
+ variant="primary"
115
+ )
116
+
117
+ # Actions
118
+ @admin.action(description="Revoke selected API keys")
119
+ def revoke_selected_keys(self, request, queryset):
120
+ """Revoke selected API keys."""
121
+ count = queryset.filter(is_active=True).count()
122
+ queryset.update(is_active=False)
123
+ self.message_user(
124
+ request,
125
+ f"Successfully revoked {count} API key(s).",
126
+ )
127
+
128
+
129
+ __all__ = ["GrpcApiKeyAdmin"]
@@ -58,6 +58,18 @@ class GRPCRequestLogAdmin(PydanticAdmin):
58
58
 
59
59
  return self.html.badge(obj.grpc_status_code, variant=variant, icon=icon)
60
60
 
61
+ @computed_field("API Key", ordering="api_key__name")
62
+ def api_key_display(self, obj):
63
+ """Display API key name if used for authentication."""
64
+ if not obj.api_key:
65
+ return self.html.empty()
66
+
67
+ return self.html.badge(
68
+ obj.api_key.name,
69
+ variant="info" if obj.api_key.is_valid else "danger",
70
+ icon=Icons.KEY
71
+ )
72
+
61
73
  @computed_field("Duration", ordering="duration_ms")
62
74
  def duration_display(self, obj):
63
75
  """Display duration with color coding based on speed."""
@@ -86,7 +98,7 @@ class GRPCRequestLogAdmin(PydanticAdmin):
86
98
  formatted = json.dumps(obj.request_data, indent=2)
87
99
  return self.html.code_block(formatted, language="json", max_height="400px")
88
100
  except Exception:
89
- return str(obj.request_data)
101
+ return self.html.code(str(obj.request_data))
90
102
 
91
103
  request_data_display.short_description = "Request Data"
92
104
 
@@ -99,7 +111,7 @@ class GRPCRequestLogAdmin(PydanticAdmin):
99
111
  formatted = json.dumps(obj.response_data, indent=2)
100
112
  return self.html.code_block(formatted, language="json", max_height="400px")
101
113
  except Exception:
102
- return str(obj.response_data)
114
+ return self.html.code(str(obj.response_data))
103
115
 
104
116
  response_data_display.short_description = "Response Data"
105
117
 
@@ -112,87 +124,84 @@ class GRPCRequestLogAdmin(PydanticAdmin):
112
124
  separator=" "
113
125
  )
114
126
 
115
- # gRPC status code
116
- code_line = self.html.key_value(
117
- "gRPC Status",
118
- self.html.badge(obj.grpc_status_code, variant="danger", icon=Icons.ERROR)
119
- ) if obj.grpc_status_code else None
120
-
121
- # Error message
122
- msg_line = self.html.key_value(
123
- "Message",
124
- self.html.text(obj.error_message, variant="danger")
125
- ) if obj.error_message else None
126
-
127
- # Error details
128
- details_line = None
127
+ # Error details JSON
128
+ error_details_json = None
129
129
  if obj.error_details:
130
130
  try:
131
131
  formatted = json.dumps(obj.error_details, indent=2)
132
- details_line = self.html.key_value(
132
+ error_details_json = self.html.key_value(
133
133
  "Details",
134
134
  self.html.code_block(formatted, language="json", max_height="200px")
135
135
  )
136
136
  except Exception:
137
137
  pass
138
138
 
139
- return self.html.breakdown(code_line, msg_line, details_line) if (code_line or msg_line) else self.html.empty()
139
+ return self.html.breakdown(
140
+ self.html.key_value(
141
+ "gRPC Status",
142
+ self.html.badge(obj.grpc_status_code, variant="danger", icon=Icons.ERROR)
143
+ ) if obj.grpc_status_code else None,
144
+ self.html.key_value(
145
+ "Message",
146
+ self.html.text(obj.error_message, variant="danger")
147
+ ) if obj.error_message else None,
148
+ error_details_json,
149
+ )
140
150
 
141
151
  error_details_display.short_description = "Error Details"
142
152
 
143
153
  def performance_stats_display(self, obj):
144
- """Display performance statistics."""
145
- # Duration
146
- duration_line = self.html.key_value(
147
- "Duration",
148
- self.html.number(obj.duration_ms, suffix="ms") if obj.duration_ms else "N/A"
149
- )
150
-
151
- # Request size
152
- request_size_line = self.html.key_value(
153
- "Request Size",
154
- self.html.number(obj.request_size, suffix=" bytes") if obj.request_size else "N/A"
155
- )
156
-
157
- # Response size
158
- response_size_line = self.html.key_value(
159
- "Response Size",
160
- self.html.number(obj.response_size, suffix=" bytes") if obj.response_size else "N/A"
161
- )
162
-
163
- # Authentication
164
- auth_line = self.html.key_value(
165
- "Authenticated",
166
- self.html.badge("Yes" if obj.is_authenticated else "No",
167
- variant="success" if obj.is_authenticated else "secondary")
154
+ """Display performance statistics and authentication info."""
155
+ return self.html.breakdown(
156
+ self.html.key_value(
157
+ "Duration",
158
+ self.html.badge(f"{obj.duration_ms}ms", variant="info", icon=Icons.TIMER)
159
+ ) if obj.duration_ms is not None else None,
160
+ self.html.key_value(
161
+ "Request Size",
162
+ self.html.text(f"{obj.request_size:,} bytes", variant="secondary")
163
+ ) if obj.request_size else None,
164
+ self.html.key_value(
165
+ "Response Size",
166
+ self.html.text(f"{obj.response_size:,} bytes", variant="secondary")
167
+ ) if obj.response_size else None,
168
+ self.html.key_value(
169
+ "Authenticated",
170
+ self.html.badge(
171
+ "Yes" if obj.is_authenticated else "No",
172
+ variant="success" if obj.is_authenticated else "secondary",
173
+ icon=Icons.VERIFIED_USER if obj.is_authenticated else Icons.PERSON
174
+ )
175
+ ),
176
+ self.html.key_value(
177
+ "API Key",
178
+ self.html.inline(
179
+ self.html.badge(obj.api_key.name, variant="info", icon=Icons.KEY),
180
+ self.html.text(f"({obj.api_key.masked_key})", variant="secondary"),
181
+ separator=" "
182
+ )
183
+ ) if obj.api_key else None,
168
184
  )
169
185
 
170
- return self.html.breakdown(duration_line, request_size_line, response_size_line, auth_line)
171
-
172
186
  performance_stats_display.short_description = "Performance Statistics"
173
187
 
174
188
  def client_info_display(self, obj):
175
189
  """Display client information."""
176
- # Client IP
177
- ip_line = self.html.key_value(
178
- "Client IP",
179
- obj.client_ip if obj.client_ip else "N/A"
180
- )
181
-
182
- # User Agent
183
- ua_line = self.html.key_value(
184
- "User Agent",
185
- obj.user_agent if obj.user_agent else "N/A"
186
- )
187
-
188
- # Peer
189
- peer_line = self.html.key_value(
190
- "Peer",
191
- self.html.text(obj.peer, variant="secondary") if obj.peer else "N/A"
190
+ return self.html.breakdown(
191
+ self.html.key_value(
192
+ "Client IP",
193
+ self.html.text(obj.client_ip, variant="info") if obj.client_ip else self.html.empty("N/A")
194
+ ),
195
+ self.html.key_value(
196
+ "User Agent",
197
+ self.html.code(obj.user_agent)
198
+ ) if obj.user_agent else None,
199
+ self.html.key_value(
200
+ "Peer",
201
+ self.html.text(obj.peer, variant="secondary")
202
+ ) if obj.peer else None,
192
203
  )
193
204
 
194
- return self.html.breakdown(ip_line, ua_line, peer_line)
195
-
196
205
  client_info_display.short_description = "Client Information"
197
206
 
198
207
  # Fieldsets for detail view
@@ -205,7 +214,7 @@ class GRPCRequestLogAdmin(PydanticAdmin):
205
214
  ),
206
215
  (
207
216
  "User Context",
208
- {"fields": ("user", "is_authenticated")},
217
+ {"fields": ("user", "api_key", "is_authenticated")},
209
218
  ),
210
219
  (
211
220
  "Performance",
@@ -0,0 +1,236 @@
1
+ """
2
+ gRPC Server Status Admin.
3
+
4
+ PydanticAdmin for GRPCServerStatus model with server monitoring and lifecycle tracking.
5
+ """
6
+
7
+ from django.contrib import admin
8
+ from django_cfg.modules.django_admin import Icons, computed_field
9
+ from django_cfg.modules.django_admin.base import PydanticAdmin
10
+
11
+ from ..models import GRPCServerStatus
12
+ from .config import grpcserverstatus_config
13
+
14
+
15
+ @admin.register(GRPCServerStatus)
16
+ class GRPCServerStatusAdmin(PydanticAdmin):
17
+ """
18
+ Admin interface for gRPC server status monitoring.
19
+
20
+ Features:
21
+ - Real-time server status indicators
22
+ - Uptime tracking and display
23
+ - Process information (PID, hostname)
24
+ - Service registration details
25
+ - Error tracking and display
26
+ """
27
+
28
+ config = grpcserverstatus_config
29
+
30
+ @computed_field("Uptime", ordering="started_at")
31
+ def uptime_display(self, obj):
32
+ """Display server uptime with performance indicator."""
33
+ uptime_text = obj.uptime_display
34
+
35
+ if not obj.is_running:
36
+ return self.html.badge(
37
+ uptime_text,
38
+ variant="secondary",
39
+ icon=Icons.SCHEDULE
40
+ )
41
+
42
+ # Color code based on uptime
43
+ uptime_seconds = obj.uptime_seconds
44
+ if uptime_seconds > 86400: # > 1 day
45
+ variant = "success"
46
+ icon = Icons.CHECK_CIRCLE
47
+ elif uptime_seconds > 3600: # > 1 hour
48
+ variant = "info"
49
+ icon = Icons.TIMER
50
+ else: # < 1 hour
51
+ variant = "warning"
52
+ icon = Icons.SCHEDULE
53
+
54
+ return self.html.badge(uptime_text, variant=variant, icon=icon)
55
+
56
+ def server_config_display(self, obj):
57
+ """Display server configuration details."""
58
+ return self.html.breakdown(
59
+ self.html.key_value(
60
+ "Address",
61
+ self.html.badge(obj.address, variant="info", icon=Icons.CLOUD)
62
+ ),
63
+ self.html.key_value(
64
+ "Host",
65
+ self.html.code(obj.host)
66
+ ),
67
+ self.html.key_value(
68
+ "Port",
69
+ self.html.text(str(obj.port), variant="primary")
70
+ ),
71
+ self.html.key_value(
72
+ "Max Workers",
73
+ self.html.badge(str(obj.max_workers), variant="secondary", icon=Icons.SETTINGS)
74
+ ),
75
+ self.html.key_value(
76
+ "Reflection",
77
+ self.html.badge(
78
+ "Enabled" if obj.enable_reflection else "Disabled",
79
+ variant="success" if obj.enable_reflection else "secondary",
80
+ icon=Icons.VISIBILITY if obj.enable_reflection else Icons.VISIBILITY_OFF
81
+ )
82
+ ),
83
+ self.html.key_value(
84
+ "Health Check",
85
+ self.html.badge(
86
+ "Enabled" if obj.enable_health_check else "Disabled",
87
+ variant="success" if obj.enable_health_check else "secondary",
88
+ icon=Icons.HEALTH_AND_SAFETY if obj.enable_health_check else Icons.CANCEL
89
+ )
90
+ ),
91
+ )
92
+
93
+ server_config_display.short_description = "Server Configuration"
94
+
95
+ def process_info_display(self, obj):
96
+ """Display process information."""
97
+ return self.html.breakdown(
98
+ self.html.key_value(
99
+ "Instance ID",
100
+ self.html.code(obj.instance_id)
101
+ ),
102
+ self.html.key_value(
103
+ "PID",
104
+ self.html.badge(str(obj.pid), variant="info", icon=Icons.MEMORY)
105
+ ),
106
+ self.html.key_value(
107
+ "Hostname",
108
+ self.html.text(obj.hostname, variant="secondary")
109
+ ),
110
+ self.html.key_value(
111
+ "Running",
112
+ self.html.badge(
113
+ "Yes" if obj.is_running else "No",
114
+ variant="success" if obj.is_running else "danger",
115
+ icon=Icons.CHECK_CIRCLE if obj.is_running else Icons.CANCEL
116
+ )
117
+ ),
118
+ )
119
+
120
+ process_info_display.short_description = "Process Information"
121
+
122
+ def registered_services_display(self, obj):
123
+ """Display registered services."""
124
+ if not obj.registered_services:
125
+ return self.html.empty("No services registered")
126
+
127
+ import json
128
+ try:
129
+ formatted = json.dumps(obj.registered_services, indent=2)
130
+ return self.html.code_block(formatted, language="json", max_height="400px")
131
+ except Exception:
132
+ return self.html.code(str(obj.registered_services))
133
+
134
+ registered_services_display.short_description = "Registered Services"
135
+
136
+ def error_display(self, obj):
137
+ """Display error information if status is ERROR."""
138
+ if obj.status != "error" or not obj.error_message:
139
+ return self.html.inline(
140
+ self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
141
+ self.html.text("No errors", variant="success"),
142
+ separator=" "
143
+ )
144
+
145
+ return self.html.breakdown(
146
+ self.html.key_value(
147
+ "Error Message",
148
+ self.html.text(obj.error_message, variant="danger")
149
+ ),
150
+ self.html.key_value(
151
+ "Stopped At",
152
+ self.html.text(
153
+ obj.stopped_at.strftime("%Y-%m-%d %H:%M:%S") if obj.stopped_at else "N/A",
154
+ variant="secondary"
155
+ )
156
+ ),
157
+ )
158
+
159
+ error_display.short_description = "Error Details"
160
+
161
+ def lifecycle_display(self, obj):
162
+ """Display server lifecycle timestamps."""
163
+ return self.html.breakdown(
164
+ self.html.key_value(
165
+ "Started",
166
+ self.html.text(
167
+ obj.started_at.strftime("%Y-%m-%d %H:%M:%S"),
168
+ variant="success"
169
+ )
170
+ ),
171
+ self.html.key_value(
172
+ "Last Heartbeat",
173
+ self.html.text(
174
+ obj.last_heartbeat.strftime("%Y-%m-%d %H:%M:%S"),
175
+ variant="info"
176
+ )
177
+ ) if obj.last_heartbeat else None,
178
+ self.html.key_value(
179
+ "Stopped",
180
+ self.html.text(
181
+ obj.stopped_at.strftime("%Y-%m-%d %H:%M:%S"),
182
+ variant="danger"
183
+ )
184
+ ) if obj.stopped_at else None,
185
+ self.html.key_value(
186
+ "Uptime",
187
+ self.html.badge(obj.uptime_display, variant="primary", icon=Icons.TIMER)
188
+ ),
189
+ )
190
+
191
+ lifecycle_display.short_description = "Lifecycle"
192
+
193
+ # Fieldsets for detail view
194
+ def get_fieldsets(self, request, obj=None):
195
+ """Dynamic fieldsets based on object state."""
196
+ fieldsets = [
197
+ (
198
+ "Server Identity",
199
+ {"fields": ("id", "instance_id", "address", "status")},
200
+ ),
201
+ (
202
+ "Configuration",
203
+ {"fields": ("server_config_display", "host", "port", "max_workers", "enable_reflection", "enable_health_check")},
204
+ ),
205
+ (
206
+ "Process Information",
207
+ {"fields": ("process_info_display", "pid", "hostname", "is_running")},
208
+ ),
209
+ (
210
+ "Lifecycle",
211
+ {"fields": ("lifecycle_display", "started_at", "last_heartbeat", "stopped_at", "uptime_display")},
212
+ ),
213
+ ]
214
+
215
+ # Add registered services section if available
216
+ if obj and obj.registered_services:
217
+ fieldsets.append(
218
+ (
219
+ "Registered Services",
220
+ {"fields": ("registered_services_display",), "classes": ("collapse",)},
221
+ )
222
+ )
223
+
224
+ # Add error section only if status is ERROR
225
+ if obj and obj.status == "error":
226
+ fieldsets.append(
227
+ (
228
+ "Error Details",
229
+ {"fields": ("error_display", "error_message")},
230
+ )
231
+ )
232
+
233
+ return fieldsets
234
+
235
+
236
+ __all__ = ["GRPCServerStatusAdmin"]
@@ -1,9 +1,17 @@
1
1
  """
2
2
  gRPC authentication components.
3
3
 
4
- Provides JWT authentication for gRPC services.
4
+ Provides API key authentication for gRPC services.
5
5
  """
6
6
 
7
- from .jwt_auth import JWTAuthInterceptor
7
+ from .api_key_auth import (
8
+ ApiKeyAuthInterceptor,
9
+ get_current_grpc_user,
10
+ get_current_grpc_api_key,
11
+ )
8
12
 
9
- __all__ = ["JWTAuthInterceptor"]
13
+ __all__ = [
14
+ "ApiKeyAuthInterceptor",
15
+ "get_current_grpc_user",
16
+ "get_current_grpc_api_key",
17
+ ]