django-cfg 1.4.61__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.

Files changed (179) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/services/otp_service.py +3 -14
  3. django_cfg/apps/centrifugo/__init__.py +57 -0
  4. django_cfg/apps/centrifugo/admin/__init__.py +13 -0
  5. django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
  6. django_cfg/apps/centrifugo/admin/config.py +82 -0
  7. django_cfg/apps/centrifugo/apps.py +31 -0
  8. django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
  9. django_cfg/apps/centrifugo/codegen/README.md +242 -0
  10. django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
  11. django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
  12. django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
  13. django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
  14. django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
  15. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
  16. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
  17. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
  18. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
  19. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
  20. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
  21. django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
  22. django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
  23. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
  24. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
  25. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
  26. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
  27. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
  28. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
  29. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
  30. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
  31. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
  32. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
  33. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
  34. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
  35. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
  36. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
  37. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
  38. django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
  39. django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
  40. django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
  41. django_cfg/apps/centrifugo/decorators.py +137 -0
  42. django_cfg/apps/centrifugo/management/__init__.py +1 -0
  43. django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
  44. django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
  45. django_cfg/apps/centrifugo/managers/__init__.py +12 -0
  46. django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
  47. django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
  48. django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
  49. django_cfg/apps/centrifugo/models/__init__.py +11 -0
  50. django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
  51. django_cfg/apps/centrifugo/registry.py +106 -0
  52. django_cfg/apps/centrifugo/router.py +125 -0
  53. django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
  54. django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
  55. django_cfg/apps/centrifugo/serializers/channels.py +26 -0
  56. django_cfg/apps/centrifugo/serializers/health.py +17 -0
  57. django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
  58. django_cfg/apps/centrifugo/serializers/stats.py +21 -0
  59. django_cfg/apps/centrifugo/services/__init__.py +12 -0
  60. django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
  61. django_cfg/apps/centrifugo/services/client/client.py +577 -0
  62. django_cfg/apps/centrifugo/services/client/config.py +228 -0
  63. django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
  64. django_cfg/apps/centrifugo/services/config_helper.py +63 -0
  65. django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
  66. django_cfg/apps/centrifugo/services/logging.py +677 -0
  67. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
  68. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
  69. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
  70. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
  71. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
  72. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
  73. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
  77. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
  78. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
  79. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
  80. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
  81. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
  82. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
  83. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
  84. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
  85. django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
  86. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
  87. django_cfg/apps/centrifugo/urls.py +31 -0
  88. django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
  89. django_cfg/apps/centrifugo/views/__init__.py +15 -0
  90. django_cfg/apps/centrifugo/views/admin_api.py +374 -0
  91. django_cfg/apps/centrifugo/views/dashboard.py +15 -0
  92. django_cfg/apps/centrifugo/views/monitoring.py +286 -0
  93. django_cfg/apps/centrifugo/views/testing_api.py +422 -0
  94. django_cfg/apps/support/utils/support_email_service.py +5 -18
  95. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
  96. django_cfg/apps/urls.py +5 -5
  97. django_cfg/core/base/config_model.py +4 -44
  98. django_cfg/core/builders/apps_builder.py +2 -2
  99. django_cfg/core/generation/integration_generators/third_party.py +8 -8
  100. django_cfg/core/utils/__init__.py +5 -0
  101. django_cfg/core/utils/url_helpers.py +73 -0
  102. django_cfg/modules/base.py +7 -7
  103. django_cfg/modules/django_client/core/__init__.py +2 -1
  104. django_cfg/modules/django_client/core/config/config.py +8 -0
  105. django_cfg/modules/django_client/core/generator/__init__.py +42 -2
  106. django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
  107. django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
  108. django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
  109. django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
  110. django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
  111. django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
  112. django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
  113. django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
  114. django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
  115. django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
  116. django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
  117. django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
  118. django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
  119. django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
  120. django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
  121. django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
  122. django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
  123. django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
  124. django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
  125. django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
  126. django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
  127. django_cfg/modules/django_client/system/schema_parser.py +5 -1
  128. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
  129. django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
  130. django_cfg/modules/django_unfold/dashboard.py +25 -19
  131. django_cfg/pyproject.toml +1 -1
  132. django_cfg/registry/core.py +2 -0
  133. django_cfg/registry/modules.py +2 -2
  134. django_cfg/static/js/api/centrifugo/client.mjs +164 -0
  135. django_cfg/static/js/api/centrifugo/index.mjs +13 -0
  136. django_cfg/static/js/api/index.mjs +5 -5
  137. django_cfg/static/js/api/types.mjs +89 -26
  138. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
  139. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -68
  140. django_cfg/apps/ipc/README.md +0 -346
  141. django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
  142. django_cfg/apps/ipc/TESTING.md +0 -539
  143. django_cfg/apps/ipc/__init__.py +0 -60
  144. django_cfg/apps/ipc/admin.py +0 -212
  145. django_cfg/apps/ipc/apps.py +0 -28
  146. django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
  147. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  148. django_cfg/apps/ipc/models.py +0 -221
  149. django_cfg/apps/ipc/serializers/__init__.py +0 -29
  150. django_cfg/apps/ipc/serializers/serializers.py +0 -343
  151. django_cfg/apps/ipc/services/__init__.py +0 -7
  152. django_cfg/apps/ipc/services/client/__init__.py +0 -23
  153. django_cfg/apps/ipc/services/client/client.py +0 -621
  154. django_cfg/apps/ipc/services/client/config.py +0 -214
  155. django_cfg/apps/ipc/services/client/exceptions.py +0 -201
  156. django_cfg/apps/ipc/services/logging.py +0 -239
  157. django_cfg/apps/ipc/services/monitor.py +0 -466
  158. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
  159. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
  160. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
  161. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
  162. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
  163. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
  164. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
  165. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
  166. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
  167. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
  168. django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
  169. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
  170. django_cfg/apps/ipc/urls.py +0 -23
  171. django_cfg/apps/ipc/views/__init__.py +0 -13
  172. django_cfg/apps/ipc/views/dashboard.py +0 -15
  173. django_cfg/apps/ipc/views/monitoring.py +0 -251
  174. django_cfg/apps/ipc/views/testing.py +0 -285
  175. django_cfg/static/js/api/ipc/client.mjs +0 -114
  176. django_cfg/static/js/api/ipc/index.mjs +0 -13
  177. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
  178. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
  179. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,81 @@
