django-cfg 1.4.84__py3-none-any.whl → 1.4.85__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (86) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/serializers/__init__.py +55 -0
  3. django_cfg/apps/dashboard/serializers/activity.py +38 -0
  4. django_cfg/apps/dashboard/serializers/apizones.py +26 -0
  5. django_cfg/apps/dashboard/serializers/base.py +16 -0
  6. django_cfg/apps/dashboard/serializers/charts.py +44 -0
  7. django_cfg/apps/dashboard/serializers/commands.py +26 -0
  8. django_cfg/apps/dashboard/serializers/overview.py +34 -0
  9. django_cfg/apps/dashboard/serializers/statistics.py +46 -0
  10. django_cfg/apps/dashboard/serializers/system.py +58 -0
  11. django_cfg/apps/dashboard/services/__init__.py +10 -1
  12. django_cfg/apps/dashboard/services/apizones_service.py +119 -0
  13. django_cfg/apps/dashboard/services/charts_service.py +266 -0
  14. django_cfg/apps/dashboard/services/commands_service.py +142 -0
  15. django_cfg/apps/dashboard/services/statistics_service.py +262 -104
  16. django_cfg/apps/dashboard/urls.py +25 -6
  17. django_cfg/apps/dashboard/views/__init__.py +23 -0
  18. django_cfg/apps/dashboard/views/activity_views.py +83 -0
  19. django_cfg/apps/dashboard/views/apizones_views.py +73 -0
  20. django_cfg/apps/dashboard/views/charts_views.py +159 -0
  21. django_cfg/apps/dashboard/views/commands_views.py +73 -0
  22. django_cfg/apps/dashboard/views/overview_views.py +92 -0
  23. django_cfg/apps/dashboard/views/statistics_views.py +105 -0
  24. django_cfg/apps/dashboard/views/system_views.py +73 -0
  25. django_cfg/modules/django_unfold/callbacks/main.py +7 -6
  26. django_cfg/modules/django_unfold/dashboard.py +1 -36
  27. django_cfg/modules/django_unfold/models/config.py +102 -73
  28. django_cfg/modules/django_unfold/tailwind.py +31 -79
  29. django_cfg/pyproject.toml +1 -1
  30. django_cfg/static/frontend/admin/404.html +1 -1
  31. django_cfg/static/frontend/admin/500.html +1 -1
  32. django_cfg/static/frontend/admin/_next/static/BembwiEtlu4eFl3OX7n1k/_buildManifest.js +1 -0
  33. django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +1 -0
  34. django_cfg/static/frontend/admin/_next/static/chunks/{20695.a7d37b6c40ad3f58.js → 43076.55dd23b6cd68edb0.js} +1 -1
  35. django_cfg/static/frontend/admin/_next/static/chunks/50314-3b9d15242191c8bc.js +1 -0
  36. django_cfg/static/frontend/admin/_next/static/chunks/{64330.2ef79bccd7d4e363.js → 64330.41858e98c0e5173b.js} +1 -1
  37. django_cfg/static/frontend/admin/_next/static/chunks/6766.8d01e44e83070e83.js +1 -0
  38. django_cfg/static/frontend/admin/_next/static/chunks/{96168.eb7fdb721b9cdb00.js → 96168.b7197f890097df6e.js} +1 -1
  39. django_cfg/static/frontend/admin/_next/static/chunks/pages/{404-c283223d1afd02a2.js → 404-cf71cd7b3cb005e5.js} +1 -1
  40. django_cfg/static/frontend/admin/_next/static/chunks/pages/{500-389d6d3e1f2f7fda.js → 500-ff19c7842e3df415.js} +1 -1
  41. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f62e5528fbcbb6b3.js +272 -0
  42. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_error-5291033275c26d09.js → _error-87f3fdc2aa131e77.js} +1 -1
  43. django_cfg/static/frontend/admin/_next/static/chunks/pages/{index-d7bc30185f52cbca.js → index-69f737d4802cc5b7.js} +1 -1
  44. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-f24beb6ed3955aa8.js +1 -0
  45. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{profile-e93a65e8e7d9022b.js → profile-b8045f993287f1a7.js} +1 -1
  46. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{ui-669e8f2a785beba2.js → ui-373fff8b42878e64.js} +1 -1
  47. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +1 -0
  48. django_cfg/static/frontend/admin/_next/static/chunks/{webpack-92add5f95c66e349.js → webpack-7c456a65e96eb97e.js} +1 -1
  49. django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +3 -0
  50. django_cfg/static/frontend/admin/auth.html +1 -1
  51. django_cfg/static/frontend/admin/index.html +1 -1
  52. django_cfg/static/frontend/admin/legal/cookies.html +1 -1
  53. django_cfg/static/frontend/admin/legal/privacy.html +1 -1
  54. django_cfg/static/frontend/admin/legal/security.html +1 -1
  55. django_cfg/static/frontend/admin/legal/terms.html +1 -1
  56. django_cfg/static/frontend/admin/private/centrifugo.html +1 -1
  57. django_cfg/static/frontend/admin/private/profile.html +1 -1
  58. django_cfg/static/frontend/admin/private/ui.html +1 -1
  59. django_cfg/static/frontend/admin/private.html +1 -1
  60. django_cfg/templates/admin/index.html +237 -5
  61. django_cfg/templates/admin/sections/commands_section.html +5 -0
  62. django_cfg/templates/admin/sections/documentation_section.html +5 -0
  63. django_cfg/templates/admin/sections/overview_section.html +5 -0
  64. django_cfg/templates/admin/sections/stats_section.html +5 -0
  65. django_cfg/templates/admin/sections/system_section.html +5 -0
  66. django_cfg/templates/admin/sections/widgets_section.html +11 -0
  67. django_cfg/templates/unfold/layouts/skeleton.html +27 -0
  68. django_cfg/templatetags/django_cfg.py +53 -0
  69. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/METADATA +1 -1
  70. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/RECORD +74 -51
  71. django_cfg/apps/dashboard/api/__init__.py +0 -27
  72. django_cfg/apps/dashboard/api/serializers.py +0 -165
  73. django_cfg/apps/dashboard/api/viewsets.py +0 -257
  74. django_cfg/static/frontend/admin/_next/static/-Zk0eDB7OJOEFrFyR5BwZ/_buildManifest.js +0 -1
  75. django_cfg/static/frontend/admin/_next/static/chunks/43076-4be6a9794e9c3e8b.js +0 -1
  76. django_cfg/static/frontend/admin/_next/static/chunks/50314-79c02212788f1ec7.js +0 -1
  77. django_cfg/static/frontend/admin/_next/static/chunks/6766.d62fed7cd4761148.js +0 -1
  78. django_cfg/static/frontend/admin/_next/static/chunks/82296-a2c8d38f62224be5.js +0 -1
  79. django_cfg/static/frontend/admin/_next/static/chunks/pages/_app-f25bec36bbdc9625.js +0 -272
  80. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/centrifugo-22532c65971225eb.js +0 -1
  81. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-a8a9ba76f2c75354.js +0 -1
  82. django_cfg/static/frontend/admin/_next/static/css/78d677ac1677c210.css +0 -3
  83. /django_cfg/static/frontend/admin/_next/static/{-Zk0eDB7OJOEFrFyR5BwZ → BembwiEtlu4eFl3OX7n1k}/_ssgManifest.js +0 -0
  84. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/WHEEL +0 -0
  85. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/entry_points.txt +0 -0
  86. {django_cfg-1.4.84.dist-info → django_cfg-1.4.85.dist-info}/licenses/LICENSE +0 -0
