django-cfg 1.4.87__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 (177) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/centrifugo/views/__init__.py +0 -2
  3. django_cfg/apps/dashboard/permissions.py +48 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +8 -1
  5. django_cfg/apps/dashboard/serializers/commands.py +29 -0
  6. django_cfg/apps/dashboard/services/__init__.py +2 -0
  7. django_cfg/{modules/django_unfold/callbacks/base.py → apps/dashboard/services/commands_security.py} +28 -90
  8. django_cfg/apps/dashboard/services/commands_service.py +208 -9
  9. django_cfg/apps/dashboard/services/overview_service.py +205 -0
  10. django_cfg/apps/dashboard/views/commands_views.py +92 -4
  11. django_cfg/apps/frontend/test_routing.py +134 -0
  12. django_cfg/apps/frontend/views.py +73 -28
  13. django_cfg/apps/urls.py +0 -1
  14. django_cfg/core/builders/apps_builder.py +0 -58
  15. django_cfg/modules/django_unfold/__init__.py +5 -24
  16. django_cfg/modules/django_unfold/models/__init__.py +0 -23
  17. django_cfg/modules/django_unfold/models/config.py +11 -65
  18. django_cfg/modules/django_unfold/{dashboard.py → navigation.py} +21 -152
  19. django_cfg/modules/django_unfold/tailwind.py +2 -4
  20. django_cfg/pyproject.toml +1 -1
  21. django_cfg/registry/third_party.py +0 -9
  22. django_cfg/routing/callbacks.py +1 -43
  23. django_cfg/static/frontend/admin/404/index.html +1 -1
  24. django_cfg/static/frontend/admin/404.html +1 -1
  25. django_cfg/static/frontend/admin/500/index.html +1 -1
  26. django_cfg/static/frontend/admin/_next/static/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
  27. django_cfg/static/frontend/admin/_next/static/chunks/{19430.fe7bff7372f8a256.js → 19430.c4c95603c23c17fe.js} +1 -1
  28. django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
  29. django_cfg/static/frontend/admin/_next/static/chunks/94141-bc6d47f419b26b21.js +1 -0
  30. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-c336f254967dd101.js → _app-c7dcd3aa616fab68.js} +6 -6
  31. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{cookies-b39c7f22c066e2c6.js → cookies-97d279800f12aab4.js} +1 -1
  32. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{privacy-5aedad0cf3a4f80f.js → privacy-1d5e6cd94689247e.js} +1 -1
  33. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{security-dbd854d0d5d483e2.js → security-55e49700e7a01f5a.js} +1 -1
  34. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{terms-f3e1d2b9e5edf12f.js → terms-14c02bb2d3198352.js} +1 -1
  35. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-22532c65971225eb.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
  36. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-d4ccbe1265cbd853.js +1 -0
  37. django_cfg/static/frontend/admin/_next/static/chunks/{webpack-da114020a6b940f5.js → webpack-5a92f81363b62aa7.js} +1 -1
  38. django_cfg/static/frontend/admin/_next/static/css/3063068f0d5a8a00.css +3 -0
  39. django_cfg/static/frontend/admin/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
  40. django_cfg/static/frontend/admin/_next/static/media/21350d82a1f187e9-s.p.woff2 +0 -0
  41. django_cfg/static/frontend/admin/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
  42. django_cfg/static/frontend/admin/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
  43. django_cfg/static/frontend/admin/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
  44. django_cfg/static/frontend/admin/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  45. django_cfg/static/frontend/admin/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
  46. django_cfg/static/frontend/admin/auth/index.html +1 -1
  47. django_cfg/static/frontend/admin/index.html +1 -1
  48. django_cfg/static/frontend/admin/legal/cookies/index.html +1 -1
  49. django_cfg/static/frontend/admin/legal/privacy/index.html +1 -1
  50. django_cfg/static/frontend/admin/legal/security/index.html +1 -1
  51. django_cfg/static/frontend/admin/legal/terms/index.html +1 -1
  52. django_cfg/static/frontend/admin/private/centrifugo/index.html +1 -1
  53. django_cfg/static/frontend/admin/private/index.html +1 -1
  54. django_cfg/static/frontend/admin/private/profile/index.html +1 -1
  55. django_cfg/static/frontend/admin/private/ui/index.html +2 -2
  56. django_cfg/templates/admin/index.html +1 -1
  57. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
  58. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/RECORD +62 -163
  59. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +0 -260
  60. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +0 -313
  61. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +0 -803
  62. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +0 -341
  63. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +0 -432
  64. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +0 -33
  65. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +0 -210
  66. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +0 -46
  67. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +0 -123
  68. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +0 -45
  69. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +0 -84
  70. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/stat_cards.html +0 -53
  71. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +0 -91
  72. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/tab_navigation.html +0 -29
  73. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +0 -415
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +0 -61
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +0 -58
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +0 -48
  77. django_cfg/apps/centrifugo/templatetags/__init__.py +0 -1
  78. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +0 -81
  79. django_cfg/apps/centrifugo/urls_admin.py +0 -20
  80. django_cfg/apps/centrifugo/views/dashboard.py +0 -28
  81. django_cfg/modules/django_dashboard/__init__.py +0 -23
  82. django_cfg/modules/django_dashboard/components.py +0 -312
  83. django_cfg/modules/django_dashboard/debug.py +0 -174
  84. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  85. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  86. django_cfg/modules/django_dashboard/management/commands/debug_dashboard.py +0 -109
  87. django_cfg/modules/django_dashboard/sections/__init__.py +0 -1
  88. django_cfg/modules/django_dashboard/sections/base.py +0 -129
  89. django_cfg/modules/django_dashboard/sections/commands.py +0 -33
  90. django_cfg/modules/django_dashboard/sections/documentation.py +0 -393
  91. django_cfg/modules/django_dashboard/sections/overview.py +0 -398
  92. django_cfg/modules/django_dashboard/sections/stats.py +0 -48
  93. django_cfg/modules/django_dashboard/sections/system.py +0 -74
  94. django_cfg/modules/django_dashboard/sections/widgets.py +0 -222
  95. django_cfg/modules/django_unfold/callbacks/__init__.py +0 -9
  96. django_cfg/modules/django_unfold/callbacks/actions.py +0 -51
  97. django_cfg/modules/django_unfold/callbacks/apizones.py +0 -122
  98. django_cfg/modules/django_unfold/callbacks/charts.py +0 -223
  99. django_cfg/modules/django_unfold/callbacks/commands.py +0 -40
  100. django_cfg/modules/django_unfold/callbacks/main.py +0 -322
  101. django_cfg/modules/django_unfold/callbacks/statistics.py +0 -240
  102. django_cfg/modules/django_unfold/callbacks/system.py +0 -180
  103. django_cfg/modules/django_unfold/callbacks/users.py +0 -65
  104. django_cfg/modules/django_unfold/models/dashboard.py +0 -207
  105. django_cfg/modules/django_unfold/models/tabs.py +0 -26
  106. django_cfg/modules/django_unfold/models.py +0 -98
  107. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +0 -102
  108. django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +0 -1
  109. django_cfg/static/frontend/admin/_next/static/chunks/50314-48bd5701f62faf27.js +0 -1
  110. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +0 -1
  111. django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +0 -3
  112. django_cfg/static/frontend/admin/_next/static/media/438aa629764e75f3-s.woff2 +0 -0
  113. django_cfg/static/frontend/admin/_next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
  114. django_cfg/static/frontend/admin/_next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
  115. django_cfg/static/frontend/admin/_next/static/media/875ae681bfde4580-s.p.woff2 +0 -0
  116. django_cfg/static/frontend/admin/_next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
  117. django_cfg/static/frontend/admin/_next/static/media/e857b654a2caa584-s.woff2 +0 -0
  118. django_cfg/templates/admin/sections/commands_section.html +0 -5
  119. django_cfg/templates/admin/sections/documentation_section.html +0 -5
  120. django_cfg/templates/admin/sections/overview_section.html +0 -5
  121. django_cfg/templates/admin/sections/stats_section.html +0 -5
  122. django_cfg/templates/admin/sections/system_section.html +0 -5
  123. django_cfg/templates/admin/sections/widgets_section.html +0 -11
  124. django_cfg/templates/admin_old/components/action_grid.html +0 -49
  125. django_cfg/templates/admin_old/components/card.html +0 -50
  126. django_cfg/templates/admin_old/components/data_table.html +0 -67
  127. django_cfg/templates/admin_old/components/metric_card.html +0 -39
  128. django_cfg/templates/admin_old/components/modal.html +0 -58
  129. django_cfg/templates/admin_old/components/progress_bar.html +0 -20
  130. django_cfg/templates/admin_old/components/section_header.html +0 -26
  131. django_cfg/templates/admin_old/components/stat_item.html +0 -32
  132. django_cfg/templates/admin_old/components/stats_grid.html +0 -72
  133. django_cfg/templates/admin_old/components/status_badge.html +0 -28
  134. django_cfg/templates/admin_old/components/user_avatar.html +0 -27
  135. django_cfg/templates/admin_old/constance/change_list.html +0 -74
  136. django_cfg/templates/admin_old/constance/includes/default_value.html +0 -24
  137. django_cfg/templates/admin_old/constance/includes/fieldset_header.html +0 -15
  138. django_cfg/templates/admin_old/constance/includes/results_list.html +0 -16
  139. django_cfg/templates/admin_old/constance/includes/setting_row.html +0 -50
  140. django_cfg/templates/admin_old/constance/includes/table_headers.html +0 -10
  141. django_cfg/templates/admin_old/examples/component_class_example.html +0 -156
  142. django_cfg/templates/admin_old/import_export/change_list_export.html +0 -24
  143. django_cfg/templates/admin_old/import_export/change_list_import.html +0 -24
  144. django_cfg/templates/admin_old/import_export/change_list_import_export.html +0 -34
  145. django_cfg/templates/admin_old/index.html +0 -80
  146. django_cfg/templates/admin_old/index_new.html +0 -119
  147. django_cfg/templates/admin_old/layouts/base_dashboard.html +0 -62
  148. django_cfg/templates/admin_old/layouts/dashboard_with_tabs.html +0 -176
  149. django_cfg/templates/admin_old/sections/commands_section.html +0 -549
  150. django_cfg/templates/admin_old/sections/documentation_section.html +0 -152
  151. django_cfg/templates/admin_old/sections/overview_section.html +0 -112
  152. django_cfg/templates/admin_old/sections/stats_section.html +0 -35
  153. django_cfg/templates/admin_old/sections/system_section.html +0 -99
  154. django_cfg/templates/admin_old/sections/widgets_section.html +0 -129
  155. django_cfg/templates/admin_old/snippets/components/activity_tracker.html +0 -70
  156. django_cfg/templates/admin_old/snippets/components/charts_section.html +0 -113
  157. django_cfg/templates/admin_old/snippets/components/django_commands.html +0 -270
  158. django_cfg/templates/admin_old/snippets/components/quick_actions.html +0 -66
  159. django_cfg/templates/admin_old/snippets/components/recent_activity_improved.html +0 -25
  160. django_cfg/templates/admin_old/snippets/components/recent_users_table.html +0 -102
  161. django_cfg/templates/admin_old/snippets/components/stats_cards.html +0 -4
  162. django_cfg/templates/admin_old/snippets/components/stats_tiles.html +0 -92
  163. django_cfg/templates/admin_old/snippets/components/system_health.html +0 -22
  164. django_cfg/templates/admin_old/snippets/components/system_metrics.html +0 -199
  165. django_cfg/templates/admin_old/snippets/components/user_permissions.html +0 -57
  166. django_cfg/templates/admin_old/snippets/tabs/app_stats_tab.html +0 -201
  167. django_cfg/templates/admin_old/snippets/tabs/commands_tab.html +0 -114
  168. django_cfg/templates/admin_old/snippets/tabs/documentation_tab.html +0 -42
  169. django_cfg/templates/admin_old/snippets/tabs/overview_tab.html +0 -116
  170. django_cfg/templates/admin_old/snippets/tabs/stats_tab.html +0 -89
  171. django_cfg/templates/admin_old/snippets/tabs/users_tab.html +0 -51
  172. django_cfg/templates/admin_old/snippets/tabs/widgets_tab.html +0 -38
  173. django_cfg/templates/admin_old/snippets/zones/zones_table.html +0 -176
  174. /django_cfg/static/frontend/admin/_next/static/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
  175. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
  176. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
  177. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -1,26 +0,0 @@
