django-cfg 1.5.8__py3-none-any.whl → 1.5.14__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/api/commands/serializers.py +152 -0
- django_cfg/apps/api/commands/views.py +32 -0
- django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
- django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
- django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
- django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
- django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
- django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
- django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
- django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +5 -5
- django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
- django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
- django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
- django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
- django_cfg/apps/integrations/grpc/admin/config.py +113 -9
- django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
- django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
- django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
- django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
- django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
- django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
- django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +130 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +171 -96
- django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
- django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
- django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
- django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
- django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
- django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
- django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
- django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
- django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
- django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
- django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
- django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
- django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
- django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
- django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
- django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
- django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
- django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
- django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
- django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
- django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
- django_cfg/apps/integrations/grpc/urls.py +8 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
- django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +177 -0
- django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
- django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
- django_cfg/apps/integrations/grpc/views/charts.py +21 -14
- django_cfg/apps/integrations/grpc/views/config.py +8 -6
- django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
- django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
- django_cfg/apps/integrations/grpc/views/services.py +30 -21
- django_cfg/apps/integrations/grpc/views/testing.py +45 -43
- django_cfg/apps/integrations/rq/views/jobs.py +19 -9
- django_cfg/apps/integrations/rq/views/schedule.py +7 -3
- django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
- django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
- django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
- django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
- django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
- django_cfg/config.py +33 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
- django_cfg/management/commands/check_endpoints.py +2 -2
- django_cfg/management/commands/check_settings.py +3 -10
- django_cfg/management/commands/clear_constance.py +3 -10
- django_cfg/management/commands/create_token.py +4 -11
- django_cfg/management/commands/list_urls.py +4 -10
- django_cfg/management/commands/migrate_all.py +18 -12
- django_cfg/management/commands/migrator.py +4 -11
- django_cfg/management/commands/script.py +4 -10
- django_cfg/management/commands/show_config.py +8 -16
- django_cfg/management/commands/show_urls.py +5 -11
- django_cfg/management/commands/superuser.py +4 -11
- django_cfg/management/commands/tree.py +5 -10
- django_cfg/management/utils/README.md +402 -0
- django_cfg/management/utils/__init__.py +29 -0
- django_cfg/management/utils/mixins.py +176 -0
- django_cfg/middleware/pagination.py +53 -54
- django_cfg/models/api/grpc/__init__.py +15 -21
- django_cfg/models/api/grpc/config.py +155 -73
- django_cfg/models/ngrok/config.py +7 -6
- django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
- django_cfg/modules/django_client/core/parser/base.py +114 -30
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
- django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
- django_cfg/modules/django_email/management/commands/test_email.py +4 -10
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
- django_cfg/modules/django_unfold/navigation.py +6 -18
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/modules.py +1 -4
- django_cfg/requirements.txt +52 -0
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/METADATA +1 -1
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/RECORD +118 -97
- django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC API Key Admin.
|
|
3
|
+
|
|
4
|
+
PydanticAdmin for GrpcApiKey model with enhanced UI and status tracking.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from django.contrib import admin
|
|
8
|
+
from django_cfg.modules.django_admin import Icons, computed_field
|
|
9
|
+
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
10
|
+
|
|
11
|
+
from ..models import GrpcApiKey
|
|
12
|
+
from .config import grpcapikey_config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@admin.register(GrpcApiKey)
|
|
16
|
+
class GrpcApiKeyAdmin(PydanticAdmin):
|
|
17
|
+
"""
|
|
18
|
+
Admin interface for gRPC API keys.
|
|
19
|
+
|
|
20
|
+
Features:
|
|
21
|
+
- List view with status indicators
|
|
22
|
+
- Search by name, description, user
|
|
23
|
+
- Filter by status, type, user
|
|
24
|
+
- Read-only key display (masked)
|
|
25
|
+
- Actions for revoking keys
|
|
26
|
+
- Type-safe configuration via AdminConfig
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
config = grpcapikey_config
|
|
30
|
+
|
|
31
|
+
# Permissions
|
|
32
|
+
def save_model(self, request, obj, form, change):
|
|
33
|
+
"""Save model with created_by tracking."""
|
|
34
|
+
if not change: # New object
|
|
35
|
+
obj.created_by = request.user
|
|
36
|
+
super().save_model(request, obj, form, change)
|
|
37
|
+
|
|
38
|
+
# Display methods
|
|
39
|
+
@computed_field("Status", ordering="is_active")
|
|
40
|
+
def status_indicator(self, obj):
|
|
41
|
+
"""Display status indicator with expiration check."""
|
|
42
|
+
if obj.is_valid:
|
|
43
|
+
return self.html.badge(
|
|
44
|
+
"Active",
|
|
45
|
+
variant="success",
|
|
46
|
+
icon=Icons.CHECK_CIRCLE
|
|
47
|
+
)
|
|
48
|
+
elif obj.is_expired:
|
|
49
|
+
return self.html.badge(
|
|
50
|
+
"Expired",
|
|
51
|
+
variant="warning",
|
|
52
|
+
icon=Icons.SCHEDULE
|
|
53
|
+
)
|
|
54
|
+
else:
|
|
55
|
+
return self.html.badge(
|
|
56
|
+
"Revoked",
|
|
57
|
+
variant="danger",
|
|
58
|
+
icon=Icons.CANCEL
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@computed_field("Masked Key")
|
|
62
|
+
def masked_key_display(self, obj):
|
|
63
|
+
"""Display masked API key."""
|
|
64
|
+
return self.html.code(obj.masked_key)
|
|
65
|
+
|
|
66
|
+
def key_display(self, obj):
|
|
67
|
+
"""Display full API key (read-only, for copying)."""
|
|
68
|
+
if obj.pk:
|
|
69
|
+
return self.html.code(obj.key)
|
|
70
|
+
return self.html.empty()
|
|
71
|
+
|
|
72
|
+
key_display.short_description = "Full API Key"
|
|
73
|
+
|
|
74
|
+
@computed_field("Requests", ordering="request_count")
|
|
75
|
+
def request_count_display(self, obj):
|
|
76
|
+
"""Display request count with badge."""
|
|
77
|
+
if obj.request_count == 0:
|
|
78
|
+
return self.html.empty("0")
|
|
79
|
+
|
|
80
|
+
# Color based on usage
|
|
81
|
+
if obj.request_count > 1000:
|
|
82
|
+
variant = "success"
|
|
83
|
+
elif obj.request_count > 100:
|
|
84
|
+
variant = "info"
|
|
85
|
+
else:
|
|
86
|
+
variant = "secondary"
|
|
87
|
+
|
|
88
|
+
return self.html.badge(
|
|
89
|
+
str(obj.request_count),
|
|
90
|
+
variant=variant,
|
|
91
|
+
icon=Icons.ANALYTICS
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
@computed_field("Expires", ordering="expires_at")
|
|
95
|
+
def expires_display(self, obj):
|
|
96
|
+
"""Display expiration date with status."""
|
|
97
|
+
if not obj.expires_at:
|
|
98
|
+
return self.html.badge(
|
|
99
|
+
"Never",
|
|
100
|
+
variant="success",
|
|
101
|
+
icon=Icons.ALL_INCLUSIVE
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
from django.utils import timezone
|
|
105
|
+
if obj.expires_at < timezone.now():
|
|
106
|
+
return self.html.badge(
|
|
107
|
+
obj.expires_at.strftime("%Y-%m-%d"),
|
|
108
|
+
variant="danger",
|
|
109
|
+
icon=Icons.ERROR
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return self.html.text(
|
|
113
|
+
obj.expires_at.strftime("%Y-%m-%d"),
|
|
114
|
+
variant="primary"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Actions
|
|
118
|
+
@admin.action(description="Revoke selected API keys")
|
|
119
|
+
def revoke_selected_keys(self, request, queryset):
|
|
120
|
+
"""Revoke selected API keys."""
|
|
121
|
+
count = queryset.filter(is_active=True).count()
|
|
122
|
+
queryset.update(is_active=False)
|
|
123
|
+
self.message_user(
|
|
124
|
+
request,
|
|
125
|
+
f"Successfully revoked {count} API key(s).",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
__all__ = ["GrpcApiKeyAdmin"]
|
|
@@ -58,6 +58,18 @@ class GRPCRequestLogAdmin(PydanticAdmin):
|
|
|
58
58
|
|
|
59
59
|
return self.html.badge(obj.grpc_status_code, variant=variant, icon=icon)
|
|
60
60
|
|
|
61
|
+
@computed_field("API Key", ordering="api_key__name")
|
|
62
|
+
def api_key_display(self, obj):
|
|
63
|
+
"""Display API key name if used for authentication."""
|
|
64
|
+
if not obj.api_key:
|
|
65
|
+
return self.html.empty()
|
|
66
|
+
|
|
67
|
+
return self.html.badge(
|
|
68
|
+
obj.api_key.name,
|
|
69
|
+
variant="info" if obj.api_key.is_valid else "danger",
|
|
70
|
+
icon=Icons.KEY
|
|
71
|
+
)
|
|
72
|
+
|
|
61
73
|
@computed_field("Duration", ordering="duration_ms")
|
|
62
74
|
def duration_display(self, obj):
|
|
63
75
|
"""Display duration with color coding based on speed."""
|
|
@@ -86,7 +98,7 @@ class GRPCRequestLogAdmin(PydanticAdmin):
|
|
|
86
98
|
formatted = json.dumps(obj.request_data, indent=2)
|
|
87
99
|
return self.html.code_block(formatted, language="json", max_height="400px")
|
|
88
100
|
except Exception:
|
|
89
|
-
return str(obj.request_data)
|
|
101
|
+
return self.html.code(str(obj.request_data))
|
|
90
102
|
|
|
91
103
|
request_data_display.short_description = "Request Data"
|
|
92
104
|
|
|
@@ -99,7 +111,7 @@ class GRPCRequestLogAdmin(PydanticAdmin):
|
|
|
99
111
|
formatted = json.dumps(obj.response_data, indent=2)
|
|
100
112
|
return self.html.code_block(formatted, language="json", max_height="400px")
|
|
101
113
|
except Exception:
|
|
102
|
-
return str(obj.response_data)
|
|
114
|
+
return self.html.code(str(obj.response_data))
|
|
103
115
|
|
|
104
116
|
response_data_display.short_description = "Response Data"
|
|
105
117
|
|
|
@@ -112,87 +124,84 @@ class GRPCRequestLogAdmin(PydanticAdmin):
|
|
|
112
124
|
separator=" "
|
|
113
125
|
)
|
|
114
126
|
|
|
115
|
-
#
|
|
116
|
-
|
|
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
|
|
127
|
+
# Error details JSON
|
|
128
|
+
error_details_json = None
|
|
129
129
|
if obj.error_details:
|
|
130
130
|
try:
|
|
131
131
|
formatted = json.dumps(obj.error_details, indent=2)
|
|
132
|
-
|
|
132
|
+
error_details_json = self.html.key_value(
|
|
133
133
|
"Details",
|
|
134
134
|
self.html.code_block(formatted, language="json", max_height="200px")
|
|
135
135
|
)
|
|
136
136
|
except Exception:
|
|
137
137
|
pass
|
|
138
138
|
|
|
139
|
-
return self.html.breakdown(
|
|
139
|
+
return self.html.breakdown(
|
|
140
|
+
self.html.key_value(
|
|
141
|
+
"gRPC Status",
|
|
142
|
+
self.html.badge(obj.grpc_status_code, variant="danger", icon=Icons.ERROR)
|
|
143
|
+
) if obj.grpc_status_code else None,
|
|
144
|
+
self.html.key_value(
|
|
145
|
+
"Message",
|
|
146
|
+
self.html.text(obj.error_message, variant="danger")
|
|
147
|
+
) if obj.error_message else None,
|
|
148
|
+
error_details_json,
|
|
149
|
+
)
|
|
140
150
|
|
|
141
151
|
error_details_display.short_description = "Error Details"
|
|
142
152
|
|
|
143
153
|
def performance_stats_display(self, obj):
|
|
144
|
-
"""Display performance statistics."""
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
self.html.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
self.html.
|
|
167
|
-
|
|
154
|
+
"""Display performance statistics and authentication info."""
|
|
155
|
+
return self.html.breakdown(
|
|
156
|
+
self.html.key_value(
|
|
157
|
+
"Duration",
|
|
158
|
+
self.html.badge(f"{obj.duration_ms}ms", variant="info", icon=Icons.TIMER)
|
|
159
|
+
) if obj.duration_ms is not None else None,
|
|
160
|
+
self.html.key_value(
|
|
161
|
+
"Request Size",
|
|
162
|
+
self.html.text(f"{obj.request_size:,} bytes", variant="secondary")
|
|
163
|
+
) if obj.request_size else None,
|
|
164
|
+
self.html.key_value(
|
|
165
|
+
"Response Size",
|
|
166
|
+
self.html.text(f"{obj.response_size:,} bytes", variant="secondary")
|
|
167
|
+
) if obj.response_size else None,
|
|
168
|
+
self.html.key_value(
|
|
169
|
+
"Authenticated",
|
|
170
|
+
self.html.badge(
|
|
171
|
+
"Yes" if obj.is_authenticated else "No",
|
|
172
|
+
variant="success" if obj.is_authenticated else "secondary",
|
|
173
|
+
icon=Icons.VERIFIED_USER if obj.is_authenticated else Icons.PERSON
|
|
174
|
+
)
|
|
175
|
+
),
|
|
176
|
+
self.html.key_value(
|
|
177
|
+
"API Key",
|
|
178
|
+
self.html.inline(
|
|
179
|
+
self.html.badge(obj.api_key.name, variant="info", icon=Icons.KEY),
|
|
180
|
+
self.html.text(f"({obj.api_key.masked_key})", variant="secondary"),
|
|
181
|
+
separator=" "
|
|
182
|
+
)
|
|
183
|
+
) if obj.api_key else None,
|
|
168
184
|
)
|
|
169
185
|
|
|
170
|
-
return self.html.breakdown(duration_line, request_size_line, response_size_line, auth_line)
|
|
171
|
-
|
|
172
186
|
performance_stats_display.short_description = "Performance Statistics"
|
|
173
187
|
|
|
174
188
|
def client_info_display(self, obj):
|
|
175
189
|
"""Display client information."""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
peer_line = self.html.key_value(
|
|
190
|
-
"Peer",
|
|
191
|
-
self.html.text(obj.peer, variant="secondary") if obj.peer else "N/A"
|
|
190
|
+
return self.html.breakdown(
|
|
191
|
+
self.html.key_value(
|
|
192
|
+
"Client IP",
|
|
193
|
+
self.html.text(obj.client_ip, variant="info") if obj.client_ip else self.html.empty("N/A")
|
|
194
|
+
),
|
|
195
|
+
self.html.key_value(
|
|
196
|
+
"User Agent",
|
|
197
|
+
self.html.code(obj.user_agent)
|
|
198
|
+
) if obj.user_agent else None,
|
|
199
|
+
self.html.key_value(
|
|
200
|
+
"Peer",
|
|
201
|
+
self.html.text(obj.peer, variant="secondary")
|
|
202
|
+
) if obj.peer else None,
|
|
192
203
|
)
|
|
193
204
|
|
|
194
|
-
return self.html.breakdown(ip_line, ua_line, peer_line)
|
|
195
|
-
|
|
196
205
|
client_info_display.short_description = "Client Information"
|
|
197
206
|
|
|
198
207
|
# Fieldsets for detail view
|
|
@@ -205,7 +214,7 @@ class GRPCRequestLogAdmin(PydanticAdmin):
|
|
|
205
214
|
),
|
|
206
215
|
(
|
|
207
216
|
"User Context",
|
|
208
|
-
{"fields": ("user", "is_authenticated")},
|
|
217
|
+
{"fields": ("user", "api_key", "is_authenticated")},
|
|
209
218
|
),
|
|
210
219
|
(
|
|
211
220
|
"Performance",
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC Server Status Admin.
|
|
3
|
+
|
|
4
|
+
PydanticAdmin for GRPCServerStatus model with server monitoring and lifecycle tracking.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from django.contrib import admin
|
|
8
|
+
from django_cfg.modules.django_admin import Icons, computed_field
|
|
9
|
+
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
10
|
+
|
|
11
|
+
from ..models import GRPCServerStatus
|
|
12
|
+
from .config import grpcserverstatus_config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@admin.register(GRPCServerStatus)
|
|
16
|
+
class GRPCServerStatusAdmin(PydanticAdmin):
|
|
17
|
+
"""
|
|
18
|
+
Admin interface for gRPC server status monitoring.
|
|
19
|
+
|
|
20
|
+
Features:
|
|
21
|
+
- Real-time server status indicators
|
|
22
|
+
- Uptime tracking and display
|
|
23
|
+
- Process information (PID, hostname)
|
|
24
|
+
- Service registration details
|
|
25
|
+
- Error tracking and display
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
config = grpcserverstatus_config
|
|
29
|
+
|
|
30
|
+
@computed_field("Uptime", ordering="started_at")
|
|
31
|
+
def uptime_display(self, obj):
|
|
32
|
+
"""Display server uptime with performance indicator."""
|
|
33
|
+
uptime_text = obj.uptime_display
|
|
34
|
+
|
|
35
|
+
if not obj.is_running:
|
|
36
|
+
return self.html.badge(
|
|
37
|
+
uptime_text,
|
|
38
|
+
variant="secondary",
|
|
39
|
+
icon=Icons.SCHEDULE
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Color code based on uptime
|
|
43
|
+
uptime_seconds = obj.uptime_seconds
|
|
44
|
+
if uptime_seconds > 86400: # > 1 day
|
|
45
|
+
variant = "success"
|
|
46
|
+
icon = Icons.CHECK_CIRCLE
|
|
47
|
+
elif uptime_seconds > 3600: # > 1 hour
|
|
48
|
+
variant = "info"
|
|
49
|
+
icon = Icons.TIMER
|
|
50
|
+
else: # < 1 hour
|
|
51
|
+
variant = "warning"
|
|
52
|
+
icon = Icons.SCHEDULE
|
|
53
|
+
|
|
54
|
+
return self.html.badge(uptime_text, variant=variant, icon=icon)
|
|
55
|
+
|
|
56
|
+
def server_config_display(self, obj):
|
|
57
|
+
"""Display server configuration details."""
|
|
58
|
+
return self.html.breakdown(
|
|
59
|
+
self.html.key_value(
|
|
60
|
+
"Address",
|
|
61
|
+
self.html.badge(obj.address, variant="info", icon=Icons.CLOUD)
|
|
62
|
+
),
|
|
63
|
+
self.html.key_value(
|
|
64
|
+
"Host",
|
|
65
|
+
self.html.code(obj.host)
|
|
66
|
+
),
|
|
67
|
+
self.html.key_value(
|
|
68
|
+
"Port",
|
|
69
|
+
self.html.text(str(obj.port), variant="primary")
|
|
70
|
+
),
|
|
71
|
+
self.html.key_value(
|
|
72
|
+
"Max Workers",
|
|
73
|
+
self.html.badge(str(obj.max_workers), variant="secondary", icon=Icons.SETTINGS)
|
|
74
|
+
),
|
|
75
|
+
self.html.key_value(
|
|
76
|
+
"Reflection",
|
|
77
|
+
self.html.badge(
|
|
78
|
+
"Enabled" if obj.enable_reflection else "Disabled",
|
|
79
|
+
variant="success" if obj.enable_reflection else "secondary",
|
|
80
|
+
icon=Icons.VISIBILITY if obj.enable_reflection else Icons.VISIBILITY_OFF
|
|
81
|
+
)
|
|
82
|
+
),
|
|
83
|
+
self.html.key_value(
|
|
84
|
+
"Health Check",
|
|
85
|
+
self.html.badge(
|
|
86
|
+
"Enabled" if obj.enable_health_check else "Disabled",
|
|
87
|
+
variant="success" if obj.enable_health_check else "secondary",
|
|
88
|
+
icon=Icons.HEALTH_AND_SAFETY if obj.enable_health_check else Icons.CANCEL
|
|
89
|
+
)
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
server_config_display.short_description = "Server Configuration"
|
|
94
|
+
|
|
95
|
+
def process_info_display(self, obj):
|
|
96
|
+
"""Display process information."""
|
|
97
|
+
return self.html.breakdown(
|
|
98
|
+
self.html.key_value(
|
|
99
|
+
"Instance ID",
|
|
100
|
+
self.html.code(obj.instance_id)
|
|
101
|
+
),
|
|
102
|
+
self.html.key_value(
|
|
103
|
+
"PID",
|
|
104
|
+
self.html.badge(str(obj.pid), variant="info", icon=Icons.MEMORY)
|
|
105
|
+
),
|
|
106
|
+
self.html.key_value(
|
|
107
|
+
"Hostname",
|
|
108
|
+
self.html.text(obj.hostname, variant="secondary")
|
|
109
|
+
),
|
|
110
|
+
self.html.key_value(
|
|
111
|
+
"Running",
|
|
112
|
+
self.html.badge(
|
|
113
|
+
"Yes" if obj.is_running else "No",
|
|
114
|
+
variant="success" if obj.is_running else "danger",
|
|
115
|
+
icon=Icons.CHECK_CIRCLE if obj.is_running else Icons.CANCEL
|
|
116
|
+
)
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
process_info_display.short_description = "Process Information"
|
|
121
|
+
|
|
122
|
+
def registered_services_display(self, obj):
|
|
123
|
+
"""Display registered services."""
|
|
124
|
+
if not obj.registered_services:
|
|
125
|
+
return self.html.empty("No services registered")
|
|
126
|
+
|
|
127
|
+
import json
|
|
128
|
+
try:
|
|
129
|
+
formatted = json.dumps(obj.registered_services, indent=2)
|
|
130
|
+
return self.html.code_block(formatted, language="json", max_height="400px")
|
|
131
|
+
except Exception:
|
|
132
|
+
return self.html.code(str(obj.registered_services))
|
|
133
|
+
|
|
134
|
+
registered_services_display.short_description = "Registered Services"
|
|
135
|
+
|
|
136
|
+
def error_display(self, obj):
|
|
137
|
+
"""Display error information if status is ERROR."""
|
|
138
|
+
if obj.status != "error" or not obj.error_message:
|
|
139
|
+
return self.html.inline(
|
|
140
|
+
self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
|
|
141
|
+
self.html.text("No errors", variant="success"),
|
|
142
|
+
separator=" "
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return self.html.breakdown(
|
|
146
|
+
self.html.key_value(
|
|
147
|
+
"Error Message",
|
|
148
|
+
self.html.text(obj.error_message, variant="danger")
|
|
149
|
+
),
|
|
150
|
+
self.html.key_value(
|
|
151
|
+
"Stopped At",
|
|
152
|
+
self.html.text(
|
|
153
|
+
obj.stopped_at.strftime("%Y-%m-%d %H:%M:%S") if obj.stopped_at else "N/A",
|
|
154
|
+
variant="secondary"
|
|
155
|
+
)
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
error_display.short_description = "Error Details"
|
|
160
|
+
|
|
161
|
+
def lifecycle_display(self, obj):
|
|
162
|
+
"""Display server lifecycle timestamps."""
|
|
163
|
+
return self.html.breakdown(
|
|
164
|
+
self.html.key_value(
|
|
165
|
+
"Started",
|
|
166
|
+
self.html.text(
|
|
167
|
+
obj.started_at.strftime("%Y-%m-%d %H:%M:%S"),
|
|
168
|
+
variant="success"
|
|
169
|
+
)
|
|
170
|
+
),
|
|
171
|
+
self.html.key_value(
|
|
172
|
+
"Last Heartbeat",
|
|
173
|
+
self.html.text(
|
|
174
|
+
obj.last_heartbeat.strftime("%Y-%m-%d %H:%M:%S"),
|
|
175
|
+
variant="info"
|
|
176
|
+
)
|
|
177
|
+
) if obj.last_heartbeat else None,
|
|
178
|
+
self.html.key_value(
|
|
179
|
+
"Stopped",
|
|
180
|
+
self.html.text(
|
|
181
|
+
obj.stopped_at.strftime("%Y-%m-%d %H:%M:%S"),
|
|
182
|
+
variant="danger"
|
|
183
|
+
)
|
|
184
|
+
) if obj.stopped_at else None,
|
|
185
|
+
self.html.key_value(
|
|
186
|
+
"Uptime",
|
|
187
|
+
self.html.badge(obj.uptime_display, variant="primary", icon=Icons.TIMER)
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
lifecycle_display.short_description = "Lifecycle"
|
|
192
|
+
|
|
193
|
+
# Fieldsets for detail view
|
|
194
|
+
def get_fieldsets(self, request, obj=None):
|
|
195
|
+
"""Dynamic fieldsets based on object state."""
|
|
196
|
+
fieldsets = [
|
|
197
|
+
(
|
|
198
|
+
"Server Identity",
|
|
199
|
+
{"fields": ("id", "instance_id", "address", "status")},
|
|
200
|
+
),
|
|
201
|
+
(
|
|
202
|
+
"Configuration",
|
|
203
|
+
{"fields": ("server_config_display", "host", "port", "max_workers", "enable_reflection", "enable_health_check")},
|
|
204
|
+
),
|
|
205
|
+
(
|
|
206
|
+
"Process Information",
|
|
207
|
+
{"fields": ("process_info_display", "pid", "hostname", "is_running")},
|
|
208
|
+
),
|
|
209
|
+
(
|
|
210
|
+
"Lifecycle",
|
|
211
|
+
{"fields": ("lifecycle_display", "started_at", "last_heartbeat", "stopped_at", "uptime_display")},
|
|
212
|
+
),
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
# Add registered services section if available
|
|
216
|
+
if obj and obj.registered_services:
|
|
217
|
+
fieldsets.append(
|
|
218
|
+
(
|
|
219
|
+
"Registered Services",
|
|
220
|
+
{"fields": ("registered_services_display",), "classes": ("collapse",)},
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Add error section only if status is ERROR
|
|
225
|
+
if obj and obj.status == "error":
|
|
226
|
+
fieldsets.append(
|
|
227
|
+
(
|
|
228
|
+
"Error Details",
|
|
229
|
+
{"fields": ("error_display", "error_message")},
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return fieldsets
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
__all__ = ["GRPCServerStatusAdmin"]
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
2
|
gRPC authentication components.
|
|
3
3
|
|
|
4
|
-
Provides
|
|
4
|
+
Provides API key authentication for gRPC services.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from .
|
|
7
|
+
from .api_key_auth import (
|
|
8
|
+
ApiKeyAuthInterceptor,
|
|
9
|
+
get_current_grpc_user,
|
|
10
|
+
get_current_grpc_api_key,
|
|
11
|
+
)
|
|
8
12
|
|
|
9
|
-
__all__ = [
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ApiKeyAuthInterceptor",
|
|
15
|
+
"get_current_grpc_user",
|
|
16
|
+
"get_current_grpc_api_key",
|
|
17
|
+
]
|