1
+ """
2
+ Centrifugo Template Tags.
3
+
4
+ Provides template tags for accessing Centrifugo configuration in templates.
5
+ """
6
+
7
+ from django import template
8
+ from ..services import get_centrifugo_config
9
+
10
+ register = template.Library()
11
+
12
+
13
+ @register.simple_tag
14
+ def centrifugo_admin_url():
15
+ """
16
+ Get Centrifugo admin UI URL from configuration.
17
+
18
+ Returns the HTTP admin URL from centrifugo_api_url.
19
+ Example: http://localhost:8002
20
+
21
+ Usage in template:
22
+ {% load centrifugo_tags %}
23
+ {% centrifugo_admin_url %}
24
+ """
25
+ config = get_centrifugo_config()
26
+ if not config or not config.centrifugo_api_url:
27
+ return ""
28
+
29
+ # Return HTTP API URL (admin UI is on same host)
30
+ url = config.centrifugo_api_url
31
+
32
+ # Remove /api suffix if present
33
+ if url.endswith('/api'):
34
+ url = url[:-len('/api')]
35
+
36
+ # Remove trailing slash
37
+ url = url.rstrip('/')
38
+
39
+ return url
40
+
41
+
42
+ @register.simple_tag
43
+ def centrifugo_is_configured():
44
+ """
45
+ Check if Centrifugo is configured.
46
+
47
+ Returns True if Centrifugo config exists and has URL.
48
+
49
+ Usage in template:
50
+ {% load centrifugo_tags %}
51
+ {% centrifugo_is_configured as is_configured %}
52
+ {% if is_configured %}...{% endif %}
53
+ """
54
+ config = get_centrifugo_config()
55
+ return bool(config and config.centrifugo_url)
56
+
57
+
58
+ @register.simple_tag
59
+ def centrifugo_wrapper_url():
60
+ """
61
+ Get Centrifugo wrapper URL from configuration.
62
+
63
+ Returns the wrapper URL (our Django proxy).
64
+ Example: http://localhost:8080
65
+
66
+ Usage in template:
67
+ {% load centrifugo_tags %}
68
+ {% centrifugo_wrapper_url %}
69
+ """
70
+ config = get_centrifugo_config()
71
+ if not config or not config.wrapper_url:
72
+ return ""
73
+
74
+ return config.wrapper_url.rstrip('/')
75
+
76
+
77
+ __all__ = [
78
+ "centrifugo_admin_url",
79
+ "centrifugo_is_configured",
80
+ "centrifugo_wrapper_url",
81
+ ]
@@ -0,0 +1,31 @@
1
+ """
2
+ URL patterns for Centrifugo module.
3
+
4
+ Public API endpoints for Centrifugo monitoring and admin API proxy.
5
+ """
6
+
7
+ from django.urls import include, path
8
+ from rest_framework import routers
9
+
10
+ from .views.admin_api import CentrifugoAdminAPIViewSet
11
+ from .views.monitoring import CentrifugoMonitorViewSet
12
+ from .views.testing_api import CentrifugoTestingAPIViewSet
13
+
14
+ app_name = 'django_cfg_centrifugo'
15
+
16
+ # Create router
17
+ router = routers.DefaultRouter()
18
+
19
+ # Monitoring endpoints (Django logs based)
20
+ router.register(r'monitor', CentrifugoMonitorViewSet, basename='monitor')
21
+
22
+ # Admin API proxy endpoints (Centrifugo server based)
23
+ router.register(r'server', CentrifugoAdminAPIViewSet, basename='server')
24
+
25
+ # Testing API endpoints (live testing from dashboard)
26
+ router.register(r'testing', CentrifugoTestingAPIViewSet, basename='testing')
27
+
28
+ urlpatterns = [
29
+ # Include router URLs
30
+ path('', include(router.urls)),
31
+ ]
@@ -1,14 +1,14 @@
1
1
  """
2
- Admin URLs for IPC/RPC dashboard.
2
+ Admin URLs for Centrifugo dashboard.
3
3
 
4
- Dashboard interface for monitoring RPC activity.
4
+ Dashboard interface for monitoring Centrifugo publish activity.
5
5
  """
