django-cfg 1.4.58__py3-none-any.whl → 1.4.60__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/ipc/RPC_LOGGING.md +321 -0
- django_cfg/apps/ipc/TESTING.md +539 -0
- django_cfg/apps/ipc/__init__.py +12 -3
- django_cfg/apps/ipc/admin.py +212 -0
- django_cfg/apps/ipc/migrations/0001_initial.py +137 -0
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +221 -0
- django_cfg/apps/ipc/serializers/__init__.py +10 -0
- django_cfg/apps/ipc/serializers/serializers.py +114 -0
- django_cfg/apps/ipc/services/client/client.py +83 -4
- django_cfg/apps/ipc/services/logging.py +239 -0
- django_cfg/apps/ipc/services/monitor.py +5 -3
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +269 -0
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +259 -0
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +375 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +22 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +9 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +9 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +23 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/stat_cards.html +50 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +47 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/tab_navigation.html +29 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +184 -0
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +56 -0
- django_cfg/apps/ipc/urls.py +4 -2
- django_cfg/apps/ipc/views/__init__.py +7 -2
- django_cfg/apps/ipc/views/dashboard.py +1 -1
- django_cfg/apps/ipc/views/{viewsets.py → monitoring.py} +17 -11
- django_cfg/apps/ipc/views/testing.py +285 -0
- django_cfg/core/backends/__init__.py +1 -0
- django_cfg/core/backends/smtp.py +69 -0
- django_cfg/middleware/authentication.py +157 -0
- django_cfg/models/api/drf/config.py +2 -2
- django_cfg/models/services/email.py +11 -1
- django_cfg/modules/django_client/system/generate_mjs_clients.py +1 -1
- django_cfg/modules/django_dashboard/sections/widgets.py +209 -0
- django_cfg/modules/django_unfold/callbacks/main.py +43 -18
- django_cfg/modules/django_unfold/dashboard.py +41 -4
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/js/api/index.mjs +8 -3
- django_cfg/static/js/api/ipc/client.mjs +40 -0
- django_cfg/static/js/api/knowbase/client.mjs +309 -0
- django_cfg/static/js/api/knowbase/index.mjs +13 -0
- django_cfg/static/js/api/payments/client.mjs +46 -1215
- django_cfg/static/js/api/types.mjs +164 -337
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +13 -1
- django_cfg/templates/admin/sections/widgets_section.html +129 -0
- django_cfg/templates/admin/snippets/tabs/widgets_tab.html +38 -0
- django_cfg/utils/smart_defaults.py +1 -1
- {django_cfg-1.4.58.dist-info → django_cfg-1.4.60.dist-info}/METADATA +1 -1
- {django_cfg-1.4.58.dist-info → django_cfg-1.4.60.dist-info}/RECORD +58 -31
- django_cfg/apps/ipc/templates/django_cfg_ipc/dashboard.html +0 -202
- /django_cfg/apps/ipc/static/django_cfg_ipc/js/{dashboard.mjs → dashboard.mjs.old} +0 -0
- /django_cfg/apps/ipc/templates/django_cfg_ipc/{base.html → layout/base.html} +0 -0
- {django_cfg-1.4.58.dist-info → django_cfg-1.4.60.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.58.dist-info → django_cfg-1.4.60.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.58.dist-info → django_cfg-1.4.60.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
|
]
|