django-cfg 1.2.7__py3-none-any.whl → 1.2.9__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.
Files changed (45) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/urls.py +2 -2
  3. django_cfg/modules/django_unfold/callbacks/__init__.py +9 -0
  4. django_cfg/modules/django_unfold/callbacks/actions.py +50 -0
  5. django_cfg/modules/django_unfold/callbacks/base.py +98 -0
  6. django_cfg/modules/django_unfold/callbacks/charts.py +224 -0
  7. django_cfg/modules/django_unfold/callbacks/commands.py +40 -0
  8. django_cfg/modules/django_unfold/callbacks/main.py +191 -0
  9. django_cfg/modules/django_unfold/callbacks/revolution.py +76 -0
  10. django_cfg/modules/django_unfold/callbacks/statistics.py +240 -0
  11. django_cfg/modules/django_unfold/callbacks/system.py +180 -0
  12. django_cfg/modules/django_unfold/callbacks/users.py +65 -0
  13. django_cfg/modules/django_unfold/models/config.py +10 -3
  14. django_cfg/modules/django_unfold/tailwind.py +68 -0
  15. django_cfg/templates/admin/components/action_grid.html +49 -0
  16. django_cfg/templates/admin/components/card.html +50 -0
  17. django_cfg/templates/admin/components/data_table.html +67 -0
  18. django_cfg/templates/admin/components/metric_card.html +39 -0
  19. django_cfg/templates/admin/components/modal.html +58 -0
  20. django_cfg/templates/admin/components/progress_bar.html +25 -0
  21. django_cfg/templates/admin/components/section_header.html +26 -0
  22. django_cfg/templates/admin/components/stat_item.html +32 -0
  23. django_cfg/templates/admin/components/stats_grid.html +72 -0
  24. django_cfg/templates/admin/components/status_badge.html +28 -0
  25. django_cfg/templates/admin/components/user_avatar.html +27 -0
  26. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +7 -7
  27. django_cfg/templates/admin/snippets/components/activity_tracker.html +48 -11
  28. django_cfg/templates/admin/snippets/components/charts_section.html +63 -13
  29. django_cfg/templates/admin/snippets/components/django_commands.html +196 -72
  30. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -47
  31. django_cfg/templates/admin/snippets/components/recent_activity.html +28 -38
  32. django_cfg/templates/admin/snippets/components/recent_users_table.html +22 -53
  33. django_cfg/templates/admin/snippets/components/stats_cards.html +2 -66
  34. django_cfg/templates/admin/snippets/components/system_health.html +13 -63
  35. django_cfg/templates/admin/snippets/components/system_metrics.html +8 -25
  36. django_cfg/templates/admin/snippets/tabs/commands_tab.html +1 -1
  37. django_cfg/templates/admin/snippets/tabs/overview_tab.html +4 -4
  38. django_cfg/templates/admin/snippets/zones/zones_table.html +12 -33
  39. django_cfg/templatetags/django_cfg.py +2 -1
  40. {django_cfg-1.2.7.dist-info → django_cfg-1.2.9.dist-info}/METADATA +2 -1
  41. {django_cfg-1.2.7.dist-info → django_cfg-1.2.9.dist-info}/RECORD +44 -24
  42. django_cfg/modules/django_unfold/callbacks.py +0 -795
  43. {django_cfg-1.2.7.dist-info → django_cfg-1.2.9.dist-info}/WHEEL +0 -0
  44. {django_cfg-1.2.7.dist-info → django_cfg-1.2.9.dist-info}/entry_points.txt +0 -0
  45. {django_cfg-1.2.7.dist-info → django_cfg-1.2.9.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.2.7"
35
+ __version__ = "1.2.9"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
django_cfg/apps/urls.py CHANGED
@@ -45,8 +45,8 @@ def get_django_cfg_urlpatterns() -> List[URLPattern]:
45
45
  # patterns.append(path('leads/', include('django_cfg.apps.leads.urls')))
46
46
 
47
47
  # Tasks app - enabled when knowbase or agents are enabled
48
- if base_module.is_tasks_enabled():
49
- patterns.append(path('tasks/', include('django_cfg.apps.tasks.urls')))
48
+ # if base_module.is_tasks_enabled():
49
+ # patterns.append(path('tasks/', include('django_cfg.apps.tasks.urls')))
50
50
 
51
51
  except Exception:
52
52
  # Fallback: include all URLs if config is not available
@@ -0,0 +1,9 @@
1
+ """
2
+ Django CFG Unfold Callbacks Module
3
+
4
+ Modular callback system for Django Unfold dashboard.
5
+ """
6
+
7
+ from .main import UnfoldCallbacks
8
+
9
+ __all__ = ['UnfoldCallbacks']
@@ -0,0 +1,50 @@
1
+ """
2
+ Quick actions callbacks.
3
+ """
4
+
5
+ import logging
6
+ from typing import List
7
+
8
+ from ..models.dashboard import QuickAction
9
+ from ..icons import Icons
10
+ from .base import get_user_admin_urls
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class ActionsCallbacks:
16
+ """Quick actions callbacks."""
17
+
18
+ def get_quick_actions(self) -> List[QuickAction]:
19
+ """Get quick action buttons as Pydantic models."""
20
+ # Get user admin URLs dynamically based on AUTH_USER_MODEL
21
+ user_admin_urls = get_user_admin_urls()
22
+
23
+ actions = [
24
+ QuickAction(
25
+ title="Add User",
26
+ description="Create new user account",
27
+ icon=Icons.PERSON_ADD,
28
+ link=user_admin_urls["add"],
29
+ color="primary",
30
+ category="admin",
31
+ ),
32
+ QuickAction(
33
+ title="Support Tickets",
34
+ description="Manage support tickets",
35
+ icon=Icons.SUPPORT_AGENT,
36
+ link="admin:django_cfg_support_ticket_changelist",
37
+ color="primary",
38
+ category="support",
39
+ ),
40
+ QuickAction(
41
+ title="Health Check",
42
+ description="System health status",
43
+ icon=Icons.HEALTH_AND_SAFETY,
44
+ link="/cfg/health/",
45
+ color="success",
46
+ category="system",
47
+ ),
48
+ ]
49
+
50
+ return actions
@@ -0,0 +1,98 @@
1
+ """
2
+ Base utilities and helper functions for callbacks.
3
+ """
4
+
5
+ import logging
6
+ from typing import Dict, Any, List
7
+ from django.contrib.auth import get_user_model
8
+ from django.core.management import get_commands
9
+ import importlib
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def get_available_commands():
15
+ """Get all available Django management commands."""
16
+ commands_dict = get_commands()
17
+ commands_list = []
18
+
19
+ for command_name, app_name in commands_dict.items():
20
+ try:
21
+ # Try to get command description
22
+ if app_name == 'django_cfg':
23
+ module_path = f'django_cfg.management.commands.{command_name}'
24
+ else:
25
+ module_path = f'{app_name}.management.commands.{command_name}'
26
+
27
+ try:
28
+ command_module = importlib.import_module(module_path)
29
+ if hasattr(command_module, 'Command'):
30
+ command_class = command_module.Command
31
+ description = getattr(command_class, 'help', f'{command_name} command')
32
+ else:
33
+ description = f'{command_name} command'
34
+ except ImportError:
35
+ description = f'{command_name} command'
36
+
37
+ commands_list.append({
38
+ 'name': command_name,
39
+ 'app': app_name,
40
+ 'description': description,
41
+ 'is_core': app_name.startswith('django.'),
42
+ 'is_custom': app_name == 'django_cfg',
43
+ })
44
+ except Exception:
45
+ # Skip problematic commands
46
+ continue
47
+
48
+ return commands_list
49
+
50
+
51
+ def get_commands_by_category():
52
+ """Get commands categorized by type."""
53
+ commands = get_available_commands()
54
+
55
+ categorized = {
56
+ 'django_cfg': [],
57
+ 'django_core': [],
58
+ 'third_party': [],
59
+ 'project': [],
60
+ }
61
+
62
+ for cmd in commands:
63
+ if cmd['app'] == 'django_cfg':
64
+ categorized['django_cfg'].append(cmd)
65
+ elif cmd['app'].startswith('django.'):
66
+ categorized['django_core'].append(cmd)
67
+ elif cmd['app'].startswith(('src.', 'api.', 'accounts.')):
68
+ categorized['project'].append(cmd)
69
+ else:
70
+ categorized['third_party'].append(cmd)
71
+
72
+ return categorized
73
+
74
+
75
+ def get_user_admin_urls():
76
+ """Get admin URLs for user model."""
77
+ try:
78
+ User = get_user_model()
79
+
80
+ app_label = User._meta.app_label
81
+ model_name = User._meta.model_name
82
+
83
+ return {
84
+ 'changelist': f'admin:{app_label}_{model_name}_changelist',
85
+ 'add': f'admin:{app_label}_{model_name}_add',
86
+ 'change': f'admin:{app_label}_{model_name}_change/{{id}}/',
87
+ 'delete': f'admin:{app_label}_{model_name}_delete/{{id}}/',
88
+ 'view': f'admin:{app_label}_{model_name}_view/{{id}}/',
89
+ }
90
+ except Exception:
91
+ # Universal fallback - return admin index for all actions
92
+ return {
93
+ 'changelist': 'admin:index',
94
+ 'add': 'admin:index',
95
+ 'change': 'admin:index',
96
+ 'delete': 'admin:index',
97
+ 'view': 'admin:index',
98
+ }
@@ -0,0 +1,224 @@
1
+ """
2
+ Charts data callbacks.
3
+ """
4
+
5
+ import logging
6
+ import random
7
+ from typing import Dict, Any, List
8
+ from datetime import timedelta
9
+
10
+ from django.db.models import Count
11
+ from django.utils import timezone
12
+ from django.contrib.auth import get_user_model
13
+
14
+ from ..models.dashboard import ChartData, ChartDataset
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ChartsCallbacks:
20
+ """Charts data callbacks."""
21
+
22
+ def _get_user_model(self):
23
+ """Get the user model safely."""
24
+ return get_user_model()
25
+
26
+ def _get_empty_chart_data(self, label: str) -> Dict[str, Any]:
27
+ """Get empty chart data structure."""
28
+ return ChartData(
29
+ labels=["No Data"],
30
+ datasets=[
31
+ ChartDataset(
32
+ label=label,
33
+ data=[0],
34
+ backgroundColor="rgba(156, 163, 175, 0.1)",
35
+ borderColor="rgb(156, 163, 175)",
36
+ tension=0.4
37
+ )
38
+ ]
39
+ ).model_dump()
40
+
41
+ def get_user_registration_chart_data(self) -> Dict[str, Any]:
42
+ """Get user registration chart data."""
43
+ try:
44
+ # Avoid database access during app initialization
45
+ from django.apps import apps
46
+ if not apps.ready:
47
+ return self._get_empty_chart_data("New Users")
48
+
49
+ User = self._get_user_model()
50
+
51
+ # Get last 7 days of registration data
52
+ end_date = timezone.now().date()
53
+ start_date = end_date - timedelta(days=6)
54
+
55
+ # Generate date range
56
+ date_range = []
57
+ current_date = start_date
58
+ while current_date <= end_date:
59
+ date_range.append(current_date)
60
+ current_date += timedelta(days=1)
61
+
62
+ # Get registration counts by date
63
+ registration_data = (
64
+ User.objects.filter(date_joined__date__gte=start_date)
65
+ .extra({'date': "date(date_joined)"})
66
+ .values('date')
67
+ .annotate(count=Count('id'))
68
+ .order_by('date')
69
+ )
70
+
71
+ # Create data dictionary for easy lookup
72
+ data_dict = {item['date']: item['count'] for item in registration_data}
73
+
74
+ # Build chart data
75
+ labels = [date.strftime("%m/%d") for date in date_range]
76
+ data_points = [data_dict.get(date, 0) for date in date_range]
77
+
78
+ chart_data = ChartData(
79
+ labels=labels,
80
+ datasets=[
81
+ ChartDataset(
82
+ label="New Users",
83
+ data=data_points,
84
+ backgroundColor="rgba(59, 130, 246, 0.1)",
85
+ borderColor="rgb(59, 130, 246)",
86
+ tension=0.4
87
+ )
88
+ ]
89
+ )
90
+
91
+ return chart_data.model_dump()
92
+
93
+ except Exception as e:
94
+ logger.error(f"Error getting user registration chart data: {e}")
95
+ return self._get_empty_chart_data("New Users")
96
+
97
+ def get_user_activity_chart_data(self) -> Dict[str, Any]:
98
+ """Get user activity chart data."""
99
+ try:
100
+ # Avoid database access during app initialization
101
+ from django.apps import apps
102
+ if not apps.ready:
103
+ return self._get_empty_chart_data("Active Users")
104
+
105
+ User = self._get_user_model()
106
+
107
+ # Get activity data for last 7 days
108
+ end_date = timezone.now().date()
109
+ start_date = end_date - timedelta(days=6)
110
+
111
+ # Generate date range
112
+ date_range = []
113
+ current_date = start_date
114
+ while current_date <= end_date:
115
+ date_range.append(current_date)
116
+ current_date += timedelta(days=1)
117
+
118
+ # Get login activity (users who logged in each day)
119
+ activity_data = (
120
+ User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
121
+ .extra({'date': "date(last_login)"})
122
+ .values('date')
123
+ .annotate(count=Count('id'))
124
+ .order_by('date')
125
+ )
126
+
127
+ # Create data dictionary for easy lookup
128
+ data_dict = {item['date']: item['count'] for item in activity_data}
129
+
130
+ # Build chart data
131
+ labels = [date.strftime("%m/%d") for date in date_range]
132
+ data_points = [data_dict.get(date, 0) for date in date_range]
133
+
134
+ chart_data = ChartData(
135
+ labels=labels,
136
+ datasets=[
137
+ ChartDataset(
138
+ label="Active Users",
139
+ data=data_points,
140
+ backgroundColor="rgba(34, 197, 94, 0.1)",
141
+ borderColor="rgb(34, 197, 94)",
142
+ tension=0.4
143
+ )
144
+ ]
145
+ )
146
+
147
+ return chart_data.model_dump()
148
+
149
+ except Exception as e:
150
+ logger.error(f"Error getting user activity chart data: {e}")
151
+ return self._get_empty_chart_data("Active Users")
152
+
153
+ def get_activity_tracker_data(self) -> List[Dict[str, str]]:
154
+ """Get activity tracker data for the last 52 weeks (GitHub-style)."""
155
+ try:
156
+ # Avoid database access during app initialization
157
+ from django.apps import apps
158
+ if not apps.ready:
159
+ return self._get_empty_tracker_data()
160
+
161
+ User = self._get_user_model()
162
+
163
+ # Get data for last 52 weeks (365 days)
164
+ end_date = timezone.now().date()
165
+ start_date = end_date - timedelta(days=364) # 52 weeks * 7 days - 1
166
+
167
+ # Get activity data by date
168
+ activity_data = (
169
+ User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
170
+ .extra({'date': "date(last_login)"})
171
+ .values('date')
172
+ .annotate(count=Count('id'))
173
+ .order_by('date')
174
+ )
175
+
176
+ # Create data dictionary for easy lookup
177
+ data_dict = {item['date']: item['count'] for item in activity_data}
178
+
179
+ # Generate tracker data for each day
180
+ tracker_data = []
181
+ current_date = start_date
182
+
183
+ while current_date <= end_date:
184
+ activity_count = data_dict.get(current_date, 0)
185
+
186
+ # Determine color based on activity level
187
+ if activity_count == 0:
188
+ color = "bg-base-200 dark:bg-base-700"
189
+ level = "No activity"
190
+ elif activity_count <= 2:
191
+ color = "bg-green-200 dark:bg-green-800"
192
+ level = "Low activity"
193
+ elif activity_count <= 5:
194
+ color = "bg-green-400 dark:bg-green-600"
195
+ level = "Medium activity"
196
+ elif activity_count <= 10:
197
+ color = "bg-green-600 dark:bg-green-500"
198
+ level = "High activity"
199
+ else:
200
+ color = "bg-green-800 dark:bg-green-400"
201
+ level = "Very high activity"
202
+
203
+ tracker_data.append({
204
+ "color": color,
205
+ "tooltip": f"{current_date.strftime('%Y-%m-%d')}: {activity_count} active users ({level})"
206
+ })
207
+
208
+ current_date += timedelta(days=1)
209
+
210
+ return tracker_data
211
+
212
+ except Exception as e:
213
+ logger.error(f"Error getting activity tracker data: {e}")
214
+ return self._get_empty_tracker_data()
215
+
216
+ def _get_empty_tracker_data(self) -> List[Dict[str, str]]:
217
+ """Get empty tracker data (365 days of no activity)."""
218
+ tracker_data = []
219
+ for i in range(365):
220
+ tracker_data.append({
221
+ "color": "bg-base-200 dark:bg-base-700",
222
+ "tooltip": f"Day {i + 1}: No data available"
223
+ })
224
+ return tracker_data
@@ -0,0 +1,40 @@
1
+ """
2
+ Django commands callbacks.
3
+ """
4
+
5
+ import logging
6
+ from typing import Dict, Any
7
+
8
+ from .base import get_available_commands, get_commands_by_category
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class CommandsCallbacks:
14
+ """Django commands callbacks."""
15
+
16
+ def get_django_commands(self) -> Dict[str, Any]:
17
+ """Get Django management commands information."""
18
+ try:
19
+ commands = get_available_commands()
20
+ categorized = get_commands_by_category()
21
+
22
+ return {
23
+ "commands": commands,
24
+ "categorized": categorized,
25
+ "total_commands": len(commands),
26
+ "categories": list(categorized.keys()),
27
+ "core_commands": len([cmd for cmd in commands if cmd['is_core']]),
28
+ "custom_commands": len([cmd for cmd in commands if cmd['is_custom']]),
29
+ }
30
+ except Exception as e:
31
+ logger.error(f"Error getting Django commands: {e}")
32
+ # Return safe fallback to prevent dashboard from breaking
33
+ return {
34
+ "commands": [],
35
+ "categorized": {},
36
+ "total_commands": 0,
37
+ "categories": [],
38
+ "core_commands": 0,
39
+ "custom_commands": 0,
40
+ }
@@ -0,0 +1,191 @@
1
+ """
2
+ Main Unfold Dashboard Callbacks
3
+
4
+ Combines all callback modules into a single interface.
5
+ """
6
+
7
+ import logging
8
+ import json
9
+ from typing import Dict, Any
10
+
11
+ from django.utils import timezone
12
+ from django.conf import settings
13
+
14
+ from ...base import BaseCfgModule
15
+ from ..models.dashboard import DashboardData
16
+
17
+ from .statistics import StatisticsCallbacks
18
+ from .system import SystemCallbacks
19
+ from .actions import ActionsCallbacks
20
+ from .charts import ChartsCallbacks
21
+ from .commands import CommandsCallbacks
22
+ from .revolution import RevolutionCallbacks
23
+ from .users import UsersCallbacks
24
+ from .base import get_user_admin_urls
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class UnfoldCallbacks(
30
+ BaseCfgModule,
31
+ StatisticsCallbacks,
32
+ SystemCallbacks,
33
+ ActionsCallbacks,
34
+ ChartsCallbacks,
35
+ CommandsCallbacks,
36
+ RevolutionCallbacks,
37
+ UsersCallbacks
38
+ ):
39
+ """
40
+ Main Unfold dashboard callbacks with full system monitoring.
41
+
42
+ Combines all callback modules using multiple inheritance for
43
+ clean separation of concerns while maintaining a single interface.
44
+ """
45
+
46
+ def main_dashboard_callback(self, request, context: Dict[str, Any]) -> Dict[str, Any]:
47
+ """
48
+ Main dashboard callback function with comprehensive system data.
49
+
50
+ Returns all dashboard data as Pydantic models for type safety.
51
+ """
52
+ try:
53
+ # Get dashboard data using Pydantic models
54
+ user_stats = self.get_user_statistics()
55
+ support_stats = self.get_support_statistics()
56
+ system_health = self.get_system_health()
57
+ quick_actions = self.get_quick_actions()
58
+
59
+ # Combine all stat cards
60
+ all_stats = user_stats + support_stats
61
+
62
+ dashboard_data = DashboardData(
63
+ stat_cards=all_stats,
64
+ system_health=system_health,
65
+ quick_actions=quick_actions,
66
+ last_updated=timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
67
+ environment=getattr(settings, "ENVIRONMENT", "development"),
68
+ )
69
+
70
+ # Convert to template context (using to_dict for Unfold compatibility)
71
+ cards_data = [card.to_dict() for card in dashboard_data.stat_cards]
72
+
73
+ context.update({
74
+ # Statistics cards
75
+ "cards": cards_data,
76
+ "user_stats": [card.to_dict() for card in user_stats],
77
+ "support_stats": [card.to_dict() for card in support_stats],
78
+
79
+ # System health (convert to dict for template)
80
+ "system_health": {
81
+ item.component + "_status": item.status
82
+ for item in dashboard_data.system_health
83
+ },
84
+
85
+ # System metrics
86
+ "system_metrics": self.get_system_metrics(),
87
+
88
+ # Quick actions
89
+ "quick_actions": [
90
+ action.model_dump() for action in dashboard_data.quick_actions
91
+ ],
92
+
93
+ # Additional categorized actions
94
+ "admin_actions": [
95
+ action.model_dump()
96
+ for action in dashboard_data.quick_actions
97
+ if action.category == "admin"
98
+ ],
99
+ "support_actions": [
100
+ action.model_dump()
101
+ for action in dashboard_data.quick_actions
102
+ if action.category == "support"
103
+ ],
104
+ "system_actions": [
105
+ action.model_dump()
106
+ for action in dashboard_data.quick_actions
107
+ if action.category == "system"
108
+ ],
109
+
110
+ # Revolution zones
111
+ "zones_table": {
112
+ "headers": [
113
+ {"label": "Zone"},
114
+ {"label": "Title"},
115
+ {"label": "Apps"},
116
+ {"label": "Endpoints"},
117
+ {"label": "Status"},
118
+ {"label": "Actions"},
119
+ ],
120
+ "rows": self.get_revolution_zones_data()[0],
121
+ },
122
+
123
+ # Recent users
124
+ "recent_users": self.get_recent_users(),
125
+ "user_admin_urls": get_user_admin_urls(),
126
+
127
+ # App statistics
128
+ "app_statistics": self.get_app_statistics(),
129
+
130
+ # Django commands
131
+ "django_commands": self.get_django_commands(),
132
+
133
+ # Charts data - serialize to JSON for JavaScript
134
+ "charts": {
135
+ "user_registrations_json": json.dumps(self.get_user_registration_chart_data()),
136
+ "user_activity_json": json.dumps(self.get_user_activity_chart_data()),
137
+ "user_registrations": self.get_user_registration_chart_data(),
138
+ "user_activity": self.get_user_activity_chart_data(),
139
+ },
140
+
141
+ # Activity tracker data
142
+ "activity_tracker": self.get_activity_tracker_data(),
143
+
144
+
145
+ # Meta information
146
+ "last_updated": dashboard_data.last_updated,
147
+ "environment": dashboard_data.environment,
148
+ "dashboard_title": "Django CFG Dashboard",
149
+ })
150
+
151
+ # Log charts data for debugging
152
+ charts_data = context.get('charts', {})
153
+ logger.info(f"Charts data added to context: {list(charts_data.keys())}")
154
+ if 'user_registrations' in charts_data:
155
+ reg_data = charts_data['user_registrations']
156
+ logger.info(f"Registration chart labels: {reg_data.get('labels', [])}")
157
+ if 'user_activity' in charts_data:
158
+ act_data = charts_data['user_activity']
159
+ logger.info(f"Activity chart labels: {act_data.get('labels', [])}")
160
+
161
+ # Log recent users data for debugging
162
+ recent_users_data = context.get('recent_users', [])
163
+ logger.info(f"Recent users data count: {len(recent_users_data)}")
164
+ if recent_users_data:
165
+ logger.info(f"First user: {recent_users_data[0].get('username', 'N/A')}")
166
+
167
+ # Log activity tracker data for debugging
168
+ activity_tracker_data = context.get('activity_tracker', [])
169
+ logger.info(f"Activity tracker data count: {len(activity_tracker_data)}")
170
+
171
+ return context
172
+
173
+ except Exception as e:
174
+ logger.error(f"Dashboard callback error: {e}")
175
+ # Return minimal safe defaults
176
+ context.update({
177
+ "cards": [
178
+ {
179
+ "title": "System Error",
180
+ "value": "N/A",
181
+ "icon": "error",
182
+ "color": "danger",
183
+ "description": "Dashboard data unavailable"
184
+ }
185
+ ],
186
+ "system_health": {},
187
+ "quick_actions": [],
188
+ "last_updated": timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
189
+ "error": f"Dashboard error: {str(e)}",
190
+ })
191
+ return context