django-cfg 1.4.120__py3-none-any.whl → 1.5.2__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 +8 -4
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
- django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/grpc/__init__.py +9 -0
- django_cfg/apps/grpc/admin/__init__.py +11 -0
- django_cfg/apps/{tasks → grpc}/admin/config.py +32 -41
- django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
- django_cfg/apps/grpc/apps.py +28 -0
- django_cfg/apps/grpc/auth/__init__.py +9 -0
- django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
- django_cfg/apps/grpc/interceptors/__init__.py +19 -0
- django_cfg/apps/grpc/interceptors/errors.py +241 -0
- django_cfg/apps/grpc/interceptors/logging.py +270 -0
- django_cfg/apps/grpc/interceptors/metrics.py +306 -0
- django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
- django_cfg/apps/grpc/management/__init__.py +1 -0
- django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
- django_cfg/apps/grpc/managers/__init__.py +10 -0
- django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
- django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
- django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
- django_cfg/apps/grpc/models/__init__.py +9 -0
- django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
- django_cfg/apps/grpc/serializers/__init__.py +23 -0
- django_cfg/apps/grpc/serializers/health.py +18 -0
- django_cfg/apps/grpc/serializers/requests.py +18 -0
- django_cfg/apps/grpc/serializers/services.py +50 -0
- django_cfg/apps/grpc/serializers/stats.py +22 -0
- django_cfg/apps/grpc/services/__init__.py +16 -0
- django_cfg/apps/grpc/services/base.py +375 -0
- django_cfg/apps/grpc/services/discovery.py +415 -0
- django_cfg/apps/grpc/urls.py +23 -0
- django_cfg/apps/grpc/utils/__init__.py +13 -0
- django_cfg/apps/grpc/utils/proto_gen.py +423 -0
- django_cfg/apps/grpc/views/__init__.py +9 -0
- django_cfg/apps/grpc/views/monitoring.py +497 -0
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +7 -9
- django_cfg/apps/maintenance/admin/site_admin.py +5 -4
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/payments/admin/balance_admin.py +26 -36
- django_cfg/apps/payments/admin/payment_admin.py +65 -85
- django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
- django_cfg/apps/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +13 -8
- django_cfg/config.py +106 -0
- django_cfg/core/base/config_model.py +16 -26
- django_cfg/core/builders/apps_builder.py +7 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
- django_cfg/core/generation/orchestrator.py +15 -15
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/api/grpc/__init__.py +59 -0
- django_cfg/models/api/grpc/config.py +364 -0
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +19 -6
- django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/__init__.py +41 -3
- django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
- django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
- django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
- django_cfg/modules/django_admin/utils/html/badges.py +47 -0
- django_cfg/modules/django_admin/utils/html/base.py +167 -0
- django_cfg/modules/django_admin/utils/html/code.py +87 -0
- django_cfg/modules/django_admin/utils/html/composition.py +205 -0
- django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
- django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
- django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
- django_cfg/modules/django_admin/utils/html/progress.py +127 -0
- django_cfg/modules/django_admin/utils/html_builder.py +55 -408
- django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
- django_cfg/modules/django_unfold/navigation.py +21 -18
- django_cfg/pyproject.toml +4 -6
- django_cfg/registry/core.py +4 -7
- django_cfg/registry/modules.py +6 -0
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/METADATA +12 -4
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/RECORD +140 -96
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/task_log.py +0 -265
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- /django_cfg/apps/{tasks/migrations → grpc/management/commands}/__init__.py +0 -0
- /django_cfg/{modules/django_q2/management → apps/grpc/migrations}/__init__.py +0 -0
- /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
- /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,6 +8,7 @@ Endpoint for complete dashboard overview:
|
|
|
8
8
|
import logging
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
|
|
11
|
+
from django.db import transaction
|
|
11
12
|
from drf_spectacular.utils import extend_schema
|
|
12
13
|
from rest_framework import status, viewsets
|
|
13
14
|
|
|
@@ -32,6 +33,11 @@ class OverviewViewSet(AdminAPIMixin, viewsets.GenericViewSet):
|
|
|
32
33
|
|
|
33
34
|
serializer_class = DashboardOverviewSerializer
|
|
34
35
|
|
|
36
|
+
@transaction.non_atomic_requests
|
|
37
|
+
def dispatch(self, request, *args, **kwargs):
|
|
38
|
+
"""Disable atomic requests for this viewset."""
|
|
39
|
+
return super().dispatch(request, *args, **kwargs)
|
|
40
|
+
|
|
35
41
|
@extend_schema(
|
|
36
42
|
summary="Get dashboard overview",
|
|
37
43
|
description="Retrieve complete dashboard data including stats, health, actions, and metrics",
|
|
@@ -40,24 +46,19 @@ class OverviewViewSet(AdminAPIMixin, viewsets.GenericViewSet):
|
|
|
40
46
|
)
|
|
41
47
|
@action(detail=False, methods=['get'], url_path='', url_name='overview')
|
|
42
48
|
def overview(self, request):
|
|
43
|
-
"""
|
|
44
|
-
Get complete dashboard overview.
|
|
45
|
-
|
|
46
|
-
Returns all dashboard data in a single request:
|
|
47
|
-
- Statistics cards
|
|
48
|
-
- System health status
|
|
49
|
-
- Quick actions
|
|
50
|
-
- Recent activity
|
|
51
|
-
- System metrics
|
|
52
|
-
- User statistics
|
|
53
|
-
"""
|
|
49
|
+
"""Get complete dashboard overview."""
|
|
54
50
|
try:
|
|
55
51
|
stats_service = StatisticsService()
|
|
56
52
|
health_service = SystemHealthService()
|
|
57
53
|
charts_service = ChartsService()
|
|
58
54
|
|
|
59
|
-
# Get app statistics
|
|
60
|
-
|
|
55
|
+
# Get app statistics - wrapped in try/except since it queries all models
|
|
56
|
+
try:
|
|
57
|
+
app_stats_dict = stats_service.get_app_statistics()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"Error getting app stats: {e}")
|
|
60
|
+
app_stats_dict = {'apps': {}}
|
|
61
|
+
|
|
61
62
|
app_statistics_list = [
|
|
62
63
|
{
|
|
63
64
|
'app_name': app_label,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Admin configuration for
|
|
2
|
+
Admin configuration for gRPC models.
|
|
3
3
|
|
|
4
4
|
Declarative AdminConfig using PydanticAdmin patterns.
|
|
5
5
|
"""
|
|
@@ -12,87 +12,78 @@ from django_cfg.modules.django_admin import (
|
|
|
12
12
|
UserField,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
from ..models import
|
|
15
|
+
from ..models import GRPCRequestLog
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
# Declarative configuration for
|
|
19
|
-
|
|
20
|
-
model=
|
|
21
|
-
|
|
18
|
+
# Declarative configuration for GRPCRequestLog
|
|
19
|
+
grpcrequestlog_config = AdminConfig(
|
|
20
|
+
model=GRPCRequestLog,
|
|
22
21
|
# Performance optimization
|
|
23
22
|
select_related=["user"],
|
|
24
23
|
|
|
25
24
|
# List display
|
|
26
25
|
list_display=[
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
26
|
+
"full_method",
|
|
27
|
+
"service_badge",
|
|
28
|
+
"method_badge",
|
|
29
|
+
"status",
|
|
30
|
+
"grpc_status_code_display",
|
|
30
31
|
"user",
|
|
31
32
|
"duration_display",
|
|
32
|
-
"retry_count",
|
|
33
33
|
"created_at",
|
|
34
|
-
"completed_at"
|
|
34
|
+
"completed_at"
|
|
35
35
|
],
|
|
36
36
|
|
|
37
37
|
# Auto-generated display methods
|
|
38
38
|
display_fields=[
|
|
39
|
-
BadgeField(
|
|
40
|
-
|
|
41
|
-
title="Queue",
|
|
42
|
-
variant="info",
|
|
43
|
-
icon=Icons.LAYERS,
|
|
44
|
-
),
|
|
39
|
+
BadgeField(name="service_name", title="Service", variant="info", icon=Icons.API),
|
|
40
|
+
BadgeField(name="method_name", title="Method", variant="secondary", icon=Icons.CODE),
|
|
45
41
|
BadgeField(
|
|
46
42
|
name="status",
|
|
47
43
|
title="Status",
|
|
48
44
|
label_map={
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
45
|
+
"pending": "warning",
|
|
46
|
+
"success": "success",
|
|
47
|
+
"error": "danger",
|
|
48
|
+
"cancelled": "secondary",
|
|
49
|
+
"timeout": "danger",
|
|
54
50
|
},
|
|
55
51
|
),
|
|
56
52
|
UserField(name="user", title="User", header=True),
|
|
57
53
|
DateTimeField(name="created_at", title="Created", ordering="created_at"),
|
|
58
|
-
DateTimeField(name="started_at", title="Started", ordering="started_at"),
|
|
59
54
|
DateTimeField(name="completed_at", title="Completed", ordering="completed_at"),
|
|
60
55
|
],
|
|
61
|
-
|
|
62
56
|
# Filters
|
|
63
|
-
list_filter=["status", "
|
|
57
|
+
list_filter=["status", "grpc_status_code", "service_name", "method_name", "is_authenticated", "created_at"],
|
|
64
58
|
search_fields=[
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
59
|
+
"request_id",
|
|
60
|
+
"service_name",
|
|
61
|
+
"method_name",
|
|
62
|
+
"full_method",
|
|
69
63
|
"user__username",
|
|
70
64
|
"user__email",
|
|
65
|
+
"error_message",
|
|
66
|
+
"client_ip",
|
|
71
67
|
],
|
|
72
|
-
|
|
73
68
|
# Autocomplete for user field
|
|
74
69
|
autocomplete_fields=["user"],
|
|
75
|
-
|
|
76
70
|
# Readonly fields
|
|
77
71
|
readonly_fields=[
|
|
78
72
|
"id",
|
|
79
|
-
"
|
|
73
|
+
"request_id",
|
|
80
74
|
"created_at",
|
|
81
|
-
"started_at",
|
|
82
75
|
"completed_at",
|
|
83
|
-
"
|
|
84
|
-
"
|
|
76
|
+
"request_data_display",
|
|
77
|
+
"response_data_display",
|
|
78
|
+
"error_details_display",
|
|
79
|
+
"performance_stats_display",
|
|
80
|
+
"client_info_display",
|
|
85
81
|
],
|
|
86
|
-
|
|
87
82
|
# Date hierarchy
|
|
88
83
|
date_hierarchy="created_at",
|
|
89
|
-
|
|
90
84
|
# Per page
|
|
91
85
|
list_per_page=50,
|
|
92
|
-
|
|
93
|
-
# Ordering
|
|
94
|
-
ordering=["-created_at"],
|
|
95
86
|
)
|
|
96
87
|
|
|
97
88
|
|
|
98
|
-
__all__ = ["
|
|
89
|
+
__all__ = ["grpcrequestlog_config"]
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC Request Log Admin.
|
|
3
|
+
|
|
4
|
+
PydanticAdmin for GRPCRequestLog model with custom computed fields.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from django.contrib import admin
|
|
10
|
+
from django_cfg.modules.django_admin import Icons, computed_field
|
|
11
|
+
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
12
|
+
|
|
13
|
+
from ..models import GRPCRequestLog
|
|
14
|
+
from .config import grpcrequestlog_config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@admin.register(GRPCRequestLog)
|
|
18
|
+
class GRPCRequestLogAdmin(PydanticAdmin):
|
|
19
|
+
"""
|
|
20
|
+
gRPC request log admin with analytics and filtering.
|
|
21
|
+
|
|
22
|
+
Features:
|
|
23
|
+
- Color-coded status badges
|
|
24
|
+
- Performance metrics visualization
|
|
25
|
+
- Duration display with performance indicators
|
|
26
|
+
- Formatted JSON for request/response data
|
|
27
|
+
- Error details with highlighted display
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
config = grpcrequestlog_config
|
|
31
|
+
|
|
32
|
+
@computed_field("Service", ordering="service_name")
|
|
33
|
+
def service_badge(self, obj):
|
|
34
|
+
"""Display service name as badge."""
|
|
35
|
+
return self.html.badge(obj.service_name, variant="info", icon=Icons.API)
|
|
36
|
+
|
|
37
|
+
@computed_field("Method", ordering="method_name")
|
|
38
|
+
def method_badge(self, obj):
|
|
39
|
+
"""Display method name as badge."""
|
|
40
|
+
return self.html.badge(obj.method_name, variant="secondary", icon=Icons.CODE)
|
|
41
|
+
|
|
42
|
+
@computed_field("gRPC Status", ordering="grpc_status_code")
|
|
43
|
+
def grpc_status_code_display(self, obj):
|
|
44
|
+
"""Display gRPC status code with color coding."""
|
|
45
|
+
if not obj.grpc_status_code:
|
|
46
|
+
return self.html.empty()
|
|
47
|
+
|
|
48
|
+
# Color code based on status
|
|
49
|
+
if obj.grpc_status_code == "OK":
|
|
50
|
+
variant = "success"
|
|
51
|
+
icon = Icons.CHECK_CIRCLE
|
|
52
|
+
elif obj.grpc_status_code in ["CANCELLED", "DEADLINE_EXCEEDED"]:
|
|
53
|
+
variant = "warning"
|
|
54
|
+
icon = Icons.TIMER
|
|
55
|
+
else:
|
|
56
|
+
variant = "danger"
|
|
57
|
+
icon = Icons.ERROR
|
|
58
|
+
|
|
59
|
+
return self.html.badge(obj.grpc_status_code, variant=variant, icon=icon)
|
|
60
|
+
|
|
61
|
+
@computed_field("Duration", ordering="duration_ms")
|
|
62
|
+
def duration_display(self, obj):
|
|
63
|
+
"""Display duration with color coding based on speed."""
|
|
64
|
+
if obj.duration_ms is None:
|
|
65
|
+
return self.html.empty()
|
|
66
|
+
|
|
67
|
+
# Color code based on duration
|
|
68
|
+
if obj.duration_ms < 100:
|
|
69
|
+
variant = "success" # Fast
|
|
70
|
+
icon = Icons.SPEED
|
|
71
|
+
elif obj.duration_ms < 1000:
|
|
72
|
+
variant = "warning" # Moderate
|
|
73
|
+
icon = Icons.TIMER
|
|
74
|
+
else:
|
|
75
|
+
variant = "danger" # Slow
|
|
76
|
+
icon = Icons.ERROR
|
|
77
|
+
|
|
78
|
+
return self.html.badge(f"{obj.duration_ms}ms", variant=variant, icon=icon)
|
|
79
|
+
|
|
80
|
+
def request_data_display(self, obj):
|
|
81
|
+
"""Display formatted JSON request data."""
|
|
82
|
+
if not obj.request_data:
|
|
83
|
+
return self.html.empty("No request data logged")
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
formatted = json.dumps(obj.request_data, indent=2)
|
|
87
|
+
return self.html.code_block(formatted, language="json", max_height="400px")
|
|
88
|
+
except Exception:
|
|
89
|
+
return str(obj.request_data)
|
|
90
|
+
|
|
91
|
+
request_data_display.short_description = "Request Data"
|
|
92
|
+
|
|
93
|
+
def response_data_display(self, obj):
|
|
94
|
+
"""Display formatted JSON response data."""
|
|
95
|
+
if not obj.response_data:
|
|
96
|
+
return self.html.empty("No response data logged")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
formatted = json.dumps(obj.response_data, indent=2)
|
|
100
|
+
return self.html.code_block(formatted, language="json", max_height="400px")
|
|
101
|
+
except Exception:
|
|
102
|
+
return str(obj.response_data)
|
|
103
|
+
|
|
104
|
+
response_data_display.short_description = "Response Data"
|
|
105
|
+
|
|
106
|
+
def error_details_display(self, obj):
|
|
107
|
+
"""Display error information if request failed."""
|
|
108
|
+
if obj.is_successful or obj.status == "pending":
|
|
109
|
+
return self.html.inline(
|
|
110
|
+
self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
|
|
111
|
+
self.html.text("No errors", variant="success"),
|
|
112
|
+
separator=" "
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# gRPC status code
|
|
116
|
+
code_line = self.html.key_value(
|
|
117
|
+
"gRPC Status",
|
|
118
|
+
self.html.badge(obj.grpc_status_code, variant="danger", icon=Icons.ERROR)
|
|
119
|
+
) if obj.grpc_status_code else None
|
|
120
|
+
|
|
121
|
+
# Error message
|
|
122
|
+
msg_line = self.html.key_value(
|
|
123
|
+
"Message",
|
|
124
|
+
self.html.text(obj.error_message, variant="danger")
|
|
125
|
+
) if obj.error_message else None
|
|
126
|
+
|
|
127
|
+
# Error details
|
|
128
|
+
details_line = None
|
|
129
|
+
if obj.error_details:
|
|
130
|
+
try:
|
|
131
|
+
formatted = json.dumps(obj.error_details, indent=2)
|
|
132
|
+
details_line = self.html.key_value(
|
|
133
|
+
"Details",
|
|
134
|
+
self.html.code_block(formatted, language="json", max_height="200px")
|
|
135
|
+
)
|
|
136
|
+
except Exception:
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
return self.html.breakdown(code_line, msg_line, details_line) if (code_line or msg_line) else self.html.empty()
|
|
140
|
+
|
|
141
|
+
error_details_display.short_description = "Error Details"
|
|
142
|
+
|
|
143
|
+
def performance_stats_display(self, obj):
|
|
144
|
+
"""Display performance statistics."""
|
|
145
|
+
# Duration
|
|
146
|
+
duration_line = self.html.key_value(
|
|
147
|
+
"Duration",
|
|
148
|
+
self.html.number(obj.duration_ms, suffix="ms") if obj.duration_ms else "N/A"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Request size
|
|
152
|
+
request_size_line = self.html.key_value(
|
|
153
|
+
"Request Size",
|
|
154
|
+
self.html.number(obj.request_size, suffix=" bytes") if obj.request_size else "N/A"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Response size
|
|
158
|
+
response_size_line = self.html.key_value(
|
|
159
|
+
"Response Size",
|
|
160
|
+
self.html.number(obj.response_size, suffix=" bytes") if obj.response_size else "N/A"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Authentication
|
|
164
|
+
auth_line = self.html.key_value(
|
|
165
|
+
"Authenticated",
|
|
166
|
+
self.html.badge("Yes" if obj.is_authenticated else "No",
|
|
167
|
+
variant="success" if obj.is_authenticated else "secondary")
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return self.html.breakdown(duration_line, request_size_line, response_size_line, auth_line)
|
|
171
|
+
|
|
172
|
+
performance_stats_display.short_description = "Performance Statistics"
|
|
173
|
+
|
|
174
|
+
def client_info_display(self, obj):
|
|
175
|
+
"""Display client information."""
|
|
176
|
+
# Client IP
|
|
177
|
+
ip_line = self.html.key_value(
|
|
178
|
+
"Client IP",
|
|
179
|
+
obj.client_ip if obj.client_ip else "N/A"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# User Agent
|
|
183
|
+
ua_line = self.html.key_value(
|
|
184
|
+
"User Agent",
|
|
185
|
+
obj.user_agent if obj.user_agent else "N/A"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Peer
|
|
189
|
+
peer_line = self.html.key_value(
|
|
190
|
+
"Peer",
|
|
191
|
+
self.html.text(obj.peer, variant="secondary") if obj.peer else "N/A"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return self.html.breakdown(ip_line, ua_line, peer_line)
|
|
195
|
+
|
|
196
|
+
client_info_display.short_description = "Client Information"
|
|
197
|
+
|
|
198
|
+
# Fieldsets for detail view
|
|
199
|
+
def get_fieldsets(self, request, obj=None):
|
|
200
|
+
"""Dynamic fieldsets based on object state."""
|
|
201
|
+
fieldsets = [
|
|
202
|
+
(
|
|
203
|
+
"Request Information",
|
|
204
|
+
{"fields": ("id", "request_id", "full_method", "service_name", "method_name", "status")},
|
|
205
|
+
),
|
|
206
|
+
(
|
|
207
|
+
"User Context",
|
|
208
|
+
{"fields": ("user", "is_authenticated")},
|
|
209
|
+
),
|
|
210
|
+
(
|
|
211
|
+
"Performance",
|
|
212
|
+
{"fields": ("performance_stats_display", "duration_ms", "created_at", "completed_at")},
|
|
213
|
+
),
|
|
214
|
+
(
|
|
215
|
+
"Client Information",
|
|
216
|
+
{"fields": ("client_info_display", "client_ip", "user_agent", "peer"), "classes": ("collapse",)},
|
|
217
|
+
),
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
# Add request/response data sections if available
|
|
221
|
+
if obj and obj.request_data:
|
|
222
|
+
fieldsets.insert(
|
|
223
|
+
3,
|
|
224
|
+
(
|
|
225
|
+
"Request Data",
|
|
226
|
+
{"fields": ("request_data_display",), "classes": ("collapse",)},
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if obj and obj.response_data:
|
|
231
|
+
fieldsets.insert(
|
|
232
|
+
4,
|
|
233
|
+
(
|
|
234
|
+
"Response Data",
|
|
235
|
+
{"fields": ("response_data_display",), "classes": ("collapse",)},
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Add error section only if failed
|
|
240
|
+
if obj and not obj.is_successful and obj.status != "pending":
|
|
241
|
+
fieldsets.insert(
|
|
242
|
+
5,
|
|
243
|
+
(
|
|
244
|
+
"Error Details",
|
|
245
|
+
{"fields": ("error_details_display", "grpc_status_code", "error_message", "error_details")},
|
|
246
|
+
),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return fieldsets
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
__all__ = ["GRPCRequestLogAdmin"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django app configuration for gRPC integration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django.apps import AppConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GRPCAppConfig(AppConfig):
|
|
9
|
+
"""
|
|
10
|
+
Django app config for gRPC integration.
|
|
11
|
+
|
|
12
|
+
Provides:
|
|
13
|
+
- gRPC server with Django ORM integration
|
|
14
|
+
- JWT authentication
|
|
15
|
+
- Request logging to database
|
|
16
|
+
- Admin interface for monitoring
|
|
17
|
+
- REST API for metrics
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
default_auto_field = 'django.db.models.BigAutoField'
|
|
21
|
+
name = 'django_cfg.apps.grpc'
|
|
22
|
+
verbose_name = 'gRPC Integration'
|
|
23
|
+
|
|
24
|
+
def ready(self):
|
|
25
|
+
"""Called when Django starts."""
|
|
26
|
+
# Import signal handlers if needed
|
|
27
|
+
# from . import signals
|
|
28
|
+
pass
|