django-cfg 1.4.62__py3-none-any.whl → 1.4.63__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +3 -14
- django_cfg/apps/centrifugo/__init__.py +57 -0
- django_cfg/apps/centrifugo/admin/__init__.py +13 -0
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
- django_cfg/apps/centrifugo/admin/config.py +82 -0
- django_cfg/apps/centrifugo/apps.py +31 -0
- django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
- django_cfg/apps/centrifugo/codegen/README.md +242 -0
- django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
- django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
- django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
- django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
- django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
- django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
- django_cfg/apps/centrifugo/decorators.py +137 -0
- django_cfg/apps/centrifugo/management/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
- django_cfg/apps/centrifugo/managers/__init__.py +12 -0
- django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
- django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
- django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
- django_cfg/apps/centrifugo/models/__init__.py +11 -0
- django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
- django_cfg/apps/centrifugo/registry.py +106 -0
- django_cfg/apps/centrifugo/router.py +125 -0
- django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
- django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
- django_cfg/apps/centrifugo/serializers/channels.py +26 -0
- django_cfg/apps/centrifugo/serializers/health.py +17 -0
- django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
- django_cfg/apps/centrifugo/serializers/stats.py +21 -0
- django_cfg/apps/centrifugo/services/__init__.py +12 -0
- django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
- django_cfg/apps/centrifugo/services/client/client.py +577 -0
- django_cfg/apps/centrifugo/services/client/config.py +228 -0
- django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
- django_cfg/apps/centrifugo/services/config_helper.py +63 -0
- django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
- django_cfg/apps/centrifugo/services/logging.py +677 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
- django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
- django_cfg/apps/centrifugo/urls.py +31 -0
- django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
- django_cfg/apps/centrifugo/views/__init__.py +15 -0
- django_cfg/apps/centrifugo/views/admin_api.py +374 -0
- django_cfg/apps/centrifugo/views/dashboard.py +15 -0
- django_cfg/apps/centrifugo/views/monitoring.py +286 -0
- django_cfg/apps/centrifugo/views/testing_api.py +422 -0
- django_cfg/apps/support/utils/support_email_service.py +5 -18
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
- django_cfg/apps/urls.py +5 -5
- django_cfg/core/base/config_model.py +4 -44
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +8 -8
- django_cfg/core/utils/__init__.py +5 -0
- django_cfg/core/utils/url_helpers.py +73 -0
- django_cfg/modules/base.py +7 -7
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/config/config.py +8 -0
- django_cfg/modules/django_client/core/generator/__init__.py +42 -2
- django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
- django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
- django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
- django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
- django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
- django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
- django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
- django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
- django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
- django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
- django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
- django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
- django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
- django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
- django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
- django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
- django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
- django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
- django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
- django_cfg/modules/django_client/system/schema_parser.py +5 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
- django_cfg/modules/django_unfold/dashboard.py +25 -19
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/core.py +2 -0
- django_cfg/registry/modules.py +2 -2
- django_cfg/static/js/api/centrifugo/client.mjs +164 -0
- django_cfg/static/js/api/centrifugo/index.mjs +13 -0
- django_cfg/static/js/api/index.mjs +5 -5
- django_cfg/static/js/api/types.mjs +89 -26
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -70
- django_cfg/apps/ipc/README.md +0 -346
- django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
- django_cfg/apps/ipc/TESTING.md +0 -539
- django_cfg/apps/ipc/__init__.py +0 -60
- django_cfg/apps/ipc/admin.py +0 -232
- django_cfg/apps/ipc/apps.py +0 -98
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -229
- django_cfg/apps/ipc/serializers/__init__.py +0 -29
- django_cfg/apps/ipc/serializers/serializers.py +0 -343
- django_cfg/apps/ipc/services/__init__.py +0 -7
- django_cfg/apps/ipc/services/client/__init__.py +0 -23
- django_cfg/apps/ipc/services/client/client.py +0 -621
- django_cfg/apps/ipc/services/client/config.py +0 -214
- django_cfg/apps/ipc/services/client/exceptions.py +0 -201
- django_cfg/apps/ipc/services/logging.py +0 -239
- django_cfg/apps/ipc/services/monitor.py +0 -466
- django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
- django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
- django_cfg/apps/ipc/urls.py +0 -23
- django_cfg/apps/ipc/views/__init__.py +0 -13
- django_cfg/apps/ipc/views/dashboard.py +0 -15
- django_cfg/apps/ipc/views/monitoring.py +0 -251
- django_cfg/apps/ipc/views/testing.py +0 -285
- django_cfg/static/js/api/ipc/client.mjs +0 -114
- django_cfg/static/js/api/ipc/index.mjs +0 -13
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Optional
|
|
|
5
5
|
from django.db import transaction
|
|
6
6
|
from django.utils import timezone
|
|
7
7
|
|
|
8
|
+
from django_cfg.core.utils import get_otp_url
|
|
8
9
|
from django_cfg.modules.django_telegram import DjangoTelegram
|
|
9
10
|
|
|
10
11
|
from ..models import CustomUser, OTPSecret
|
|
@@ -17,20 +18,8 @@ logger = logging.getLogger(__name__)
|
|
|
17
18
|
class OTPService:
|
|
18
19
|
"""Simple OTP service for authentication."""
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"""Get OTP verification URL from configuration."""
|
|
23
|
-
try:
|
|
24
|
-
from django_cfg.core.state import get_current_config
|
|
25
|
-
config = get_current_config()
|
|
26
|
-
if config and hasattr(config, 'get_otp_url'):
|
|
27
|
-
return config.get_otp_url(otp_code)
|
|
28
|
-
else:
|
|
29
|
-
# Fallback URL if config is not available
|
|
30
|
-
return f"#otp-{otp_code}"
|
|
31
|
-
except Exception as e:
|
|
32
|
-
logger.warning(f"Could not generate OTP URL: {e}")
|
|
33
|
-
return f"#otp-{otp_code}"
|
|
21
|
+
# Expose get_otp_url as a static method for backward compatibility
|
|
22
|
+
_get_otp_url = staticmethod(get_otp_url)
|
|
34
23
|
|
|
35
24
|
@staticmethod
|
|
36
25
|
@transaction.atomic
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django-CFG Centrifugo Integration.
|
|
3
|
+
|
|
4
|
+
Provides Django integration with Centrifugo WebSocket server for real-time
|
|
5
|
+
notifications with delivery confirmation (ACK tracking).
|
|
6
|
+
|
|
7
|
+
Main Components:
|
|
8
|
+
- CentrifugoClient: Django client for publishing messages
|
|
9
|
+
- CentrifugoLog: Model for tracking publish operations
|
|
10
|
+
- CentrifugoLogger: Helper for automatic logging
|
|
11
|
+
- DjangoCfgCentrifugoConfig: Pydantic configuration model
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> from django_cfg.apps.centrifugo import get_centrifugo_client
|
|
15
|
+
>>>
|
|
16
|
+
>>> client = get_centrifugo_client()
|
|
17
|
+
>>> result = await client.publish_with_ack(
|
|
18
|
+
... channel="user#123",
|
|
19
|
+
... data={"title": "Hello", "message": "World"},
|
|
20
|
+
... ack_timeout=10
|
|
21
|
+
... )
|
|
22
|
+
>>> print(f"Delivered: {result.delivered}")
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from .services.client.client import CentrifugoClient, get_centrifugo_client, PublishResponse
|
|
26
|
+
from .services.client.config import DjangoCfgCentrifugoConfig
|
|
27
|
+
from .services.client.exceptions import (
|
|
28
|
+
CentrifugoBaseException,
|
|
29
|
+
CentrifugoConfigurationError,
|
|
30
|
+
CentrifugoConnectionError,
|
|
31
|
+
CentrifugoPublishError,
|
|
32
|
+
CentrifugoTimeoutError,
|
|
33
|
+
CentrifugoValidationError,
|
|
34
|
+
)
|
|
35
|
+
from .services.logging import CentrifugoLogContext, CentrifugoLogger
|
|
36
|
+
|
|
37
|
+
# Note: CentrifugoLog model is not imported here to avoid AppRegistryNotReady errors
|
|
38
|
+
# Import it directly from .models when needed: from django_cfg.apps.centrifugo.models import CentrifugoLog
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
# Config
|
|
42
|
+
"DjangoCfgCentrifugoConfig",
|
|
43
|
+
# Client
|
|
44
|
+
"CentrifugoClient",
|
|
45
|
+
"get_centrifugo_client",
|
|
46
|
+
"PublishResponse",
|
|
47
|
+
# Logging
|
|
48
|
+
"CentrifugoLogger",
|
|
49
|
+
"CentrifugoLogContext",
|
|
50
|
+
# Exceptions
|
|
51
|
+
"CentrifugoBaseException",
|
|
52
|
+
"CentrifugoTimeoutError",
|
|
53
|
+
"CentrifugoPublishError",
|
|
54
|
+
"CentrifugoConnectionError",
|
|
55
|
+
"CentrifugoConfigurationError",
|
|
56
|
+
"CentrifugoValidationError",
|
|
57
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django Admin for Centrifugo models.
|
|
3
|
+
|
|
4
|
+
Uses PydanticAdmin with declarative configuration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .centrifugo_log import CentrifugoLogAdmin
|
|
8
|
+
from .config import centrifugolog_config
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"centrifugolog_config",
|
|
12
|
+
"CentrifugoLogAdmin",
|
|
13
|
+
]
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centrifugo Log Admin.
|
|
3
|
+
|
|
4
|
+
PydanticAdmin for CentrifugoLog 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 CentrifugoLog
|
|
14
|
+
from .config import centrifugolog_config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@admin.register(CentrifugoLog)
|
|
18
|
+
class CentrifugoLogAdmin(PydanticAdmin):
|
|
19
|
+
"""
|
|
20
|
+
Centrifugo log admin with analytics and filtering.
|
|
21
|
+
|
|
22
|
+
Features:
|
|
23
|
+
- Color-coded status badges
|
|
24
|
+
- ACK tracking visualization
|
|
25
|
+
- Duration display with performance indicators
|
|
26
|
+
- Formatted JSON for message data
|
|
27
|
+
- Error details with highlighted display
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
config = centrifugolog_config
|
|
31
|
+
|
|
32
|
+
@computed_field("Type", ordering="wait_for_ack")
|
|
33
|
+
def type_badge(self, obj):
|
|
34
|
+
"""Display badge showing if this publish waited for ACK."""
|
|
35
|
+
if obj.wait_for_ack:
|
|
36
|
+
return self.html.badge("ACK", variant="success", icon=Icons.CHECK_CIRCLE)
|
|
37
|
+
else:
|
|
38
|
+
return self.html.badge("FIRE", variant="secondary", icon=Icons.NOTIFICATIONS)
|
|
39
|
+
|
|
40
|
+
@computed_field("ACKs", ordering="acks_received")
|
|
41
|
+
def acks_display(self, obj):
|
|
42
|
+
"""Display ACK count with context."""
|
|
43
|
+
if not obj.wait_for_ack:
|
|
44
|
+
return self.html.empty()
|
|
45
|
+
|
|
46
|
+
# If we have expected count
|
|
47
|
+
if obj.acks_expected:
|
|
48
|
+
rate = obj.delivery_rate
|
|
49
|
+
if rate == 1.0:
|
|
50
|
+
variant = "success"
|
|
51
|
+
icon = Icons.CHECK_CIRCLE
|
|
52
|
+
elif rate > 0:
|
|
53
|
+
variant = "warning"
|
|
54
|
+
icon = Icons.TIMER
|
|
55
|
+
else:
|
|
56
|
+
variant = "danger"
|
|
57
|
+
icon = Icons.ERROR
|
|
58
|
+
|
|
59
|
+
return self.html.badge(
|
|
60
|
+
f"{obj.acks_received}/{obj.acks_expected}",
|
|
61
|
+
variant=variant,
|
|
62
|
+
icon=icon,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Just show count
|
|
66
|
+
if obj.acks_received > 0:
|
|
67
|
+
return self.html.badge(
|
|
68
|
+
f"{obj.acks_received} ACKs", variant="success", icon=Icons.CHECK_CIRCLE
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
return self.html.badge("0 ACKs", variant="danger", icon=Icons.ERROR)
|
|
72
|
+
|
|
73
|
+
@computed_field("Duration", ordering="duration_ms")
|
|
74
|
+
def duration_display(self, obj):
|
|
75
|
+
"""Display duration with color coding based on speed."""
|
|
76
|
+
if obj.duration_ms is None:
|
|
77
|
+
return self.html.empty()
|
|
78
|
+
|
|
79
|
+
# Color code based on duration
|
|
80
|
+
if obj.duration_ms < 100:
|
|
81
|
+
variant = "success" # Fast
|
|
82
|
+
icon = Icons.SPEED
|
|
83
|
+
elif obj.duration_ms < 500:
|
|
84
|
+
variant = "warning" # Moderate
|
|
85
|
+
icon = Icons.TIMER
|
|
86
|
+
else:
|
|
87
|
+
variant = "danger" # Slow
|
|
88
|
+
icon = Icons.ERROR
|
|
89
|
+
|
|
90
|
+
return self.html.badge(f"{obj.duration_ms}ms", variant=variant, icon=icon)
|
|
91
|
+
|
|
92
|
+
def data_display(self, obj):
|
|
93
|
+
"""Display formatted JSON message data."""
|
|
94
|
+
if not obj.data:
|
|
95
|
+
return self.html.empty("No data")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
formatted = json.dumps(obj.data, indent=2)
|
|
99
|
+
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>'
|
|
100
|
+
except Exception:
|
|
101
|
+
return str(obj.data)
|
|
102
|
+
|
|
103
|
+
data_display.short_description = "Message Data"
|
|
104
|
+
|
|
105
|
+
def error_details_display(self, obj):
|
|
106
|
+
"""Display error information if publish failed."""
|
|
107
|
+
if obj.is_successful or obj.status == "pending":
|
|
108
|
+
return self.html.inline(
|
|
109
|
+
[
|
|
110
|
+
self.html.icon(Icons.CHECK_CIRCLE, size="sm"),
|
|
111
|
+
self.html.span("No errors", "text-green-600"),
|
|
112
|
+
]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
details = []
|
|
116
|
+
|
|
117
|
+
if obj.error_code:
|
|
118
|
+
details.append(
|
|
119
|
+
self.html.inline(
|
|
120
|
+
[
|
|
121
|
+
self.html.span("Error Code:", "font-semibold"),
|
|
122
|
+
self.html.badge(obj.error_code, variant="danger", icon=Icons.ERROR),
|
|
123
|
+
],
|
|
124
|
+
separator=" ",
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if obj.error_message:
|
|
129
|
+
details.append(
|
|
130
|
+
self.html.inline(
|
|
131
|
+
[
|
|
132
|
+
self.html.span("Message:", "font-semibold"),
|
|
133
|
+
self.html.span(obj.error_message, "text-red-600"),
|
|
134
|
+
],
|
|
135
|
+
separator=" ",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return "<br>".join(details) if details else self.html.empty()
|
|
140
|
+
|
|
141
|
+
error_details_display.short_description = "Error Details"
|
|
142
|
+
|
|
143
|
+
def delivery_stats_display(self, obj):
|
|
144
|
+
"""Display delivery statistics."""
|
|
145
|
+
if not obj.wait_for_ack:
|
|
146
|
+
return self.html.empty("No ACK tracking")
|
|
147
|
+
|
|
148
|
+
stats = []
|
|
149
|
+
|
|
150
|
+
# ACK timeout
|
|
151
|
+
if obj.ack_timeout:
|
|
152
|
+
stats.append(
|
|
153
|
+
self.html.inline(
|
|
154
|
+
[
|
|
155
|
+
self.html.span("Timeout:", "font-semibold"),
|
|
156
|
+
self.html.span(f"{obj.ack_timeout}s", "text-gray-600"),
|
|
157
|
+
],
|
|
158
|
+
separator=" ",
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# ACKs received
|
|
163
|
+
stats.append(
|
|
164
|
+
self.html.inline(
|
|
165
|
+
[
|
|
166
|
+
self.html.span("ACKs Received:", "font-semibold"),
|
|
167
|
+
self.html.badge(str(obj.acks_received), variant="info"),
|
|
168
|
+
],
|
|
169
|
+
separator=" ",
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# ACKs expected (if known)
|
|
174
|
+
if obj.acks_expected:
|
|
175
|
+
stats.append(
|
|
176
|
+
self.html.inline(
|
|
177
|
+
[
|
|
178
|
+
self.html.span("ACKs Expected:", "font-semibold"),
|
|
179
|
+
self.html.span(str(obj.acks_expected), "text-gray-600"),
|
|
180
|
+
],
|
|
181
|
+
separator=" ",
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Delivery rate
|
|
186
|
+
if obj.delivery_rate is not None:
|
|
187
|
+
rate_pct = obj.delivery_rate * 100
|
|
188
|
+
stats.append(
|
|
189
|
+
self.html.inline(
|
|
190
|
+
[
|
|
191
|
+
self.html.span("Delivery Rate:", "font-semibold"),
|
|
192
|
+
self.html.span(f"{rate_pct:.1f}%", "text-blue-600"),
|
|
193
|
+
],
|
|
194
|
+
separator=" ",
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return "<br>".join(stats) if stats else self.html.empty()
|
|
199
|
+
|
|
200
|
+
delivery_stats_display.short_description = "Delivery Statistics"
|
|
201
|
+
|
|
202
|
+
# Fieldsets for detail view
|
|
203
|
+
def get_fieldsets(self, request, obj=None):
|
|
204
|
+
"""Dynamic fieldsets based on object state."""
|
|
205
|
+
fieldsets = [
|
|
206
|
+
(
|
|
207
|
+
"Publish Information",
|
|
208
|
+
{"fields": ("id", "message_id", "channel", "user", "status")},
|
|
209
|
+
),
|
|
210
|
+
(
|
|
211
|
+
"Message Data",
|
|
212
|
+
{"fields": ("data_display",), "classes": ("collapse",)},
|
|
213
|
+
),
|
|
214
|
+
("Performance", {"fields": ("duration_ms", "created_at", "completed_at")}),
|
|
215
|
+
("Metadata", {"fields": ("caller_ip", "user_agent"), "classes": ("collapse",)}),
|
|
216
|
+
]
|
|
217
|
+
|
|
218
|
+
# Add ACK tracking section if enabled
|
|
219
|
+
if obj and obj.wait_for_ack:
|
|
220
|
+
fieldsets.insert(
|
|
221
|
+
2,
|
|
222
|
+
(
|
|
223
|
+
"ACK Tracking",
|
|
224
|
+
{
|
|
225
|
+
"fields": (
|
|
226
|
+
"delivery_stats_display",
|
|
227
|
+
"wait_for_ack",
|
|
228
|
+
"ack_timeout",
|
|
229
|
+
"acks_received",
|
|
230
|
+
"acks_expected",
|
|
231
|
+
)
|
|
232
|
+
},
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Add error section only if failed
|
|
237
|
+
if obj and not obj.is_successful and obj.status != "pending":
|
|
238
|
+
fieldsets.insert(
|
|
239
|
+
3,
|
|
240
|
+
(
|
|
241
|
+
"Error Details",
|
|
242
|
+
{"fields": ("error_details_display", "error_code", "error_message")},
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return fieldsets
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
__all__ = ["CentrifugoLogAdmin"]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Admin configuration for Centrifugo models.
|
|
3
|
+
|
|
4
|
+
Declarative AdminConfig using PydanticAdmin patterns.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from django_cfg.modules.django_admin import (
|
|
8
|
+
AdminConfig,
|
|
9
|
+
BadgeField,
|
|
10
|
+
DateTimeField,
|
|
11
|
+
Icons,
|
|
12
|
+
UserField,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from ..models import CentrifugoLog
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Declarative configuration for CentrifugoLog
|
|
19
|
+
centrifugolog_config = AdminConfig(
|
|
20
|
+
model=CentrifugoLog,
|
|
21
|
+
# Performance optimization
|
|
22
|
+
select_related=["user"],
|
|
23
|
+
|
|
24
|
+
# List display
|
|
25
|
+
list_display=[
|
|
26
|
+
"channel",
|
|
27
|
+
"type_badge",
|
|
28
|
+
"status",
|
|
29
|
+
"user",
|
|
30
|
+
"acks_display",
|
|
31
|
+
"duration_display",
|
|
32
|
+
"created_at",
|
|
33
|
+
"completed_at"
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
# Auto-generated display methods
|
|
37
|
+
display_fields=[
|
|
38
|
+
BadgeField(name="channel", title="Channel", variant="info", icon=Icons.NOTIFICATIONS),
|
|
39
|
+
BadgeField(
|
|
40
|
+
name="status",
|
|
41
|
+
title="Status",
|
|
42
|
+
label_map={
|
|
43
|
+
"pending": "warning",
|
|
44
|
+
"success": "success",
|
|
45
|
+
"failed": "danger",
|
|
46
|
+
"timeout": "danger",
|
|
47
|
+
"partial": "warning",
|
|
48
|
+
},
|
|
49
|
+
),
|
|
50
|
+
UserField(name="user", title="User", header=True),
|
|
51
|
+
DateTimeField(name="created_at", title="Created", ordering="created_at"),
|
|
52
|
+
DateTimeField(name="completed_at", title="Completed", ordering="completed_at"),
|
|
53
|
+
],
|
|
54
|
+
# Filters
|
|
55
|
+
list_filter=["status", "wait_for_ack", "channel", "created_at"],
|
|
56
|
+
search_fields=[
|
|
57
|
+
"message_id",
|
|
58
|
+
"channel",
|
|
59
|
+
"user__username",
|
|
60
|
+
"user__email",
|
|
61
|
+
"error_message",
|
|
62
|
+
],
|
|
63
|
+
# Autocomplete for user field
|
|
64
|
+
autocomplete_fields=["user"],
|
|
65
|
+
# Readonly fields
|
|
66
|
+
readonly_fields=[
|
|
67
|
+
"id",
|
|
68
|
+
"message_id",
|
|
69
|
+
"created_at",
|
|
70
|
+
"completed_at",
|
|
71
|
+
"data_display",
|
|
72
|
+
"error_details_display",
|
|
73
|
+
"delivery_stats_display",
|
|
74
|
+
],
|
|
75
|
+
# Date hierarchy
|
|
76
|
+
date_hierarchy="created_at",
|
|
77
|
+
# Per page
|
|
78
|
+
list_per_page=50,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = ["centrifugolog_config"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django app configuration for Centrifugo module.
|
|
3
|
+
|
|
4
|
+
Provides Centrifugo pub/sub client with ACK tracking.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from django.apps import AppConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CentrifugoConfig(AppConfig):
|
|
11
|
+
"""
|
|
12
|
+
Centrifugo application configuration.
|
|
13
|
+
|
|
14
|
+
Provides:
|
|
15
|
+
- Async client for publishing messages to Centrifugo
|
|
16
|
+
- ACK tracking for delivery confirmation
|
|
17
|
+
- Logging of all publish operations
|
|
18
|
+
- Migration-friendly API (mirrors django-ipc patterns)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
22
|
+
name = "django_cfg.apps.centrifugo"
|
|
23
|
+
label = "django_cfg_centrifugo"
|
|
24
|
+
verbose_name = "Centrifugo WebSocket"
|
|
25
|
+
|
|
26
|
+
def ready(self):
|
|
27
|
+
"""Initialize app when Django starts."""
|
|
28
|
+
from django_cfg.modules.django_logging import get_logger
|
|
29
|
+
|
|
30
|
+
logger = get_logger("centrifugo.apps")
|
|
31
|
+
logger.info("Centrifugo app initialized")
|