django-cfg 1.4.62__py3-none-any.whl → 1.4.63__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 (181) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/services/otp_service.py +3 -14
  3. django_cfg/apps/centrifugo/__init__.py +57 -0
  4. django_cfg/apps/centrifugo/admin/__init__.py +13 -0
  5. django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
  6. django_cfg/apps/centrifugo/admin/config.py +82 -0
  7. django_cfg/apps/centrifugo/apps.py +31 -0
  8. django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
  9. django_cfg/apps/centrifugo/codegen/README.md +242 -0
  10. django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
  11. django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
  12. django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
  13. django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
  14. django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
  15. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
  16. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
  17. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
  18. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
  19. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
  20. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
  21. django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
  22. django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
  23. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
  24. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
  25. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
  26. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
  27. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
  28. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
  29. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
  30. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
  31. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
  32. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
  33. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
  34. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
  35. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
  36. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
  37. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
  38. django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
  39. django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
  40. django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
  41. django_cfg/apps/centrifugo/decorators.py +137 -0
  42. django_cfg/apps/centrifugo/management/__init__.py +1 -0
  43. django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
  44. django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
  45. django_cfg/apps/centrifugo/managers/__init__.py +12 -0
  46. django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
  47. django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
  48. django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
  49. django_cfg/apps/centrifugo/models/__init__.py +11 -0
  50. django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
  51. django_cfg/apps/centrifugo/registry.py +106 -0
  52. django_cfg/apps/centrifugo/router.py +125 -0
  53. django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
  54. django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
  55. django_cfg/apps/centrifugo/serializers/channels.py +26 -0
  56. django_cfg/apps/centrifugo/serializers/health.py +17 -0
  57. django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
  58. django_cfg/apps/centrifugo/serializers/stats.py +21 -0
  59. django_cfg/apps/centrifugo/services/__init__.py +12 -0
  60. django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
  61. django_cfg/apps/centrifugo/services/client/client.py +577 -0
  62. django_cfg/apps/centrifugo/services/client/config.py +228 -0
  63. django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
  64. django_cfg/apps/centrifugo/services/config_helper.py +63 -0
  65. django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
  66. django_cfg/apps/centrifugo/services/logging.py +677 -0
  67. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
  68. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
  69. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
  70. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
  71. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
  72. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
  73. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
  77. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
  78. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
  79. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
  80. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
  81. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
  82. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
  83. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
  84. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
  85. django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
  86. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
  87. django_cfg/apps/centrifugo/urls.py +31 -0
  88. django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
  89. django_cfg/apps/centrifugo/views/__init__.py +15 -0
  90. django_cfg/apps/centrifugo/views/admin_api.py +374 -0
  91. django_cfg/apps/centrifugo/views/dashboard.py +15 -0
  92. django_cfg/apps/centrifugo/views/monitoring.py +286 -0
  93. django_cfg/apps/centrifugo/views/testing_api.py +422 -0
  94. django_cfg/apps/support/utils/support_email_service.py +5 -18
  95. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
  96. django_cfg/apps/urls.py +5 -5
  97. django_cfg/core/base/config_model.py +4 -44
  98. django_cfg/core/builders/apps_builder.py +2 -2
  99. django_cfg/core/generation/integration_generators/third_party.py +8 -8
  100. django_cfg/core/utils/__init__.py +5 -0
  101. django_cfg/core/utils/url_helpers.py +73 -0
  102. django_cfg/modules/base.py +7 -7
  103. django_cfg/modules/django_client/core/__init__.py +2 -1
  104. django_cfg/modules/django_client/core/config/config.py +8 -0
  105. django_cfg/modules/django_client/core/generator/__init__.py +42 -2
  106. django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
  107. django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
  108. django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
  109. django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
  110. django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
  111. django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
  112. django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
  113. django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
  114. django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
  115. django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
  116. django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
  117. django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
  118. django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
  119. django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
  120. django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
  121. django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
  122. django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
  123. django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
  124. django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
  125. django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
  126. django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
  127. django_cfg/modules/django_client/system/schema_parser.py +5 -1
  128. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
  129. django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
  130. django_cfg/modules/django_unfold/dashboard.py +25 -19
  131. django_cfg/pyproject.toml +1 -1
  132. django_cfg/registry/core.py +2 -0
  133. django_cfg/registry/modules.py +2 -2
  134. django_cfg/static/js/api/centrifugo/client.mjs +164 -0
  135. django_cfg/static/js/api/centrifugo/index.mjs +13 -0
  136. django_cfg/static/js/api/index.mjs +5 -5
  137. django_cfg/static/js/api/types.mjs +89 -26
  138. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
  139. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -70
  140. django_cfg/apps/ipc/README.md +0 -346
  141. django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
  142. django_cfg/apps/ipc/TESTING.md +0 -539
  143. django_cfg/apps/ipc/__init__.py +0 -60
  144. django_cfg/apps/ipc/admin.py +0 -232
  145. django_cfg/apps/ipc/apps.py +0 -98
  146. django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
  147. django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
  148. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  149. django_cfg/apps/ipc/models.py +0 -229
  150. django_cfg/apps/ipc/serializers/__init__.py +0 -29
  151. django_cfg/apps/ipc/serializers/serializers.py +0 -343
  152. django_cfg/apps/ipc/services/__init__.py +0 -7
  153. django_cfg/apps/ipc/services/client/__init__.py +0 -23
  154. django_cfg/apps/ipc/services/client/client.py +0 -621
  155. django_cfg/apps/ipc/services/client/config.py +0 -214
  156. django_cfg/apps/ipc/services/client/exceptions.py +0 -201
  157. django_cfg/apps/ipc/services/logging.py +0 -239
  158. django_cfg/apps/ipc/services/monitor.py +0 -466
  159. django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
  160. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
  161. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
  162. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
  163. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
  164. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
  165. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
  166. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
  167. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
  168. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
  169. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
  170. django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
  171. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
  172. django_cfg/apps/ipc/urls.py +0 -23
  173. django_cfg/apps/ipc/views/__init__.py +0 -13
  174. django_cfg/apps/ipc/views/dashboard.py +0 -15
  175. django_cfg/apps/ipc/views/monitoring.py +0 -251
  176. django_cfg/apps/ipc/views/testing.py +0 -285
  177. django_cfg/static/js/api/ipc/client.mjs +0 -114
  178. django_cfg/static/js/api/ipc/index.mjs +0 -13
  179. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
