django-cfg 1.4.83__py3-none-any.whl → 1.4.85__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 (169) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/__init__.py +8 -0
  3. django_cfg/apps/dashboard/apps.py +23 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +55 -0
  5. django_cfg/apps/dashboard/serializers/activity.py +38 -0
  6. django_cfg/apps/dashboard/serializers/apizones.py +26 -0
  7. django_cfg/apps/dashboard/serializers/base.py +16 -0
  8. django_cfg/apps/dashboard/serializers/charts.py +44 -0
  9. django_cfg/apps/dashboard/serializers/commands.py +26 -0
  10. django_cfg/apps/dashboard/serializers/overview.py +34 -0
  11. django_cfg/apps/dashboard/serializers/statistics.py +46 -0
  12. django_cfg/apps/dashboard/serializers/system.py +58 -0
  13. django_cfg/apps/dashboard/services/__init__.py +20 -0
  14. django_cfg/apps/dashboard/services/apizones_service.py +119 -0
  15. django_cfg/apps/dashboard/services/charts_service.py +266 -0
  16. django_cfg/apps/dashboard/services/commands_service.py +142 -0
  17. django_cfg/apps/dashboard/services/statistics_service.py +393 -0
  18. django_cfg/apps/dashboard/services/system_health_service.py +280 -0
  19. django_cfg/apps/dashboard/urls.py +42 -0
  20. django_cfg/apps/dashboard/views/__init__.py +23 -0
  21. django_cfg/apps/dashboard/views/activity_views.py +83 -0
  22. django_cfg/apps/dashboard/views/apizones_views.py +73 -0
  23. django_cfg/apps/dashboard/views/charts_views.py +159 -0
  24. django_cfg/apps/dashboard/views/commands_views.py +73 -0
  25. django_cfg/apps/dashboard/views/overview_views.py +92 -0
  26. django_cfg/apps/dashboard/views/statistics_views.py +105 -0
  27. django_cfg/apps/dashboard/views/system_views.py +73 -0
  28. django_cfg/apps/frontend/views.py +5 -0
  29. django_cfg/apps/urls.py +2 -1
  30. django_cfg/core/builders/apps_builder.py +1 -0
  31. django_cfg/modules/django_client/core/parser/openapi30.py +12 -5
  32. django_cfg/modules/django_client/core/parser/openapi31.py +12 -5
  33. django_cfg/modules/django_unfold/callbacks/main.py +7 -6
  34. django_cfg/modules/django_unfold/dashboard.py +1 -36
  35. django_cfg/modules/django_unfold/models/config.py +102 -73
  36. django_cfg/modules/django_unfold/tailwind.py +31 -79
  37. django_cfg/pyproject.toml +1 -1
  38. django_cfg/static/frontend/admin/404.html +1 -1
  39. django_cfg/static/frontend/admin/500.html +1 -1
  40. django_cfg/static/frontend/admin/_next/static/BembwiEtlu4eFl3OX7n1k/_buildManifest.js +1 -0
  41. django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +1 -0
  42. django_cfg/static/frontend/admin/_next/static/chunks/{25033.d626f78bc99bc4a1.js → 25033.ee3e206d5a2877b6.js} +2 -2
  43. django_cfg/static/frontend/admin/_next/static/chunks/{25892.964150a58f94ce06.js → 25892.5cbed319f9226fdc.js} +1 -1
  44. django_cfg/static/frontend/admin/_next/static/chunks/{2d7a934f.dfef67639279d59d.js → 2d7a934f.329c61f23af1a7ec.js} +1 -1
  45. django_cfg/static/frontend/admin/_next/static/chunks/{30649.00c679812a56aee3.js → 30649.963cfb7268b5864a.js} +1 -1
  46. django_cfg/static/frontend/admin/_next/static/chunks/{30875.784491146c38dbcb.js → 30875.82c3741757b8aa32.js} +1 -1
  47. django_cfg/static/frontend/admin/_next/static/chunks/{32163.ab0ca435b3f26c04.js → 32163.109a03a7252f1508.js} +1 -1
  48. django_cfg/static/frontend/admin/_next/static/chunks/{49978.fb8ba7ee52ffe666.js → 49978.db5a86a8eb233f35.js} +1 -1
  49. django_cfg/static/frontend/admin/_next/static/chunks/50314-3b9d15242191c8bc.js +1 -0
  50. django_cfg/static/frontend/admin/_next/static/chunks/{50319.f786248384877960.js → 50319.fd78c7f7e3f1966e.js} +1 -1
  51. django_cfg/static/frontend/admin/_next/static/chunks/{52908.b690e323d8f8efdd.js → 52908.da5b850b0bc0970c.js} +1 -1
  52. django_cfg/static/frontend/admin/_next/static/chunks/{53710.80ca863525d137db.js → 53710.7176bbee6c7b78be.js} +1 -1
  53. django_cfg/static/frontend/admin/_next/static/chunks/{57982.251fed8d58adcf53.js → 57982.2c90b33b0934522a.js} +2 -2
  54. django_cfg/static/frontend/admin/_next/static/chunks/{60181.86e18057c4caaa97.js → 60181.c94d78d10eb5da37.js} +1 -1
  55. django_cfg/static/frontend/admin/_next/static/chunks/{60374.bde0ec1249aa79c6.js → 60374.5d80cfc45439b2b0.js} +1 -1
  56. django_cfg/static/frontend/admin/_next/static/chunks/{64330.2ef79bccd7d4e363.js → 64330.41858e98c0e5173b.js} +1 -1
  57. django_cfg/static/frontend/admin/_next/static/chunks/6766.8d01e44e83070e83.js +1 -0
  58. django_cfg/static/frontend/admin/_next/static/chunks/{6884.7b1db804c88280ed.js → 6884.624d563508cf6db4.js} +1 -1
  59. django_cfg/static/frontend/admin/_next/static/chunks/{69436.9515b854cdf4b57a.js → 69436.be44021e3d7c99c7.js} +1 -1
  60. django_cfg/static/frontend/admin/_next/static/chunks/{70628.00cdd98f672e684f.js → 70628.58e8c38a66543d5e.js} +1 -1
  61. django_cfg/static/frontend/admin/_next/static/chunks/{73218.a826c2248612b37f.js → 73218.d712e7bd678e23a8.js} +1 -1
  62. django_cfg/static/frontend/admin/_next/static/chunks/{76334.64fbaa923d9ac293.js → 76334.f43f2d8b4bbf8dd6.js} +1 -1
  63. django_cfg/static/frontend/admin/_next/static/chunks/{7799.2b280f8ddf067d49.js → 7799.1575cc212bc750c7.js} +1 -1
  64. django_cfg/static/frontend/admin/_next/static/chunks/{80574.620a8a5b4eb91c25.js → 80574.92638dd7b9979664.js} +1 -1
  65. django_cfg/static/frontend/admin/_next/static/chunks/{81127.a0603c3394892d4e.js → 81127.3ead500eec887152.js} +1 -1
  66. django_cfg/static/frontend/admin/_next/static/chunks/{8383.eb6188b22c453e14.js → 8383.e25a442df26b2e26.js} +1 -1
  67. django_cfg/static/frontend/admin/_next/static/chunks/{85833.35e6ca25ac32a7d2.js → 85833.b0dead4fbcbfdd1b.js} +1 -1
  68. django_cfg/static/frontend/admin/_next/static/chunks/{95365.fc9d7653a78839d0.js → 95365.2b430045fc2e5acf.js} +1 -1
  69. django_cfg/static/frontend/admin/_next/static/chunks/{96168.eb7fdb721b9cdb00.js → 96168.b7197f890097df6e.js} +1 -1
  70. django_cfg/static/frontend/admin/_next/static/chunks/{96424.0793b94836eb13a6.js → 96424.11d76570e9a94b85.js} +1 -1
  71. django_cfg/static/frontend/admin/_next/static/chunks/pages/{404-c283223d1afd02a2.js → 404-cf71cd7b3cb005e5.js} +1 -1
  72. django_cfg/static/frontend/admin/_next/static/chunks/pages/{500-389d6d3e1f2f7fda.js → 500-ff19c7842e3df415.js} +1 -1
  73. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f62e5528fbcbb6b3.js +272 -0
  74. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_error-5291033275c26d09.js → _error-87f3fdc2aa131e77.js} +1 -1
  75. django_cfg/static/frontend/admin/_next/static/chunks/pages/index-69f737d4802cc5b7.js +1 -0
  76. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{cookies-bb5507a122775f30.js → cookies-b39c7f22c066e2c6.js} +1 -1
  77. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{privacy-f8a3d8db1a197be3.js → privacy-5aedad0cf3a4f80f.js} +1 -1
  78. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{security-aba50addd2179f8f.js → security-dbd854d0d5d483e2.js} +1 -1
  79. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{terms-4aa35cd30b5c08ad.js → terms-f3e1d2b9e5edf12f.js} +1 -1
  80. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-f24beb6ed3955aa8.js +1 -0
  81. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{profile-e93a65e8e7d9022b.js → profile-b8045f993287f1a7.js} +1 -1
  82. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/ui-373fff8b42878e64.js +1 -0
  83. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +1 -0
  84. django_cfg/static/frontend/admin/_next/static/chunks/{webpack-905bba30877f6490.js → webpack-7c456a65e96eb97e.js} +1 -1
  85. django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +3 -0
  86. django_cfg/static/frontend/admin/auth.html +1 -1
  87. django_cfg/static/frontend/admin/index.html +1 -1
  88. django_cfg/static/frontend/admin/legal/cookies.html +1 -1
  89. django_cfg/static/frontend/admin/legal/privacy.html +1 -1
  90. django_cfg/static/frontend/admin/legal/security.html +1 -1
  91. django_cfg/static/frontend/admin/legal/terms.html +1 -1
  92. django_cfg/static/frontend/admin/private/centrifugo.html +1 -1
  93. django_cfg/static/frontend/admin/private/profile.html +1 -1
  94. django_cfg/static/frontend/admin/private/ui.html +1 -0
  95. django_cfg/static/frontend/admin/private.html +1 -1
  96. django_cfg/templates/admin/index.html +328 -62
  97. django_cfg/templates/admin/sections/commands_section.html +5 -549
  98. django_cfg/templates/admin/sections/documentation_section.html +5 -152
  99. django_cfg/templates/admin/sections/overview_section.html +5 -112
  100. django_cfg/templates/admin/sections/stats_section.html +5 -35
  101. django_cfg/templates/admin/sections/system_section.html +5 -99
  102. django_cfg/templates/admin/sections/widgets_section.html +10 -128
  103. django_cfg/templates/admin_old/index.html +80 -0
  104. django_cfg/templates/admin_old/sections/commands_section.html +549 -0
  105. django_cfg/templates/admin_old/sections/documentation_section.html +152 -0
  106. django_cfg/templates/admin_old/sections/overview_section.html +112 -0
  107. django_cfg/templates/admin_old/sections/stats_section.html +35 -0
  108. django_cfg/templates/admin_old/sections/system_section.html +99 -0
  109. django_cfg/templates/admin_old/sections/widgets_section.html +129 -0
  110. django_cfg/templates/unfold/layouts/skeleton.html +27 -0
  111. django_cfg/templatetags/django_cfg.py +53 -0
  112. {django_cfg-1.4.83.dist-info → django_cfg-1.4.85.dist-info}/METADATA +1 -1
  113. {django_cfg-1.4.83.dist-info → django_cfg-1.4.85.dist-info}/RECORD +160 -124
  114. django_cfg/static/frontend/admin/_next/static/chunks/6766.d62fed7cd4761148.js +0 -1
  115. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-16701a4e1bc3e6ac.js +0 -272
  116. django_cfg/static/frontend/admin/_next/static/chunks/pages/index-88751d9f44a32105.js +0 -1
  117. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-1c5f00c26c77a47b.js +0 -1
  118. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-2f58633ddf63a5bc.js +0 -1
  119. django_cfg/static/frontend/admin/_next/static/chunks/pages/ui-0e6c0e35862789ec.js +0 -1
  120. django_cfg/static/frontend/admin/_next/static/css/806300fb98c42afb.css +0 -3
  121. django_cfg/static/frontend/admin/_next/static/ibMHm1p66p0UGKsKnDWxn/_buildManifest.js +0 -1
  122. django_cfg/static/frontend/admin/ui.html +0 -92
  123. /django_cfg/static/frontend/admin/_next/static/{ibMHm1p66p0UGKsKnDWxn → BembwiEtlu4eFl3OX7n1k}/_ssgManifest.js +0 -0
  124. /django_cfg/templates/{admin → admin_old}/components/action_grid.html +0 -0
  125. /django_cfg/templates/{admin → admin_old}/components/card.html +0 -0
  126. /django_cfg/templates/{admin → admin_old}/components/data_table.html +0 -0
  127. /django_cfg/templates/{admin → admin_old}/components/metric_card.html +0 -0
  128. /django_cfg/templates/{admin → admin_old}/components/modal.html +0 -0
  129. /django_cfg/templates/{admin → admin_old}/components/progress_bar.html +0 -0
  130. /django_cfg/templates/{admin → admin_old}/components/section_header.html +0 -0
  131. /django_cfg/templates/{admin → admin_old}/components/stat_item.html +0 -0
  132. /django_cfg/templates/{admin → admin_old}/components/stats_grid.html +0 -0
  133. /django_cfg/templates/{admin → admin_old}/components/status_badge.html +0 -0
  134. /django_cfg/templates/{admin → admin_old}/components/user_avatar.html +0 -0
  135. /django_cfg/templates/{admin → admin_old}/constance/change_list.html +0 -0
  136. /django_cfg/templates/{admin → admin_old}/constance/includes/default_value.html +0 -0
  137. /django_cfg/templates/{admin → admin_old}/constance/includes/fieldset_header.html +0 -0
  138. /django_cfg/templates/{admin → admin_old}/constance/includes/results_list.html +0 -0
  139. /django_cfg/templates/{admin → admin_old}/constance/includes/setting_row.html +0 -0
  140. /django_cfg/templates/{admin → admin_old}/constance/includes/table_headers.html +0 -0
  141. /django_cfg/templates/{admin → admin_old}/examples/component_class_example.html +0 -0
  142. /django_cfg/templates/{admin → admin_old}/import_export/change_list_export.html +0 -0
  143. /django_cfg/templates/{admin → admin_old}/import_export/change_list_import.html +0 -0
  144. /django_cfg/templates/{admin → admin_old}/import_export/change_list_import_export.html +0 -0
  145. /django_cfg/templates/{admin → admin_old}/index_new.html +0 -0
  146. /django_cfg/templates/{admin → admin_old}/layouts/base_dashboard.html +0 -0
  147. /django_cfg/templates/{admin → admin_old}/layouts/dashboard_with_tabs.html +0 -0
  148. /django_cfg/templates/{admin → admin_old}/snippets/components/activity_tracker.html +0 -0
  149. /django_cfg/templates/{admin → admin_old}/snippets/components/charts_section.html +0 -0
  150. /django_cfg/templates/{admin → admin_old}/snippets/components/django_commands.html +0 -0
  151. /django_cfg/templates/{admin → admin_old}/snippets/components/quick_actions.html +0 -0
  152. /django_cfg/templates/{admin → admin_old}/snippets/components/recent_activity_improved.html +0 -0
  153. /django_cfg/templates/{admin → admin_old}/snippets/components/recent_users_table.html +0 -0
  154. /django_cfg/templates/{admin → admin_old}/snippets/components/stats_cards.html +0 -0
  155. /django_cfg/templates/{admin → admin_old}/snippets/components/stats_tiles.html +0 -0
  156. /django_cfg/templates/{admin → admin_old}/snippets/components/system_health.html +0 -0
  157. /django_cfg/templates/{admin → admin_old}/snippets/components/system_metrics.html +0 -0
  158. /django_cfg/templates/{admin → admin_old}/snippets/components/user_permissions.html +0 -0
  159. /django_cfg/templates/{admin → admin_old}/snippets/tabs/app_stats_tab.html +0 -0
  160. /django_cfg/templates/{admin → admin_old}/snippets/tabs/commands_tab.html +0 -0
  161. /django_cfg/templates/{admin → admin_old}/snippets/tabs/documentation_tab.html +0 -0
  162. /django_cfg/templates/{admin → admin_old}/snippets/tabs/overview_tab.html +0 -0
  163. /django_cfg/templates/{admin → admin_old}/snippets/tabs/stats_tab.html +0 -0
  164. /django_cfg/templates/{admin → admin_old}/snippets/tabs/users_tab.html +0 -0
  165. /django_cfg/templates/{admin → admin_old}/snippets/tabs/widgets_tab.html +0 -0
  166. /django_cfg/templates/{admin → admin_old}/snippets/zones/zones_table.html +0 -0
  167. {django_cfg-1.4.83.dist-info → django_cfg-1.4.85.dist-info}/WHEEL +0 -0
  168. {django_cfg-1.4.83.dist-info → django_cfg-1.4.85.dist-info}/entry_points.txt +0 -0
  169. {django_cfg-1.4.83.dist-info → django_cfg-1.4.85.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,393 @@
1
+ """
2
+ Statistics Service
3
+
4
+ Collects and aggregates statistics for dashboard display.
5
+ Uses Django ORM for real data collection.
6
+ """
7
+
8
+ import logging
9
+ from datetime import datetime, timedelta
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from django.apps import apps
13
+ from django.contrib.auth import get_user_model
14
+ from django.utils import timezone
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class StatisticsService:
20
+ """
21
+ Service for collecting dashboard statistics.
22
+
23
+ %%PRIORITY:HIGH%%
24
+ %%AI_HINT: This service collects data from various Django models using ORM%%
25
+
26
+ TAGS: statistics, dashboard, service
27
+ """
28
+
29
+ def __init__(self):
30
+ """Initialize statistics service."""
31
+ self.logger = logger
32
+
33
+ def _get_user_model(self):
34
+ """Get the user model safely."""
35
+ return get_user_model()
36
+
37
+ def get_user_statistics(self) -> Dict[str, Any]:
38
+ """
39
+ Get user-related statistics.
40
+
41
+ Returns:
42
+ Dictionary containing user stats:
43
+ - total_users: Total number of users
44
+ - active_users: Number of active users
45
+ - new_users: New users in last 7 days
46
+ - superusers: Number of superusers
47
+
48
+ %%AI_HINT: Uses real Django ORM queries from User model%%
49
+ """
50
+ try:
51
+ User = self._get_user_model()
52
+
53
+ total_users = User.objects.count()
54
+ active_users = User.objects.filter(is_active=True).count()
55
+ new_users_7d = User.objects.filter(
56
+ date_joined__gte=timezone.now() - timedelta(days=7)
57
+ ).count()
58
+ superusers = User.objects.filter(is_superuser=True).count()
59
+
60
+ return {
61
+ 'total_users': total_users,
62
+ 'active_users': active_users,
63
+ 'new_users': new_users_7d,
64
+ 'superusers': superusers,
65
+ }
66
+ except Exception as e:
67
+ self.logger.error(f"Error getting user statistics: {e}")
68
+ return {
69
+ 'total_users': 0,
70
+ 'active_users': 0,
71
+ 'new_users': 0,
72
+ 'superusers': 0,
73
+ 'error': str(e)
74
+ }
75
+
76
+ def get_app_statistics(self) -> Dict[str, Any]:
77
+ """
78
+ Get application-specific statistics.
79
+
80
+ Returns:
81
+ Dictionary with aggregated app statistics.
82
+ {
83
+ 'apps': {app_label: {...stats...}},
84
+ 'total_records': int,
85
+ 'total_models': int,
86
+ 'total_apps': int
87
+ }
88
+
89
+ %%AI_HINT: Real app introspection using Django apps registry%%
90
+ """
91
+ try:
92
+ stats = {"apps": {}, "total_records": 0, "total_models": 0, "total_apps": 0}
93
+
94
+ # Get all installed apps
95
+ for app_config in apps.get_app_configs():
96
+ app_label = app_config.label
97
+
98
+ # Skip system apps
99
+ if app_label in ["admin", "contenttypes", "sessions", "auth"]:
100
+ continue
101
+
102
+ app_stats = self._get_app_stats(app_label)
103
+ if app_stats:
104
+ stats["apps"][app_label] = app_stats
105
+ stats["total_records"] += app_stats.get("total_records", 0)
106
+ stats["total_models"] += app_stats.get("model_count", 0)
107
+ stats["total_apps"] += 1
108
+
109
+ return stats
110
+ except Exception as e:
111
+ self.logger.error(f"Error getting app statistics: {e}")
112
+ return {'apps': {}, 'total_records': 0, 'total_models': 0, 'total_apps': 0, 'error': str(e)}
113
+
114
+ def _get_app_stats(self, app_label: str) -> Optional[Dict[str, Any]]:
115
+ """Get statistics for a specific app."""
116
+ try:
117
+ app_config = apps.get_app_config(app_label)
118
+ # Convert generator to list to avoid len() error
119
+ models_list = list(app_config.get_models())
120
+
121
+ if not models_list:
122
+ return None
123
+
124
+ app_stats = {
125
+ "name": app_config.verbose_name or app_label.title(),
126
+ "models": {},
127
+ "total_records": 0,
128
+ "model_count": len(models_list),
129
+ }
130
+
131
+ for model in models_list:
132
+ try:
133
+ # Get model statistics
134
+ model_stats = self._get_model_stats(model)
135
+ if model_stats:
136
+ app_stats["models"][model._meta.model_name] = model_stats
137
+ app_stats["total_records"] += model_stats.get("count", 0)
138
+ except Exception:
139
+ continue
140
+
141
+ return app_stats
142
+
143
+ except Exception:
144
+ return None
145
+
146
+ def _get_model_stats(self, model) -> Optional[Dict[str, Any]]:
147
+ """Get statistics for a specific model."""
148
+ try:
149
+ # Get basic model info
150
+ model_stats = {
151
+ "name": model._meta.verbose_name_plural
152
+ or model._meta.verbose_name
153
+ or model._meta.model_name,
154
+ "count": model.objects.count(),
155
+ "fields_count": len(model._meta.fields),
156
+ "admin_url": f"admin:{model._meta.app_label}_{model._meta.model_name}_changelist",
157
+ }
158
+
159
+ return model_stats
160
+
161
+ except Exception:
162
+ return None
163
+
164
+ def get_stat_cards(self) -> List[Dict[str, Any]]:
165
+ """
166
+ Get statistics cards for dashboard overview.
167
+
168
+ Returns:
169
+ List of stat card dictionaries ready for serialization.
170
+ Each card contains: title, value, icon, change, change_type
171
+
172
+ USED_BY: DashboardViewSet.overview endpoint
173
+ %%AI_HINT: Real data from User model with calculated changes%%
174
+ """
175
+ try:
176
+ user_stats = self.get_user_statistics()
177
+
178
+ total_users = user_stats['total_users']
179
+ active_users = user_stats['active_users']
180
+ new_users_7d = user_stats['new_users']
181
+ superusers = user_stats['superusers']
182
+
183
+ cards = [
184
+ {
185
+ 'title': 'Total Users',
186
+ 'value': f"{total_users:,}",
187
+ 'icon': 'people',
188
+ 'change': f"+{new_users_7d}" if new_users_7d > 0 else None,
189
+ 'change_type': 'positive' if new_users_7d > 0 else 'neutral',
190
+ 'color': 'primary',
191
+ 'description': 'Registered users',
192
+ },
193
+ {
194
+ 'title': 'Active Users',
195
+ 'value': f"{active_users:,}",
196
+ 'icon': 'person',
197
+ 'change': (
198
+ f"{(active_users/total_users*100):.1f}%"
199
+ if total_users > 0
200
+ else "0%"
201
+ ),
202
+ 'change_type': (
203
+ 'positive' if active_users > total_users * 0.7 else 'neutral'
204
+ ),
205
+ 'color': 'success',
206
+ 'description': 'Currently active',
207
+ },
208
+ {
209
+ 'title': 'New This Week',
210
+ 'value': f"{new_users_7d:,}",
211
+ 'icon': 'person_add',
212
+ 'change_type': 'positive' if new_users_7d > 0 else 'neutral',
213
+ 'color': 'info',
214
+ 'description': 'Last 7 days',
215
+ },
216
+ {
217
+ 'title': 'Superusers',
218
+ 'value': f"{superusers:,}",
219
+ 'icon': 'admin_panel_settings',
220
+ 'change': (
221
+ f"{(superusers/total_users*100):.1f}%" if total_users > 0 else "0%"
222
+ ),
223
+ 'change_type': 'neutral',
224
+ 'color': 'warning',
225
+ 'description': 'Administrative access',
226
+ },
227
+ ]
228
+
229
+ return cards
230
+
231
+ except Exception as e:
232
+ self.logger.error(f"Error generating stat cards: {e}")
233
+ return []
234
+
235
+ def get_recent_activity(self, limit: int = 10) -> List[Dict[str, Any]]:
236
+ """
237
+ Get recent activity entries for dashboard.
238
+
239
+ Args:
240
+ limit: Maximum number of activity entries to return
241
+
242
+ Returns:
243
+ List of recent activity entries
244
+
245
+ %%AI_HINT: Returns placeholder - connect to django-auditlog when available%%
246
+ """
247
+ try:
248
+ # Note: This is a placeholder. Real implementation should connect to:
249
+ # - django-auditlog (if installed)
250
+ # - Custom activity/history tracking
251
+ # - Django admin logs (LogEntry model)
252
+
253
+ # Try to get Django admin logs as fallback
254
+ from django.contrib.admin.models import LogEntry
255
+
256
+ activities = []
257
+ log_entries = LogEntry.objects.select_related('user', 'content_type').order_by('-action_time')[:limit]
258
+
259
+ for entry in log_entries:
260
+ activities.append({
261
+ 'id': entry.pk,
262
+ 'user': entry.user.get_username() if entry.user else 'System',
263
+ 'action': entry.get_action_flag_display().lower(),
264
+ 'resource': str(entry),
265
+ 'timestamp': entry.action_time.isoformat(),
266
+ 'icon': self._get_activity_icon(entry.action_flag),
267
+ 'color': self._get_activity_color(entry.action_flag),
268
+ })
269
+
270
+ return activities
271
+
272
+ except Exception as e:
273
+ self.logger.error(f"Error getting recent activity: {e}")
274
+ return []
275
+
276
+ def _get_activity_icon(self, action_flag: int) -> str:
277
+ """Get icon for activity based on action flag."""
278
+ icons = {
279
+ 1: 'add_circle', # ADDITION
280
+ 2: 'edit', # CHANGE
281
+ 3: 'delete', # DELETION
282
+ }
283
+ return icons.get(action_flag, 'circle')
284
+
285
+ def _get_activity_color(self, action_flag: int) -> str:
286
+ """Get color for activity based on action flag."""
287
+ colors = {
288
+ 1: 'success', # ADDITION
289
+ 2: 'primary', # CHANGE
290
+ 3: 'error', # DELETION
291
+ }
292
+ return colors.get(action_flag, 'default')
293
+
294
+ def get_recent_users(self, limit: int = 10) -> List[Dict[str, Any]]:
295
+ """
296
+ Get recent users data.
297
+
298
+ Args:
299
+ limit: Maximum number of users to return
300
+
301
+ Returns:
302
+ List of recent user dictionaries with admin URLs
303
+
304
+ %%AI_HINT: Real data from User model with admin URLs%%
305
+ """
306
+ try:
307
+ User = self._get_user_model()
308
+ recent_users = User.objects.select_related().order_by("-date_joined")[:limit]
309
+
310
+ return [
311
+ {
312
+ "id": user.id,
313
+ "username": user.username,
314
+ "email": user.email or "No email",
315
+ "date_joined": (
316
+ user.date_joined.strftime("%Y-%m-%d %H:%M")
317
+ if user.date_joined
318
+ else "Unknown"
319
+ ),
320
+ "is_active": user.is_active,
321
+ "is_staff": user.is_staff,
322
+ "is_superuser": user.is_superuser,
323
+ "last_login": (
324
+ user.last_login.strftime("%Y-%m-%d %H:%M")
325
+ if user.last_login
326
+ else None
327
+ ),
328
+ }
329
+ for user in recent_users
330
+ ]
331
+ except Exception as e:
332
+ self.logger.error(f"Error getting recent users: {e}")
333
+ return []
334
+
335
+ def get_system_metrics(self) -> Dict[str, Any]:
336
+ """
337
+ Get system performance metrics.
338
+
339
+ Returns:
340
+ Dictionary with system metrics (CPU, memory, disk, etc.)
341
+
342
+ %%AI_HINT: Uses psutil for real system metrics when available%%
343
+ """
344
+ try:
345
+ # Try to use psutil for real system metrics
346
+ try:
347
+ import psutil
348
+ import time
349
+
350
+ # Get CPU usage
351
+ cpu_percent = psutil.cpu_percent(interval=0.1)
352
+
353
+ # Get memory usage
354
+ memory = psutil.virtual_memory()
355
+ memory_percent = memory.percent
356
+
357
+ # Get disk usage for root partition
358
+ disk = psutil.disk_usage('/')
359
+ disk_percent = disk.percent
360
+
361
+ # Get network IO
362
+ net_io = psutil.net_io_counters()
363
+
364
+ # Get system uptime
365
+ boot_time = psutil.boot_time()
366
+ uptime_seconds = time.time() - boot_time
367
+ uptime_days = int(uptime_seconds // 86400)
368
+ uptime_hours = int((uptime_seconds % 86400) // 3600)
369
+
370
+ return {
371
+ 'cpu_usage': round(cpu_percent, 1),
372
+ 'memory_usage': round(memory_percent, 1),
373
+ 'disk_usage': round(disk_percent, 1),
374
+ 'network_in': f"{net_io.bytes_recv / (1024**2):.1f} MB",
375
+ 'network_out': f"{net_io.bytes_sent / (1024**2):.1f} MB",
376
+ 'response_time': '< 100ms', # Placeholder - can be calculated from middleware
377
+ 'uptime': f"{uptime_days} days, {uptime_hours} hours",
378
+ }
379
+ except ImportError:
380
+ self.logger.warning("psutil not installed, returning placeholder metrics")
381
+ # Fallback to placeholder data if psutil not available
382
+ return {
383
+ 'cpu_usage': 0,
384
+ 'memory_usage': 0,
385
+ 'disk_usage': 0,
386
+ 'network_in': 'N/A',
387
+ 'network_out': 'N/A',
388
+ 'response_time': 'N/A',
389
+ 'uptime': 'N/A',
390
+ }
391
+ except Exception as e:
392
+ self.logger.error(f"Error getting system metrics: {e}")
393
+ return {'error': str(e)}
@@ -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