django-cfg 1.4.59__py3-none-any.whl → 1.4.61__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 (53) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/ipc/RPC_LOGGING.md +321 -0
  3. django_cfg/apps/ipc/TESTING.md +539 -0
  4. django_cfg/apps/ipc/__init__.py +12 -3
  5. django_cfg/apps/ipc/admin.py +212 -0
  6. django_cfg/apps/ipc/migrations/0001_initial.py +137 -0
  7. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  8. django_cfg/apps/ipc/models.py +221 -0
  9. django_cfg/apps/ipc/serializers/__init__.py +10 -0
  10. django_cfg/apps/ipc/serializers/serializers.py +114 -0
  11. django_cfg/apps/ipc/services/client/client.py +83 -4
  12. django_cfg/apps/ipc/services/logging.py +239 -0
  13. django_cfg/apps/ipc/services/monitor.py +5 -3
  14. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +269 -0
  15. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +259 -0
  16. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +375 -0
  17. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +22 -0
  18. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +9 -0
  19. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +9 -0
  20. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +23 -0
  21. django_cfg/apps/ipc/templates/django_cfg_ipc/components/stat_cards.html +50 -0
  22. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +47 -0
  23. django_cfg/apps/ipc/templates/django_cfg_ipc/components/tab_navigation.html +29 -0
  24. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +184 -0
  25. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +56 -0
  26. django_cfg/apps/ipc/urls.py +4 -2
  27. django_cfg/apps/ipc/views/__init__.py +7 -2
  28. django_cfg/apps/ipc/views/dashboard.py +1 -1
  29. django_cfg/apps/ipc/views/{viewsets.py → monitoring.py} +17 -11
  30. django_cfg/apps/ipc/views/testing.py +285 -0
  31. django_cfg/modules/django_client/system/generate_mjs_clients.py +1 -1
  32. django_cfg/modules/django_dashboard/sections/widgets.py +209 -0
  33. django_cfg/modules/django_unfold/callbacks/main.py +43 -18
  34. django_cfg/modules/django_unfold/dashboard.py +41 -4
  35. django_cfg/pyproject.toml +1 -1
  36. django_cfg/static/js/api/index.mjs +8 -3
  37. django_cfg/static/js/api/ipc/client.mjs +40 -0
  38. django_cfg/static/js/api/knowbase/client.mjs +309 -0
  39. django_cfg/static/js/api/knowbase/index.mjs +13 -0
  40. django_cfg/static/js/api/payments/client.mjs +46 -1215
  41. django_cfg/static/js/api/types.mjs +164 -337
  42. django_cfg/templates/admin/index.html +8 -0
  43. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +13 -1
  44. django_cfg/templates/admin/sections/widgets_section.html +129 -0
  45. django_cfg/templates/admin/snippets/tabs/widgets_tab.html +38 -0
  46. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/METADATA +1 -1
  47. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/RECORD +52 -28
  48. django_cfg/apps/ipc/templates/django_cfg_ipc/dashboard.html +0 -202
  49. /django_cfg/apps/ipc/static/django_cfg_ipc/js/{dashboard.mjs → dashboard.mjs.old} +0 -0
  50. /django_cfg/apps/ipc/templates/django_cfg_ipc/{base.html → layout/base.html} +0 -0
  51. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/WHEEL +0 -0
  52. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/entry_points.txt +0 -0
  53. {django_cfg-1.4.59.dist-info → django_cfg-1.4.61.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,212 @@
1
+ """
2
+ Django Admin for IPC/RPC models.
3
+
4
+ Uses PydanticAdmin with declarative configuration.
5
+ """
6
+
7
+ import json
8
+
9
+ from django.contrib import admin
10
+ from django_cfg.modules.django_admin import (
11
+ AdminConfig,
12
+ BadgeField,
13
+ DateTimeField,
14
+ Icons,
15
+ UserField,
16
+ computed_field,
17
+ )
18
+ from django_cfg.modules.django_admin.base import PydanticAdmin
19
+
20
+ from .models import RPCLog
21
+
22
+
23
+ # Declarative configuration for RPCLog
24
+ rpclog_config = AdminConfig(
25
+ model=RPCLog,
26
+
27
+ # Performance optimization
28
+ select_related=["user"],
29
+
30
+ # List display
31
+ list_display=[
32
+ "method",
33
+ "status",
34
+ "user",
35
+ "duration_ms",
36
+ "created_at",
37
+ "completed_at"
38
+ ],
39
+
40
+ # Auto-generated display methods
41
+ display_fields=[
42
+ BadgeField(
43
+ name="method",
44
+ title="RPC Method",
45
+ variant="info",
46
+ icon=Icons.API
47
+ ),
48
+ BadgeField(
49
+ name="status",
50
+ title="Status",
51
+ label_map={
52
+ "pending": "warning",
53
+ "success": "success",
54
+ "failed": "danger",
55
+ "timeout": "danger"
56
+ }
57
+ ),
58
+ UserField(
59
+ name="user",
60
+ title="User",
61
+ header=True # Show with avatar
62
+ ),
63
+ DateTimeField(
64
+ name="created_at",
65
+ title="Created",
66
+ ordering="created_at"
67
+ ),
68
+ DateTimeField(
69
+ name="completed_at",
70
+ title="Completed",
71
+ ordering="completed_at"
72
+ ),
73
+ ],
74
+
75
+ # Filters
76
+ list_filter=["status", "method", "created_at"],
77
+ search_fields=["method", "correlation_id", "user__username", "user__email", "error_message"],
78
+
79
+ # Readonly fields (custom methods below)
80
+ readonly_fields=[
81
+ "id",
82
+ "correlation_id",
83
+ "created_at",
84
+ "completed_at",
85
+ "params_display",
86
+ "response_display",
87
+ "error_details_display"
88
+ ],
89
+
90
+ # Date hierarchy
91
+ date_hierarchy="created_at",
92
+
93
+ # Per page
94
+ list_per_page=50,
95
+ )
96
+
97
+
98
+ @admin.register(RPCLog)
99
+ class RPCLogAdmin(PydanticAdmin):
100
+ """
101
+ RPC log admin with analytics and filtering.
102
+
103
+ Features:
104
+ - Color-coded status badges
105
+ - Duration display with performance indicators
106
+ - Formatted JSON for params/response
107
+ - Error details with highlighted display
108
+ """
109
+ config = rpclog_config
110
+
111
+ @computed_field("Duration", ordering="duration_ms")
112
+ def duration_display(self, obj):
113
+ """Display duration with color coding based on speed."""
114
+ if obj.duration_ms is None:
115
+ return self.html.empty()
116
+
117
+ # Color code based on duration
118
+ if obj.duration_ms < 100:
119
+ variant = "success" # Fast
120
+ icon = Icons.SPEED
121
+ elif obj.duration_ms < 500:
122
+ variant = "warning" # Moderate
123
+ icon = Icons.TIMER
124
+ else:
125
+ variant = "danger" # Slow
126
+ icon = Icons.ERROR
127
+
128
+ return self.html.badge(
129
+ f"{obj.duration_ms}ms",
130
+ variant=variant,
131
+ icon=icon
132
+ )
133
+
134
+ def params_display(self, obj):
135
+ """Display formatted JSON params."""
136
+ if not obj.params:
137
+ return self.html.empty("No parameters")
138
+
139
+ try:
140
+ formatted = json.dumps(obj.params, indent=2)
141
+ return f'<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto; font-size: 12px; line-height: 1.5;">{formatted}</pre>'
142
+ except Exception:
143
+ return str(obj.params)
144
+
145
+ params_display.short_description = "Request Parameters"
146
+
147
+ def response_display(self, obj):
148
+ """Display formatted JSON response."""
149
+ if not obj.response:
150
+ return self.html.empty("No response")
151
+
152
+ try:
153
+ formatted = json.dumps(obj.response, indent=2)
154
+ return f'<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto; font-size: 12px; line-height: 1.5;">{formatted}</pre>'
155
+ except Exception:
156
+ return str(obj.response)
157
+
158
+ response_display.short_description = "Response Data"
159
+
160
+ def error_details_display(self, obj):
161
+ """Display error information if call failed."""
162
+ if not obj.is_failed:
163
+ return self.html.inline([
164
+ self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
165
+ self.html.span("No errors", "text-green-600")
166
+ ])
167
+
168
+ details = []
169
+
170
+ if obj.error_code:
171
+ details.append(self.html.inline([
172
+ self.html.span("Error Code:", "font-semibold"),
173
+ self.html.badge(obj.error_code, variant="danger", icon=Icons.ERROR)
174
+ ], separator=" "))
175
+
176
+ if obj.error_message:
177
+ details.append(self.html.inline([
178
+ self.html.span("Message:", "font-semibold"),
179
+ self.html.span(obj.error_message, "text-red-600")
180
+ ], separator=" "))
181
+
182
+ return "<br>".join(details) if details else self.html.empty()
183
+
184
+ error_details_display.short_description = "Error Details"
185
+
186
+ # Fieldsets for detail view
187
+ def get_fieldsets(self, request, obj=None):
188
+ """Dynamic fieldsets based on object state."""
189
+ fieldsets = [
190
+ ("RPC Call Information", {
191
+ 'fields': ('id', 'correlation_id', 'method', 'user', 'status')
192
+ }),
193
+ ("Request & Response", {
194
+ 'fields': ('params_display', 'response_display'),
195
+ 'classes': ('collapse',)
196
+ }),
197
+ ("Performance", {
198
+ 'fields': ('duration_ms', 'created_at', 'completed_at')
199
+ }),
200
+ ("Metadata", {
201
+ 'fields': ('caller_ip', 'user_agent'),
202
+ 'classes': ('collapse',)
203
+ }),
204
+ ]
205
+
206
+ # Add error section only if failed
207
+ if obj and obj.is_failed:
208
+ fieldsets.insert(2, ("Error Details", {
209
+ 'fields': ('error_details_display', 'error_code', 'error_message')
210
+ }))
211
+
212
+ return fieldsets
@@ -0,0 +1,137 @@
1
+ # Generated by Django 5.2.7 on 2025-10-23 12:12
2
+
3
+ import django.db.models.deletion
4
+ import django.utils.timezone
5
+ import uuid
6
+ from django.conf import settings
7
+ from django.db import migrations, models
8
+
9
+
10
+ class Migration(migrations.Migration):
11
+ initial = True
12
+
13
+ dependencies = [
14
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15
+ ]
16
+
17
+ operations = [
18
+ migrations.CreateModel(
19
+ name="RPCLog",
20
+ fields=[
21
+ (
22
+ "id",
23
+ models.UUIDField(
24
+ default=uuid.uuid4, editable=False, primary_key=True, serialize=False
25
+ ),
26
+ ),
27
+ (
28
+ "correlation_id",
29
+ models.CharField(
30
+ db_index=True,
31
+ help_text="UUID from RPC request",
32
+ max_length=100,
33
+ verbose_name="Correlation ID",
34
+ ),
35
+ ),
36
+ (
37
+ "method",
38
+ models.CharField(
39
+ db_index=True,
40
+ help_text="e.g., send_notification, workspace.file_changed",
41
+ max_length=100,
42
+ verbose_name="RPC Method",
43
+ ),
44
+ ),
45
+ (
46
+ "params",
47
+ models.JSONField(
48
+ help_text="Parameters sent to RPC method", verbose_name="Request Params"
49
+ ),
50
+ ),
51
+ (
52
+ "response",
53
+ models.JSONField(
54
+ blank=True,
55
+ help_text="Result from RPC call",
56
+ null=True,
57
+ verbose_name="Response Data",
58
+ ),
59
+ ),
60
+ (
61
+ "status",
62
+ models.CharField(
63
+ choices=[
64
+ ("pending", "Pending"),
65
+ ("success", "Success"),
66
+ ("failed", "Failed"),
67
+ ("timeout", "Timeout"),
68
+ ],
69
+ db_index=True,
70
+ default="pending",
71
+ max_length=20,
72
+ verbose_name="Status",
73
+ ),
74
+ ),
75
+ (
76
+ "error_code",
77
+ models.CharField(
78
+ blank=True, max_length=50, null=True, verbose_name="Error Code"
79
+ ),
80
+ ),
81
+ (
82
+ "error_message",
83
+ models.TextField(blank=True, null=True, verbose_name="Error Message"),
84
+ ),
85
+ (
86
+ "duration_ms",
87
+ models.IntegerField(
88
+ blank=True,
89
+ help_text="Time taken for RPC call in milliseconds",
90
+ null=True,
91
+ verbose_name="Duration (ms)",
92
+ ),
93
+ ),
94
+ (
95
+ "caller_ip",
96
+ models.GenericIPAddressField(blank=True, null=True, verbose_name="Caller IP"),
97
+ ),
98
+ ("user_agent", models.TextField(blank=True, null=True, verbose_name="User Agent")),
99
+ (
100
+ "created_at",
101
+ models.DateTimeField(
102
+ db_index=True, default=django.utils.timezone.now, verbose_name="Created At"
103
+ ),
104
+ ),
105
+ (
106
+ "completed_at",
107
+ models.DateTimeField(blank=True, null=True, verbose_name="Completed At"),
108
+ ),
109
+ (
110
+ "user",
111
+ models.ForeignKey(
112
+ blank=True,
113
+ null=True,
114
+ on_delete=django.db.models.deletion.SET_NULL,
115
+ related_name="rpc_logs",
116
+ to=settings.AUTH_USER_MODEL,
117
+ verbose_name="User",
118
+ ),
119
+ ),
120
+ ],
121
+ options={
122
+ "verbose_name": "RPC Log",
123
+ "verbose_name_plural": "RPC Logs",
124
+ "ordering": ["-created_at"],
125
+ "indexes": [
126
+ models.Index(fields=["method", "status"], name="django_cfg__method_916fd9_idx"),
127
+ models.Index(
128
+ fields=["status", "created_at"], name="django_cfg__status_ee629e_idx"
129
+ ),
130
+ models.Index(fields=["correlation_id"], name="django_cfg__correla_fc7bcf_idx"),
131
+ models.Index(
132
+ fields=["user", "created_at"], name="django_cfg__user_id_c33866_idx"
133
+ ),
134
+ ],
135
+ },
136
+ ),
137
+ ]
File without changes
@@ -0,0 +1,221 @@
1
+ """
2
+ IPC/RPC Models for logging and monitoring.
3
+
4
+ Models:
5
+ - RPCLog: Log of all RPC calls for debugging and analytics
6
+ """
7
+
8
+ import uuid
9
+ from django.conf import settings
10
+ from django.db import models
11
+ from django.utils import timezone
12
+
13
+
14
+ class RPCLogManager(models.Manager):
15
+ """Custom manager for RPCLog model."""
16
+
17
+ def recent(self, hours=24):
18
+ """Get logs from last N hours."""
19
+ since = timezone.now() - timezone.timedelta(hours=hours)
20
+ return self.filter(created_at__gte=since)
21
+
22
+ def failed(self):
23
+ """Get failed RPC calls."""
24
+ return self.filter(status=RPCLog.StatusChoices.FAILED)
25
+
26
+ def by_method(self, method):
27
+ """Get logs for specific RPC method."""
28
+ return self.filter(method=method)
29
+
30
+ def stats_by_method(self):
31
+ """
32
+ Get statistics grouped by method.
33
+
34
+ Returns:
35
+ QuerySet with aggregated data per method
36
+ """
37
+ from django.db.models import Count, Avg, Max, Min
38
+
39
+ return self.values('method').annotate(
40
+ total_calls=Count('id'),
41
+ avg_duration_ms=Avg('duration_ms'),
42
+ max_duration_ms=Max('duration_ms'),
43
+ min_duration_ms=Min('duration_ms'),
44
+ success_count=Count('id', filter=models.Q(status='success')),
45
+ failed_count=Count('id', filter=models.Q(status='failed')),
46
+ ).order_by('-total_calls')
47
+
48
+
49
+ class RPCLog(models.Model):
50
+ """
51
+ Log of RPC calls between Django and WebSocket server.
52
+
53
+ Used for:
54
+ - Debugging RPC communication
55
+ - Performance monitoring
56
+ - Analytics (most used methods, success rate)
57
+ - Audit trail (who called what and when)
58
+ """
59
+
60
+ class StatusChoices(models.TextChoices):
61
+ PENDING = 'pending', 'Pending'
62
+ SUCCESS = 'success', 'Success'
63
+ FAILED = 'failed', 'Failed'
64
+ TIMEOUT = 'timeout', 'Timeout'
65
+
66
+ # Primary key
67
+ id = models.UUIDField(
68
+ primary_key=True,
69
+ default=uuid.uuid4,
70
+ editable=False
71
+ )
72
+
73
+ # Correlation ID (matches RPC correlation_id)
74
+ correlation_id = models.CharField(
75
+ max_length=100,
76
+ db_index=True,
77
+ verbose_name="Correlation ID",
78
+ help_text="UUID from RPC request"
79
+ )
80
+
81
+ # User who initiated the call (optional)
82
+ user = models.ForeignKey(
83
+ settings.AUTH_USER_MODEL,
84
+ on_delete=models.SET_NULL,
85
+ null=True,
86
+ blank=True,
87
+ related_name='rpc_logs',
88
+ verbose_name="User"
89
+ )
90
+
91
+ # RPC method name
92
+ method = models.CharField(
93
+ max_length=100,
94
+ db_index=True,
95
+ verbose_name="RPC Method",
96
+ help_text="e.g., send_notification, workspace.file_changed"
97
+ )
98
+
99
+ # Request parameters (JSON)
100
+ params = models.JSONField(
101
+ verbose_name="Request Params",
102
+ help_text="Parameters sent to RPC method"
103
+ )
104
+
105
+ # Response data (JSON, nullable if failed)
106
+ response = models.JSONField(
107
+ null=True,
108
+ blank=True,
109
+ verbose_name="Response Data",
110
+ help_text="Result from RPC call"
111
+ )
112
+
113
+ # Status
114
+ status = models.CharField(
115
+ max_length=20,
116
+ choices=StatusChoices.choices,
117
+ default=StatusChoices.PENDING,
118
+ db_index=True,
119
+ verbose_name="Status"
120
+ )
121
+
122
+ # Error details (if failed)
123
+ error_code = models.CharField(
124
+ max_length=50,
125
+ null=True,
126
+ blank=True,
127
+ verbose_name="Error Code"
128
+ )
129
+ error_message = models.TextField(
130
+ null=True,
131
+ blank=True,
132
+ verbose_name="Error Message"
133
+ )
134
+
135
+ # Performance metrics
136
+ duration_ms = models.IntegerField(
137
+ null=True,
138
+ blank=True,
139
+ verbose_name="Duration (ms)",
140
+ help_text="Time taken for RPC call in milliseconds"
141
+ )
142
+
143
+ # Metadata
144
+ caller_ip = models.GenericIPAddressField(
145
+ null=True,
146
+ blank=True,
147
+ verbose_name="Caller IP"
148
+ )
149
+ user_agent = models.TextField(
150
+ null=True,
151
+ blank=True,
152
+ verbose_name="User Agent"
153
+ )
154
+
155
+ # Timestamps
156
+ created_at = models.DateTimeField(
157
+ default=timezone.now,
158
+ db_index=True,
159
+ verbose_name="Created At"
160
+ )
161
+ completed_at = models.DateTimeField(
162
+ null=True,
163
+ blank=True,
164
+ verbose_name="Completed At"
165
+ )
166
+
167
+ # Custom manager
168
+ objects = RPCLogManager()
169
+
170
+ class Meta:
171
+ app_label = 'django_cfg_ipc'
172
+ verbose_name = "RPC Log"
173
+ verbose_name_plural = "RPC Logs"
174
+ ordering = ['-created_at']
175
+ indexes = [
176
+ models.Index(fields=['method', 'status']),
177
+ models.Index(fields=['status', 'created_at']),
178
+ models.Index(fields=['correlation_id']),
179
+ models.Index(fields=['user', 'created_at']),
180
+ ]
181
+
182
+ def __str__(self):
183
+ return f"{self.method} ({self.status}) - {self.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
184
+
185
+ @property
186
+ def is_successful(self):
187
+ """Check if RPC call was successful."""
188
+ return self.status == self.StatusChoices.SUCCESS
189
+
190
+ @property
191
+ def is_failed(self):
192
+ """Check if RPC call failed."""
193
+ return self.status in [self.StatusChoices.FAILED, self.StatusChoices.TIMEOUT]
194
+
195
+ def mark_success(self, response_data, duration_ms=None):
196
+ """Mark RPC call as successful."""
197
+ self.status = self.StatusChoices.SUCCESS
198
+ self.response = response_data
199
+ self.completed_at = timezone.now()
200
+ if duration_ms is not None:
201
+ self.duration_ms = duration_ms
202
+ self.save()
203
+
204
+ def mark_failed(self, error_code, error_message, duration_ms=None):
205
+ """Mark RPC call as failed."""
206
+ self.status = self.StatusChoices.FAILED
207
+ self.error_code = error_code
208
+ self.error_message = error_message
209
+ self.completed_at = timezone.now()
210
+ if duration_ms is not None:
211
+ self.duration_ms = duration_ms
212
+ self.save()
213
+
214
+ def mark_timeout(self, timeout_seconds):
215
+ """Mark RPC call as timed out."""
216
+ self.status = self.StatusChoices.TIMEOUT
217
+ self.error_code = 'timeout'
218
+ self.error_message = f'RPC call timed out after {timeout_seconds}s'
219
+ self.completed_at = timezone.now()
220
+ self.duration_ms = timeout_seconds * 1000
221
+ self.save()
@@ -4,10 +4,15 @@ Serializers for IPC/RPC module.
4
4
 
5
5
  from .serializers import (
6
6
  HealthCheckSerializer,
7
+ LoadTestRequestSerializer,
8
+ LoadTestResponseSerializer,
9
+ LoadTestStatusSerializer,
7
10
  MethodStatsSerializer,
8
11
  NotificationStatsSerializer,
9
12
  OverviewStatsSerializer,
10
13
  RecentRequestsSerializer,
14
+ TestRPCRequestSerializer,
15
+ TestRPCResponseSerializer,
11
16
  )
12
17
 
13
18
  __all__ = [
@@ -16,4 +21,9 @@ __all__ = [
16
21
  'RecentRequestsSerializer',
17
22
  'NotificationStatsSerializer',
18
23
  'MethodStatsSerializer',
24
+ 'TestRPCRequestSerializer',
25
+ 'TestRPCResponseSerializer',
26
+ 'LoadTestRequestSerializer',
27
+ 'LoadTestResponseSerializer',
28
+ 'LoadTestStatusSerializer',
19
29
  ]