@@ -1,330 +0,0 @@
1
- """
2
- RPC Log Consumer - Redis Stream to Django ORM.
3
-
4
- Consumes RPC events from Redis Stream (published by WebSocket server)
5
- and saves them to Django database for monitoring and analytics.
6
-
7
- Architecture:
8
- - WebSocket Server -> Redis Stream (stream:rpc-logs)
9
- - This Consumer <- Redis Stream -> Django ORM (RPCLog model)
10
-
11
- This maintains service independence while enabling centralized logging.
12
- """
13
-
14
- import asyncio
15
- import json
16
- import time
17
- from typing import Dict, Optional, Any
18
- from django.contrib.auth import get_user_model
19
- from django_cfg.modules.django_logging import get_logger
20
- import redis.asyncio as redis
21
-
22
- logger = get_logger("ipc.consumer")
23
- User = get_user_model()
24
-
25
-
26
- class RPCLogConsumer:
27
- """
28
- Consumes RPC events from Redis Stream and saves to Django ORM.
29
-
30
- Features:
31
- - Async consumer for high throughput
32
- - Automatic reconnection on Redis errors
33
- - Batched processing for performance
34
- - Graceful shutdown
35
- - Error tolerance (continues on DB errors)
36
-
37
- Example:
38
- >>> consumer = RPCLogConsumer(
39
- ... redis_url="redis://localhost:6379/2",
40
- ... stream_name="stream:rpc-logs",
41
- ... )
42
- >>> await consumer.start() # Runs in background
43
- """
44
-
45
- def __init__(
46
- self,
47
- redis_url: str = "redis://localhost:6379/2",
48
- stream_name: str = "stream:rpc-logs",
49
- consumer_group: str = "django-rpc-loggers",
50
- consumer_name: str = "django-1",
51
- batch_size: int = 10,
52
- block_ms: int = 1000,
53
- ):
54
- """
55
- Initialize RPC Log Consumer.
56
-
57
- Args:
58
- redis_url: Redis connection URL
59
- stream_name: Redis Stream name to consume
60
- consumer_group: Consumer group name
61
- consumer_name: Unique consumer name
62
- batch_size: Number of messages to process per batch
63
- block_ms: Block time for XREADGROUP (milliseconds)
64
- """
65
- self.redis_url = redis_url
66
- self.stream_name = stream_name
67
- self.consumer_group = consumer_group
68
- self.consumer_name = consumer_name
69
- self.batch_size = batch_size
70
- self.block_ms = block_ms
71
-
72
- self._redis: Optional[redis.Redis] = None
73
- self._running = False
74
- self._task: Optional[asyncio.Task] = None
75
-
76
- # Pending requests (correlation_id -> log_entry)
77
- self._pending_requests: Dict[str, Any] = {}
78
-
79
- async def initialize(self) -> None:
80
- """Initialize Redis connection and consumer group."""
81
- try:
82
- self._redis = await redis.from_url(
83
- self.redis_url,
84
- decode_responses=True, # Decode Redis responses to strings
85
- socket_keepalive=True,
86
- )
87
-
88
- # Test connection
89
- await self._redis.ping()
90
-
91
- # Create consumer group if not exists
92
- try:
93
- await self._redis.xgroup_create(
94
- name=self.stream_name,
95
- groupname=self.consumer_group,
96
- id="0", # Start from beginning
97
- mkstream=True, # Create stream if not exists
98
- )
99
- logger.info(f"✅ Created consumer group: {self.consumer_group}")
100
- except redis.ResponseError as e:
101
- if "BUSYGROUP" in str(e):
102
- logger.debug(f"Consumer group already exists: {self.consumer_group}")
103
- else:
104
- raise
105
-
106
- logger.info(f"✅ RPC Log Consumer initialized: {self.stream_name}")
107
-
108
- except Exception as e:
109
- logger.error(f"❌ Failed to initialize RPC Log Consumer: {e}")
110
- raise
111
-
112
- async def start(self) -> None:
113
- """Start consuming RPC events from Redis Stream."""
114
- if self._running:
115
- logger.warning("RPC Log Consumer already running")
116
- return
117
-
118
- self._running = True
119
- self._task = asyncio.create_task(self._consume_loop())
120
- logger.info(f"🚀 RPC Log Consumer started: {self.consumer_name}")
121
-
122
- async def stop(self) -> None:
123
- """Stop consuming and cleanup."""
124
- if not self._running:
125
- return
126
-
127
- logger.info("⏹️ Stopping RPC Log Consumer...")
128
- self._running = False
129
-
130
- if self._task:
131
- self._task.cancel()
132
- try:
133
- await self._task
134
- except asyncio.CancelledError:
135
- pass
136
-
137
- if self._redis:
138
- await self._redis.close()
139
-
140
- logger.info("✅ RPC Log Consumer stopped")
141
-
142
- async def _consume_loop(self) -> None:
143
- """Main consume loop - reads from Redis Stream and processes events."""
144
- last_id = ">" # Read only new messages
145
-
146
- while self._running:
147
- try:
148
- # Read from stream
149
- messages = await self._redis.xreadgroup(
150
- groupname=self.consumer_group,
151
- consumername=self.consumer_name,
152
- streams={self.stream_name: last_id},
153
- count=self.batch_size,
154
- block=self.block_ms,
155
- )
156
-
157
- if not messages:
158
- continue # No new messages, continue
159
-
160
- # Process messages
161
- for stream_name, stream_messages in messages:
162
- for message_id, message_data in stream_messages:
163
- try:
164
- await self._process_event(message_id, message_data)
165
-
166
- # Acknowledge message
167
- await self._redis.xack(
168
- self.stream_name,
169
- self.consumer_group,
170
- message_id,
171
- )
172
-
173
- except Exception as e:
174
- logger.error(
175
- f"Error processing RPC event {message_id}: {e}",
176
- exc_info=True
177
- )
178
- # Continue processing other messages
179
-
180
- except asyncio.CancelledError:
181
- logger.info("RPC Log Consumer loop cancelled")
182
- break
183
- except Exception as e:
184
- logger.error(f"Error in RPC Log Consumer loop: {e}", exc_info=True)
185
- # Wait before retrying
186
- await asyncio.sleep(5)
187
-
188
- async def _process_event(self, message_id: str, event_data: Dict[str, str]) -> None:
189
- """
190
- Process single RPC event from Redis Stream.
191
-
192
- Args:
193
- message_id: Redis message ID
194
- event_data: Event data from stream
195
- """
196
- event_type = event_data.get("event_type")
197
- correlation_id = event_data.get("correlation_id")
198
-
199
- if not correlation_id:
200
- logger.warning(f"Event missing correlation_id: {message_id}")
201
- return
202
-
203
- if event_type == "request":
204
- await self._handle_request(correlation_id, event_data)
205
- elif event_type == "response":
206
- await self._handle_response(correlation_id, event_data)
207
- else:
208
- logger.warning(f"Unknown event type: {event_type}")
209
-
210
- async def _handle_request(self, correlation_id: str, event_data: Dict[str, str]) -> None:
211
- """
212
- Handle RPC request event - create RPCLog entry.
213
-
214
- Args:
215
- correlation_id: Correlation ID
216
- event_data: Event data from stream
217
- """
218
- try:
219
- from ..models import RPCLog
220
-
221
- method = event_data.get("method")
222
- params_json = event_data.get("params", "{}")
223
- user_id = event_data.get("user_id")
224
-
225
- # Parse params
226
- try:
227
- params = json.loads(params_json)
228
- except json.JSONDecodeError:
229
- params = {}
230
-
231
- # Get user if user_id provided
232
- user = None
233
- if user_id:
234
- try:
235
- user = await asyncio.to_thread(User.objects.get, pk=user_id)
236
- except User.DoesNotExist:
237
- pass
238
-
239
- # Create log entry in database (sync operation in thread)
240
- log_entry = await asyncio.to_thread(
241
- RPCLog.objects.create,
242
- correlation_id=correlation_id,
243
- method=method,
244
- params=params,
245
- user=user,
246
- status=RPCLog.StatusChoices.PENDING,
247
- )
248
-
249
- # Store in pending for later update
250
- self._pending_requests[correlation_id] = log_entry
251
-
252
- logger.debug(f"Created RPC log entry: {method} ({correlation_id})")
253
-
254
- except Exception as e:
255
- logger.error(f"Failed to handle RPC request: {e}", exc_info=True)
256
-
257
- async def _handle_response(self, correlation_id: str, event_data: Dict[str, str]) -> None:
258
- """
259
- Handle RPC response event - update RPCLog entry.
260
-
261
- Args:
262
- correlation_id: Correlation ID
263
- event_data: Event data from stream
264
- """
265
- try:
266
- from ..models import RPCLog
267
-
268
- # Get log entry from pending or database
269
- log_entry = self._pending_requests.pop(correlation_id, None)
270
-
271
- if not log_entry:
272
- # Try to find in database with retry (race condition with request handler)
273
- max_retries = 3
274
- retry_delay = 0.1 # 100ms
275
-
276
- for attempt in range(max_retries):
277
- try:
278
- log_entry = await asyncio.to_thread(
279
- RPCLog.objects.get,
280
- correlation_id=correlation_id
281
- )
282
- break # Found it!
283
- except RPCLog.DoesNotExist:
284
- if attempt < max_retries - 1:
285
- # Wait a bit and retry
286
- await asyncio.sleep(retry_delay)
287
- else:
288
- # Give up after retries
289
- logger.warning(f"RPCLog not found for correlation_id: {correlation_id} after {max_retries} retries")
290
- return
291
-
292
- # Parse response data
293
- success = event_data.get("success") == "1"
294
- duration_ms = int(event_data.get("duration_ms", 0))
295
-
296
- if success:
297
- result_json = event_data.get("result", "{}")
298
- try:
299
- result = json.loads(result_json)
300
- except json.JSONDecodeError:
301
- result = {}
302
-
303
- # Mark as success (sync operation in thread)
304
- await asyncio.to_thread(
305
- log_entry.mark_success,
306
- result,
307
- duration_ms
308
- )
309
-
310
- logger.debug(f"Marked RPC log as success: {correlation_id}")
311
-
312
- else:
313
- error_code = event_data.get("error_code", "unknown")
314
- error_message = event_data.get("error_message", "")
315
-
316
- # Mark as failed (sync operation in thread)
317
- await asyncio.to_thread(
318
- log_entry.mark_failed,
319
- error_code,
320
- error_message,
321
- duration_ms
322
- )
323
-
324
- logger.debug(f"Marked RPC log as failed: {correlation_id}")
325
-
326
- except Exception as e:
327
- logger.error(f"Failed to handle RPC response: {e}", exc_info=True)
328
-
329
-
330
- __all__ = ["RPCLogConsumer"]
@@ -1,269 +0,0 @@
1
- /**
2
- * Main RPC Dashboard Controller
3
- * Orchestrates all dashboard modules and handles tab navigation
4
- */
5
- import { OverviewModule } from './overview.mjs';
6
- import { TestingModule } from './testing.mjs';
7
-
8
- class RPCDashboard {
9
- constructor() {
10
- this.api = window.ipcAPI;
11
- this.currentTab = 'overview';
12
- this.autoRefresh = true;
13
- this.refreshInterval = null;
14
- this.refreshRate = 5000; // 5 seconds
15
-
16
- // Initialize modules
17
- this.overviewModule = new OverviewModule(this.api, this);
18
- this.testingModule = new TestingModule(this.api, this);
19
- }
20
-
21
- /**
22
- * Initialize dashboard
23
- */
24
- init() {
25
- console.log('🚀 RPC Dashboard initializing...');
26
- this.setupEventListeners();
27
- this.testingModule.init();
28
- this.loadInitialData();
29
- this.startAutoRefresh();
30
- console.log('✅ RPC Dashboard initialized');
31
- }
32
-
33
- /**
34
- * Setup event listeners
35
- */
36
- setupEventListeners() {
37
- // Tab buttons
38
- const tabButtons = document.querySelectorAll('.tab-button');
39
- tabButtons.forEach(button => {
40
- button.addEventListener('click', (e) => {
41
- // Use currentTarget to get the button element, not the clicked child element
42
- const tabName = e.currentTarget.dataset.tab;
43
- this.switchTab(tabName);
44
- });
45
- });
46
-
47
- // Auto-refresh toggle
48
- const autoRefreshToggle = document.getElementById('auto-refresh-toggle');
49
- if (autoRefreshToggle) {
50
- autoRefreshToggle.checked = this.autoRefresh;
51
- autoRefreshToggle.addEventListener('change', (e) => {
52
- this.autoRefresh = e.target.checked;
53
- if (this.autoRefresh) {
54
- this.startAutoRefresh();
55
- } else {
56
- this.stopAutoRefresh();
57
- }
58
- });
59
- }
60
- }
61
-
62
- /**
63
- * Switch tabs
64
- */
65
- switchTab(tabName) {
66
- console.log('Switching to tab:', tabName);
67
-
68
- // Update tab buttons styling
69
- document.querySelectorAll('.tab-button').forEach(btn => {
70
- if (btn.dataset.tab === tabName) {
71
- btn.classList.add('active', 'text-blue-600', 'dark:text-blue-400', 'border-blue-600', 'dark:border-blue-400');
72
- btn.classList.remove('text-gray-600', 'dark:text-gray-400', 'border-transparent');
73
- } else {
74
- btn.classList.remove('active', 'text-blue-600', 'dark:text-blue-400', 'border-blue-600', 'dark:border-blue-400');
75
- btn.classList.add('text-gray-600', 'dark:text-gray-400', 'border-transparent');
76
- }
77
- });
78
-
79
- // Update tab panels
80
- document.querySelectorAll('.tab-panel').forEach(panel => {
81
- panel.classList.add('hidden');
82
- });
83
-
84
- const activePanel = document.getElementById(`${tabName}-tab`);
85
- if (activePanel) {
86
- activePanel.classList.remove('hidden');
87
- }
88
-
89
- this.currentTab = tabName;
90
- this.loadTabData(tabName);
91
- }
92
-
93
- /**
94
- * Load initial data
95
- */
96
- async loadInitialData() {
97
- console.log('Loading initial data...');
98
- await this.loadHealthStatus();
99
- await this.loadTabData(this.currentTab);
100
- }
101
-
102
- /**
103
- * Load data for specific tab
104
- */
105
- async loadTabData(tabName) {
106
- console.log('Loading data for tab:', tabName);
107
-
108
- try {
109
- // Load overview stats for stat cards (always visible)
110
- await this.overviewModule.loadOverviewStats();
111
-
112
- // Load tab-specific data
113
- if (tabName === 'testing') {
114
- // Testing tab doesn't need data loading, it's interactive
115
- } else {
116
- // Overview, requests, notifications, methods tabs
117
- await this.overviewModule.loadData(tabName);
118
- }
119
-
120
- this.updateLastUpdate();
121
-
122
- } catch (error) {
123
- console.error('Error loading tab data:', error);
124
- }
125
- }
126
-
127
- /**
128
- * Load health status
129
- */
130
- async loadHealthStatus() {
131
- try {
132
- const health = await this.api.ipcAdminApiMonitorHealthRetrieve();
133
-
134
- if (health) {
135
- // Update health indicator
136
- const indicator = document.getElementById('health-indicator');
137
- if (indicator) {
138
- const isHealthy = health.redis_connected && health.stream_exists;
139
- indicator.innerHTML = `
140
- <span class="pulse-dot w-2 h-2 ${isHealthy ? 'bg-green-500' : 'bg-red-500'} rounded-full"></span>
141
- <span class="text-gray-700 dark:text-gray-300">${isHealthy ? 'Connected' : 'Disconnected'}</span>
142
- `;
143
- }
144
-
145
- // Update system status section
146
- this.updateSystemStatus(health);
147
- }
148
- } catch (error) {
149
- console.error('Error loading health status:', error);
150
- }
151
- }
152
-
153
- /**
154
- * Update system status section
155
- */
156
- updateSystemStatus(health) {
157
- const statusContainer = document.getElementById('system-status');
158
- if (!statusContainer) return;
159
-
160
- statusContainer.innerHTML = `
161
- <!-- Redis Status -->
162
- <div class="flex items-start gap-3">
163
- <span class="material-icons flex-shrink-0 text-2xl ${health.redis_connected ? 'text-green-500' : 'text-red-500'}">
164
- ${health.redis_connected ? 'check_circle' : 'cancel'}
165
- </span>
166
- <div class="min-w-0">
167
- <p class="text-sm font-medium text-gray-900 dark:text-white">Redis</p>
168
- <p class="text-xs text-gray-600 dark:text-gray-400">
169
- ${health.redis_connected ? 'Connected (DB 2)' : 'Disconnected'}
170
- </p>
171
- </div>
172
- </div>
173
-
174
- <!-- Stream Status -->
175
- <div class="flex items-start gap-3">
176
- <span class="material-icons flex-shrink-0 text-2xl ${health.stream_exists ? 'text-green-500' : 'text-gray-500'}">
177
- ${health.stream_exists ? 'stream' : 'stream_off'}
178
- </span>
179
- <div class="min-w-0">
180
- <p class="text-sm font-medium text-gray-900 dark:text-white">Request Stream</p>
181
- <p class="text-xs text-gray-600 dark:text-gray-400">
182
- ${health.stream_exists ? `${health.stream_length} entries` : 'Not initialized'}
183
- </p>
184
- </div>
185
- </div>
186
-
187
- <!-- Activity Status -->
188
- <div class="flex items-start gap-3">
189
- <span class="material-icons flex-shrink-0 text-2xl ${health.recent_activity ? 'text-green-500' : 'text-yellow-500'}">
190
- ${health.recent_activity ? 'notifications_active' : 'notifications_paused'}
191
- </span>
192
- <div class="min-w-0">
193
- <p class="text-sm font-medium text-gray-900 dark:text-white">Recent Activity</p>
194
- <p class="text-xs text-gray-600 dark:text-gray-400">
195
- ${health.recent_activity ? 'Active (last 5 min)' : 'No recent activity'}
196
- </p>
197
- </div>
198
- </div>
199
- `;
200
- }
201
-
202
- /**
203
- * Update last update timestamp
204
- */
205
- updateLastUpdate() {
206
- const element = document.getElementById('last-update');
207
- if (element) {
208
- const now = new Date();
209
- element.textContent = `Last updated: ${now.toLocaleTimeString()}`;
210
- }
211
- }
212
-
213
- /**
214
- * Start auto-refresh
215
- */
216
- startAutoRefresh() {
217
- if (this.refreshInterval) {
218
- clearInterval(this.refreshInterval);
219
- }
220
-
221
- this.refreshInterval = setInterval(() => {
222
- if (this.autoRefresh) {
223
- this.loadHealthStatus();
224
- this.loadTabData(this.currentTab);
225
- }
226
- }, this.refreshRate);
227
- }
228
-
229
- /**
230
- * Stop auto-refresh
231
- */
232
- stopAutoRefresh() {
233
- if (this.refreshInterval) {
234
- clearInterval(this.refreshInterval);
235
- this.refreshInterval = null;
236
- }
237
- }
238
- }
239
-
240
- /**
241
- * Wait for both DOM and API to be ready
242
- */
243
- async function initializeDashboard() {
244
- console.log('DOM loaded, waiting for IpcAPI...');
245
-
246
- // Wait for API to be available (max 5 seconds)
247
- let attempts = 0;
248
- const maxAttempts = 100; // 100 * 50ms = 5 seconds
249
-
250
- while (!window.ipcAPI && attempts < maxAttempts) {
251
- await new Promise(resolve => setTimeout(resolve, 50));
252
- attempts++;
253
- }
254
-
255
- if (!window.ipcAPI) {
256
- console.error('❌ IpcAPI failed to load after 5 seconds');
257
- return;
258
- }
259
-
260
- console.log('✅ IpcAPI ready, initializing dashboard...');
261
- window.rpcDashboard = new RPCDashboard();
262
- window.rpcDashboard.init();
263
- }
264
-
265
- // Initialize dashboard when DOM is ready
266
- document.addEventListener('DOMContentLoaded', initializeDashboard);
267
-
268
- // Export for debugging
269
- export default RPCDashboard;