@@ -2,13 +2,17 @@
2
2
  Statistics Service
3
3
 
4
4
  Collects and aggregates statistics for dashboard display.
5
- No ORM dependencies - can work with any data source.
5
+ Uses Django ORM for real data collection.
6
6
  """
7
7
 
8
8
  import logging
9
9
  from datetime import datetime, timedelta
10
10
  from typing import Any, Dict, List, Optional
11
11
 
12
+ from django.apps import apps
13
+ from django.contrib.auth import get_user_model
14
+ from django.utils import timezone
15
+
12
16
  logger = logging.getLogger(__name__)
13
17
 
14
18
 
@@ -17,7 +21,7 @@ class StatisticsService:
17
21
  Service for collecting dashboard statistics.
18
22
 
19
23
  %%PRIORITY:HIGH%%
20
- %%AI_HINT: This service collects data from various sources without ORM%%
24
+ %%AI_HINT: This service collects data from various Django models using ORM%%
21
25
 
22
26
  TAGS: statistics, dashboard, service
23
27
  """
@@ -26,6 +30,10 @@ class StatisticsService:
26
30
  """Initialize statistics service."""
27
31
  self.logger = logger
28
32
 
33
+ def _get_user_model(self):
34
+ """Get the user model safely."""
35
+ return get_user_model()
36
+
29
37
  def get_user_statistics(self) -> Dict[str, Any]:
