django-cfg 1.2.7__py3-none-any.whl → 1.2.8__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/urls.py +2 -2
- django_cfg/modules/django_unfold/callbacks/__init__.py +9 -0
- django_cfg/modules/django_unfold/callbacks/actions.py +50 -0
- django_cfg/modules/django_unfold/callbacks/base.py +98 -0
- django_cfg/modules/django_unfold/callbacks/charts.py +224 -0
- django_cfg/modules/django_unfold/callbacks/commands.py +40 -0
- django_cfg/modules/django_unfold/callbacks/main.py +191 -0
- django_cfg/modules/django_unfold/callbacks/revolution.py +76 -0
- django_cfg/modules/django_unfold/callbacks/statistics.py +240 -0
- django_cfg/modules/django_unfold/callbacks/system.py +180 -0
- django_cfg/modules/django_unfold/callbacks/users.py +65 -0
- django_cfg/modules/django_unfold/models/config.py +10 -3
- django_cfg/modules/django_unfold/tailwind.py +68 -0
- django_cfg/templates/admin/components/action_grid.html +49 -0
- django_cfg/templates/admin/components/card.html +50 -0
- django_cfg/templates/admin/components/data_table.html +67 -0
- django_cfg/templates/admin/components/metric_card.html +39 -0
- django_cfg/templates/admin/components/modal.html +58 -0
- django_cfg/templates/admin/components/progress_bar.html +25 -0
- django_cfg/templates/admin/components/section_header.html +26 -0
- django_cfg/templates/admin/components/stat_item.html +32 -0
- django_cfg/templates/admin/components/stats_grid.html +72 -0
- django_cfg/templates/admin/components/status_badge.html +28 -0
- django_cfg/templates/admin/components/user_avatar.html +27 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +7 -7
- django_cfg/templates/admin/snippets/components/activity_tracker.html +48 -11
- django_cfg/templates/admin/snippets/components/charts_section.html +63 -13
- django_cfg/templates/admin/snippets/components/django_commands.html +18 -18
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -47
- django_cfg/templates/admin/snippets/components/recent_activity.html +28 -38
- django_cfg/templates/admin/snippets/components/recent_users_table.html +22 -53
- django_cfg/templates/admin/snippets/components/stats_cards.html +2 -66
- django_cfg/templates/admin/snippets/components/system_health.html +13 -63
- django_cfg/templates/admin/snippets/components/system_metrics.html +8 -25
- django_cfg/templates/admin/snippets/tabs/commands_tab.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +4 -4
- django_cfg/templates/admin/snippets/zones/zones_table.html +12 -33
- django_cfg/templatetags/django_cfg.py +2 -1
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/METADATA +2 -1
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/RECORD +44 -24
- django_cfg/modules/django_unfold/callbacks.py +0 -795
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
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
|
-
|
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,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
|