django-cfg 1.2.6__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.
Files changed (53) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/agents/admin/execution_admin.py +9 -2
  3. django_cfg/apps/agents/admin/registry_admin.py +10 -2
  4. django_cfg/apps/agents/admin/toolsets_admin.py +14 -3
  5. django_cfg/apps/knowbase/admin/archive_admin.py +3 -2
  6. django_cfg/apps/knowbase/admin/chat_admin.py +3 -2
  7. django_cfg/apps/knowbase/admin/document_admin.py +11 -2
  8. django_cfg/apps/knowbase/admin/external_data_admin.py +2 -1
  9. django_cfg/apps/urls.py +2 -2
  10. django_cfg/modules/django_import_export/__init__.py +12 -5
  11. django_cfg/modules/django_unfold/callbacks/__init__.py +9 -0
  12. django_cfg/modules/django_unfold/callbacks/actions.py +50 -0
  13. django_cfg/modules/django_unfold/callbacks/base.py +98 -0
  14. django_cfg/modules/django_unfold/callbacks/charts.py +224 -0
  15. django_cfg/modules/django_unfold/callbacks/commands.py +40 -0
  16. django_cfg/modules/django_unfold/callbacks/main.py +191 -0
  17. django_cfg/modules/django_unfold/callbacks/revolution.py +76 -0
  18. django_cfg/modules/django_unfold/callbacks/statistics.py +240 -0
  19. django_cfg/modules/django_unfold/callbacks/system.py +180 -0
  20. django_cfg/modules/django_unfold/callbacks/users.py +65 -0
  21. django_cfg/modules/django_unfold/models/config.py +10 -3
  22. django_cfg/modules/django_unfold/tailwind.py +68 -0
  23. django_cfg/templates/admin/components/action_grid.html +49 -0
  24. django_cfg/templates/admin/components/card.html +50 -0
  25. django_cfg/templates/admin/components/data_table.html +67 -0
  26. django_cfg/templates/admin/components/metric_card.html +39 -0
  27. django_cfg/templates/admin/components/modal.html +58 -0
  28. django_cfg/templates/admin/components/progress_bar.html +25 -0
  29. django_cfg/templates/admin/components/section_header.html +26 -0
  30. django_cfg/templates/admin/components/stat_item.html +32 -0
  31. django_cfg/templates/admin/components/stats_grid.html +72 -0
  32. django_cfg/templates/admin/components/status_badge.html +28 -0
  33. django_cfg/templates/admin/components/user_avatar.html +27 -0
  34. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +7 -7
  35. django_cfg/templates/admin/snippets/components/activity_tracker.html +48 -11
  36. django_cfg/templates/admin/snippets/components/charts_section.html +63 -13
  37. django_cfg/templates/admin/snippets/components/django_commands.html +18 -18
  38. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -47
  39. django_cfg/templates/admin/snippets/components/recent_activity.html +28 -38
  40. django_cfg/templates/admin/snippets/components/recent_users_table.html +22 -53
  41. django_cfg/templates/admin/snippets/components/stats_cards.html +2 -66
  42. django_cfg/templates/admin/snippets/components/system_health.html +13 -63
  43. django_cfg/templates/admin/snippets/components/system_metrics.html +8 -25
  44. django_cfg/templates/admin/snippets/tabs/commands_tab.html +1 -1
  45. django_cfg/templates/admin/snippets/tabs/overview_tab.html +4 -4
  46. django_cfg/templates/admin/snippets/zones/zones_table.html +12 -33
  47. django_cfg/templatetags/django_cfg.py +2 -1
  48. {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/METADATA +2 -1
  49. {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/RECORD +52 -32
  50. django_cfg/modules/django_unfold/callbacks.py +0 -795
  51. {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/WHEEL +0 -0
  52. {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/entry_points.txt +0 -0
  53. {django_cfg-1.2.6.dist-info → django_cfg-1.2.8.dist-info}/licenses/LICENSE +0 -0
@@ -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
@@ -0,0 +1,76 @@
1
+ """
2
+ Django Revolution integration callbacks.
3
+ """
4
+
5
+ import logging
6
+ from typing import Dict, Any, List, Tuple
7
+
8
+ from django.conf import settings
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class RevolutionCallbacks:
14
+ """Django Revolution integration callbacks."""
15
+
16
+ def get_revolution_zones_data(self) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
17
+ """Get Django Revolution zones data."""
18
+ try:
19
+ # Try to get revolution config from Django settings
20
+ revolution_config = getattr(settings, "DJANGO_REVOLUTION", {})
21
+ zones = revolution_config.get("zones", {})
22
+ api_prefix = revolution_config.get("api_prefix", "apix")
23
+
24
+ zones_data = []
25
+ total_apps = 0
26
+ total_endpoints = 0
27
+
28
+ for zone_name, zone_config in zones.items():
29
+ # Handle both dict and object access
30
+ if isinstance(zone_config, dict):
31
+ title = zone_config.get("title", zone_name.title())
32
+ description = zone_config.get("description", f"{zone_name} zone")
33
+ apps = zone_config.get("apps", [])
34
+ public = zone_config.get("public", False)
35
+ auth_required = zone_config.get("auth_required", True)
36
+ else:
37
+ # Handle object access (for ZoneConfig instances)
38
+ title = getattr(zone_config, "title", zone_name.title())
39
+ description = getattr(zone_config, "description", f"{zone_name} zone")
40
+ apps = getattr(zone_config, "apps", [])
41
+ public = getattr(zone_config, "public", False)
42
+ auth_required = getattr(zone_config, "auth_required", True)
43
+
44
+ # Count actual endpoints by checking URL patterns (simplified estimate)
45
+ endpoint_count = len(apps) * 3 # Conservative estimate
46
+
47
+ zones_data.append({
48
+ "name": zone_name,
49
+ "title": title,
50
+ "description": description,
51
+ "app_count": len(apps),
52
+ "endpoint_count": endpoint_count,
53
+ "status": "active",
54
+ "public": public,
55
+ "auth_required": auth_required,
56
+ "schema_url": f"/schema/{zone_name}/schema/",
57
+ "swagger_url": f"/schema/{zone_name}/schema/swagger/",
58
+ "redoc_url": f"/schema/{zone_name}/redoc/",
59
+ "api_url": f"/{api_prefix}/{zone_name}/",
60
+ })
61
+
62
+ total_apps += len(apps)
63
+ total_endpoints += endpoint_count
64
+
65
+ return zones_data, {
66
+ "total_apps": total_apps,
67
+ "total_endpoints": total_endpoints,
68
+ "total_zones": len(zones),
69
+ }
70
+ except Exception as e:
71
+ logger.error(f"Error getting revolution zones: {e}")
72
+ return [], {
73
+ "total_apps": 0,
74
+ "total_endpoints": 0,
75
+ "total_zones": 0,
76
+ }
@@ -0,0 +1,240 @@
1
+ """
2
+ Statistics callbacks for dashboard.
3
+ """
4
+
5
+ import logging
6
+ from typing import List, Dict, Any
7
+ from datetime import timedelta
8
+
9
+ from django.db.models import Count
10
+ from django.utils import timezone
11
+ from django.contrib.auth import get_user_model
12
+ from django.apps import apps
13
+
14
+ from ..models.dashboard import StatCard
15
+ from ..icons import Icons
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class StatisticsCallbacks:
21
+ """Statistics-related callbacks."""
22
+
23
+ def _get_user_model(self):
24
+ """Get the user model safely."""
25
+ return get_user_model()
26
+
27
+ def get_user_statistics(self) -> List[StatCard]:
28
+ """Get user-related statistics as Pydantic models."""
29
+ try:
30
+ User = self._get_user_model()
31
+
32
+ total_users = User.objects.count()
33
+ active_users = User.objects.filter(is_active=True).count()
34
+ new_users_7d = User.objects.filter(
35
+ date_joined__gte=timezone.now() - timedelta(days=7)
36
+ ).count()
37
+ staff_users = User.objects.filter(is_staff=True).count()
38
+
39
+ return [
40
+ StatCard(
41
+ title="Total Users",
42
+ value=f"{total_users:,}",
43
+ icon=Icons.PEOPLE,
44
+ change=f"+{new_users_7d}" if new_users_7d > 0 else None,
45
+ change_type="positive" if new_users_7d > 0 else "neutral",
46
+ description="Registered users",
47
+ ),
48
+ StatCard(
49
+ title="Active Users",
50
+ value=f"{active_users:,}",
51
+ icon=Icons.PERSON,
52
+ change=(
53
+ f"{(active_users/total_users*100):.1f}%"
54
+ if total_users > 0
55
+ else "0%"
56
+ ),
57
+ change_type=(
58
+ "positive" if active_users > total_users * 0.7 else "neutral"
59
+ ),
60
+ description="Currently active",
61
+ ),
62
+ StatCard(
63
+ title="New This Week",
64
+ value=f"{new_users_7d:,}",
65
+ icon=Icons.PERSON_ADD,
66
+ change_type="positive" if new_users_7d > 0 else "neutral",
67
+ description="Last 7 days",
68
+ ),
69
+ StatCard(
70
+ title="Staff Members",
71
+ value=f"{staff_users:,}",
72
+ icon=Icons.ADMIN_PANEL_SETTINGS,
73
+ change=(
74
+ f"{(staff_users/total_users*100):.1f}%" if total_users > 0 else "0%"
75
+ ),
76
+ change_type="neutral",
77
+ description="Administrative access",
78
+ ),
79
+ ]
80
+ except Exception as e:
81
+ logger.error(f"Error getting user statistics: {e}")
82
+ return [
83
+ StatCard(
84
+ title="Users",
85
+ value="N/A",
86
+ icon=Icons.PEOPLE,
87
+ description="Data unavailable",
88
+ )
89
+ ]
90
+
91
+ def get_support_statistics(self) -> List[StatCard]:
92
+ """Get support ticket statistics as Pydantic models."""
93
+ try:
94
+ # Check if support is enabled
95
+ if not self.is_support_enabled():
96
+ return []
97
+
98
+ from django_cfg.apps.support.models import Ticket
99
+
100
+ total_tickets = Ticket.objects.count()
101
+ open_tickets = Ticket.objects.filter(status='open').count()
102
+ resolved_tickets = Ticket.objects.filter(status='resolved').count()
103
+ new_tickets_7d = Ticket.objects.filter(
104
+ created_at__gte=timezone.now() - timedelta(days=7)
105
+ ).count()
106
+
107
+ return [
108
+ StatCard(
109
+ title="Total Tickets",
110
+ value=f"{total_tickets:,}",
111
+ icon=Icons.SUPPORT_AGENT,
112
+ change=f"+{new_tickets_7d}" if new_tickets_7d > 0 else None,
113
+ change_type="positive" if new_tickets_7d > 0 else "neutral",
114
+ description="All support tickets",
115
+ ),
116
+ StatCard(
117
+ title="Open Tickets",
118
+ value=f"{open_tickets:,}",
119
+ icon=Icons.PENDING,
120
+ change=(
121
+ f"{(open_tickets/total_tickets*100):.1f}%"
122
+ if total_tickets > 0
123
+ else "0%"
124
+ ),
125
+ change_type=(
126
+ "negative" if open_tickets > total_tickets * 0.3
127
+ else "positive" if open_tickets == 0
128
+ else "neutral"
129
+ ),
130
+ description="Awaiting response",
131
+ ),
132
+ StatCard(
133
+ title="Resolved",
134
+ value=f"{resolved_tickets:,}",
135
+ icon=Icons.CHECK_CIRCLE,
136
+ change=(
137
+ f"{(resolved_tickets/total_tickets*100):.1f}%"
138
+ if total_tickets > 0
139
+ else "0%"
140
+ ),
141
+ change_type="positive",
142
+ description="Successfully resolved",
143
+ ),
144
+ StatCard(
145
+ title="New This Week",
146
+ value=f"{new_tickets_7d:,}",
147
+ icon=Icons.NEW_RELEASES,
148
+ change_type="positive" if new_tickets_7d > 0 else "neutral",
149
+ description="Last 7 days",
150
+ ),
151
+ ]
152
+ except Exception as e:
153
+ logger.error(f"Error getting support statistics: {e}")
154
+ return [
155
+ StatCard(
156
+ title="Support",
157
+ value="N/A",
158
+ icon=Icons.SUPPORT_AGENT,
159
+ description="Data unavailable",
160
+ )
161
+ ]
162
+
163
+ def get_app_statistics(self) -> Dict[str, Any]:
164
+ """Get statistics for all apps and their models."""
165
+ stats = {"apps": {}, "total_records": 0, "total_models": 0, "total_apps": 0}
166
+
167
+ # Get all installed apps
168
+ for app_config in apps.get_app_configs():
169
+ app_label = app_config.label
170
+
171
+ # Skip system apps
172
+ if app_label in ["admin", "contenttypes", "sessions", "auth"]:
173
+ continue
174
+
175
+ app_stats = self._get_app_stats(app_label)
176
+ if app_stats:
177
+ stats["apps"][app_label] = app_stats
178
+ stats["total_records"] += app_stats.get("total_records", 0)
179
+ stats["total_models"] += app_stats.get("model_count", 0)
180
+ stats["total_apps"] += 1
181
+
182
+ return stats
183
+
184
+ def _get_app_stats(self, app_label: str) -> Dict[str, Any]:
185
+ """Get statistics for a specific app."""
186
+ try:
187
+ app_config = apps.get_app_config(app_label)
188
+ # Convert generator to list to avoid len() error
189
+ models_list = list(app_config.get_models())
190
+
191
+ if not models_list:
192
+ return None
193
+
194
+ app_stats = {
195
+ "name": app_config.verbose_name or app_label.title(),
196
+ "models": {},
197
+ "total_records": 0,
198
+ "model_count": len(models_list),
199
+ }
200
+
201
+ for model in models_list:
202
+ try:
203
+ # Get model statistics
204
+ model_stats = self._get_model_stats(model)
205
+ if model_stats:
206
+ app_stats["models"][model._meta.model_name] = model_stats
207
+ app_stats["total_records"] += model_stats.get("count", 0)
208
+ except Exception:
209
+ continue
210
+
211
+ return app_stats
212
+
213
+ except Exception:
214
+ return None
215
+
216
+ def _get_model_stats(self, model) -> Dict[str, Any]:
217
+ """Get statistics for a specific model."""
218
+ try:
219
+ # Get basic model info
220
+ model_stats = {
221
+ "name": model._meta.verbose_name_plural
222
+ or model._meta.verbose_name
223
+ or model._meta.model_name,
224
+ "count": model.objects.count(),
225
+ "fields_count": len(model._meta.fields),
226
+ "admin_url": f"admin:{model._meta.app_label}_{model._meta.model_name}_changelist",
227
+ }
228
+
229
+ return model_stats
230
+
231
+ except Exception:
232
+ return None
233
+
234
+ def is_support_enabled(self) -> bool:
235
+ """Check if support module is enabled."""
236
+ try:
237
+ from django_cfg.apps.support.models import Ticket
238
+ return True
239
+ except ImportError:
240
+ return False