30
38
  """
31
39
  Get user-related statistics.
@@ -33,23 +41,27 @@ class StatisticsService:
33
41
  Returns:
34
42
  Dictionary containing user stats:
35
43
  - total_users: Total number of users
36
- - active_users: Number of active users (last 30 days)
44
+ - active_users: Number of active users
37
45
  - new_users: New users in last 7 days
38
46
  - superusers: Number of superusers
39
47
 
40
- %%AI_HINT: Currently returns mock data. Replace with real data source%%
48
+ %%AI_HINT: Uses real Django ORM queries from User model%%
41
49
  """
42
50
  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()
51
+ User = self._get_user_model()
52
+
53
+ total_users = User.objects.count()
54
+ active_users = User.objects.filter(is_active=True).count()
55
+ new_users_7d = User.objects.filter(
56
+ date_joined__gte=timezone.now() - timedelta(days=7)
57
+ ).count()
58
+ superusers = User.objects.filter(is_superuser=True).count()
47
59
 
48
60
  return {
49
- 'total_users': 1250,
50
- 'active_users': 892,
51
- 'new_users': 45,
52
- 'superusers': 3,
61
+ 'total_users': total_users,
62
+ 'active_users': active_users,
63
+ 'new_users': new_users_7d,
64
+ 'superusers': superusers,
53
65
  }
54
66
  except Exception as e:
55
67
  self.logger.error(f"Error getting user statistics: {e}")
@@ -61,48 +73,93 @@ class StatisticsService:
61
73
  'error': str(e)
62
74
  }
63
75
 
64
- def get_app_statistics(self) -> Dict[str, Dict[str, int]]:
76
+ def get_app_statistics(self) -> Dict[str, Any]:
65
77
  """
66
78
  Get application-specific statistics.
67
79
 
68
80
  Returns:
69
- Dictionary with app names as keys and their stats as values.
70
- Example:
81
+ Dictionary with aggregated app statistics.
71
82
  {
72
- 'leads': {'total': 450, 'active': 120},
73
- 'tasks': {'total': 1200, 'completed': 890},
74
- ...
83
+ 'apps': {app_label: {...stats...}},
84
+ 'total_records': int,
85
+ 'total_models': int,
86
+ 'total_apps': int
75
87
  }
76
88
 
77
- %%AI_HINT: Aggregates statistics from different django-cfg apps%%
89
+ %%AI_HINT: Real app introspection using Django apps registry%%
78
90
  """
79
91
  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
- }
92
+ stats = {"apps": {}, "total_records": 0, "total_models": 0, "total_apps": 0}
93
+
94
+ # Get all installed apps
95
+ for app_config in apps.get_app_configs():
96
+ app_label = app_config.label
97
+
98
+ # Skip system apps
99
+ if app_label in ["admin", "contenttypes", "sessions", "auth"]:
100
+ continue
101
+
102
+ app_stats = self._get_app_stats(app_label)
103
+ if app_stats:
104
+ stats["apps"][app_label] = app_stats
105
+ stats["total_records"] += app_stats.get("total_records", 0)
106
+ stats["total_models"] += app_stats.get("model_count", 0)
107
+ stats["total_apps"] += 1
108
+
109
+ return stats
103
110
  except Exception as e:
104
111
  self.logger.error(f"Error getting app statistics: {e}")
