django-cfg 1.4.88__py3-none-any.whl → 1.4.89__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 (146) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/centrifugo/views/__init__.py +0 -2
  3. django_cfg/apps/dashboard/services/__init__.py +2 -0
  4. django_cfg/apps/dashboard/services/overview_service.py +205 -0
  5. django_cfg/apps/frontend/test_routing.py +134 -0
  6. django_cfg/apps/frontend/views.py +69 -28
  7. django_cfg/apps/urls.py +0 -1
  8. django_cfg/core/builders/apps_builder.py +0 -58
  9. django_cfg/modules/django_unfold/__init__.py +5 -24
  10. django_cfg/modules/django_unfold/models/__init__.py +0 -23
  11. django_cfg/modules/django_unfold/models/config.py +11 -65
  12. django_cfg/modules/django_unfold/{dashboard.py → navigation.py} +21 -152
  13. django_cfg/modules/django_unfold/tailwind.py +2 -4
  14. django_cfg/pyproject.toml +1 -1
  15. django_cfg/registry/third_party.py +0 -9
  16. django_cfg/routing/callbacks.py +1 -43
  17. django_cfg/static/frontend/admin/404/index.html +1 -1
  18. django_cfg/static/frontend/admin/404.html +1 -1
  19. django_cfg/static/frontend/admin/500/index.html +1 -1
  20. django_cfg/static/frontend/admin/_next/static/{D_d9HRw5Yn7BRHAX5q89_ → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
  21. django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
  22. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-1c0fff0f59a6d683.js → _app-c7dcd3aa616fab68.js} +1 -1
  23. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-44a8313fa040e9ad.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
  24. django_cfg/static/frontend/admin/auth/index.html +1 -1
  25. django_cfg/static/frontend/admin/index.html +1 -1
  26. django_cfg/static/frontend/admin/legal/cookies/index.html +1 -1
  27. django_cfg/static/frontend/admin/legal/privacy/index.html +1 -1
  28. django_cfg/static/frontend/admin/legal/security/index.html +1 -1
  29. django_cfg/static/frontend/admin/legal/terms/index.html +1 -1
  30. django_cfg/static/frontend/admin/private/centrifugo/index.html +1 -1
  31. django_cfg/static/frontend/admin/private/index.html +1 -1
  32. django_cfg/static/frontend/admin/private/profile/index.html +1 -1
  33. django_cfg/static/frontend/admin/private/ui/index.html +2 -2
  34. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
  35. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/RECORD +39 -143
  36. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +0 -260
  37. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +0 -313
  38. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +0 -803
  39. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +0 -341
  40. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +0 -432
  41. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +0 -33
  42. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +0 -210
  43. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +0 -46
  44. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +0 -123
  45. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +0 -45
  46. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +0 -84
  47. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/stat_cards.html +0 -53
  48. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +0 -91
  49. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/tab_navigation.html +0 -29
  50. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +0 -415
  51. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +0 -61
  52. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +0 -58
  53. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +0 -48
  54. django_cfg/apps/centrifugo/templatetags/__init__.py +0 -1
  55. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +0 -81
  56. django_cfg/apps/centrifugo/urls_admin.py +0 -20
  57. django_cfg/apps/centrifugo/views/dashboard.py +0 -28
  58. django_cfg/modules/django_dashboard/__init__.py +0 -23
  59. django_cfg/modules/django_dashboard/components.py +0 -312
  60. django_cfg/modules/django_dashboard/debug.py +0 -174
  61. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  62. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  63. django_cfg/modules/django_dashboard/management/commands/debug_dashboard.py +0 -109
  64. django_cfg/modules/django_dashboard/sections/__init__.py +0 -1
  65. django_cfg/modules/django_dashboard/sections/base.py +0 -129
  66. django_cfg/modules/django_dashboard/sections/commands.py +0 -33
  67. django_cfg/modules/django_dashboard/sections/documentation.py +0 -393
  68. django_cfg/modules/django_dashboard/sections/overview.py +0 -398
  69. django_cfg/modules/django_dashboard/sections/stats.py +0 -48
  70. django_cfg/modules/django_dashboard/sections/system.py +0 -74
  71. django_cfg/modules/django_dashboard/sections/widgets.py +0 -222
  72. django_cfg/modules/django_unfold/callbacks/__init__.py +0 -9
  73. django_cfg/modules/django_unfold/callbacks/actions.py +0 -51
  74. django_cfg/modules/django_unfold/callbacks/apizones.py +0 -122
  75. django_cfg/modules/django_unfold/callbacks/base.py +0 -290
  76. django_cfg/modules/django_unfold/callbacks/charts.py +0 -223
  77. django_cfg/modules/django_unfold/callbacks/commands.py +0 -40
  78. django_cfg/modules/django_unfold/callbacks/main.py +0 -322
  79. django_cfg/modules/django_unfold/callbacks/statistics.py +0 -240
  80. django_cfg/modules/django_unfold/callbacks/system.py +0 -180
  81. django_cfg/modules/django_unfold/callbacks/users.py +0 -65
  82. django_cfg/modules/django_unfold/models/dashboard.py +0 -207
  83. django_cfg/modules/django_unfold/models/tabs.py +0 -26
  84. django_cfg/modules/django_unfold/models.py +0 -98
  85. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +0 -102
  86. django_cfg/static/frontend/admin/_next/static/chunks/50314-5ec79b293c2283dd.js +0 -1
  87. django_cfg/templates/admin/sections/commands_section.html +0 -5
  88. django_cfg/templates/admin/sections/documentation_section.html +0 -5
  89. django_cfg/templates/admin/sections/overview_section.html +0 -5
  90. django_cfg/templates/admin/sections/stats_section.html +0 -5
  91. django_cfg/templates/admin/sections/system_section.html +0 -5
  92. django_cfg/templates/admin/sections/widgets_section.html +0 -11
  93. django_cfg/templates/admin_old/components/action_grid.html +0 -49
  94. django_cfg/templates/admin_old/components/card.html +0 -50
  95. django_cfg/templates/admin_old/components/data_table.html +0 -67
  96. django_cfg/templates/admin_old/components/metric_card.html +0 -39
  97. django_cfg/templates/admin_old/components/modal.html +0 -58
  98. django_cfg/templates/admin_old/components/progress_bar.html +0 -20
  99. django_cfg/templates/admin_old/components/section_header.html +0 -26
  100. django_cfg/templates/admin_old/components/stat_item.html +0 -32
  101. django_cfg/templates/admin_old/components/stats_grid.html +0 -72
  102. django_cfg/templates/admin_old/components/status_badge.html +0 -28
  103. django_cfg/templates/admin_old/components/user_avatar.html +0 -27
  104. django_cfg/templates/admin_old/constance/change_list.html +0 -74
  105. django_cfg/templates/admin_old/constance/includes/default_value.html +0 -24
  106. django_cfg/templates/admin_old/constance/includes/fieldset_header.html +0 -15
  107. django_cfg/templates/admin_old/constance/includes/results_list.html +0 -16
  108. django_cfg/templates/admin_old/constance/includes/setting_row.html +0 -50
  109. django_cfg/templates/admin_old/constance/includes/table_headers.html +0 -10
  110. django_cfg/templates/admin_old/examples/component_class_example.html +0 -156
  111. django_cfg/templates/admin_old/import_export/change_list_export.html +0 -24
  112. django_cfg/templates/admin_old/import_export/change_list_import.html +0 -24
  113. django_cfg/templates/admin_old/import_export/change_list_import_export.html +0 -34
  114. django_cfg/templates/admin_old/index.html +0 -80
  115. django_cfg/templates/admin_old/index_new.html +0 -119
  116. django_cfg/templates/admin_old/layouts/base_dashboard.html +0 -62
  117. django_cfg/templates/admin_old/layouts/dashboard_with_tabs.html +0 -176
  118. django_cfg/templates/admin_old/sections/commands_section.html +0 -549
  119. django_cfg/templates/admin_old/sections/documentation_section.html +0 -152
  120. django_cfg/templates/admin_old/sections/overview_section.html +0 -112
  121. django_cfg/templates/admin_old/sections/stats_section.html +0 -35
  122. django_cfg/templates/admin_old/sections/system_section.html +0 -99
  123. django_cfg/templates/admin_old/sections/widgets_section.html +0 -129
  124. django_cfg/templates/admin_old/snippets/components/activity_tracker.html +0 -70
  125. django_cfg/templates/admin_old/snippets/components/charts_section.html +0 -113
  126. django_cfg/templates/admin_old/snippets/components/django_commands.html +0 -270
  127. django_cfg/templates/admin_old/snippets/components/quick_actions.html +0 -66
  128. django_cfg/templates/admin_old/snippets/components/recent_activity_improved.html +0 -25
  129. django_cfg/templates/admin_old/snippets/components/recent_users_table.html +0 -102
  130. django_cfg/templates/admin_old/snippets/components/stats_cards.html +0 -4
  131. django_cfg/templates/admin_old/snippets/components/stats_tiles.html +0 -92
  132. django_cfg/templates/admin_old/snippets/components/system_health.html +0 -22
  133. django_cfg/templates/admin_old/snippets/components/system_metrics.html +0 -199
  134. django_cfg/templates/admin_old/snippets/components/user_permissions.html +0 -57
  135. django_cfg/templates/admin_old/snippets/tabs/app_stats_tab.html +0 -201
  136. django_cfg/templates/admin_old/snippets/tabs/commands_tab.html +0 -114
  137. django_cfg/templates/admin_old/snippets/tabs/documentation_tab.html +0 -42
  138. django_cfg/templates/admin_old/snippets/tabs/overview_tab.html +0 -116
  139. django_cfg/templates/admin_old/snippets/tabs/stats_tab.html +0 -89
  140. django_cfg/templates/admin_old/snippets/tabs/users_tab.html +0 -51
  141. django_cfg/templates/admin_old/snippets/tabs/widgets_tab.html +0 -38
  142. django_cfg/templates/admin_old/snippets/zones/zones_table.html +0 -176
  143. /django_cfg/static/frontend/admin/_next/static/{D_d9HRw5Yn7BRHAX5q89_ → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
  144. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
  145. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
  146. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
@@ -1,240 +0,0 @@
1
- """
2
- Statistics callbacks for dashboard.
3
- """
4
-
5
- import logging
6
- from datetime import timedelta
7
- from typing import Any, Dict, List
8
-
9
- from django.apps import apps
10
- from django.contrib.auth import get_user_model
11
- from django.utils import timezone
12
-
13
- from django_cfg.modules.django_admin.icons import Icons
14
-
15
- from ..models.dashboard import StatCard
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- class StatisticsCallbacks:
21
- """Statistics-related callbacks."""
22
-
23
- def _get_user_model(self):
24
- """Get the user model safely."""
25
- return get_user_model()
26
-
27
- def get_user_statistics(self) -> List[StatCard]:
28
- """Get user-related statistics as Pydantic models."""
29
- try:
30
- User = self._get_user_model()
31
-
32
- total_users = User.objects.count()
33
- active_users = User.objects.filter(is_active=True).count()
34
- new_users_7d = User.objects.filter(
35
- date_joined__gte=timezone.now() - timedelta(days=7)
36
- ).count()
37
- staff_users = User.objects.filter(is_staff=True).count()
38
-
39
- return [
40
- StatCard(
41
- title="Total Users",
42
- value=f"{total_users:,}",
43
- icon=Icons.PEOPLE,
44
- change=f"+{new_users_7d}" if new_users_7d > 0 else None,
45
- change_type="positive" if new_users_7d > 0 else "neutral",
46
- description="Registered users",
47
- ),
48
- StatCard(
49
- title="Active Users",
50
- value=f"{active_users:,}",
51
- icon=Icons.PERSON,
52
- change=(
53
- f"{(active_users/total_users*100):.1f}%"
54
- if total_users > 0
55
- else "0%"
56
- ),
57
- change_type=(
58
- "positive" if active_users > total_users * 0.7 else "neutral"
59
- ),
60
- description="Currently active",
61
- ),
62
- StatCard(
63
- title="New This Week",
64
- value=f"{new_users_7d:,}",
65
- icon=Icons.PERSON_ADD,
66
- change_type="positive" if new_users_7d > 0 else "neutral",
67
- description="Last 7 days",
68
- ),
69
- StatCard(
70
- title="Staff Members",
71
- value=f"{staff_users:,}",
72
- icon=Icons.ADMIN_PANEL_SETTINGS,
73
- change=(
74
- f"{(staff_users/total_users*100):.1f}%" if total_users > 0 else "0%"
75
- ),
76
- change_type="neutral",
77
- description="Administrative access",
78
- ),
79
- ]
80
- except Exception as e:
81
- logger.error(f"Error getting user statistics: {e}")
82
- return [
83
- StatCard(
84
- title="Users",
85
- value="N/A",
86
- icon=Icons.PEOPLE,
87
- description="Data unavailable",
88
- )
89
- ]
90
-
91
- def get_support_statistics(self) -> List[StatCard]:
92
- """Get support ticket statistics as Pydantic models."""
93
- try:
94
- # Check if support is enabled
95
- if not self.is_support_enabled():
96
- return []
97
-
98
- from django_cfg.apps.support.models import Ticket
99
-
100
- total_tickets = Ticket.objects.count()
101
- open_tickets = Ticket.objects.filter(status='open').count()
102
- resolved_tickets = Ticket.objects.filter(status='resolved').count()
103
- new_tickets_7d = Ticket.objects.filter(
104
- created_at__gte=timezone.now() - timedelta(days=7)
105
- ).count()
106
-
107
- return [
108
- StatCard(
109
- title="Total Tickets",
110
- value=f"{total_tickets:,}",
111
- icon=Icons.SUPPORT_AGENT,
112
- change=f"+{new_tickets_7d}" if new_tickets_7d > 0 else None,
113
- change_type="positive" if new_tickets_7d > 0 else "neutral",
114
- description="All support tickets",
115
- ),
116
- StatCard(
117
- title="Open Tickets",
118
- value=f"{open_tickets:,}",
119
- icon=Icons.PENDING,
120
- change=(
121
- f"{(open_tickets/total_tickets*100):.1f}%"
122
- if total_tickets > 0
123
- else "0%"
124
- ),
125
- change_type=(
126
- "negative" if open_tickets > total_tickets * 0.3
127
- else "positive" if open_tickets == 0
128
- else "neutral"
129
- ),
130
- description="Awaiting response",
131
- ),
132
- StatCard(
133
- title="Resolved",
134
- value=f"{resolved_tickets:,}",
135
- icon=Icons.CHECK_CIRCLE,
136
- change=(
137
- f"{(resolved_tickets/total_tickets*100):.1f}%"
138
- if total_tickets > 0
139
- else "0%"
140
- ),
141
- change_type="positive",
142
- description="Successfully resolved",
143
- ),
144
- StatCard(
145
- title="New This Week",
146
- value=f"{new_tickets_7d:,}",
147
- icon=Icons.NEW_RELEASES,
148
- change_type="positive" if new_tickets_7d > 0 else "neutral",
149
- description="Last 7 days",
150
- ),
151
- ]
152
- except Exception as e:
153
- logger.error(f"Error getting support statistics: {e}")
154
- return [
155
- StatCard(
156
- title="Support",
157
- value="N/A",
158
- icon=Icons.SUPPORT_AGENT,
159
- description="Data unavailable",
160
- )
161
- ]
162
-
163
- def get_app_statistics(self) -> Dict[str, Any]:
164
- """Get statistics for all apps and their models."""
165
- stats = {"apps": {}, "total_records": 0, "total_models": 0, "total_apps": 0}
166
-
167
- # Get all installed apps
168
- for app_config in apps.get_app_configs():
169
- app_label = app_config.label
170
-
171
- # Skip system apps
172
- if app_label in ["admin", "contenttypes", "sessions", "auth"]:
173
- continue
174
-
175
- app_stats = self._get_app_stats(app_label)
176
- if app_stats:
177
- stats["apps"][app_label] = app_stats
178
- stats["total_records"] += app_stats.get("total_records", 0)
179
- stats["total_models"] += app_stats.get("model_count", 0)
180
- stats["total_apps"] += 1
181
-
182
- return stats
183
-
184
- def _get_app_stats(self, app_label: str) -> Dict[str, Any]:
185
- """Get statistics for a specific app."""
186
- try:
187
- app_config = apps.get_app_config(app_label)
188
- # Convert generator to list to avoid len() error
189
- models_list = list(app_config.get_models())
190
-
191
- if not models_list:
192
- return None
193
-
194
- app_stats = {
195
- "name": app_config.verbose_name or app_label.title(),
196
- "models": {},
197
- "total_records": 0,
198
- "model_count": len(models_list),
199
- }
200
-
201
- for model in models_list:
202
- try:
203
- # Get model statistics
204
- model_stats = self._get_model_stats(model)
205
- if model_stats:
206
- app_stats["models"][model._meta.model_name] = model_stats
207
- app_stats["total_records"] += model_stats.get("count", 0)
208
- except Exception:
209
- continue
210
-
211
- return app_stats
212
-
213
- except Exception:
214
- return None
215
-
216
- def _get_model_stats(self, model) -> Dict[str, Any]:
217
- """Get statistics for a specific model."""
218
- try:
219
- # Get basic model info
220
- model_stats = {
221
- "name": model._meta.verbose_name_plural
222
- or model._meta.verbose_name
223
- or model._meta.model_name,
224
- "count": model.objects.count(),
225
- "fields_count": len(model._meta.fields),
226
- "admin_url": f"admin:{model._meta.app_label}_{model._meta.model_name}_changelist",
227
- }
228
-
229
- return model_stats
230
-
231
- except Exception:
232
- return None
233
-
234
- def is_support_enabled(self) -> bool:
235
- """Check if support module is enabled."""
236
- try:
237
- from django_cfg.apps.support.models import Ticket
238
- return True
239
- except ImportError:
240
- return False
@@ -1,180 +0,0 @@
1
- """
2
- System health and metrics callbacks.
3
- """
4
-
5
- import logging
6
- import shutil
7
- from typing import Any, Dict, List
8
-
9
- from django.core.cache import cache
10
- from django.db import connection
11
- from django.utils import timezone
12
-
13
- from ..models.dashboard import SystemHealthItem
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class SystemCallbacks:
19
- """System health and metrics callbacks."""
20
-
21
- def get_system_health(self) -> List[SystemHealthItem]:
22
- """Get system health status as Pydantic models."""
23
- health_items = []
24
-
25
- # Database health
26
- try:
27
- with connection.cursor() as cursor:
28
- cursor.execute("SELECT 1")
29
- health_items.append(
30
- SystemHealthItem(
31
- component="database",
32
- status="healthy",
33
- description="Connection successful",
34
- last_check=timezone.now().strftime("%H:%M:%S"),
35
- health_percentage=95,
36
- )
37
- )
38
- except Exception as e:
39
- health_items.append(
40
- SystemHealthItem(
41
- component="database",
42
- status="error",
43
- description=f"Connection failed: {str(e)[:50]}",
44
- last_check=timezone.now().strftime("%H:%M:%S"),
45
- health_percentage=0,
46
- )
47
- )
48
-
49
- # Cache health
50
- try:
51
- cache.set("health_check", "ok", 10)
52
- if cache.get("health_check") == "ok":
53
- health_items.append(
54
- SystemHealthItem(
55
- component="cache",
56
- status="healthy",
57
- description="Cache operational",
58
- last_check=timezone.now().strftime("%H:%M:%S"),
59
- health_percentage=90,
60
- )
61
- )
62
- else:
63
- health_items.append(
64
- SystemHealthItem(
65
- component="cache",
66
- status="warning",
67
- description="Cache not responding",
68
- last_check=timezone.now().strftime("%H:%M:%S"),
69
- health_percentage=50,
70
- )
71
- )
72
- except Exception:
73
- health_items.append(
74
- SystemHealthItem(
75
- component="cache",
76
- status="unknown",
77
- description="Cache not configured",
78
- last_check=timezone.now().strftime("%H:%M:%S"),
79
- health_percentage=0,
80
- )
81
- )
82
-
83
- # Storage health
84
- try:
85
- total, used, free = shutil.disk_usage("/")
86
- usage_percentage = (used / total) * 100
87
- free_percentage = 100 - usage_percentage
88
-
89
- if free_percentage > 20:
90
- status = "healthy"
91
- desc = f"Disk space: {free_percentage:.1f}% free"
92
- elif free_percentage > 10:
93
- status = "warning"
94
- desc = f"Low disk space: {free_percentage:.1f}% free"
95
- else:
96
- status = "error"
97
- desc = f"Critical disk space: {free_percentage:.1f}% free"
98
-
99
- health_items.append(
100
- SystemHealthItem(
101
- component="storage",
102
- status=status,
103
- description=desc,
104
- last_check=timezone.now().strftime("%H:%M:%S"),
105
- health_percentage=int(free_percentage),
106
- )
107
- )
108
- except Exception:
109
- health_items.append(
110
- SystemHealthItem(
111
- component="storage",
112
- status="error",
113
- description="Storage check failed",
114
- last_check=timezone.now().strftime("%H:%M:%S"),
115
- health_percentage=0,
116
- )
117
- )
118
-
119
- # API health
120
- health_items.append(
121
- SystemHealthItem(
122
- component="api",
123
- status="healthy",
124
- description="API server running",
125
- last_check=timezone.now().strftime("%H:%M:%S"),
126
- health_percentage=100,
127
- )
128
- )
129
-
130
- return health_items
131
-
132
- def get_system_metrics(self) -> Dict[str, Any]:
133
- """Get system metrics for dashboard."""
134
- metrics = {}
135
-
136
- # Database metrics
137
- try:
138
- with connection.cursor() as cursor:
139
- cursor.execute("SELECT 1")
140
- metrics["database"] = {
141
- "status": "healthy",
142
- "type": "PostgreSQL",
143
- "health_percentage": 95,
144
- "description": "Connection successful",
145
- }
146
- except Exception as e:
147
- metrics["database"] = {
148
- "status": "error",
149
- "type": "PostgreSQL",
150
- "health_percentage": 0,
151
- "description": f"Connection failed: {str(e)}",
152
- }
153
-
154
- # Cache metrics
155
- try:
156
- cache.set("health_check", "ok", 10)
157
- cache_result = cache.get("health_check")
158
- if cache_result == "ok":
159
- metrics["cache"] = {
160
- "status": "healthy",
161
- "type": "Memory Cache",
162
- "health_percentage": 90,
163
- "description": "Cache working properly",
164
- }
165
- else:
166
- metrics["cache"] = {
167
- "status": "warning",
168
- "type": "Memory Cache",
169
- "health_percentage": 50,
170
- "description": "Cache response delayed",
171
- }
172
- except Exception as e:
173
- metrics["cache"] = {
174
- "status": "error",
175
- "type": "Memory Cache",
176
- "health_percentage": 0,
177
- "description": f"Cache error: {str(e)}",
178
- }
179
-
180
- return metrics
@@ -1,65 +0,0 @@
1
- """
2
- Users data callbacks.
3
- """
4
-
5
- import logging
6
- from typing import Any, Dict, List
7
-
8
- from django.contrib.auth import get_user_model
9
-
10
- from .base import get_user_admin_urls
11
-
12
- logger = logging.getLogger(__name__)
13
-
14
-
15
- class UsersCallbacks:
16
- """Users data callbacks."""
17
-
18
- def _get_user_model(self):
19
- """Get the user model safely."""
20
- return get_user_model()
21
-
22
- def get_recent_users(self) -> List[Dict[str, Any]]:
23
- """Get recent users data for template."""
24
- try:
25
- # Avoid database access during app initialization
26
- from django.apps import apps
27
- if not apps.ready:
28
- return []
29
-
30
- User = self._get_user_model()
31
- recent_users = User.objects.select_related().order_by("-date_joined")[:10]
32
-
33
- # Get admin URLs for user model
34
- user_admin_urls = get_user_admin_urls()
35
-
36
- return [
37
- {
38
- "id": user.id,
39
- "username": user.username,
40
- "email": user.email or "No email",
41
- "date_joined": (
42
- user.date_joined.strftime("%Y-%m-%d")
43
- if user.date_joined
44
- else "Unknown"
45
- ),
46
- "is_active": user.is_active,
47
- "is_staff": user.is_staff,
48
- "is_superuser": user.is_superuser,
49
- "last_login": user.last_login,
50
- "admin_urls": {
51
- "change": (
52
- user_admin_urls["change"].format(id=user.id)
53
- if user.id
54
- else None
55
- ),
56
- "view": (
57
- user_admin_urls["view"].format(id=user.id) if user.id else None
58
- ),
59
- },
60
- }
61
- for user in recent_users
62
- ]
63
- except Exception as e:
64
- logger.error(f"Error getting recent users: {e}")
65
- return []
@@ -1,207 +0,0 @@
1
- """
2
- Dashboard Components Models for Unfold
3
-
4
- Pydantic models for dashboard components like stat cards, health items, etc.
5
- """
6
-
7
- from typing import Any, Dict, List, Literal, Optional
8
-
9
- from pydantic import BaseModel, ConfigDict, Field, computed_field
10
-
11
-
12
- class StatCard(BaseModel):
13
- """Dashboard statistics card model."""
14
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
15
-
16
- title: str = Field(..., description="Card title")
17
- value: str = Field(..., description="Main value to display")
18
- icon: str = Field(..., description="Material icon name")
19
- change: Optional[str] = Field(None, description="Change indicator (e.g., '+12%')")
20
- change_type: Literal["positive", "negative", "neutral"] = Field(default="neutral", description="Change type")
21
- description: Optional[str] = Field(None, description="Additional description")
22
- color: str = Field("primary", description="Card color theme")
23
-
24
- @computed_field
25
- @property
26
- def css_classes(self) -> Dict[str, str]:
27
- """Get CSS classes for different states."""
28
- return {
29
- "positive": "text-emerald-600 bg-emerald-100 dark:bg-emerald-900/20 dark:text-emerald-400",
30
- "negative": "text-red-600 bg-red-100 dark:bg-red-900/20 dark:text-red-400",
31
- "neutral": "text-slate-600 bg-slate-100 dark:bg-slate-700 dark:text-slate-400"
32
- }
33
-
34
- def to_dict(self) -> Dict[str, Any]:
35
- """Convert to dictionary for Unfold dashboard widgets."""
36
- return {
37
- "title": self.title,
38
- "value_template": self.value,
39
- "icon": self.icon,
40
- "color": self.color,
41
- "change": self.change,
42
- "change_type": self.change_type,
43
- "description": self.description,
44
- }
45
-
46
-
47
- class SystemHealthItem(BaseModel):
48
- """System health status item."""
49
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
50
-
51
- component: str = Field(..., description="Component name")
52
- status: Literal["healthy", "warning", "error", "unknown"] = Field(..., description="Health status")
53
- description: str = Field(..., description="Status description")
54
- last_check: str = Field(..., description="Last check time")
55
- health_percentage: Optional[int] = Field(None, description="Health percentage (0-100)")
56
-
57
- @computed_field
58
- @property
59
- def icon(self) -> str:
60
- """Get icon based on component type."""
61
- icons = {
62
- "database": "storage",
63
- "cache": "memory",
64
- "queue": "queue",
65
- "storage": "folder",
66
- "api": "api",
67
- }
68
- return icons.get(self.component.lower(), "info")
69
-
70
- @computed_field
71
- @property
72
- def status_icon(self) -> str:
73
- """Get status icon."""
74
- icons = {
75
- "healthy": "check_circle",
76
- "warning": "warning",
77
- "error": "error",
78
- "unknown": "help"
79
- }
80
- return icons.get(self.status, "help")
81
-
82
-
83
- class QuickAction(BaseModel):
84
- """Quick action button model."""
85
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
86
-
87
- title: str = Field(..., description="Action title")
88
- description: str = Field(..., description="Action description")
89
- icon: str = Field(..., description="Material icon name")
90
- link: str = Field(..., description="Action URL or URL name")
91
- color: Literal["primary", "success", "warning", "danger", "secondary"] = Field(default="primary", description="Button color theme")
92
- category: str = Field("general", description="Action category (admin, user, system)")
93
-
94
- def get_resolved_url(self) -> str:
95
- """
96
- Resolve URL name to full URL if needed.
97
-
98
- Returns:
99
- Full URL string - either the original link if it's already a URL,
100
- or the resolved URL if it's a URL name.
101
- """
102
- # If link starts with '/' or 'http', it's already a full URL
103
- if self.link.startswith(("/", "http")):
104
- return self.link
105
-
106
- # Try to resolve as URL name
107
- try:
108
- from django.urls import reverse
109
- from django.urls.exceptions import NoReverseMatch
110
- return reverse(self.link)
111
- except (NoReverseMatch, ImportError, Exception):
112
- # If reverse fails, return the original link
113
- return self.link
114
-
115
- def model_dump(self, **kwargs) -> dict:
116
- """Override model_dump to include resolved URL."""
117
- data = super().model_dump(**kwargs)
118
- # Replace link with resolved URL
119
- data["link"] = self.get_resolved_url()
120
- return data
121
-
122
-
123
- class DashboardWidget(BaseModel):
124
- """Dashboard widget configuration."""
125
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
126
-
127
- title: str = Field(..., description="Widget title")
128
- template: Optional[str] = Field(None, description="Custom template path")
129
- callback: Optional[str] = Field(None, description="Callback function path")
130
- width: int = Field(12, description="Widget width (1-12)")
131
- order: int = Field(0, description="Widget order")
132
-
133
- def to_dict(self) -> Dict[str, Any]:
134
- """Convert to dictionary for Unfold dashboard widgets."""
135
- return {
136
- "title": self.title,
137
- "template": self.template,
138
- "callback": self.callback,
139
- "width": self.width,
140
- "order": self.order,
141
- }
142
-
143
-
144
- class StatsCardsWidget(BaseModel):
145
- """Stats cards widget for dashboard."""
146
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
147
-
148
- type: Literal["stats_cards"] = Field(default="stats_cards", description="Widget type")
149
- title: str = Field(..., description="Widget title")
150
- cards: List[StatCard] = Field(default_factory=list, description="Statistics cards")
151
-
152
- def to_dict(self) -> Dict[str, Any]:
153
- """Convert to dictionary for Unfold dashboard widgets."""
154
- return {
155
- "type": self.type,
156
- "title": self.title,
157
- "cards": [card.to_dict() for card in self.cards],
158
- }
159
-
160
-
161
- class ChartDataset(BaseModel):
162
- """Chart dataset for dashboard charts."""
163
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
164
-
165
- label: str = Field(..., description="Dataset label")
166
- data: List[int] = Field(default_factory=list, description="Data points")
167
- backgroundColor: str = Field(..., description="Background color")
168
- borderColor: str = Field(..., description="Border color")
169
- tension: float = Field(0.4, description="Line tension")
170
-
171
-
172
- class ChartData(BaseModel):
173
- """Chart data structure."""
174
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
175
-
176
- labels: List[str] = Field(default_factory=list, description="Chart labels")
177
- datasets: List[ChartDataset] = Field(default_factory=list, description="Chart datasets")
178
-
179
-
180
- class DashboardData(BaseModel):
181
- """Main dashboard data container."""
182
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
183
-
184
- # Statistics cards
185
- stat_cards: List[StatCard] = Field(default_factory=list, description="Dashboard statistics cards")
186
-
187
- # System health
188
- system_health: List[SystemHealthItem] = Field(default_factory=list, description="System health items")
189
-
190
- # Quick actions
191
- quick_actions: List[QuickAction] = Field(default_factory=list, description="Quick action buttons")
192
-
193
- # Additional data
194
- last_updated: str = Field(..., description="Last update timestamp")
195
- environment: str = Field("development", description="Current environment")
196
-
197
- @computed_field
198
- @property
199
- def total_users(self) -> int:
200
- """Get total users from stat cards."""
201
- for card in self.stat_cards:
202
- if "user" in card.title.lower():
203
- try:
204
- return int(card.value.replace(",", ""))
205
- except (ValueError, AttributeError):
206
- pass
207
- return 0