django-cfg 1.4.82__py3-none-any.whl → 1.4.84__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 (152) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/centrifugo/serializers/channels.py +3 -0
  3. django_cfg/apps/centrifugo/serializers/publishes.py +2 -0
  4. django_cfg/apps/centrifugo/views/admin_api.py +1 -1
  5. django_cfg/apps/centrifugo/views/monitoring.py +116 -6
  6. django_cfg/apps/centrifugo/views/testing_api.py +1 -1
  7. django_cfg/apps/dashboard/__init__.py +8 -0
  8. django_cfg/apps/dashboard/api/__init__.py +27 -0
  9. django_cfg/apps/dashboard/api/serializers.py +165 -0
  10. django_cfg/apps/dashboard/api/viewsets.py +257 -0
  11. django_cfg/apps/dashboard/apps.py +23 -0
  12. django_cfg/apps/dashboard/services/__init__.py +11 -0
  13. django_cfg/apps/dashboard/services/statistics_service.py +235 -0
  14. django_cfg/apps/dashboard/services/system_health_service.py +280 -0
  15. django_cfg/apps/dashboard/urls.py +23 -0
  16. django_cfg/apps/frontend/JWT_AUTO_INJECTION.md +224 -0
  17. django_cfg/apps/frontend/views.py +121 -7
  18. django_cfg/apps/tasks/api/serializers.py +82 -0
  19. django_cfg/apps/tasks/api/views.py +571 -0
  20. django_cfg/apps/urls.py +2 -1
  21. django_cfg/core/builders/apps_builder.py +1 -0
  22. django_cfg/middleware/README.md +12 -0
  23. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +1 -1
  24. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +7 -10
  25. django_cfg/modules/django_client/core/parser/openapi30.py +26 -1
  26. django_cfg/modules/django_client/core/parser/openapi31.py +26 -1
  27. django_cfg/pyproject.toml +1 -1
  28. django_cfg/static/frontend/admin/404.html +1 -1
  29. django_cfg/static/frontend/admin/500.html +1 -1
  30. django_cfg/static/frontend/admin/_next/static/-Zk0eDB7OJOEFrFyR5BwZ/_buildManifest.js +1 -0
  31. django_cfg/static/frontend/admin/_next/static/chunks/{43076.55dd23b6cd68edb0.js → 20695.a7d37b6c40ad3f58.js} +1 -1
  32. django_cfg/static/frontend/admin/_next/static/chunks/{25033.d626f78bc99bc4a1.js → 25033.ee3e206d5a2877b6.js} +2 -2
  33. django_cfg/static/frontend/admin/_next/static/chunks/{25892.964150a58f94ce06.js → 25892.5cbed319f9226fdc.js} +1 -1
  34. django_cfg/static/frontend/admin/_next/static/chunks/{2d7a934f.dfef67639279d59d.js → 2d7a934f.329c61f23af1a7ec.js} +1 -1
  35. django_cfg/static/frontend/admin/_next/static/chunks/{30649.00c679812a56aee3.js → 30649.963cfb7268b5864a.js} +1 -1
  36. django_cfg/static/frontend/admin/_next/static/chunks/{30875.784491146c38dbcb.js → 30875.82c3741757b8aa32.js} +1 -1
  37. django_cfg/static/frontend/admin/_next/static/chunks/{32163.ab0ca435b3f26c04.js → 32163.109a03a7252f1508.js} +1 -1
  38. django_cfg/static/frontend/admin/_next/static/chunks/43076-4be6a9794e9c3e8b.js +1 -0
  39. django_cfg/static/frontend/admin/_next/static/chunks/{49978.fb8ba7ee52ffe666.js → 49978.db5a86a8eb233f35.js} +1 -1
  40. django_cfg/static/frontend/admin/_next/static/chunks/50314-79c02212788f1ec7.js +1 -0
  41. django_cfg/static/frontend/admin/_next/static/chunks/{50319.f786248384877960.js → 50319.fd78c7f7e3f1966e.js} +1 -1
  42. django_cfg/static/frontend/admin/_next/static/chunks/{52908.b690e323d8f8efdd.js → 52908.da5b850b0bc0970c.js} +1 -1
  43. django_cfg/static/frontend/admin/_next/static/chunks/{53710.80ca863525d137db.js → 53710.7176bbee6c7b78be.js} +1 -1
  44. django_cfg/static/frontend/admin/_next/static/chunks/{57982.251fed8d58adcf53.js → 57982.2c90b33b0934522a.js} +2 -2
  45. django_cfg/static/frontend/admin/_next/static/chunks/{60181.86e18057c4caaa97.js → 60181.c94d78d10eb5da37.js} +1 -1
  46. django_cfg/static/frontend/admin/_next/static/chunks/{60374.bde0ec1249aa79c6.js → 60374.5d80cfc45439b2b0.js} +1 -1
  47. django_cfg/static/frontend/admin/_next/static/chunks/{6884.7b1db804c88280ed.js → 6884.624d563508cf6db4.js} +1 -1
  48. django_cfg/static/frontend/admin/_next/static/chunks/{69436.9515b854cdf4b57a.js → 69436.be44021e3d7c99c7.js} +1 -1
  49. django_cfg/static/frontend/admin/_next/static/chunks/{70628.00cdd98f672e684f.js → 70628.58e8c38a66543d5e.js} +1 -1
  50. django_cfg/static/frontend/admin/_next/static/chunks/{73218.a826c2248612b37f.js → 73218.d712e7bd678e23a8.js} +1 -1
  51. django_cfg/static/frontend/admin/_next/static/chunks/{76334.64fbaa923d9ac293.js → 76334.f43f2d8b4bbf8dd6.js} +1 -1
  52. django_cfg/static/frontend/admin/_next/static/chunks/{7799.2b280f8ddf067d49.js → 7799.1575cc212bc750c7.js} +1 -1
  53. django_cfg/static/frontend/admin/_next/static/chunks/{80574.620a8a5b4eb91c25.js → 80574.92638dd7b9979664.js} +1 -1
  54. django_cfg/static/frontend/admin/_next/static/chunks/{81127.a0603c3394892d4e.js → 81127.3ead500eec887152.js} +1 -1
  55. django_cfg/static/frontend/admin/_next/static/chunks/82296-a2c8d38f62224be5.js +1 -0
  56. django_cfg/static/frontend/admin/_next/static/chunks/{8383.eb6188b22c453e14.js → 8383.e25a442df26b2e26.js} +1 -1
  57. django_cfg/static/frontend/admin/_next/static/chunks/{85833.35e6ca25ac32a7d2.js → 85833.b0dead4fbcbfdd1b.js} +1 -1
  58. django_cfg/static/frontend/admin/_next/static/chunks/{95365.fc9d7653a78839d0.js → 95365.2b430045fc2e5acf.js} +1 -1
  59. django_cfg/static/frontend/admin/_next/static/chunks/{96424.0793b94836eb13a6.js → 96424.11d76570e9a94b85.js} +1 -1
  60. django_cfg/static/frontend/admin/_next/static/chunks/pages/404-c283223d1afd02a2.js +1 -0
  61. django_cfg/static/frontend/admin/_next/static/chunks/pages/500-389d6d3e1f2f7fda.js +1 -0
  62. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f25bec36bbdc9625.js +272 -0
  63. django_cfg/static/frontend/admin/_next/static/chunks/pages/_error-5291033275c26d09.js +1 -0
  64. django_cfg/static/frontend/admin/_next/static/chunks/pages/index-d7bc30185f52cbca.js +1 -0
  65. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{cookies-24588bf5551f30df.js → cookies-b39c7f22c066e2c6.js} +1 -1
  66. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{privacy-354dae34a4c4da59.js → privacy-5aedad0cf3a4f80f.js} +1 -1
  67. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{security-0a5d7fa591ebb1ae.js → security-dbd854d0d5d483e2.js} +1 -1
  68. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{terms-c3d80322f52dc112.js → terms-f3e1d2b9e5edf12f.js} +1 -1
  69. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-22532c65971225eb.js +1 -0
  70. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/profile-e93a65e8e7d9022b.js +1 -0
  71. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/ui-669e8f2a785beba2.js +1 -0
  72. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-a8a9ba76f2c75354.js +1 -0
  73. django_cfg/static/frontend/admin/_next/static/chunks/{webpack-905bba30877f6490.js → webpack-92add5f95c66e349.js} +1 -1
  74. django_cfg/static/frontend/admin/_next/static/css/78d677ac1677c210.css +3 -0
  75. django_cfg/static/frontend/admin/auth.html +1 -1
  76. django_cfg/static/frontend/admin/index.html +1 -1
  77. django_cfg/static/frontend/admin/legal/cookies.html +1 -1
  78. django_cfg/static/frontend/admin/legal/privacy.html +1 -1
  79. django_cfg/static/frontend/admin/legal/security.html +1 -1
  80. django_cfg/static/frontend/admin/legal/terms.html +1 -1
  81. django_cfg/static/frontend/admin/private/centrifugo.html +1 -0
  82. django_cfg/static/frontend/admin/private/profile.html +1 -0
  83. django_cfg/static/frontend/admin/private/ui.html +1 -0
  84. django_cfg/static/frontend/admin/private.html +1 -1
  85. django_cfg/templates/admin/index.html +97 -63
  86. django_cfg/templates/admin_old/index.html +80 -0
  87. django_cfg/templatetags/django_cfg.py +57 -10
  88. {django_cfg-1.4.82.dist-info → django_cfg-1.4.84.dist-info}/METADATA +1 -1
  89. {django_cfg-1.4.82.dist-info → django_cfg-1.4.84.dist-info}/RECORD +142 -122
  90. django_cfg/static/frontend/admin/_next/static/chunks/pages/404-7cdad2942c3fb179.js +0 -1
  91. django_cfg/static/frontend/admin/_next/static/chunks/pages/500-6cdb27b00678364f.js +0 -1
  92. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-9c5ca2471de6b000.js +0 -272
  93. django_cfg/static/frontend/admin/_next/static/chunks/pages/_error-b8071a05cabe1c2d.js +0 -1
  94. django_cfg/static/frontend/admin/_next/static/chunks/pages/index-bf88192a30e013a9.js +0 -1
  95. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-2f58633ddf63a5bc.js +0 -1
  96. django_cfg/static/frontend/admin/_next/static/chunks/pages/ui-73632f2d9c6b11ab.js +0 -1
  97. django_cfg/static/frontend/admin/_next/static/css/e201974f9a4d64e6.css +0 -3
  98. django_cfg/static/frontend/admin/_next/static/qEBrQJUidlI_maQ4xQnI0/_buildManifest.js +0 -1
  99. django_cfg/static/frontend/admin/ui.html +0 -92
  100. /django_cfg/static/frontend/admin/_next/static/{qEBrQJUidlI_maQ4xQnI0 → -Zk0eDB7OJOEFrFyR5BwZ}/_ssgManifest.js +0 -0
  101. /django_cfg/templates/{admin → admin_old}/components/action_grid.html +0 -0
  102. /django_cfg/templates/{admin → admin_old}/components/card.html +0 -0
  103. /django_cfg/templates/{admin → admin_old}/components/data_table.html +0 -0
  104. /django_cfg/templates/{admin → admin_old}/components/metric_card.html +0 -0
  105. /django_cfg/templates/{admin → admin_old}/components/modal.html +0 -0
  106. /django_cfg/templates/{admin → admin_old}/components/progress_bar.html +0 -0
  107. /django_cfg/templates/{admin → admin_old}/components/section_header.html +0 -0
  108. /django_cfg/templates/{admin → admin_old}/components/stat_item.html +0 -0
  109. /django_cfg/templates/{admin → admin_old}/components/stats_grid.html +0 -0
  110. /django_cfg/templates/{admin → admin_old}/components/status_badge.html +0 -0
  111. /django_cfg/templates/{admin → admin_old}/components/user_avatar.html +0 -0
  112. /django_cfg/templates/{admin → admin_old}/constance/change_list.html +0 -0
  113. /django_cfg/templates/{admin → admin_old}/constance/includes/default_value.html +0 -0
  114. /django_cfg/templates/{admin → admin_old}/constance/includes/fieldset_header.html +0 -0
  115. /django_cfg/templates/{admin → admin_old}/constance/includes/results_list.html +0 -0
  116. /django_cfg/templates/{admin → admin_old}/constance/includes/setting_row.html +0 -0
  117. /django_cfg/templates/{admin → admin_old}/constance/includes/table_headers.html +0 -0
  118. /django_cfg/templates/{admin → admin_old}/examples/component_class_example.html +0 -0
  119. /django_cfg/templates/{admin → admin_old}/import_export/change_list_export.html +0 -0
  120. /django_cfg/templates/{admin → admin_old}/import_export/change_list_import.html +0 -0
  121. /django_cfg/templates/{admin → admin_old}/import_export/change_list_import_export.html +0 -0
  122. /django_cfg/templates/{admin → admin_old}/index_new.html +0 -0
  123. /django_cfg/templates/{admin → admin_old}/layouts/base_dashboard.html +0 -0
  124. /django_cfg/templates/{admin → admin_old}/layouts/dashboard_with_tabs.html +0 -0
  125. /django_cfg/templates/{admin → admin_old}/sections/commands_section.html +0 -0
  126. /django_cfg/templates/{admin → admin_old}/sections/documentation_section.html +0 -0
  127. /django_cfg/templates/{admin → admin_old}/sections/overview_section.html +0 -0
  128. /django_cfg/templates/{admin → admin_old}/sections/stats_section.html +0 -0
  129. /django_cfg/templates/{admin → admin_old}/sections/system_section.html +0 -0
  130. /django_cfg/templates/{admin → admin_old}/sections/widgets_section.html +0 -0
  131. /django_cfg/templates/{admin → admin_old}/snippets/components/activity_tracker.html +0 -0
  132. /django_cfg/templates/{admin → admin_old}/snippets/components/charts_section.html +0 -0
  133. /django_cfg/templates/{admin → admin_old}/snippets/components/django_commands.html +0 -0
  134. /django_cfg/templates/{admin → admin_old}/snippets/components/quick_actions.html +0 -0
  135. /django_cfg/templates/{admin → admin_old}/snippets/components/recent_activity_improved.html +0 -0
  136. /django_cfg/templates/{admin → admin_old}/snippets/components/recent_users_table.html +0 -0
  137. /django_cfg/templates/{admin → admin_old}/snippets/components/stats_cards.html +0 -0
  138. /django_cfg/templates/{admin → admin_old}/snippets/components/stats_tiles.html +0 -0
  139. /django_cfg/templates/{admin → admin_old}/snippets/components/system_health.html +0 -0
  140. /django_cfg/templates/{admin → admin_old}/snippets/components/system_metrics.html +0 -0
  141. /django_cfg/templates/{admin → admin_old}/snippets/components/user_permissions.html +0 -0
  142. /django_cfg/templates/{admin → admin_old}/snippets/tabs/app_stats_tab.html +0 -0
  143. /django_cfg/templates/{admin → admin_old}/snippets/tabs/commands_tab.html +0 -0
  144. /django_cfg/templates/{admin → admin_old}/snippets/tabs/documentation_tab.html +0 -0
  145. /django_cfg/templates/{admin → admin_old}/snippets/tabs/overview_tab.html +0 -0
  146. /django_cfg/templates/{admin → admin_old}/snippets/tabs/stats_tab.html +0 -0
  147. /django_cfg/templates/{admin → admin_old}/snippets/tabs/users_tab.html +0 -0
  148. /django_cfg/templates/{admin → admin_old}/snippets/tabs/widgets_tab.html +0 -0
  149. /django_cfg/templates/{admin → admin_old}/snippets/zones/zones_table.html +0 -0
  150. {django_cfg-1.4.82.dist-info → django_cfg-1.4.84.dist-info}/WHEEL +0 -0
  151. {django_cfg-1.4.82.dist-info → django_cfg-1.4.84.dist-info}/entry_points.txt +0 -0
  152. {django_cfg-1.4.82.dist-info → django_cfg-1.4.84.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,280 @@
1
+ """
2
+ System Health Service
3
+
4
+ Monitors system components health status.
5
+ Checks database, cache, queue, storage, and API availability.
6
+ """
7
+
8
+ import logging
9
+ from datetime import datetime
10
+ from typing import Any, Dict, List, Literal
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class SystemHealthService:
16
+ """
17
+ Service for monitoring system component health.
18
+
19
+ %%PRIORITY:HIGH%%
20
+ %%AI_HINT: Checks health of various system components%%
21
+
22
+ TAGS: health, monitoring, system, service
23
+ DEPENDS_ON: [django.db.connection, django.core.cache, redis]
24
+ """
25
+
26
+ def __init__(self):
27
+ """Initialize system health service."""
28
+ self.logger = logger
29
+
30
+ def check_database_health(self) -> Dict[str, Any]:
31
+ """
32
+ Check database connectivity and health.
33
+
34
+ Returns:
35
+ Health status dictionary with status, description, last_check
36
+ """
37
+ try:
38
+ from django.db import connection
39
+
40
+ # Test database connection
41
+ with connection.cursor() as cursor:
42
+ cursor.execute("SELECT 1")
43
+ cursor.fetchone()
44
+
45
+ return {
46
+ 'component': 'database',
47
+ 'status': 'healthy',
48
+ 'description': 'Database connection is working',
49
+ 'last_check': datetime.now().isoformat(),
50
+ 'health_percentage': 100,
51
+ }
52
+ except Exception as e:
53
+ self.logger.error(f"Database health check failed: {e}")
54
+ return {
55
+ 'component': 'database',
56
+ 'status': 'error',
57
+ 'description': f'Database error: {str(e)}',
58
+ 'last_check': datetime.now().isoformat(),
59
+ 'health_percentage': 0,
60
+ }
61
+
62
+ def check_cache_health(self) -> Dict[str, Any]:
63
+ """
64
+ Check cache (Redis/Memcached) connectivity and health.
65
+
66
+ Returns:
67
+ Health status dictionary
68
+ """
69
+ try:
70
+ from django.core.cache import cache
71
+
72
+ # Test cache by setting and getting a test value
73
+ test_key = 'health_check_test'
74
+ test_value = 'ok'
75
+ cache.set(test_key, test_value, timeout=10)
76
+ result = cache.get(test_key)
77
+
78
+ if result == test_value:
79
+ cache.delete(test_key)
80
+ return {
81
+ 'component': 'cache',
82
+ 'status': 'healthy',
83
+ 'description': 'Cache is working correctly',
84
+ 'last_check': datetime.now().isoformat(),
85
+ 'health_percentage': 100,
86
+ }
87
+ else:
88
+ return {
89
+ 'component': 'cache',
90
+ 'status': 'warning',
91
+ 'description': 'Cache test failed',
92
+ 'last_check': datetime.now().isoformat(),
93
+ 'health_percentage': 50,
94
+ }
95
+
96
+ except Exception as e:
97
+ self.logger.error(f"Cache health check failed: {e}")
98
+ return {
99
+ 'component': 'cache',
100
+ 'status': 'error',
101
+ 'description': f'Cache error: {str(e)}',
102
+ 'last_check': datetime.now().isoformat(),
103
+ 'health_percentage': 0,
104
+ }
105
+
106
+ def check_queue_health(self) -> Dict[str, Any]:
107
+ """
108
+ Check task queue (Celery/Dramatiq) health.
109
+
110
+ Returns:
111
+ Health status dictionary
112
+ """
113
+ try:
114
+ # TODO: Add real queue health check
115
+ # Example: Check Redis connection, queue sizes, worker status
116
+ from django_cfg.modules.django_tasks import DjangoTasks
117
+
118
+ tasks = DjangoTasks()
119
+ redis_client = tasks.get_redis_client()
120
+
121
+ if redis_client and redis_client.ping():
122
+ return {
123
+ 'component': 'queue',
124
+ 'status': 'healthy',
125
+ 'description': 'Queue system is operational',
126
+ 'last_check': datetime.now().isoformat(),
127
+ 'health_percentage': 100,
128
+ }
129
+ else:
130
+ return {
131
+ 'component': 'queue',
132
+ 'status': 'error',
133
+ 'description': 'Queue system unavailable',
134
+ 'last_check': datetime.now().isoformat(),
135
+ 'health_percentage': 0,
136
+ }
137
+
138
+ except Exception as e:
139
+ self.logger.error(f"Queue health check failed: {e}")
140
+ return {
141
+ 'component': 'queue',
142
+ 'status': 'error',
143
+ 'description': f'Queue error: {str(e)}',
144
+ 'last_check': datetime.now().isoformat(),
145
+ 'health_percentage': 0,
146
+ }
147
+
148
+ def check_storage_health(self) -> Dict[str, Any]:
149
+ """
150
+ Check storage/file system health.
151
+
152
+ Returns:
153
+ Health status dictionary
154
+ """
155
+ try:
156
+ import os
157
+ from django.conf import settings
158
+
159
+ # Check if media directory is writable
160
+ media_root = getattr(settings, 'MEDIA_ROOT', None)
161
+
162
+ if media_root and os.path.exists(media_root) and os.access(media_root, os.W_OK):
163
+ return {
164
+ 'component': 'storage',
165
+ 'status': 'healthy',
166
+ 'description': 'Storage is accessible and writable',
167
+ 'last_check': datetime.now().isoformat(),
168
+ 'health_percentage': 100,
169
+ }
170
+ else:
171
+ return {
172
+ 'component': 'storage',
173
+ 'status': 'warning',
174
+ 'description': 'Storage may have limited access',
175
+ 'last_check': datetime.now().isoformat(),
176
+ 'health_percentage': 70,
177
+ }
178
+
179
+ except Exception as e:
180
+ self.logger.error(f"Storage health check failed: {e}")
181
+ return {
182
+ 'component': 'storage',
183
+ 'status': 'error',
184
+ 'description': f'Storage error: {str(e)}',
185
+ 'last_check': datetime.now().isoformat(),
186
+ 'health_percentage': 0,
187
+ }
188
+
189
+ def get_all_health_checks(self) -> List[Dict[str, Any]]:
190
+ """
191
+ Run all health checks and return aggregated results.
192
+
193
+ Returns:
194
+ List of health check results for all components
195
+
196
+ USED_BY: DashboardViewSet.system_health endpoint
197
+ """
198
+ checks = [
199
+ self.check_database_health(),
200
+ self.check_cache_health(),
201
+ self.check_queue_health(),
202
+ self.check_storage_health(),
203
+ ]
204
+
205
+ return checks
206
+
207
+ def get_overall_health_status(self) -> Dict[str, Any]:
208
+ """
209
+ Get overall system health status.
210
+
211
+ Returns:
212
+ Dictionary with overall status, percentage, and component details
213
+ """
214
+ checks = self.get_all_health_checks()
215
+
216
+ # Calculate overall health percentage
217
+ total_health = sum(check.get('health_percentage', 0) for check in checks)
218
+ overall_percentage = total_health // len(checks) if checks else 0
219
+
220
+ # Determine overall status
221
+ statuses = [check.get('status') for check in checks]
222
+ if 'error' in statuses:
223
+ overall_status = 'error'
224
+ elif 'warning' in statuses:
225
+ overall_status = 'warning'
226
+ else:
227
+ overall_status = 'healthy'
228
+
229
+ return {
230
+ 'overall_status': overall_status,
231
+ 'overall_health_percentage': overall_percentage,
232
+ 'components': checks,
233
+ 'timestamp': datetime.now().isoformat(),
234
+ }
235
+
236
+ def get_quick_actions(self) -> List[Dict[str, Any]]:
237
+ """
238
+ Get quick action buttons for dashboard.
239
+
240
+ Returns:
241
+ List of quick action dictionaries
242
+
243
+ %%AI_HINT: Actions link to admin pages or trigger common tasks%%
244
+ """
245
+ actions = [
246
+ {
247
+ 'title': 'User Management',
248
+ 'description': 'Manage users and permissions',
249
+ 'icon': 'people',
250
+ 'link': '/admin/auth/user/',
251
+ 'color': 'primary',
252
+ 'category': 'admin',
253
+ },
254
+ {
255
+ 'title': 'View Logs',
256
+ 'description': 'Check system logs',
257
+ 'icon': 'description',
258
+ 'link': '/admin/django_cfg/logs/',
259
+ 'color': 'secondary',
260
+ 'category': 'system',
261
+ },
262
+ {
263
+ 'title': 'Clear Cache',
264
+ 'description': 'Clear application cache',
265
+ 'icon': 'refresh',
266
+ 'link': '/cfg/admin/cache/clear/',
267
+ 'color': 'warning',
268
+ 'category': 'system',
269
+ },
270
+ {
271
+ 'title': 'Run Backup',
272
+ 'description': 'Create system backup',
273
+ 'icon': 'backup',
274
+ 'link': '/cfg/admin/backup/create/',
275
+ 'color': 'success',
276
+ 'category': 'system',
277
+ },
278
+ ]
279
+
280
+ return actions
@@ -0,0 +1,23 @@
1
+ """
2
+ Dashboard URLs
3
+
4
+ RESTful API endpoints for dashboard data.
5
+ Follows the same pattern as tasks app.
6
+ """
7
+
8
+ from django.urls import include, path
9
+ from rest_framework.routers import DefaultRouter
10
+
11
+ from .api.viewsets import DashboardViewSet
12
+
13
+ app_name = 'dashboard'
14
+
15
+ # Main router for ViewSets
16
+ router = DefaultRouter()
17
+ router.register(r'', DashboardViewSet, basename='dashboard')
18
+
19
+ urlpatterns = [
20
+ # RESTful API endpoints using ViewSets
21
+ # Mounted at cfg/dashboard/api/
22
+ path('api/', include(router.urls)),
23
+ ]
@@ -0,0 +1,224 @@
1
+ # JWT Auto-Injection для Next.js приложений
2
+
3
+ ## Обзор
4
+
5
+ Django-CFG автоматически инжектирует JWT токены (`auth_token` и `refresh_token`) в `localStorage` для авторизованных пользователей при загрузке Next.js приложений через **NextJSStaticView**.
6
+
7
+ ## Как это работает
8
+
9
+ ### Автоматическая инжекция в Next.js apps (рекомендуется)
10
+
11
+ **NextJSStaticView** автоматически инжектирует JWT токены во все HTML ответы для авторизованных пользователей.
12
+
13
+ ```python
14
+ # urls.py
15
+ from django_cfg.apps.frontend.views import AdminView
16
+
17
+ urlpatterns = [
18
+ path('admin/', include('django_cfg.apps.frontend.urls')), # JWT injection automatic
19
+ ]
20
+ ```
21
+
22
+ При загрузке **любой страницы Next.js приложения**, если пользователь авторизован:
23
+ 1. View обслуживает статический файл Next.js
24
+ 2. Генерируются JWT токены (access + refresh)
25
+ 3. Токены автоматически инжектируются в `<head>` или `<body>` через `<script>` тег
26
+ 4. Токены сохраняются в `localStorage`
27
+
28
+ **Преимущества:**
29
+ - Работает только для Next.js приложений (безопасный scope)
30
+ - Не нужно модифицировать templates
31
+ - Централизованная логика в базовом view
32
+ - Не влияет на другие HTML responses (Django admin, etc.)
33
+
34
+ ### Template Tags (для кастомных шаблонов)
35
+
36
+ Если вы используете собственные Django шаблоны, можете использовать готовые template tags:
37
+
38
+ #### 1. Полная автоматическая инжекция
39
+
40
+ ```django
41
+ {% load django_cfg %}
42
+
43
+ <!DOCTYPE html>
44
+ <html>
45
+ <head>
46
+ <title>My App</title>
47
+ {% inject_jwt_tokens_script %} {# Автоматически инжектит оба токена #}
48
+ </head>
49
+ <body>
50
+ <!-- Your content -->
51
+ </body>
52
+ </html>
53
+ ```
54
+
55
+ #### 2. Отдельные токены
56
+
57
+ ```django
58
+ {% load django_cfg %}
59
+
60
+ <script>
61
+ // Access token
62
+ const accessToken = '{% user_jwt_token %}';
63
+
64
+ // Refresh token
65
+ const refreshToken = '{% user_jwt_refresh_token %}';
66
+
67
+ // Manual storage
68
+ localStorage.setItem('auth_token', accessToken);
69
+ localStorage.setItem('refresh_token', refreshToken);
70
+ </script>
71
+ ```
72
+
73
+ ## Использование в Next.js
74
+
75
+ После инжекции токены доступны в вашем Next.js приложении:
76
+
77
+ ```typescript
78
+ // В любом Next.js компоненте или API клиенте
79
+ const accessToken = localStorage.getItem('auth_token');
80
+ const refreshToken = localStorage.getItem('refresh_token');
81
+
82
+ // Использование с API клиентом
83
+ import { API } from './generated/cfg';
84
+
85
+ const api = new API('http://localhost:8000', {
86
+ storage: {
87
+ getItem: (key) => localStorage.getItem(key),
88
+ setItem: (key, value) => localStorage.setItem(key, value),
89
+ removeItem: (key) => localStorage.removeItem(key),
90
+ }
91
+ });
92
+ ```
93
+
94
+ ## Безопасность
95
+
96
+ ### Что инжектируется
97
+ - `auth_token` - JWT access token (короткий срок жизни)
98
+ - `refresh_token` - JWT refresh token (длинный срок жизни)
99
+
100
+ ### Когда инжектируется
101
+ Токены генерируются **только** если:
102
+ 1. Пользователь **авторизован** через Django session
103
+ 2. Загружается **HTML файл** (не JS, CSS и т.д.)
104
+ 3. `rest_framework_simplejwt` установлен
105
+
106
+ ### Безопасность токенов
107
+ - Токены генерируются **на лету** при каждом запросе
108
+ - Access token имеет короткий срок жизни (настраивается в `JWTConfig`)
109
+ - Refresh token позволяет получить новый access token без повторной авторизации
110
+ - Токены хранятся только в `localStorage` на клиенте
111
+
112
+ ## Конфигурация JWT
113
+
114
+ Настройка времени жизни токенов в `django_cfg`:
115
+
116
+ ```python
117
+ from django_cfg.models.api import JWTConfig
118
+
119
+ jwt_config = JWTConfig(
120
+ access_token_lifetime_hours=24, # Access token на 24 часа
121
+ refresh_token_lifetime_days=30, # Refresh token на 30 дней
122
+ rotate_refresh_tokens=True, # Ротация refresh токенов
123
+ blacklist_after_rotation=True, # Блэклист старых токенов
124
+ )
125
+ ```
126
+
127
+ ## Отладка
128
+
129
+ Проверьте в консоли браузера:
130
+
131
+ ```javascript
132
+ // Проверить наличие токенов
133
+ console.log('Access Token:', localStorage.getItem('auth_token'));
134
+ console.log('Refresh Token:', localStorage.getItem('refresh_token'));
135
+
136
+ // Сообщение об успешной инжекции
137
+ // "JWT tokens injected successfully"
138
+ ```
139
+
140
+ ## Примеры
141
+
142
+ ### Пример 1: Автоматическая инжекция в Next.js приложении (рекомендуется)
143
+
144
+ ```python
145
+ # urls.py - JWT injection работает автоматически
146
+ urlpatterns = [
147
+ path('cfg/admin/', include('django_cfg.apps.frontend.urls')), # Admin Panel with JWT
148
+ ]
149
+
150
+ # views.py - создайте свой Next.js app view
151
+ from django_cfg.apps.frontend.views import NextJSStaticView
152
+
153
+ class MyAppView(NextJSStaticView):
154
+ """Custom Next.js app with automatic JWT injection."""
155
+ app_name = 'my_app' # Serves from static/frontend/my_app/
156
+ ```
157
+
158
+ При переходе на **любую страницу** Next.js приложения авторизованный пользователь автоматически получит JWT токены в localStorage.
159
+
160
+ **⚠️ На /cfg/admin/auth токены НЕ инжектятся** - это страница логина, пользователь ещё не авторизован!
161
+
162
+ ### Пример 2: Кастомный шаблон с инжекцией
163
+
164
+ ```django
165
+ {% load django_cfg %}
166
+
167
+ <!DOCTYPE html>
168
+ <html>
169
+ <head>
170
+ <meta charset="utf-8">
171
+ <title>Centrifugo Monitor</title>
172
+
173
+ {# Автоматическая инжекция JWT токенов #}
174
+ {% inject_jwt_tokens_script %}
175
+ </head>
176
+ <body>
177
+ <div id="root"></div>
178
+ <script src="/_next/static/chunks/main.js"></script>
179
+ </body>
180
+ </html>
181
+ ```
182
+
183
+ ## Требования
184
+
185
+ - Django с включенной аутентификацией
186
+ - `rest_framework_simplejwt` установлен
187
+ - Пользователь авторизован через Django session
188
+
189
+ ## API Reference
190
+
191
+ ### Template Tags
192
+
193
+ #### `{% user_jwt_token %}`
194
+ Возвращает JWT access token для текущего пользователя.
195
+
196
+ #### `{% user_jwt_refresh_token %}`
197
+ Возвращает JWT refresh token для текущего пользователя.
198
+
199
+ #### `{% inject_jwt_tokens_script %}`
200
+ Генерирует полный `<script>` тег с автоматической инжекцией обоих токенов в localStorage.
201
+
202
+ ### View Classes
203
+
204
+ #### `NextJSStaticView`
205
+ Базовый view для обслуживания Next.js статических сборок с автоматической JWT инжекцией.
206
+
207
+ **Features:**
208
+ - Serves Next.js static export files
209
+ - Automatically injects JWT tokens for authenticated users
210
+ - Tokens injected into HTML responses only
211
+ - Handles Next.js client-side routing (.html fallback)
212
+
213
+ **Usage:**
214
+ ```python
215
+ from django_cfg.apps.frontend.views import NextJSStaticView
216
+
217
+ class MyAppView(NextJSStaticView):
218
+ app_name = 'my_app' # Serves from static/frontend/my_app/
219
+ ```
220
+
221
+ #### `AdminView`
222
+ Специализированный view для Admin Panel (наследует `NextJSStaticView`).
223
+
224
+ **Built-in JWT injection** - no additional configuration needed.
@@ -1,20 +1,34 @@
1
- """Views for serving Next.js static builds."""
1
+ """Views for serving Next.js static builds with automatic JWT injection.
2
2
 
3
+ JWT tokens are automatically injected into HTML responses for authenticated users.
4
+ This is specific to Next.js frontend apps only.
5
+ """
6
+
7
+ import logging
3
8
  from pathlib import Path
4
- from django.http import Http404
9
+ from django.http import Http404, HttpResponse, FileResponse
5
10
  from django.views.static import serve
6
11
  from django.views import View
12
+ from rest_framework_simplejwt.tokens import RefreshToken
13
+
14
+ logger = logging.getLogger(__name__)
7
15
 
8
16
 
9
17
  class NextJSStaticView(View):
10
18
  """
11
- Serve Next.js static build files using Django's built-in static file serving.
19
+ Serve Next.js static build files with automatic JWT token injection.
20
+
21
+ Features:
22
+ - Serves Next.js static export files
23
+ - Automatically injects JWT tokens for authenticated users
24
+ - Tokens injected into HTML responses only
25
+ - Handles Next.js client-side routing (.html fallback)
12
26
  """
13
27
 
14
28
  app_name = 'admin'
15
29
 
16
30
  def get(self, request, path=''):
17
- """Serve static files from Next.js build."""
31
+ """Serve static files from Next.js build with JWT injection."""
18
32
  import django_cfg
19
33
 
20
34
  base_dir = Path(django_cfg.__file__).parent / 'static' / 'frontend' / self.app_name
@@ -26,17 +40,117 @@ class NextJSStaticView(View):
26
40
  if not path or path == '/':
27
41
  path = 'index.html'
28
42
 
43
+ # Handle trailing slash (Next.js static export behavior)
44
+ # /private/ -> private.html
45
+ if path.endswith('/') and path != '/':
46
+ path = path.rstrip('/') + '.html'
47
+
29
48
  # For routes without extension, try .html (Next.js static export behavior)
30
49
  file_path = base_dir / path
31
50
  if not file_path.exists() and not path.endswith('.html') and '.' not in Path(path).name:
32
- # Try adding .html extension
33
51
  html_path = path + '.html'
34
52
  html_file = base_dir / html_path
35
53
  if html_file.exists():
36
54
  path = html_path
37
55
 
38
- # Use Django's static serve view (handles security, content-type, etc.)
39
- return serve(request, path, document_root=str(base_dir))
56
+ # For HTML files, remove conditional GET headers to force full response
57
+ # This allows JWT token injection (can't inject into 304 Not Modified responses)
58
+ is_html_file = path.endswith('.html')
59
+ if is_html_file and request.user.is_authenticated:
60
+ request.META.pop('HTTP_IF_MODIFIED_SINCE', None)
61
+ request.META.pop('HTTP_IF_NONE_MATCH', None)
62
+
63
+ # Serve the static file
64
+ response = serve(request, path, document_root=str(base_dir))
65
+
66
+ # Convert FileResponse to HttpResponse for HTML files to enable JWT injection
67
+ if isinstance(response, FileResponse):
68
+ content_type = response.get('Content-Type', '')
69
+ if 'text/html' in content_type and request.user.is_authenticated:
70
+ content = b''.join(response.streaming_content)
71
+ original_response = response
72
+ response = HttpResponse(
73
+ content=content,
74
+ status=original_response.status_code,
75
+ content_type=content_type
76
+ )
77
+ # Copy headers from original response
78
+ for header, value in original_response.items():
79
+ if header.lower() not in ('content-length', 'content-type'):
80
+ response[header] = value
81
+
82
+ # Inject JWT tokens for authenticated users on HTML responses
83
+ if self._should_inject_jwt(request, response):
84
+ self._inject_jwt_tokens(request, response)
85
+
86
+ return response
87
+
88
+ def _should_inject_jwt(self, request, response):
89
+ """Check if JWT tokens should be injected."""
90
+ # Only for authenticated users
91
+ if not request.user or not request.user.is_authenticated:
92
+ return False
93
+
94
+ # Only for HttpResponse (not FileResponse or StreamingHttpResponse)
95
+ if not isinstance(response, HttpResponse) or isinstance(response, FileResponse):
96
+ return False
97
+
98
+ # Check if response has content attribute
99
+ if not hasattr(response, 'content'):
100
+ return False
101
+
102
+ # Only for HTML responses
103
+ content_type = response.get('Content-Type', '')
104
+ return 'text/html' in content_type
105
+
106
+ def _inject_jwt_tokens(self, request, response):
107
+ """Inject JWT tokens into HTML response."""
108
+ try:
109
+ # Generate JWT tokens
110
+ refresh = RefreshToken.for_user(request.user)
111
+ access_token = str(refresh.access_token)
112
+ refresh_token = str(refresh)
113
+
114
+ # Create injection script
115
+ injection_script = f"""
116
+ <script>
117
+ (function() {{
118
+ try {{
119
+ localStorage.setItem('auth_token', '{access_token}');
120
+ localStorage.setItem('refresh_token', '{refresh_token}');
121
+ console.log('[Django-CFG] JWT tokens injected successfully');
122
+ }} catch (e) {{
123
+ console.error('[Django-CFG] Failed to inject JWT tokens:', e);
124
+ }}
125
+ }})();
126
+ </script>
127
+ """
128
+
129
+ # Decode response content
130
+ try:
131
+ content = response.content.decode('utf-8')
132
+ except UnicodeDecodeError:
133
+ logger.warning("Failed to decode response content as UTF-8, skipping JWT injection")
134
+ return
135
+
136
+ # Inject before </head> or </body>
137
+ if '</head>' in content:
138
+ content = content.replace('</head>', f'{injection_script}</head>', 1)
139
+ logger.debug(f"JWT tokens injected before </head> for user {request.user.pk}")
140
+ elif '</body>' in content:
141
+ content = content.replace('</body>', f'{injection_script}</body>', 1)
142
+ logger.debug(f"JWT tokens injected before </body> for user {request.user.pk}")
143
+ else:
144
+ logger.warning(f"No </head> or </body> tag found in HTML, skipping JWT injection")
145
+ return
146
+
147
+ # Update response
148
+ response.content = content.encode('utf-8')
149
+ response['Content-Length'] = len(response.content)
150
+
151
+ except Exception as e:
152
+ # Log error but don't break the response
153
+ logger.error(f"Failed to inject JWT tokens for user {request.user.pk}: {e}", exc_info=True)
40
154
 
41
155
 
42
156
  class AdminView(NextJSStaticView):