105
- return {'error': str(e)}
112
+ return {'apps': {}, 'total_records': 0, 'total_models': 0, 'total_apps': 0, 'error': str(e)}
113
+
114
+ def _get_app_stats(self, app_label: str) -> Optional[Dict[str, Any]]:
115
+ """Get statistics for a specific app."""
116
+ try:
117
+ app_config = apps.get_app_config(app_label)
118
+ # Convert generator to list to avoid len() error
119
+ models_list = list(app_config.get_models())
120
+
121
+ if not models_list:
122
+ return None
123
+
124
+ app_stats = {
125
+ "name": app_config.verbose_name or app_label.title(),
126
+ "models": {},
127
+ "total_records": 0,
128
+ "model_count": len(models_list),
129
+ }
130
+
131
+ for model in models_list:
132
+ try:
133
+ # Get model statistics
134
+ model_stats = self._get_model_stats(model)
135
+ if model_stats:
136
+ app_stats["models"][model._meta.model_name] = model_stats
137
+ app_stats["total_records"] += model_stats.get("count", 0)
138
+ except Exception:
139
+ continue
140
+
141
+ return app_stats
142
+
143
+ except Exception:
144
+ return None
145
+
146
+ def _get_model_stats(self, model) -> Optional[Dict[str, Any]]:
147
+ """Get statistics for a specific model."""
148
+ try:
149
+ # Get basic model info
150
+ model_stats = {
151
+ "name": model._meta.verbose_name_plural
152
+ or model._meta.verbose_name
153
+ or model._meta.model_name,
154
+ "count": model.objects.count(),
155
+ "fields_count": len(model._meta.fields),
156
+ "admin_url": f"admin:{model._meta.app_label}_{model._meta.model_name}_changelist",
157
+ }
158
+
159
+ return model_stats
160
+
161
+ except Exception:
162
+ return None
106
163
 
107
164
  def get_stat_cards(self) -> List[Dict[str, Any]]:
108
165
  """
@@ -113,42 +170,59 @@ class StatisticsService:
113
170
  Each card contains: title, value, icon, change, change_type
114
171
 
115
172
  USED_BY: DashboardViewSet.overview endpoint
173
+ %%AI_HINT: Real data from User model with calculated changes%%
116
174
  """
117
175
  try:
118
176
  user_stats = self.get_user_statistics()
119
177
 
178
+ total_users = user_stats['total_users']
179
+ active_users = user_stats['active_users']
180
+ new_users_7d = user_stats['new_users']
181
+ superusers = user_stats['superusers']
182
+
120
183
  cards = [
121
184
  {
122
185
  'title': 'Total Users',
123
- 'value': str(user_stats['total_users']),
186
+ 'value': f"{total_users:,}",
124
187
  'icon': 'people',
125
- 'change': '+12%',
126
- 'change_type': 'positive',
188
+ 'change': f"+{new_users_7d}" if new_users_7d > 0 else None,
189
+ 'change_type': 'positive' if new_users_7d > 0 else 'neutral',
127
190
  'color': 'primary',
191
+ 'description': 'Registered users',
128
192
  },
129
193
  {
130
194
  'title': 'Active Users',
131
- 'value': str(user_stats['active_users']),
195
+ 'value': f"{active_users:,}",
132
196
  'icon': 'person',
133
- 'change': '+5%',
134
- 'change_type': 'positive',
197
+ 'change': (
198
+ f"{(active_users/total_users*100):.1f}%"
199
+ if total_users > 0
200
+ else "0%"
201
+ ),
202
+ 'change_type': (
203
+ 'positive' if active_users > total_users * 0.7 else 'neutral'
204
+ ),
135
205
  'color': 'success',
206
+ 'description': 'Currently active',
136
207
  },
137
208
  {
138
- 'title': 'New Users (7d)',
139
- 'value': str(user_stats['new_users']),
209
+ 'title': 'New This Week',
210
+ 'value': f"{new_users_7d:,}",
140
211
  'icon': 'person_add',
141
- 'change': '-2%',
142
- 'change_type': 'negative',
143
- 'color': 'warning',
212
+ 'change_type': 'positive' if new_users_7d > 0 else 'neutral',
213
+ 'color': 'info',
214
+ 'description': 'Last 7 days',
144
215
  },
145
216
  {
146
- 'title': 'System Health',
147
- 'value': '98%',
148
- 'icon': 'health_and_safety',
149
- 'change': '+1%',
150
- 'change_type': 'positive',
151
- 'color': 'success',
217
+ 'title': 'Superusers',
218
+ 'value': f"{superusers:,}",
219
+ 'icon': 'admin_panel_settings',
220
+ 'change': (
221
+ f"{(superusers/total_users*100):.1f}%" if total_users > 0 else "0%"
222
+ ),
223
+ 'change_type': 'neutral',
224
+ 'color': 'warning',
225
+ 'description': 'Administrative access',
152
226
  },
153
227
  ]