6
6
 
7
7
  from django.urls import include, path
8
8
 
9
9
  from .views import dashboard_view
10
10
 
11
- app_name = 'django_cfg_ipc_admin'
11
+ app_name = 'django_cfg_centrifugo_admin'
12
12
 
13
13
 
14
14
  urlpatterns = [
@@ -16,5 +16,5 @@ urlpatterns = [
16
16
  path('', dashboard_view, name='dashboard'),
17
17
 
18
18
  # Include API endpoints for dashboard AJAX calls
19
- path('api/', include('django_cfg.apps.ipc.urls')),
19
+ path('api/', include('django_cfg.apps.centrifugo.urls')),
20
20
  ]
@@ -0,0 +1,15 @@
1
+ """
2
+ Views for Centrifugo module.
3
+ """
4
+
5
+ from .admin_api import CentrifugoAdminAPIViewSet
6
+ from .dashboard import dashboard_view
7
+ from .monitoring import CentrifugoMonitorViewSet
8
+ from .testing_api import CentrifugoTestingAPIViewSet
9
+
10
+ __all__ = [
11
+ 'CentrifugoMonitorViewSet',
12
+ 'CentrifugoAdminAPIViewSet',
13
+ 'CentrifugoTestingAPIViewSet',
14
+ 'dashboard_view',
15
+ ]
@@ -0,0 +1,374 @@
1
+ """
2
+ Centrifugo Admin API Proxy.
3
+
4
+ Proxies requests to Centrifugo server API with authentication and type safety.
5
+ Provides Django endpoints that map to Centrifugo HTTP API methods.
6
+ """
7
+
8
+ import asyncio
9
+ import httpx
10
+ from django.http import JsonResponse
11
+ from django_cfg.modules.django_logging import get_logger
12
+ from drf_spectacular.utils import OpenApiParameter, extend_schema
13
+ from rest_framework import status, viewsets
14
+ from rest_framework.authentication import SessionAuthentication
15
+ from rest_framework.decorators import action
16
+ from rest_framework.permissions import IsAdminUser
17
+ from rest_framework.response import Response
18
+
19
+ from ..serializers import (
20
+ CentrifugoInfoRequest,
21
+ CentrifugoInfoResponse,
22
+ CentrifugoChannelsRequest,
23
+ CentrifugoChannelsResponse,
24
+ CentrifugoPresenceRequest,
25
+ CentrifugoPresenceResponse,
26
+ CentrifugoPresenceStatsRequest,
27
+ CentrifugoPresenceStatsResponse,
28
+ CentrifugoHistoryRequest,
29
+ CentrifugoHistoryResponse,
30
+ )
31
+ from ..services import get_centrifugo_config
32
+
33
+ logger = get_logger("centrifugo.admin_api")
34
+
35
+
36
+ class CentrifugoAdminAPIViewSet(viewsets.ViewSet):
37
+ """
38
+ Centrifugo Admin API Proxy ViewSet.
39
+
40
+ Provides type-safe proxy endpoints to Centrifugo server API.
41
+ All requests are authenticated via Django session and proxied to Centrifugo.
42
+ """
43
+
44
+ authentication_classes = [SessionAuthentication]
45
+ permission_classes = [IsAdminUser]
46
+
47
+ def __init__(self, *args, **kwargs):
48
+ super().__init__(*args, **kwargs)
49
+ self._http_client = None
50
+
51
+ @property
52
+ def http_client(self) -> httpx.AsyncClient:
53
+ """Get or create HTTP client for Centrifugo API calls."""
54
+ if self._http_client is None:
55
+ config = get_centrifugo_config()
56
+ if not config:
57
+ raise ValueError("Centrifugo not configured")
58
+
59
+ headers = {"Content-Type": "application/json"}
60
+ if config.centrifugo_api_key:
61
+ headers["X-API-Key"] = config.centrifugo_api_key
62
+
63
+ # Use base URL without /api suffix since we'll add it in requests
64
+ base_url = config.centrifugo_api_url.rstrip('/api').rstrip('/')
65
+
66
+ self._http_client = httpx.AsyncClient(
67
+ base_url=base_url,
68
+ headers=headers,
69
+ timeout=httpx.Timeout(10.0),
70
+ )
71
+
72
+ return self._http_client
73
+
74
+ async def _call_centrifugo_api(self, method: str, params: dict = None) -> dict:
75
+ """
76
+ Call Centrifugo API method.
77
+
78
+ Args:
79
+ method: API method name (e.g., "info", "channels", "presence")
80
+ params: Method parameters (optional)
81
+
82
+ Returns:
83
+ API response data or error dict
84
+
85
+ Raises:
86
+ httpx.HTTPError: If API call fails
87
+ """
88
+ payload = {"method": method, "params": params or {}}
89
+
90
+ try:
91
+ response = await self.http_client.post("/api", json=payload)
92
+ response.raise_for_status()
93
+ result = response.json()
94
+
95
+ # Return both error and result - let caller decide
96
+ return result
97
+
98
+ except httpx.HTTPError as e:
99
+ logger.error(f"Centrifugo API HTTP error: {e}", exc_info=True)
100
+ raise
101
+
102
+ @extend_schema(
103
+ tags=["Centrifugo Admin API"],
104
+ summary="Get Centrifugo server info",
105
+ description="Returns server information including node count, version, and uptime.",
106
+ request=CentrifugoInfoRequest,
107
+ responses={
108
+ 200: CentrifugoInfoResponse,
109
+ 500: {"description": "Server error"},
110
+ },
111
+ )
112
+ @action(detail=False, methods=["post"], url_path="info")
113
+ def info(self, request):
114
+ """Get Centrifugo server information."""
115
+ try:
116
+ result = asyncio.run(self._call_centrifugo_api("info", params={}))
117
+
118
+ # Check for Centrifugo API error
119
+ if "error" in result and result["error"]:
120
+ return Response(
121
+ {"error": result["error"], "result": None},
122
+ status=status.HTTP_200_OK # Not a server error, just API error
123
+ )
124
+
125
+ # Success response
126
+ response = CentrifugoInfoResponse(error=None, result=result.get("result", {}))
127
+ return Response(response.model_dump())
128
+
129
+ except Exception as e:
130
+ logger.error(f"Failed to get server info: {e}", exc_info=True)
131
+ return Response(
132
+ {"error": {"code": 102, "message": str(e)}},
133
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
134
+ )
135
+
136
+ @extend_schema(
137
+ tags=["Centrifugo Admin API"],
138
+ summary="List active channels",
139
+ description="Returns list of active channels with optional pattern filter.",
140
+ request=CentrifugoChannelsRequest,
141
+ responses={
142
+ 200: CentrifugoChannelsResponse,
143
+ 500: {"description": "Server error"},
144
+ },
145
+ )
146
+ @action(detail=False, methods=["post"], url_path="channels")
147
+ def channels(self, request):
148
+ """List active channels."""
149
+ try:
150
+ req_data = CentrifugoChannelsRequest(**request.data)
151
+ result = asyncio.run(self._call_centrifugo_api(
152
+ "channels", params=req_data.model_dump(exclude_none=True)
153
+ ))
154
+
155
+ # Check for Centrifugo API error
156
+ if "error" in result and result["error"]:
157
+ return Response(
158
+ {"error": result["error"], "result": None},
159
+ status=status.HTTP_200_OK
160
+ )
161
+
162
+ response = CentrifugoChannelsResponse(error=None, result=result.get("result", {}))
163
+ return Response(response.model_dump())
164
+
165
+ except Exception as e:
166
+ logger.error(f"Failed to list channels: {e}", exc_info=True)
167
+ return Response(
168
+ {"error": {"code": 102, "message": str(e)}},
169
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
170
+ )
171
+
172
+ @extend_schema(
173
+ tags=["Centrifugo Admin API"],
174
+ summary="Get channel presence",
175
+ description="Returns list of clients currently subscribed to a channel.",
176
+ request=CentrifugoPresenceRequest,
177
+ responses={
178
+ 200: CentrifugoPresenceResponse,
179
+ 500: {"description": "Server error"},
180
+ },
181
+ )
182
+ @action(detail=False, methods=["post"], url_path="presence")
183
+ def presence(self, request):
184
+ """Get channel presence (active subscribers)."""
185
+ try:
186
+ req_data = CentrifugoPresenceRequest(**request.data)
187
+ result = asyncio.run(self._call_centrifugo_api(
188
+ "presence", params=req_data.model_dump()
189
+ ))
190
+
191
+ # Check for Centrifugo API error (e.g., code 108 "not available")
192
+ if "error" in result and result["error"]:
193
+ return Response(
194
+ {"error": result["error"], "result": None},
195
+ status=status.HTTP_200_OK
196
+ )
197
+
198
+ response = CentrifugoPresenceResponse(error=None, result=result.get("result", {}))
199
+ return Response(response.model_dump())
200
+
201
+ except Exception as e:
202
+ logger.error(f"Failed to get presence: {e}", exc_info=True)
203
+ return Response(
204
+ {"error": {"code": 102, "message": str(e)}},
205
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
206
+ )
207
+
208
+ @extend_schema(
209
+ tags=["Centrifugo Admin API"],
210
+ summary="Get channel presence statistics",
211
+ description="Returns quick statistics about channel presence (num_clients, num_users).",
212
+ request=CentrifugoPresenceStatsRequest,
213
+ responses={
214
+ 200: CentrifugoPresenceStatsResponse,
215
+ 500: {"description": "Server error"},
216
+ },
217
+ )
218
+ @action(detail=False, methods=["post"], url_path="presence-stats")
219
+ def presence_stats(self, request):
220
+ """Get channel presence statistics."""
221
+ try:
222
+ req_data = CentrifugoPresenceStatsRequest(**request.data)
223
+ result = asyncio.run(self._call_centrifugo_api(
224
+ "presence_stats", params=req_data.model_dump()
225
+ ))
226
+
227
+ # Check for Centrifugo API error (e.g., code 108 "not available")
228
+ if "error" in result and result["error"]:
229
+ return Response(
230
+ {"error": result["error"], "result": None},
231
+ status=status.HTTP_200_OK
232
+ )
233
+
234
+ response = CentrifugoPresenceStatsResponse(error=None, result=result.get("result", {}))
235
+ return Response(response.model_dump())
236
+
237
+ except Exception as e:
238
+ logger.error(f"Failed to get presence stats: {e}", exc_info=True)
239
+ return Response(
240
+ {"error": {"code": 102, "message": str(e)}},
241
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
242
+ )
243
+
244
+ @extend_schema(
245
+ tags=["Centrifugo Admin API"],
246
+ summary="Get channel history",
247
+ description="Returns message history for a channel.",
248
+ request=CentrifugoHistoryRequest,
249
+ responses={
250
+ 200: CentrifugoHistoryResponse,
251
+ 500: {"description": "Server error"},
252
+ },
253
+ )
254
+ @action(detail=False, methods=["post"], url_path="history")
255
+ def history(self, request):
256
+ """Get channel message history."""
257
+ try:
258
+ req_data = CentrifugoHistoryRequest(**request.data)
259
+ result = asyncio.run(self._call_centrifugo_api(
260
+ "history", params=req_data.model_dump(exclude_none=True)
261
+ ))
262
+
263
+ # Check for Centrifugo API error (e.g., code 108 "not available")
264
+ if "error" in result and result["error"]:
265
+ return Response(
266
+ {"error": result["error"], "result": None},
267
+ status=status.HTTP_200_OK
268
+ )
269
+
270
+ response = CentrifugoHistoryResponse(error=None, result=result.get("result", {}))
271
+ return Response(response.model_dump())
272
+
273
+ except Exception as e:
274
+ logger.error(f"Failed to get history: {e}", exc_info=True)
275
+ return Response(
276
+ {"error": {"code": 102, "message": str(e)}},
277
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
278
+ )
279
+
280
+ @extend_schema(
281
+ tags=["Centrifugo Admin API"],
282
+ summary="Get connection token for dashboard",
283
+ description="Returns JWT token and config for WebSocket connection to Centrifugo.",
284
+ request=None,
285
+ responses={
286
+ 200: {
287
+ "type": "object",
288
+ "properties": {
289
+ "token": {"type": "string"},
290
+ "config": {
291
+ "type": "object",
292
+ "properties": {
293
+ "centrifugo_url": {"type": "string"},
294
+ "expires_at": {"type": "string"},
295
+ }
296
+ }
297
+ }
298
+ },
299
+ 400: {"description": "Centrifugo not configured"},
300
+ 500: {"description": "Server error"},
301
+ },
302
+ )
303
+ @action(detail=False, methods=["post"], url_path="auth/token")
304
+ def auth_token(self, request):
305
+ """
306
+ Generate JWT token for dashboard WebSocket connection.
307
+
308
+ Returns token that authenticates the current user to subscribe
309
+ to the dashboard channel for real-time updates.
310
+ """
311
+ try:
312
+ import jwt
313
+ import time
314
+ from datetime import datetime, timezone
315
+
316
+ config = get_centrifugo_config()
317
+ if not config:
318
+ return Response(
319
+ {"error": "Centrifugo is not configured"},
320
+ status=status.HTTP_400_BAD_REQUEST
321
+ )
322
+
323
+ # Generate JWT token for current user
324
+ now = int(time.time())
325
+ exp = now + 3600 # 1 hour
326
+
327
+ payload = {
328
+ "sub": str(request.user.id), # User ID
329
+ "exp": exp, # Expiration
330
+ "iat": now, # Issued at
331
+ }
332
+
333
+ # Subscribe to dashboard channel
334
+ payload["channels"] = ["centrifugo#dashboard"]
335
+
336
+ # Use HMAC secret from config or Django SECRET_KEY
337
+ secret = config.centrifugo_token_hmac_secret or ""
338
+ if not secret:
339
+ from django.conf import settings
340
+ secret = settings.SECRET_KEY
341
+
342
+ token = jwt.encode(payload, secret, algorithm="HS256")
343
+
344
+ return Response({
345
+ "token": token,
346
+ "config": {
347
+ "centrifugo_url": config.centrifugo_url,
348
+ "expires_at": datetime.fromtimestamp(exp, tz=timezone.utc).isoformat(),
349
+ }
350
+ })
351
+
352
+ except Exception as e:
353
+ logger.error(f"Failed to generate auth token: {e}", exc_info=True)
354
+ return Response(
355
+ {"error": str(e)},
356
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
357
+ )
358
+
359
+ def __del__(self):
360
+ """Cleanup HTTP client on deletion."""
361
+ if self._http_client:
362
+ import asyncio
363
+
364
+ try:
365
+ loop = asyncio.get_event_loop()
366
+ if loop.is_running():
367
+ loop.create_task(self._http_client.aclose())
368
+ else:
369
+ loop.run_until_complete(self._http_client.aclose())
370
+ except Exception:
371
+ pass # Ignore cleanup errors
372
+
373
+
374
+ __all__ = ["CentrifugoAdminAPIViewSet"]
@@ -0,0 +1,15 @@
1
+ """
2
+ Dashboard view for Centrifugo monitoring.
3
+ """
4
+
5
+ from django.contrib.admin.views.decorators import staff_member_required
6
+ from django.shortcuts import render
7
+
8
+
9
+ @staff_member_required
10
+ def dashboard_view(request):
11
+ """Render the Centrifugo dashboard template."""
12
+ context = {
13
+ 'page_title': 'Centrifugo Monitor Dashboard',
14
+ }
15
+ return render(request, 'django_cfg_centrifugo/pages/dashboard.html', context)