1
- """
2
- Tab Configuration Models for Unfold Dashboard
3
-
4
- Pydantic models for tab configurations.
5
- """
6
-
7
- from typing import List, Optional
8
-
9
- from pydantic import BaseModel, ConfigDict, Field
10
-
11
-
12
- class TabItem(BaseModel):
13
- """Tab item configuration."""
14
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
15
-
16
- title: str = Field(..., min_length=1, description="Tab title")
17
- link: str = Field(..., min_length=1, description="Tab URL")
18
- permission: Optional[str] = Field(None, description="Permission callback")
19
-
20
-
21
- class TabConfiguration(BaseModel):
22
- """Tab configuration for admin models."""
23
- model_config = ConfigDict(validate_assignment=True, extra="forbid")
24
-
25
- models: List[str] = Field(..., min_length=1, description="Model names for tab")
26
- items: List[TabItem] = Field(..., min_length=1, description="Tab items")
@@ -1,98 +0,0 @@
1
- """
2
- Dashboard Pydantic Models for Django CFG Unfold Module
3
-
4
- All dashboard data models with type safety.
5
- Following CRITICAL_REQUIREMENTS.md - NO raw dicts, ALL type-safe.
6
- """
7
-
8
- from typing import List, Optional
9
-
10
- from pydantic import BaseModel, Field
11
-
12
-
13
- class StatCard(BaseModel):
14
- """Statistics card model for dashboard."""
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., '+5', '-2%')")
20
- change_type: str = Field("neutral", description="Change type: positive, negative, neutral")
21
- description: Optional[str] = Field(None, description="Additional description")
22
- color: str = Field("primary", description="Card color theme")
23
-
24
-
25
- class SystemHealthItem(BaseModel):
26
- """System health status item."""
27
-
28
- component: str = Field(..., description="Component name (database, cache, etc.)")
29
- status: str = Field(..., description="Status: healthy, warning, error, unknown")
30
- description: str = Field(..., description="Status description")
31
- last_check: str = Field(..., description="Last check time")
32
- health_percentage: Optional[int] = Field(None, description="Health percentage (0-100)")
33
-
34
-
35
- class QuickAction(BaseModel):
36
- """Quick action button for dashboard."""
37
-
38
- title: str = Field(..., description="Action title")
39
- description: str = Field(..., description="Action description")
40
- icon: str = Field(..., description="Material icon name")
41
- link: str = Field(..., description="Action URL or URL name")
42
- color: str = Field("primary", description="Button color")
43
- category: str = Field("general", description="Action category")
44
-
45
- def get_resolved_url(self) -> str:
46
- """
47
- Resolve URL name to full URL if needed.
48
-
49
- Returns:
50
- Full URL string - either the original link if it's already a URL,
51
- or the resolved URL if it's a URL name.
52
- """
53
- # If link starts with '/' or 'http', it's already a full URL
54
- if self.link.startswith(("/", "http")):
55
- return self.link
56
-
57
- # Try to resolve as URL name
58
- try:
59
- from django.urls import reverse
60
- from django.urls.exceptions import NoReverseMatch
61
- return reverse(self.link)
62
- except (NoReverseMatch, ImportError, Exception):
63
- # If reverse fails, return the original link
64
- return self.link
65
-
66
- def model_dump(self, **kwargs) -> dict:
67
- """Override model_dump to include resolved URL."""
68
- data = super().model_dump(**kwargs)
69
- # Replace link with resolved URL
70
- data["link"] = self.get_resolved_url()
71
- return data
72
-
73
-
74
- class DashboardData(BaseModel):
75
- """Main dashboard data container."""
76
-
77
- stat_cards: List[StatCard] = Field(default_factory=list, description="Statistics cards")
78
- system_health: List[SystemHealthItem] = Field(default_factory=list, description="System health items")
79
- quick_actions: List[QuickAction] = Field(default_factory=list, description="Quick action buttons")
80
- last_updated: str = Field(..., description="Last update timestamp")
81
- environment: str = Field("development", description="Current environment")
82
-
83
-
84
- class ChartDataset(BaseModel):
85
- """Chart dataset for dashboard charts."""
86
-
87
- label: str = Field(..., description="Dataset label")
88
- data: List[int] = Field(default_factory=list, description="Data points")
89
- backgroundColor: str = Field(..., description="Background color")
90
- borderColor: str = Field(..., description="Border color")
91
- tension: float = Field(0.4, description="Line tension")
92
-
93
-
94
- class ChartData(BaseModel):
95
- """Chart data structure."""
96
-
97
- labels: List[str] = Field(default_factory=list, description="Chart labels")
98
- datasets: List[ChartDataset] = Field(default_factory=list, description="Chart datasets")
@@ -1,102 +0,0 @@
1
- {% load i18n unfold %}
2
-
3
- {% if sidebar_navigation %}
4
- <div class="h-0 grow overflow-auto" data-simplebar>
5
- {% for group in sidebar_navigation %}
6
- {% if group.items %}
7
- {% has_nav_item_active group.items as has_active %}
8
-
9
- <div class="hidden mb-2 has-[ol]:has-[li]:block" {% if group.collapsible %}x-data="{navigationOpen: {% if group.open %}true{% elif has_active %}true{% else %}false{% endif %}}"{% endif %}>
10
- {% if group.separator %}
11
- <hr class="border-t border-base-200 mx-6 my-2 dark:border-base-800" />
12
- {% endif %}
13
-
14
- {% if group.title %}
15
- <h2 class="font-semibold flex flex-row group items-center mb-1 mx-3 py-1.5 px-3 select-none text-font-important-light text-sm dark:text-font-important-dark {% if group.collapsible %}cursor-pointer hover:text-primary-600 dark:hover:text-primary-500{% endif %}" {% if group.collapsible %}x-on:click="navigationOpen = !navigationOpen"{% endif %}>
16
- {{ group.title }}
17
-
18
- {% if group.collapsible %}
19
- <span class="material-symbols-outlined ml-auto transition-all group-hover:text-primary-600 dark:group-hover:text-primary-500" x-bind:class="{'rotate-90': navigationOpen}">
20
- chevron_right
21
- </span>
22
- {% endif %}
23
- </h2>
24
- {% endif %}
25
-
26
- <ol class="flex flex-col gap-1 px-6" {% if group.collapsible %}x-show="navigationOpen"{% endif %}>
27
- {% for item in group.items %}
28
- {% if item.has_permission %}
29
- <li>
30
- <a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}" class="flex h-[38px] items-center -mx-3 px-3 rounded-default hover:text-primary-600 dark:hover:text-primary-500 {% if item.active %}bg-base-100 font-semibold text-primary-600 dark:bg-white/[.06] dark:text-primary-500{% endif %}">
31
- {% if item.icon %}
32
- <span class="material-symbols-outlined md-18 mr-3 w-[18px]">
33
- {{ item.icon }}
34
- </span>
35
- {% endif %}
36
-
37
- <span class="text-sm">
38
- {{ item.title|safe }}
39
- </span>
40
-
41
- {% if item.badge %}
42
- <span class="bg-red-600 font-semibold ml-2 px-1 relative rounded-xs text-xs text-white">
43
- {% if item.badge_callback %}
44
- {{ item.badge_callback }}
45
- {% else %}
46
- {{ item.badge }}
47
- {% endif %}
48
- </span>
49
- {% endif %}
50
- </a>
51
- </li>
52
- {% endif %}
53
- {% endfor %}
54
- </ol>
55
- </div>
56
- {% endif %}
57
- {% endfor %}
58
- </div>
59
-
60
- {% if sidebar_show_all_applications and app_list|length > 0 %}
61
- <div class="mt-auto" x-data="{ openAllApplications: false }">
62
- <a class="cursor-pointer flex items-center h-[64px] px-6 py-3 text-sm dark:text-font-default-dark hover:text-primary-600 dark:hover:text-primary-500" x-on:click="openAllApplications = !openAllApplications">
63
- <span class="material-symbols-outlined md-18 mr-3">
64
- apps
65
- </span>
66
-
67
- <span>
68
- {% trans "All applications" %}
69
- </span>
70
- </a>
71
-
72
- <div class="absolute bottom-0 left-0 right-0 top-0 z-50 md:left-72" x-cloak x-show="openAllApplications">
73
- <div class="absolute bg-base-900/80 backdrop-blur-xs bottom-0 left-0 right-0 top-0 z-10 w-screen"></div>
74
-
75
- <div class="bg-white flex flex-col h-full overflow-x-hidden overflow-y-auto py-5 px-8 relative text-sm w-80 z-20 dark:bg-base-900 dark:border-r dark:border-base-800" x-on:click.outside="openAllApplications = false" x-on:keydown.escape.window="openAllApplications = false" data-simplebar>
76
- {% for app in app_list %}
77
- <div class="mb-6 last:mb-0">
78
- <h2 class="mb-4 font-semibold text-font-important-light truncate dark:text-font-important-dark">
79
- {{ app.name }}
80
- </h2>
81
-
82
- <ul>
83
- {% for model in app.models %}
84
- <li class="block mb-4 last:mb-0">
85
- <a href="{{ model.admin_url }}" class="block truncate hover:text-primary-600 dark:hover:text-primary-500">
86
- {{ model.name }}
87
- </a>
88
- </li>
89
- {% endfor %}
90
- </ul>
91
- </div>
92
- {% endfor %}
93
- </div>
94
- </div>
95
- </div>
96
- {% endif %}
97
- {% else %}
98
- <p>
99
- {% trans "You don’t have permission to view or edit anything." as error_message %}
100
- {% include "unfold/helpers/messages/error.html" with error=error_message %}
101
- </p>
102
- {% endif %}