154
228
 
@@ -168,48 +242,96 @@ class StatisticsService:
168
242
  Returns:
169
243
  List of recent activity entries
170
244
 
171
- %%AI_HINT: Can be connected to django-auditlog or custom activity tracking%%
245
+ %%AI_HINT: Returns placeholder - connect to django-auditlog when available%%
172
246
  """
173
247
  try:
174
- # TODO: Connect to real activity logging system
175
- now = datetime.now()
248
+ # Note: This is a placeholder. Real implementation should connect to:
249
+ # - django-auditlog (if installed)
250
+ # - Custom activity/history tracking
251
+ # - Django admin logs (LogEntry model)
176
252
 
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
- ]
253
+ # Try to get Django admin logs as fallback
254
+ from django.contrib.admin.models import LogEntry
255
+
256
+ activities = []
257
+ log_entries = LogEntry.objects.select_related('user', 'content_type').order_by('-action_time')[:limit]
206
258
 
207
- return activities[:limit]
259
+ for entry in log_entries:
260
+ activities.append({
261
+ 'id': entry.pk,
262
+ 'user': entry.user.get_username() if entry.user else 'System',
263
+ 'action': entry.get_action_flag_display().lower(),
264
+ 'resource': str(entry),
265
+ 'timestamp': entry.action_time.isoformat(),
266
+ 'icon': self._get_activity_icon(entry.action_flag),
267
+ 'color': self._get_activity_color(entry.action_flag),
268
+ })
269
+
270
+ return activities
208
271
 
209
272
  except Exception as e:
210
273
  self.logger.error(f"Error getting recent activity: {e}")
211
274
  return []
212
275
 
276
+ def _get_activity_icon(self, action_flag: int) -> str:
277
+ """Get icon for activity based on action flag."""
278
+ icons = {
279
+ 1: 'add_circle', # ADDITION
280
+ 2: 'edit', # CHANGE
281
+ 3: 'delete', # DELETION
282
+ }
283
+ return icons.get(action_flag, 'circle')
284
+
285
+ def _get_activity_color(self, action_flag: int) -> str:
286
+ """Get color for activity based on action flag."""
287
+ colors = {
288
+ 1: 'success', # ADDITION
289
+ 2: 'primary', # CHANGE
290
+ 3: 'error', # DELETION
291
+ }
292
+ return colors.get(action_flag, 'default')
293
+
294
+ def get_recent_users(self, limit: int = 10) -> List[Dict[str, Any]]:
295
+ """
296
+ Get recent users data.
297
+
298
+ Args:
299
+ limit: Maximum number of users to return
300
+
301
+ Returns:
302
+ List of recent user dictionaries with admin URLs
303
+
304
+ %%AI_HINT: Real data from User model with admin URLs%%
305
+ """
306
+ try:
307
+ User = self._get_user_model()
308
+ recent_users = User.objects.select_related().order_by("-date_joined")[:limit]
309
+
310
+ return [
311
+ {
312
+ "id": user.id,
313
+ "username": user.username,
314
+ "email": user.email or "No email",
315
+ "date_joined": (
316
+ user.date_joined.strftime("%Y-%m-%d %H:%M")
317
+ if user.date_joined
318
+ else "Unknown"
319
+ ),
320
+ "is_active": user.is_active,
321
+ "is_staff": user.is_staff,
322
+ "is_superuser": user.is_superuser,
323
+ "last_login": (
324
+ user.last_login.strftime("%Y-%m-%d %H:%M")
325
+ if user.last_login
326
+ else None
327
+ ),
328
+ }
329
+ for user in recent_users
330
+ ]
331
+ except Exception as e:
332
+ self.logger.error(f"Error getting recent users: {e}")
333
+ return []
334
+
213
335
  def get_system_metrics(self) -> Dict[str, Any]:
214
336
  """
215
337
  Get system performance metrics.
@@ -217,19 +339,55 @@ class StatisticsService:
217
339
  Returns:
