django-cfg 1.4.62__py3-none-any.whl → 1.4.64__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/accounts/services/otp_service.py +3 -14
- django_cfg/apps/centrifugo/__init__.py +57 -0
- django_cfg/apps/centrifugo/admin/__init__.py +13 -0
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
- django_cfg/apps/centrifugo/admin/config.py +82 -0
- django_cfg/apps/centrifugo/apps.py +31 -0
- django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
- django_cfg/apps/centrifugo/codegen/README.md +242 -0
- django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
- django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
- django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
- django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
- django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
- django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
- django_cfg/apps/centrifugo/decorators.py +137 -0
- django_cfg/apps/centrifugo/management/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
- django_cfg/apps/centrifugo/managers/__init__.py +12 -0
- django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
- django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
- django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
- django_cfg/apps/centrifugo/models/__init__.py +11 -0
- django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
- django_cfg/apps/centrifugo/registry.py +106 -0
- django_cfg/apps/centrifugo/router.py +125 -0
- django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
- django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
- django_cfg/apps/centrifugo/serializers/channels.py +26 -0
- django_cfg/apps/centrifugo/serializers/health.py +17 -0
- django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
- django_cfg/apps/centrifugo/serializers/stats.py +21 -0
- django_cfg/apps/centrifugo/services/__init__.py +12 -0
- django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
- django_cfg/apps/centrifugo/services/client/client.py +582 -0
- django_cfg/apps/centrifugo/services/client/config.py +236 -0
- django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
- django_cfg/apps/centrifugo/services/config_helper.py +63 -0
- django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
- django_cfg/apps/centrifugo/services/logging.py +677 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
- django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
- django_cfg/apps/centrifugo/urls.py +31 -0
- django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
- django_cfg/apps/centrifugo/views/__init__.py +15 -0
- django_cfg/apps/centrifugo/views/admin_api.py +380 -0
- django_cfg/apps/centrifugo/views/dashboard.py +15 -0
- django_cfg/apps/centrifugo/views/monitoring.py +286 -0
- django_cfg/apps/centrifugo/views/testing_api.py +422 -0
- django_cfg/apps/support/utils/support_email_service.py +5 -18
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
- django_cfg/apps/urls.py +5 -5
- django_cfg/core/base/config_model.py +4 -44
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +8 -8
- django_cfg/core/utils/__init__.py +5 -0
- django_cfg/core/utils/url_helpers.py +73 -0
- django_cfg/modules/base.py +7 -7
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/config/config.py +8 -0
- django_cfg/modules/django_client/core/generator/__init__.py +42 -2
- django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
- django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
- django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
- django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
- django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
- django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
- django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
- django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
- django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
- django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
- django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
- django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
- django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
- django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
- django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
- django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
- django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
- django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
- django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
- django_cfg/modules/django_client/system/schema_parser.py +5 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
- django_cfg/modules/django_unfold/dashboard.py +25 -19
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/core.py +2 -0
- django_cfg/registry/modules.py +2 -2
- django_cfg/static/js/api/centrifugo/client.mjs +164 -0
- django_cfg/static/js/api/centrifugo/index.mjs +13 -0
- django_cfg/static/js/api/index.mjs +5 -5
- django_cfg/static/js/api/types.mjs +89 -26
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/METADATA +1 -1
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/RECORD +142 -70
- django_cfg/apps/ipc/README.md +0 -346
- django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
- django_cfg/apps/ipc/TESTING.md +0 -539
- django_cfg/apps/ipc/__init__.py +0 -60
- django_cfg/apps/ipc/admin.py +0 -232
- django_cfg/apps/ipc/apps.py +0 -98
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -229
- django_cfg/apps/ipc/serializers/__init__.py +0 -29
- django_cfg/apps/ipc/serializers/serializers.py +0 -343
- django_cfg/apps/ipc/services/__init__.py +0 -7
- django_cfg/apps/ipc/services/client/__init__.py +0 -23
- django_cfg/apps/ipc/services/client/client.py +0 -621
- django_cfg/apps/ipc/services/client/config.py +0 -214
- django_cfg/apps/ipc/services/client/exceptions.py +0 -201
- django_cfg/apps/ipc/services/logging.py +0 -239
- django_cfg/apps/ipc/services/monitor.py +0 -466
- django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
- django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
- django_cfg/apps/ipc/urls.py +0 -23
- django_cfg/apps/ipc/views/__init__.py +0 -13
- django_cfg/apps/ipc/views/dashboard.py +0 -15
- django_cfg/apps/ipc/views/monitoring.py +0 -251
- django_cfg/apps/ipc/views/testing.py +0 -285
- django_cfg/static/js/api/ipc/client.mjs +0 -114
- django_cfg/static/js/api/ipc/index.mjs +0 -13
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.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;
|