django-cfg 1.4.83__py3-none-any.whl → 1.4.84__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/dashboard/__init__.py +8 -0
- django_cfg/apps/dashboard/api/__init__.py +27 -0
- django_cfg/apps/dashboard/api/serializers.py +165 -0
- django_cfg/apps/dashboard/api/viewsets.py +257 -0
- django_cfg/apps/dashboard/apps.py +23 -0
- django_cfg/apps/dashboard/services/__init__.py +11 -0
- django_cfg/apps/dashboard/services/statistics_service.py +235 -0
- django_cfg/apps/dashboard/services/system_health_service.py +280 -0
- django_cfg/apps/dashboard/urls.py +23 -0
- django_cfg/apps/frontend/views.py +5 -0
- django_cfg/apps/urls.py +2 -1
- django_cfg/core/builders/apps_builder.py +1 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +12 -5
- django_cfg/modules/django_client/core/parser/openapi31.py +12 -5
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/frontend/admin/404.html +1 -1
- django_cfg/static/frontend/admin/500.html +1 -1
- django_cfg/static/frontend/admin/_next/static/-Zk0eDB7OJOEFrFyR5BwZ/_buildManifest.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{43076.55dd23b6cd68edb0.js → 20695.a7d37b6c40ad3f58.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{25033.d626f78bc99bc4a1.js → 25033.ee3e206d5a2877b6.js} +2 -2
- django_cfg/static/frontend/admin/_next/static/chunks/{25892.964150a58f94ce06.js → 25892.5cbed319f9226fdc.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{2d7a934f.dfef67639279d59d.js → 2d7a934f.329c61f23af1a7ec.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{30649.00c679812a56aee3.js → 30649.963cfb7268b5864a.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{30875.784491146c38dbcb.js → 30875.82c3741757b8aa32.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{32163.ab0ca435b3f26c04.js → 32163.109a03a7252f1508.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/43076-4be6a9794e9c3e8b.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{49978.fb8ba7ee52ffe666.js → 49978.db5a86a8eb233f35.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/50314-79c02212788f1ec7.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{50319.f786248384877960.js → 50319.fd78c7f7e3f1966e.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{52908.b690e323d8f8efdd.js → 52908.da5b850b0bc0970c.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{53710.80ca863525d137db.js → 53710.7176bbee6c7b78be.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{57982.251fed8d58adcf53.js → 57982.2c90b33b0934522a.js} +2 -2
- django_cfg/static/frontend/admin/_next/static/chunks/{60181.86e18057c4caaa97.js → 60181.c94d78d10eb5da37.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{60374.bde0ec1249aa79c6.js → 60374.5d80cfc45439b2b0.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{6884.7b1db804c88280ed.js → 6884.624d563508cf6db4.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{69436.9515b854cdf4b57a.js → 69436.be44021e3d7c99c7.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{70628.00cdd98f672e684f.js → 70628.58e8c38a66543d5e.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{73218.a826c2248612b37f.js → 73218.d712e7bd678e23a8.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{76334.64fbaa923d9ac293.js → 76334.f43f2d8b4bbf8dd6.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{7799.2b280f8ddf067d49.js → 7799.1575cc212bc750c7.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{80574.620a8a5b4eb91c25.js → 80574.92638dd7b9979664.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{81127.a0603c3394892d4e.js → 81127.3ead500eec887152.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/82296-a2c8d38f62224be5.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{8383.eb6188b22c453e14.js → 8383.e25a442df26b2e26.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{85833.35e6ca25ac32a7d2.js → 85833.b0dead4fbcbfdd1b.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{95365.fc9d7653a78839d0.js → 95365.2b430045fc2e5acf.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{96424.0793b94836eb13a6.js → 96424.11d76570e9a94b85.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-16701a4e1bc3e6ac.js → _app-f25bec36bbdc9625.js} +46 -46
- django_cfg/static/frontend/admin/_next/static/chunks/pages/index-d7bc30185f52cbca.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{cookies-bb5507a122775f30.js → cookies-b39c7f22c066e2c6.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{privacy-f8a3d8db1a197be3.js → privacy-5aedad0cf3a4f80f.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{security-aba50addd2179f8f.js → security-dbd854d0d5d483e2.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{terms-4aa35cd30b5c08ad.js → terms-f3e1d2b9e5edf12f.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-22532c65971225eb.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/ui-669e8f2a785beba2.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private-a8a9ba76f2c75354.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{webpack-905bba30877f6490.js → webpack-92add5f95c66e349.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/css/78d677ac1677c210.css +3 -0
- django_cfg/static/frontend/admin/auth.html +1 -1
- django_cfg/static/frontend/admin/index.html +1 -1
- django_cfg/static/frontend/admin/legal/cookies.html +1 -1
- django_cfg/static/frontend/admin/legal/privacy.html +1 -1
- django_cfg/static/frontend/admin/legal/security.html +1 -1
- django_cfg/static/frontend/admin/legal/terms.html +1 -1
- django_cfg/static/frontend/admin/private/centrifugo.html +1 -1
- django_cfg/static/frontend/admin/private/profile.html +1 -1
- django_cfg/static/frontend/admin/private/ui.html +1 -0
- django_cfg/static/frontend/admin/private.html +1 -1
- django_cfg/templates/admin/index.html +97 -63
- django_cfg/templates/admin_old/index.html +80 -0
- {django_cfg-1.4.83.dist-info → django_cfg-1.4.84.dist-info}/METADATA +1 -1
- {django_cfg-1.4.83.dist-info → django_cfg-1.4.84.dist-info}/RECORD +126 -113
- django_cfg/static/frontend/admin/_next/static/chunks/pages/index-88751d9f44a32105.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-1c5f00c26c77a47b.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private-2f58633ddf63a5bc.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/ui-0e6c0e35862789ec.js +0 -1
- django_cfg/static/frontend/admin/_next/static/css/806300fb98c42afb.css +0 -3
- django_cfg/static/frontend/admin/_next/static/ibMHm1p66p0UGKsKnDWxn/_buildManifest.js +0 -1
- django_cfg/static/frontend/admin/ui.html +0 -92
- /django_cfg/static/frontend/admin/_next/static/{ibMHm1p66p0UGKsKnDWxn → -Zk0eDB7OJOEFrFyR5BwZ}/_ssgManifest.js +0 -0
- /django_cfg/templates/{admin → admin_old}/components/action_grid.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/card.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/data_table.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/metric_card.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/modal.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/progress_bar.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/section_header.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/stat_item.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/stats_grid.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/status_badge.html +0 -0
- /django_cfg/templates/{admin → admin_old}/components/user_avatar.html +0 -0
- /django_cfg/templates/{admin → admin_old}/constance/change_list.html +0 -0
- /django_cfg/templates/{admin → admin_old}/constance/includes/default_value.html +0 -0
- /django_cfg/templates/{admin → admin_old}/constance/includes/fieldset_header.html +0 -0
- /django_cfg/templates/{admin → admin_old}/constance/includes/results_list.html +0 -0
- /django_cfg/templates/{admin → admin_old}/constance/includes/setting_row.html +0 -0
- /django_cfg/templates/{admin → admin_old}/constance/includes/table_headers.html +0 -0
- /django_cfg/templates/{admin → admin_old}/examples/component_class_example.html +0 -0
- /django_cfg/templates/{admin → admin_old}/import_export/change_list_export.html +0 -0
- /django_cfg/templates/{admin → admin_old}/import_export/change_list_import.html +0 -0
- /django_cfg/templates/{admin → admin_old}/import_export/change_list_import_export.html +0 -0
- /django_cfg/templates/{admin → admin_old}/index_new.html +0 -0
- /django_cfg/templates/{admin → admin_old}/layouts/base_dashboard.html +0 -0
- /django_cfg/templates/{admin → admin_old}/layouts/dashboard_with_tabs.html +0 -0
- /django_cfg/templates/{admin → admin_old}/sections/commands_section.html +0 -0
- /django_cfg/templates/{admin → admin_old}/sections/documentation_section.html +0 -0
- /django_cfg/templates/{admin → admin_old}/sections/overview_section.html +0 -0
- /django_cfg/templates/{admin → admin_old}/sections/stats_section.html +0 -0
- /django_cfg/templates/{admin → admin_old}/sections/system_section.html +0 -0
- /django_cfg/templates/{admin → admin_old}/sections/widgets_section.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/activity_tracker.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/charts_section.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/django_commands.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/quick_actions.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/recent_activity_improved.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/recent_users_table.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/stats_cards.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/stats_tiles.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/system_health.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/system_metrics.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/components/user_permissions.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/tabs/app_stats_tab.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/tabs/commands_tab.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/tabs/documentation_tab.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/tabs/overview_tab.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/tabs/stats_tab.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/tabs/users_tab.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/tabs/widgets_tab.html +0 -0
- /django_cfg/templates/{admin → admin_old}/snippets/zones/zones_table.html +0 -0
- {django_cfg-1.4.83.dist-info → django_cfg-1.4.84.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.83.dist-info → django_cfg-1.4.84.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.83.dist-info → django_cfg-1.4.84.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Statistics Service
|
|
3
|
+
|
|
4
|
+
Collects and aggregates statistics for dashboard display.
|
|
5
|
+
No ORM dependencies - can work with any data source.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StatisticsService:
|
|
16
|
+
"""
|
|
17
|
+
Service for collecting dashboard statistics.
|
|
18
|
+
|
|
19
|
+
%%PRIORITY:HIGH%%
|
|
20
|
+
%%AI_HINT: This service collects data from various sources without ORM%%
|
|
21
|
+
|
|
22
|
+
TAGS: statistics, dashboard, service
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
"""Initialize statistics service."""
|
|
27
|
+
self.logger = logger
|
|
28
|
+
|
|
29
|
+
def get_user_statistics(self) -> Dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Get user-related statistics.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dictionary containing user stats:
|
|
35
|
+
- total_users: Total number of users
|
|
36
|
+
- active_users: Number of active users (last 30 days)
|
|
37
|
+
- new_users: New users in last 7 days
|
|
38
|
+
- superusers: Number of superusers
|
|
39
|
+
|
|
40
|
+
%%AI_HINT: Currently returns mock data. Replace with real data source%%
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
# TODO: Replace with real data collection
|
|
44
|
+
# Example: from django.contrib.auth import get_user_model
|
|
45
|
+
# User = get_user_model()
|
|
46
|
+
# total_users = User.objects.count()
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
'total_users': 1250,
|
|
50
|
+
'active_users': 892,
|
|
51
|
+
'new_users': 45,
|
|
52
|
+
'superusers': 3,
|
|
53
|
+
}
|
|
54
|
+
except Exception as e:
|
|
55
|
+
self.logger.error(f"Error getting user statistics: {e}")
|
|
56
|
+
return {
|
|
57
|
+
'total_users': 0,
|
|
58
|
+
'active_users': 0,
|
|
59
|
+
'new_users': 0,
|
|
60
|
+
'superusers': 0,
|
|
61
|
+
'error': str(e)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def get_app_statistics(self) -> Dict[str, Dict[str, int]]:
|
|
65
|
+
"""
|
|
66
|
+
Get application-specific statistics.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dictionary with app names as keys and their stats as values.
|
|
70
|
+
Example:
|
|
71
|
+
{
|
|
72
|
+
'leads': {'total': 450, 'active': 120},
|
|
73
|
+
'tasks': {'total': 1200, 'completed': 890},
|
|
74
|
+
...
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
%%AI_HINT: Aggregates statistics from different django-cfg apps%%
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
# TODO: Collect real statistics from enabled apps
|
|
81
|
+
return {
|
|
82
|
+
'leads': {
|
|
83
|
+
'total': 450,
|
|
84
|
+
'active': 120,
|
|
85
|
+
'converted': 85,
|
|
86
|
+
},
|
|
87
|
+
'tasks': {
|
|
88
|
+
'total': 1200,
|
|
89
|
+
'completed': 890,
|
|
90
|
+
'pending': 310,
|
|
91
|
+
},
|
|
92
|
+
'support': {
|
|
93
|
+
'total': 340,
|
|
94
|
+
'open': 45,
|
|
95
|
+
'closed': 295,
|
|
96
|
+
},
|
|
97
|
+
'newsletter': {
|
|
98
|
+
'subscribers': 2500,
|
|
99
|
+
'campaigns': 12,
|
|
100
|
+
'sent': 28000,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
except Exception as e:
|
|
104
|
+
self.logger.error(f"Error getting app statistics: {e}")
|
|
105
|
+
return {'error': str(e)}
|
|
106
|
+
|
|
107
|
+
def get_stat_cards(self) -> List[Dict[str, Any]]:
|
|
108
|
+
"""
|
|
109
|
+
Get statistics cards for dashboard overview.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
List of stat card dictionaries ready for serialization.
|
|
113
|
+
Each card contains: title, value, icon, change, change_type
|
|
114
|
+
|
|
115
|
+
USED_BY: DashboardViewSet.overview endpoint
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
user_stats = self.get_user_statistics()
|
|
119
|
+
|
|
120
|
+
cards = [
|
|
121
|
+
{
|
|
122
|
+
'title': 'Total Users',
|
|
123
|
+
'value': str(user_stats['total_users']),
|
|
124
|
+
'icon': 'people',
|
|
125
|
+
'change': '+12%',
|
|
126
|
+
'change_type': 'positive',
|
|
127
|
+
'color': 'primary',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
'title': 'Active Users',
|
|
131
|
+
'value': str(user_stats['active_users']),
|
|
132
|
+
'icon': 'person',
|
|
133
|
+
'change': '+5%',
|
|
134
|
+
'change_type': 'positive',
|
|
135
|
+
'color': 'success',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
'title': 'New Users (7d)',
|
|
139
|
+
'value': str(user_stats['new_users']),
|
|
140
|
+
'icon': 'person_add',
|
|
141
|
+
'change': '-2%',
|
|
142
|
+
'change_type': 'negative',
|
|
143
|
+
'color': 'warning',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
'title': 'System Health',
|
|
147
|
+
'value': '98%',
|
|
148
|
+
'icon': 'health_and_safety',
|
|
149
|
+
'change': '+1%',
|
|
150
|
+
'change_type': 'positive',
|
|
151
|
+
'color': 'success',
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
return cards
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
self.logger.error(f"Error generating stat cards: {e}")
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
def get_recent_activity(self, limit: int = 10) -> List[Dict[str, Any]]:
|
|
162
|
+
"""
|
|
163
|
+
Get recent activity entries for dashboard.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
limit: Maximum number of activity entries to return
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
List of recent activity entries
|
|
170
|
+
|
|
171
|
+
%%AI_HINT: Can be connected to django-auditlog or custom activity tracking%%
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
# TODO: Connect to real activity logging system
|
|
175
|
+
now = datetime.now()
|
|
176
|
+
|
|
177
|
+
activities = [
|
|
178
|
+
{
|
|
179
|
+
'id': 1,
|
|
180
|
+
'user': 'john@example.com',
|
|
181
|
+
'action': 'created',
|
|
182
|
+
'resource': 'Lead #1234',
|
|
183
|
+
'timestamp': (now - timedelta(minutes=5)).isoformat(),
|
|
184
|
+
'icon': 'add_circle',
|
|
185
|
+
'color': 'success',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
'id': 2,
|
|
189
|
+
'user': 'admin@example.com',
|
|
190
|
+
'action': 'updated',
|
|
191
|
+
'resource': 'User Settings',
|
|
192
|
+
'timestamp': (now - timedelta(minutes=15)).isoformat(),
|
|
193
|
+
'icon': 'edit',
|
|
194
|
+
'color': 'primary',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
'id': 3,
|
|
198
|
+
'user': 'sarah@example.com',
|
|
199
|
+
'action': 'completed',
|
|
200
|
+
'resource': 'Task #456',
|
|
201
|
+
'timestamp': (now - timedelta(hours=1)).isoformat(),
|
|
202
|
+
'icon': 'check_circle',
|
|
203
|
+
'color': 'success',
|
|
204
|
+
},
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
return activities[:limit]
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
self.logger.error(f"Error getting recent activity: {e}")
|
|
211
|
+
return []
|
|
212
|
+
|
|
213
|
+
def get_system_metrics(self) -> Dict[str, Any]:
|
|
214
|
+
"""
|
|
215
|
+
Get system performance metrics.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Dictionary with system metrics (CPU, memory, disk, etc.)
|
|
219
|
+
|
|
220
|
+
%%AI_HINT: Can use psutil or similar for real system metrics%%
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
# TODO: Collect real system metrics
|
|
224
|
+
return {
|
|
225
|
+
'cpu_usage': 45.2,
|
|
226
|
+
'memory_usage': 62.8,
|
|
227
|
+
'disk_usage': 38.5,
|
|
228
|
+
'network_in': '125 MB/s',
|
|
229
|
+
'network_out': '89 MB/s',
|
|
230
|
+
'response_time': '245 ms',
|
|
231
|
+
'uptime': '15 days, 3 hours',
|
|
232
|
+
}
|
|
233
|
+
except Exception as e:
|
|
234
|
+
self.logger.error(f"Error getting system metrics: {e}")
|
|
235
|
+
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
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dashboard URLs
|
|
3
|
+
|
|
4
|
+
RESTful API endpoints for dashboard data.
|
|
5
|
+
Follows the same pattern as tasks app.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from django.urls import include, path
|
|
9
|
+
from rest_framework.routers import DefaultRouter
|
|
10
|
+
|
|
11
|
+
from .api.viewsets import DashboardViewSet
|
|
12
|
+
|
|
13
|
+
app_name = 'dashboard'
|
|
14
|
+
|
|
15
|
+
# Main router for ViewSets
|
|
16
|
+
router = DefaultRouter()
|
|
17
|
+
router.register(r'', DashboardViewSet, basename='dashboard')
|
|
18
|
+
|
|
19
|
+
urlpatterns = [
|
|
20
|
+
# RESTful API endpoints using ViewSets
|
|
21
|
+
# Mounted at cfg/dashboard/api/
|
|
22
|
+
path('api/', include(router.urls)),
|
|
23
|
+
]
|
|
@@ -40,6 +40,11 @@ class NextJSStaticView(View):
|
|
|
40
40
|
if not path or path == '/':
|
|
41
41
|
path = 'index.html'
|
|
42
42
|
|
|
43
|
+
# Handle trailing slash (Next.js static export behavior)
|
|
44
|
+
# /private/ -> private.html
|
|
45
|
+
if path.endswith('/') and path != '/':
|
|
46
|
+
path = path.rstrip('/') + '.html'
|
|
47
|
+
|
|
43
48
|
# For routes without extension, try .html (Next.js static export behavior)
|
|
44
49
|
file_path = base_dir / path
|
|
45
50
|
if not file_path.exists() and not path.endswith('.html') and '.' not in Path(path).name:
|
django_cfg/apps/urls.py
CHANGED
|
@@ -84,7 +84,7 @@ def get_default_cfg_group():
|
|
|
84
84
|
name="cfg",
|
|
85
85
|
apps=get_enabled_cfg_apps(),
|
|
86
86
|
title="Django-CFG API",
|
|
87
|
-
description="Authentication (OTP), Support, Newsletter, Leads, Knowledge Base, AI Agents, Tasks, Payments",
|
|
87
|
+
description="Authentication (OTP), Support, Newsletter, Leads, Knowledge Base, AI Agents, Tasks, Payments, Dashboard",
|
|
88
88
|
version="1.0.0",
|
|
89
89
|
)
|
|
90
90
|
|
|
@@ -134,6 +134,7 @@ urlpatterns = [
|
|
|
134
134
|
path('cfg/commands/', include('django_cfg.apps.api.commands.urls')),
|
|
135
135
|
path('cfg/openapi/', include('django_cfg.modules.django_client.urls')),
|
|
136
136
|
path('cfg/admin/', include('django_cfg.apps.frontend.urls')), # Next.js admin panel
|
|
137
|
+
path('cfg/dashboard/', include('django_cfg.apps.dashboard.urls')), # Dashboard API
|
|
137
138
|
]
|
|
138
139
|
|
|
139
140
|
# Django-CFG apps - conditionally registered based on config
|
|
@@ -109,6 +109,7 @@ class InstalledAppsBuilder:
|
|
|
109
109
|
"django_cfg.modules.django_tailwind", # Universal Tailwind layouts
|
|
110
110
|
"django_cfg.apps.api.health",
|
|
111
111
|
"django_cfg.apps.api.commands",
|
|
112
|
+
"django_cfg.apps.dashboard", # Dashboard API
|
|
112
113
|
]
|
|
113
114
|
|
|
114
115
|
if self.config.enable_frontend:
|
|
@@ -59,16 +59,23 @@ class OpenAPI30Parser(BaseParser):
|
|
|
59
59
|
return True
|
|
60
60
|
|
|
61
61
|
# Check anyOf: [{"type": "X"}, {"type": "null"}] format (Pydantic)
|
|
62
|
+
# or anyOf: [{"$ref": "..."}, {"type": "null"}] format
|
|
62
63
|
if schema.anyOf and len(schema.anyOf) == 2:
|
|
63
|
-
|
|
64
|
+
has_null = False
|
|
65
|
+
has_actual_type = False
|
|
66
|
+
|
|
64
67
|
for item in schema.anyOf:
|
|
65
68
|
if not isinstance(item, SchemaObject):
|
|
66
69
|
continue
|
|
67
|
-
if item.base_type:
|
|
68
|
-
types.append(item.base_type)
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
if item.base_type == 'null':
|
|
72
|
+
has_null = True
|
|
73
|
+
elif item.base_type or item.ref:
|
|
74
|
+
# Has actual type (either base_type or $ref)
|
|
75
|
+
has_actual_type = True
|
|
76
|
+
|
|
77
|
+
# If one is null and another is actual type, it's nullable
|
|
78
|
+
if has_null and has_actual_type:
|
|
72
79
|
return True
|
|
73
80
|
|
|
74
81
|
return False
|
|
@@ -67,16 +67,23 @@ class OpenAPI31Parser(BaseParser):
|
|
|
67
67
|
return True
|
|
68
68
|
|
|
69
69
|
# Check anyOf: [{"type": "X"}, {"type": "null"}] format (Pydantic)
|
|
70
|
+
# or anyOf: [{"$ref": "..."}, {"type": "null"}] format
|
|
70
71
|
if schema.anyOf and len(schema.anyOf) == 2:
|
|
71
|
-
|
|
72
|
+
has_null = False
|
|
73
|
+
has_actual_type = False
|
|
74
|
+
|
|
72
75
|
for item in schema.anyOf:
|
|
73
76
|
if not isinstance(item, SchemaObject):
|
|
74
77
|
continue
|
|
75
|
-
if item.base_type:
|
|
76
|
-
types.append(item.base_type)
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
if item.base_type == 'null':
|
|
80
|
+
has_null = True
|
|
81
|
+
elif item.base_type or item.ref:
|
|
82
|
+
# Has actual type (either base_type or $ref)
|
|
83
|
+
has_actual_type = True
|
|
84
|
+
|
|
85
|
+
# If one is null and another is actual type, it's nullable
|
|
86
|
+
if has_null and has_actual_type:
|
|
80
87
|
return True
|
|
81
88
|
|
|
82
89
|
return False
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.84"
|
|
8
8
|
description = "Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
|