218
340
  Dictionary with system metrics (CPU, memory, disk, etc.)
219
341
 
220
- %%AI_HINT: Can use psutil or similar for real system metrics%%
342
+ %%AI_HINT: Uses psutil for real system metrics when available%%
221
343
  """
222
344
  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
- }
345
+ # Try to use psutil for real system metrics
346
+ try:
347
+ import psutil
348
+ import time
349
+
350
+ # Get CPU usage
351
+ cpu_percent = psutil.cpu_percent(interval=0.1)
352
+
353
+ # Get memory usage
354
+ memory = psutil.virtual_memory()
355
+ memory_percent = memory.percent
356
+
357
+ # Get disk usage for root partition
358
+ disk = psutil.disk_usage('/')
359
+ disk_percent = disk.percent
360
+
361
+ # Get network IO
362
+ net_io = psutil.net_io_counters()
363
+
364
+ # Get system uptime
365
+ boot_time = psutil.boot_time()
366
+ uptime_seconds = time.time() - boot_time
367
+ uptime_days = int(uptime_seconds // 86400)
368
+ uptime_hours = int((uptime_seconds % 86400) // 3600)
369
+
370
+ return {
371
+ 'cpu_usage': round(cpu_percent, 1),
372
+ 'memory_usage': round(memory_percent, 1),
373
+ 'disk_usage': round(disk_percent, 1),
374
+ 'network_in': f"{net_io.bytes_recv / (1024**2):.1f} MB",
375
+ 'network_out': f"{net_io.bytes_sent / (1024**2):.1f} MB",
376
+ 'response_time': '< 100ms', # Placeholder - can be calculated from middleware
377
+ 'uptime': f"{uptime_days} days, {uptime_hours} hours",
378
+ }
379
+ except ImportError:
380
+ self.logger.warning("psutil not installed, returning placeholder metrics")
381
+ # Fallback to placeholder data if psutil not available
382
+ return {
383
+ 'cpu_usage': 0,
384
+ 'memory_usage': 0,
385
+ 'disk_usage': 0,
386
+ 'network_in': 'N/A',
387
+ 'network_out': 'N/A',
388
+ 'response_time': 'N/A',
389
+ 'uptime': 'N/A',
390
+ }
233
391
  except Exception as e:
234
392
  self.logger.error(f"Error getting system metrics: {e}")
235
393
  return {'error': str(e)}
@@ -1,23 +1,42 @@
1
1
  """
2
2
  Dashboard URLs
3
3
 
4
- RESTful API endpoints for dashboard data.
5
- Follows the same pattern as tasks app.
4
+ RESTful API endpoints for dashboard data organized by domain.
5
+
6
+ API Structure:
7
+ - /api/overview/ - Complete dashboard overview
8
+ - /api/statistics/ - Statistics endpoints (cards, users, apps)
9
+ - /api/system/ - System monitoring (health, metrics)
10
+ - /api/activity/ - Activity tracking (recent, actions)
6
11
  """
7
12
 
8
13
  from django.urls import include, path
9
14
  from rest_framework.routers import DefaultRouter
10
15
 
11
- from .api.viewsets import DashboardViewSet
16
+ from .views import (
17
+ OverviewViewSet,
18
+ StatisticsViewSet,
19
+ SystemViewSet,
20
+ ActivityViewSet,
21
+ ChartsViewSet,
22
+ CommandsViewSet,
23
+ APIZonesViewSet,
24
+ )
12
25
 
13
- app_name = 'dashboard'
26
+ app_name = 'django_cfg_dashboard'
14
27
 
15
28
  # Main router for ViewSets
16
29
  router = DefaultRouter()
17
- router.register(r'', DashboardViewSet, basename='dashboard')
30
+ router.register(r'overview', OverviewViewSet, basename='overview')
31
+ router.register(r'statistics', StatisticsViewSet, basename='statistics')
32
+ router.register(r'system', SystemViewSet, basename='system')
33
+ router.register(r'activity', ActivityViewSet, basename='activity')
34
+ router.register(r'charts', ChartsViewSet, basename='charts')
35
+ router.register(r'commands', CommandsViewSet, basename='commands')
36
+ router.register(r'zones', APIZonesViewSet, basename='zones')
18
37
 
19
38
  urlpatterns = [
20
39
  # RESTful API endpoints using ViewSets
21
- # Mounted at cfg/dashboard/api/
40
+ # Mounted at /cfg/dashboard/api/
22
41
  path('api/', include(router.urls)),
23
42
  ]
@@ -0,0 +1,23 @@
1
+ """
2
+ Dashboard Views
3
+
4
+ ViewSets organized by domain for better maintainability.
5
+ """
6
+
7
+ from .overview_views import OverviewViewSet
8
+ from .statistics_views import StatisticsViewSet
9
+ from .system_views import SystemViewSet
10
+ from .activity_views import ActivityViewSet
11
+ from .charts_views import ChartsViewSet
12
+ from .commands_views import CommandsViewSet
13
+ from .apizones_views import APIZonesViewSet
14
+
15
+ __all__ = [
16
+ 'OverviewViewSet',
17
+ 'StatisticsViewSet',
18
+ 'SystemViewSet',
19
+ 'ActivityViewSet',
20
+ 'ChartsViewSet',
21
+ 'CommandsViewSet',
22
+ 'APIZonesViewSet',
23
+ ]
@@ -0,0 +1,83 @@
1
+ """
2
+ Activity ViewSet
3
+
4
+ Endpoints for activity tracking:
5
+ - GET /activity/recent/ - Recent activity entries
6
+ - GET /activity/actions/ - Quick action buttons
7
+ """
8
+
9
+ import logging
10
+
11
+ from drf_spectacular.utils import OpenApiParameter, extend_schema
12
+ from rest_framework import status, viewsets
13
+ from rest_framework.authentication import BasicAuthentication, SessionAuthentication
14
+ from rest_framework.decorators import action
15
+ from rest_framework.permissions import IsAdminUser
16
+ from rest_framework.response import Response
17
+
18
+ from ..services import StatisticsService, SystemHealthService
19
+ from ..serializers import ActivityEntrySerializer, QuickActionSerializer
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class ActivityViewSet(viewsets.GenericViewSet):
25
+ """
26
+ Activity Tracking ViewSet
27
+
28
+ Provides endpoints for recent activity and quick actions.
29
+ """
30
+
31
+ authentication_classes = [SessionAuthentication, BasicAuthentication]
32
+ permission_classes = [IsAdminUser]
33
+ serializer_class = ActivityEntrySerializer
34
+
35
+ @extend_schema(
36
+ summary="Get recent activity",
37
+ description="Retrieve recent system activity entries",
38
+ parameters=[
39
+ OpenApiParameter(
40
+ name='limit',
41
+ description='Maximum number of entries to return',
42
+ required=False,
43
+ type=int,
44
+ default=10
45
+ ),
46
+ ],
47
+ responses=ActivityEntrySerializer(many=True),
48
+ tags=["Dashboard - Activity"]
49
+ )
50
+ @action(detail=False, methods=['get'], url_path='recent', pagination_class=None, serializer_class=ActivityEntrySerializer)
51
+ def recent(self, request):
52
+ """Get recent activity entries."""
53
+ try:
54
+ limit = int(request.query_params.get('limit', 10))
55
+ stats_service = StatisticsService()
56
+ activity = stats_service.get_recent_activity(limit=limit)
57
+ return Response(activity)
58
+
59
+ except Exception as e:
60
+ logger.error(f"Recent activity API error: {e}")
61
+ return Response({
62
+ 'error': str(e)
63
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
64
+
65
+ @extend_schema(
66
+ summary="Get quick actions",
67
+ description="Retrieve quick action buttons for dashboard",
68
+ responses=QuickActionSerializer(many=True),
69
+ tags=["Dashboard - Activity"]
70
+ )
71
+ @action(detail=False, methods=['get'], url_path='actions', pagination_class=None, serializer_class=QuickActionSerializer)
72
+ def actions(self, request):
73
+ """Get quick action buttons."""
74
+ try:
75
+ health_service = SystemHealthService()
76
+ actions = health_service.get_quick_actions()
77
+ return Response(actions)
78
+
79
+ except Exception as e:
80
+ logger.error(f"Quick actions API error: {e}")
81
+ return Response({
82
+ 'error': str(e)
83
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)