django-cfg 1.4.111__py3-none-any.whl → 1.4.113__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 (33) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/dashboard/serializers/__init__.py +10 -0
  3. django_cfg/apps/dashboard/serializers/crontab.py +84 -0
  4. django_cfg/apps/dashboard/serializers/overview.py +22 -11
  5. django_cfg/apps/dashboard/services/__init__.py +2 -0
  6. django_cfg/apps/dashboard/services/crontab_service.py +210 -0
  7. django_cfg/apps/dashboard/services/system_health_service.py +72 -0
  8. django_cfg/apps/dashboard/urls.py +2 -0
  9. django_cfg/apps/dashboard/views/__init__.py +2 -0
  10. django_cfg/apps/dashboard/views/crontab_views.py +72 -0
  11. django_cfg/apps/dashboard/views/overview_views.py +16 -2
  12. django_cfg/config.py +3 -4
  13. django_cfg/core/base/config_model.py +7 -0
  14. django_cfg/core/builders/apps_builder.py +4 -0
  15. django_cfg/core/generation/integration_generators/__init__.py +3 -0
  16. django_cfg/core/generation/integration_generators/crontab.py +64 -0
  17. django_cfg/core/generation/orchestrator.py +13 -0
  18. django_cfg/core/integration/display/startup.py +2 -2
  19. django_cfg/core/integration/url_integration.py +2 -2
  20. django_cfg/models/__init__.py +3 -0
  21. django_cfg/models/django/__init__.py +3 -0
  22. django_cfg/models/django/crontab.py +303 -0
  23. django_cfg/modules/django_admin/utils/html_builder.py +50 -2
  24. django_cfg/pyproject.toml +2 -2
  25. django_cfg/registry/core.py +4 -0
  26. django_cfg/static/frontend/admin.zip +0 -0
  27. django_cfg/templates/admin/index.html +389 -166
  28. django_cfg/templatetags/django_cfg.py +8 -0
  29. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/METADATA +2 -1
  30. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/RECORD +33 -28
  31. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/WHEEL +0 -0
  32. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.dist-info}/entry_points.txt +0 -0
  33. {django_cfg-1.4.111.dist-info → django_cfg-1.4.113.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.4.111"
35
+ __version__ = "1.4.113"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
@@ -22,6 +22,11 @@ from .commands import (
22
22
  CommandHelpResponseSerializer,
23
23
  )
24
24
  from .apizones import APIZoneSerializer, APIZonesSummarySerializer
25
+ from .crontab import (
26
+ CrontabJobSerializer,
27
+ CrontabJobsSerializer,
28
+ CrontabStatusSerializer,
29
+ )
25
30
 
26
31
  __all__ = [
27
32
  # Base
@@ -59,4 +64,9 @@ __all__ = [
59
64
  # API Zones
60
65
  'APIZoneSerializer',
61
66
  'APIZonesSummarySerializer',
67
+
68
+ # Crontab
69
+ 'CrontabJobSerializer',
70
+ 'CrontabJobsSerializer',
71
+ 'CrontabStatusSerializer',
62
72
  ]
@@ -0,0 +1,84 @@
1
+ """
2
+ Crontab Serializers
3
+
4
+ Serializers for crontab monitoring endpoints.
5
+ """
6
+
7
+ from rest_framework import serializers
8
+
9
+
10
+ class CrontabJobSerializer(serializers.Serializer):
11
+ """
12
+ Serializer for individual cron job configuration.
13
+
14
+ Maps to CrontabJobConfig Pydantic model.
15
+ """
16
+
17
+ name = serializers.CharField(help_text="Job identifier name")
18
+ job_type = serializers.ChoiceField(
19
+ choices=['command', 'callable'],
20
+ help_text="Job type: Django command or Python callable"
21
+ )
22
+ command = serializers.CharField(
23
+ required=False,
24
+ allow_null=True,
25
+ help_text="Management command name (for command type jobs)"
26
+ )
27
+ callable_path = serializers.CharField(
28
+ required=False,
29
+ allow_null=True,
30
+ help_text="Python callable path (for callable type jobs)"
31
+ )
32
+ command_args = serializers.ListField(
33
+ child=serializers.CharField(),
34
+ required=False,
35
+ help_text="Command positional arguments"
36
+ )
37
+ command_kwargs = serializers.DictField(
38
+ required=False,
39
+ help_text="Command keyword arguments"
40
+ )
41
+ minute = serializers.CharField(help_text="Cron minute field")
42
+ hour = serializers.CharField(help_text="Cron hour field")
43
+ day_of_month = serializers.CharField(help_text="Cron day of month field")
44
+ month_of_year = serializers.CharField(help_text="Cron month field")
45
+ day_of_week = serializers.CharField(help_text="Cron day of week field")
46
+ enabled = serializers.BooleanField(help_text="Whether job is enabled")
47
+ comment = serializers.CharField(
48
+ required=False,
49
+ allow_null=True,
50
+ help_text="Optional job description"
51
+ )
52
+ schedule_display = serializers.CharField(
53
+ required=False,
54
+ help_text="Human-readable schedule description"
55
+ )
56
+
57
+
58
+ class CrontabJobsSerializer(serializers.Serializer):
59
+ """Serializer for list of all cron jobs."""
60
+
61
+ enabled = serializers.BooleanField(help_text="Whether crontab is enabled")
62
+ jobs_count = serializers.IntegerField(help_text="Total number of jobs")
63
+ enabled_jobs_count = serializers.IntegerField(help_text="Number of enabled jobs")
64
+ jobs = CrontabJobSerializer(many=True, help_text="List of all cron jobs")
65
+
66
+
67
+ class CrontabStatusSerializer(serializers.Serializer):
68
+ """Serializer for crontab configuration status."""
69
+
70
+ enabled = serializers.BooleanField(help_text="Whether crontab is enabled")
71
+ jobs_count = serializers.IntegerField(help_text="Total number of jobs")
72
+ enabled_jobs_count = serializers.IntegerField(help_text="Number of enabled jobs")
73
+ lock_jobs = serializers.BooleanField(help_text="Whether job locking is enabled")
74
+ command_prefix = serializers.CharField(
75
+ required=False,
76
+ allow_null=True,
77
+ help_text="Command prefix for all jobs"
78
+ )
79
+ comment = serializers.CharField(
80
+ required=False,
81
+ allow_null=True,
82
+ help_text="Crontab configuration comment"
83
+ )
84
+ timestamp = serializers.CharField(help_text="Status check timestamp (ISO format)")
@@ -5,30 +5,41 @@ Serializers for dashboard overview endpoint.
5
5
  """
6
6
 
7
7
  from rest_framework import serializers
8
+ from .statistics import StatCardSerializer, UserStatisticsSerializer, AppStatisticsSerializer
9
+ from .system import SystemHealthSerializer, SystemMetricsSerializer
10
+ from .activity import QuickActionSerializer, ActivityEntrySerializer
8
11
 
9
12
 
10
13
  class DashboardOverviewSerializer(serializers.Serializer):
11
14
  """
12
15
  Main serializer for dashboard overview endpoint.
13
- Uses DictField to avoid allOf generation in OpenAPI.
16
+ Uses typed serializers for proper OpenAPI schema generation.
14
17
  """
15
18
 
16
- stat_cards = serializers.ListField(
17
- child=serializers.DictField(),
19
+ stat_cards = StatCardSerializer(
20
+ many=True,
18
21
  help_text="Dashboard statistics cards"
19
22
  )
20
- system_health = serializers.ListField(
21
- child=serializers.DictField(),
23
+ system_health = SystemHealthSerializer(
22
24
  help_text="System health status"
23
25
  )
24
- quick_actions = serializers.ListField(
25
- child=serializers.DictField(),
26
+ quick_actions = QuickActionSerializer(
27
+ many=True,
26
28
  help_text="Quick action buttons"
27
29
  )
28
- recent_activity = serializers.ListField(
29
- child=serializers.DictField(),
30
+ recent_activity = ActivityEntrySerializer(
31
+ many=True,
30
32
  help_text="Recent activity entries"
31
33
  )
32
- system_metrics = serializers.DictField(help_text="System performance metrics")
33
- user_statistics = serializers.DictField(help_text="User statistics")
34
+ system_metrics = SystemMetricsSerializer(
35
+ help_text="System performance metrics"
36
+ )
37
+ user_statistics = UserStatisticsSerializer(
38
+ help_text="User statistics"
39
+ )
40
+ app_statistics = AppStatisticsSerializer(
41
+ many=True,
42
+ required=False,
43
+ help_text="Application statistics"
44
+ )
34
45
  timestamp = serializers.CharField(help_text="Data timestamp (ISO format)")
@@ -11,6 +11,7 @@ from .charts_service import ChartsService
11
11
  from .commands_service import CommandsService
12
12
  from .apizones_service import APIZonesService
13
13
  from .overview_service import OverviewService
14
+ from .crontab_service import CrontabService
14
15
 
15
16
  __all__ = [
16
17
  'StatisticsService',
@@ -19,4 +20,5 @@ __all__ = [
19
20
  'CommandsService',
20
21
  'APIZonesService',
21
22
  'OverviewService',
23
+ 'CrontabService',
22
24
  ]
@@ -0,0 +1,210 @@
1
+ """
2
+ Crontab Service
3
+
4
+ Business logic for crontab/scheduled jobs monitoring and information.
5
+ """
6
+
7
+ import logging
8
+ from datetime import datetime
9
+ from typing import Any, Dict, List
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class CrontabService:
15
+ """
16
+ Service for crontab monitoring and information.
17
+
18
+ %%PRIORITY:HIGH%%
19
+ %%AI_HINT: Provides information about scheduled cron jobs%%
20
+
21
+ TAGS: crontab, scheduling, monitoring, service
22
+ DEPENDS_ON: [django_cfg.core.config]
23
+ """
24
+
25
+ def __init__(self):
26
+ """Initialize crontab service."""
27
+ self.logger = logger
28
+
29
+ def _get_schedule_display(self, job) -> str:
30
+ """
31
+ Generate human-readable schedule description.
32
+
33
+ Args:
34
+ job: CrontabJobConfig instance
35
+
36
+ Returns:
37
+ Human-readable schedule string
38
+ """
39
+ parts = []
40
+
41
+ # Minute
42
+ if job.minute == "*":
43
+ parts.append("every minute")
44
+ elif job.minute.startswith("*/"):
45
+ interval = job.minute[2:]
46
+ parts.append(f"every {interval} minutes")
47
+ else:
48
+ parts.append(f"at minute {job.minute}")
49
+
50
+ # Hour
51
+ if job.hour == "*":
52
+ parts.append("of every hour")
53
+ elif job.hour.startswith("*/"):
54
+ interval = job.hour[2:]
55
+ parts.append(f"every {interval} hours")
56
+ else:
57
+ parts.append(f"at {job.hour}:00")
58
+
59
+ # Day of month
60
+ if job.day_of_month != "*":
61
+ parts.append(f"on day {job.day_of_month}")
62
+
63
+ # Month
64
+ if job.month_of_year != "*":
65
+ months = {
66
+ "1": "January", "2": "February", "3": "March", "4": "April",
67
+ "5": "May", "6": "June", "7": "July", "8": "August",
68
+ "9": "September", "10": "October", "11": "November", "12": "December"
69
+ }
70
+ month_name = months.get(job.month_of_year, job.month_of_year)
71
+ parts.append(f"in {month_name}")
72
+
73
+ # Day of week
74
+ if job.day_of_week != "*":
75
+ days = {
76
+ "0": "Sunday", "1": "Monday", "2": "Tuesday", "3": "Wednesday",
77
+ "4": "Thursday", "5": "Friday", "6": "Saturday"
78
+ }
79
+ if "-" in job.day_of_week:
80
+ parts.append(f"on weekdays ({job.day_of_week})")
81
+ else:
82
+ day_name = days.get(job.day_of_week, job.day_of_week)
83
+ parts.append(f"on {day_name}")
84
+
85
+ return " ".join(parts)
86
+
87
+ def _format_job(self, job) -> Dict[str, Any]:
88
+ """
89
+ Format a single job for API response.
90
+
91
+ Args:
92
+ job: CrontabJobConfig instance
93
+
94
+ Returns:
95
+ Formatted job dictionary
96
+ """
97
+ job_data = {
98
+ 'name': job.name,
99
+ 'job_type': job.job_type,
100
+ 'minute': job.minute,
101
+ 'hour': job.hour,
102
+ 'day_of_month': job.day_of_month,
103
+ 'month_of_year': job.month_of_year,
104
+ 'day_of_week': job.day_of_week,
105
+ 'enabled': job.enabled,
106
+ 'schedule_display': self._get_schedule_display(job),
107
+ }
108
+
109
+ # Add command or callable info
110
+ if job.job_type == 'command':
111
+ job_data['command'] = job.command
112
+ if job.command_args:
113
+ job_data['command_args'] = job.command_args
114
+ if job.command_kwargs:
115
+ job_data['command_kwargs'] = job.command_kwargs
116
+ else:
117
+ job_data['callable_path'] = job.callable_path
118
+
119
+ # Add comment if present
120
+ if job.comment:
121
+ job_data['comment'] = job.comment
122
+
123
+ return job_data
124
+
125
+ def get_all_jobs(self) -> Dict[str, Any]:
126
+ """
127
+ Get all configured cron jobs.
128
+
129
+ Returns:
130
+ Dictionary with jobs list and summary
131
+
132
+ USED_BY: CrontabViewSet.jobs endpoint
133
+ """
134
+ try:
135
+ from django_cfg.core.config import get_current_config
136
+
137
+ config = get_current_config()
138
+
139
+ # Check if crontab is configured
140
+ if not hasattr(config, 'crontab') or not config.crontab:
141
+ return {
142
+ 'enabled': False,
143
+ 'jobs_count': 0,
144
+ 'enabled_jobs_count': 0,
145
+ 'jobs': []
146
+ }
147
+
148
+ crontab_config = config.crontab
149
+
150
+ # Format all jobs
151
+ jobs = [self._format_job(job) for job in crontab_config.jobs]
152
+
153
+ # Count enabled jobs
154
+ enabled_jobs_count = sum(1 for job in crontab_config.jobs if job.enabled)
155
+
156
+ return {
157
+ 'enabled': crontab_config.enabled,
158
+ 'jobs_count': len(jobs),
159
+ 'enabled_jobs_count': enabled_jobs_count,
160
+ 'jobs': jobs
161
+ }
162
+
163
+ except Exception as e:
164
+ self.logger.error(f"Failed to get cron jobs: {e}", exc_info=True)
165
+ raise
166
+
167
+ def get_status(self) -> Dict[str, Any]:
168
+ """
169
+ Get crontab configuration status and summary.
170
+
171
+ Returns:
172
+ Dictionary with crontab configuration status
173
+
174
+ USED_BY: CrontabViewSet.crontab_status endpoint
175
+ """
176
+ try:
177
+ from django_cfg.core.config import get_current_config
178
+
179
+ config = get_current_config()
180
+
181
+ # Check if crontab is configured
182
+ if not hasattr(config, 'crontab') or not config.crontab:
183
+ return {
184
+ 'enabled': False,
185
+ 'jobs_count': 0,
186
+ 'enabled_jobs_count': 0,
187
+ 'lock_jobs': False,
188
+ 'command_prefix': None,
189
+ 'comment': None,
190
+ 'timestamp': datetime.now().isoformat(),
191
+ }
192
+
193
+ crontab_config = config.crontab
194
+
195
+ # Count enabled jobs
196
+ enabled_jobs_count = sum(1 for job in crontab_config.jobs if job.enabled)
197
+
198
+ return {
199
+ 'enabled': crontab_config.enabled,
200
+ 'jobs_count': len(crontab_config.jobs),
201
+ 'enabled_jobs_count': enabled_jobs_count,
202
+ 'lock_jobs': crontab_config.lock_jobs,
203
+ 'command_prefix': crontab_config.command_prefix,
204
+ 'comment': crontab_config.comment,
205
+ 'timestamp': datetime.now().isoformat(),
206
+ }
207
+
208
+ except Exception as e:
209
+ self.logger.error(f"Failed to get crontab status: {e}", exc_info=True)
210
+ raise
@@ -191,6 +191,77 @@ class SystemHealthService:
191
191
  'health_percentage': 0,
192
192
  }
193
193
 
194
+ def check_crontab_health(self) -> Dict[str, Any]:
195
+ """
196
+ Check crontab/scheduled jobs configuration and status.
197
+
198
+ Returns:
199
+ Health status dictionary with job count and configuration details
200
+ """
201
+ try:
202
+ from django_cfg.core.config import get_current_config
203
+
204
+ config = get_current_config()
205
+
206
+ # Check if crontab is configured
207
+ if not hasattr(config, 'crontab') or not config.crontab:
208
+ return {
209
+ 'component': 'crontab',
210
+ 'status': 'info',
211
+ 'description': 'Crontab scheduling not configured',
212
+ 'last_check': datetime.now().isoformat(),
213
+ 'health_percentage': 100,
214
+ 'details': {
215
+ 'enabled': False,
216
+ 'jobs_count': 0,
217
+ }
218
+ }
219
+
220
+ crontab_config = config.crontab
221
+
222
+ # Check if enabled
223
+ if not crontab_config.enabled:
224
+ return {
225
+ 'component': 'crontab',
226
+ 'status': 'warning',
227
+ 'description': 'Crontab scheduling is disabled',
228
+ 'last_check': datetime.now().isoformat(),
229
+ 'health_percentage': 50,
230
+ 'details': {
231
+ 'enabled': False,
232
+ 'jobs_count': len(crontab_config.jobs),
233
+ }
234
+ }
235
+
236
+ # Count enabled jobs
237
+ enabled_jobs = [job for job in crontab_config.jobs if job.enabled]
238
+ jobs_count = len(enabled_jobs)
239
+
240
+ return {
241
+ 'component': 'crontab',
242
+ 'status': 'healthy',
243
+ 'description': f'{jobs_count} scheduled job(s) configured',
244
+ 'last_check': datetime.now().isoformat(),
245
+ 'health_percentage': 100,
246
+ 'details': {
247
+ 'enabled': True,
248
+ 'jobs_count': jobs_count,
249
+ 'total_jobs': len(crontab_config.jobs),
250
+ 'lock_jobs': crontab_config.lock_jobs,
251
+ 'comment': crontab_config.comment,
252
+ }
253
+ }
254
+
255
+ except Exception as e:
256
+ self.logger.error(f"Crontab health check failed: {e}")
257
+ return {
258
+ 'component': 'crontab',
259
+ 'status': 'error',
260
+ 'description': f'Crontab check error: {str(e)}',
261
+ 'last_check': datetime.now().isoformat(),
262
+ 'health_percentage': 0,
263
+ }
264
+
194
265
  def get_all_health_checks(self) -> List[Dict[str, Any]]:
195
266
  """
196
267
  Run all health checks and return aggregated results.
@@ -205,6 +276,7 @@ class SystemHealthService:
205
276
  self.check_cache_health(),
206
277
  self.check_queue_health(),
207
278
  self.check_storage_health(),
279
+ self.check_crontab_health(),
208
280
  ]
209
281
 
210
282
  return checks
@@ -21,6 +21,7 @@ from .views import (
21
21
  ChartsViewSet,
22
22
  CommandsViewSet,
23
23
  APIZonesViewSet,
24
+ CrontabViewSet,
24
25
  )
25
26
 
26
27
  app_name = 'django_cfg_dashboard'
@@ -34,6 +35,7 @@ router.register(r'activity', ActivityViewSet, basename='activity')
34
35
  router.register(r'charts', ChartsViewSet, basename='charts')
35
36
  router.register(r'commands', CommandsViewSet, basename='commands')
36
37
  router.register(r'zones', APIZonesViewSet, basename='zones')
38
+ router.register(r'crontab', CrontabViewSet, basename='crontab')
37
39
 
38
40
  urlpatterns = [
39
41
  # RESTful API endpoints using ViewSets
@@ -11,6 +11,7 @@ from .activity_views import ActivityViewSet
11
11
  from .charts_views import ChartsViewSet
12
12
  from .commands_views import CommandsViewSet
13
13
  from .apizones_views import APIZonesViewSet
14
+ from .crontab_views import CrontabViewSet
14
15
 
15
16
  __all__ = [
16
17
  'OverviewViewSet',
@@ -20,4 +21,5 @@ __all__ = [
20
21
  'ChartsViewSet',
21
22
  'CommandsViewSet',
22
23
  'APIZonesViewSet',
24
+ 'CrontabViewSet',
23
25
  ]
@@ -0,0 +1,72 @@
1
+ """
2
+ Crontab ViewSet
3
+
4
+ Endpoints for cron job monitoring:
5
+ - GET /crontab/jobs/ - List all configured cron jobs
6
+ - GET /crontab/status/ - Crontab configuration status
7
+ """
8
+
9
+ import logging
10
+
11
+ from drf_spectacular.utils import extend_schema
12
+ from rest_framework import status, viewsets
13
+
14
+ from django_cfg.mixins import AdminAPIMixin
15
+ from rest_framework.decorators import action
16
+ from rest_framework.response import Response
17
+
18
+ from ..services import CrontabService
19
+ from ..serializers import CrontabJobsSerializer, CrontabStatusSerializer
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class CrontabViewSet(AdminAPIMixin, viewsets.GenericViewSet):
25
+ """
26
+ Crontab Monitoring ViewSet
27
+
28
+ Provides endpoints for monitoring scheduled cron jobs.
29
+ Requires admin authentication (JWT, Session, or Basic Auth).
30
+ """
31
+
32
+ serializer_class = CrontabJobsSerializer
33
+
34
+ @extend_schema(
35
+ summary="Get all cron jobs",
36
+ description="Retrieve list of all configured cron jobs with schedules and details",
37
+ responses={200: CrontabJobsSerializer},
38
+ tags=["Dashboard - Crontab"]
39
+ )
40
+ @action(detail=False, methods=['get'], url_path='jobs', serializer_class=CrontabJobsSerializer)
41
+ def jobs(self, request):
42
+ """Get all configured cron jobs."""
43
+ try:
44
+ crontab_service = CrontabService()
45
+ jobs_data = crontab_service.get_all_jobs()
46
+ return Response(jobs_data)
47
+
48
+ except Exception as e:
49
+ logger.error(f"Crontab jobs API error: {e}", exc_info=True)
50
+ return Response({
51
+ 'error': str(e)
52
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
53
+
54
+ @extend_schema(
55
+ summary="Get crontab status",
56
+ description="Retrieve crontab configuration status and summary",
57
+ responses={200: CrontabStatusSerializer},
58
+ tags=["Dashboard - Crontab"]
59
+ )
60
+ @action(detail=False, methods=['get'], url_path='status', serializer_class=CrontabStatusSerializer)
61
+ def crontab_status(self, request):
62
+ """Get crontab configuration status."""
63
+ try:
64
+ crontab_service = CrontabService()
65
+ status_data = crontab_service.get_status()
66
+ return Response(status_data)
67
+
68
+ except Exception as e:
69
+ logger.error(f"Crontab status API error: {e}", exc_info=True)
70
+ return Response({
71
+ 'error': str(e)
72
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@@ -56,14 +56,28 @@ class OverviewViewSet(AdminAPIMixin, viewsets.GenericViewSet):
56
56
  health_service = SystemHealthService()
57
57
  charts_service = ChartsService()
58
58
 
59
+ # Get app statistics and convert to list format
60
+ app_stats_dict = stats_service.get_app_statistics()
61
+ app_statistics_list = [
62
+ {
63
+ 'app_name': app_label,
64
+ 'statistics': {
65
+ 'name': app_data.get('name', ''),
66
+ 'total_records': app_data.get('total_records', 0),
67
+ 'model_count': app_data.get('model_count', 0),
68
+ }
69
+ }
70
+ for app_label, app_data in app_stats_dict.get('apps', {}).items()
71
+ ]
72
+
59
73
  data = {
60
74
  # Statistics
61
75
  'stat_cards': stats_service.get_stat_cards(),
62
76
  'user_statistics': stats_service.get_user_statistics(),
63
- 'app_statistics': stats_service.get_app_statistics(),
77
+ 'app_statistics': app_statistics_list,
64
78
 
65
79
  # System
66
- 'system_health': health_service.get_all_health_checks(),
80
+ 'system_health': health_service.get_overall_health_status(),
67
81
  'system_metrics': stats_service.get_system_metrics(),
68
82
 
69
83
  # Activity
django_cfg/config.py CHANGED
@@ -12,15 +12,14 @@ from .modules.django_unfold.models.dropdown import SiteDropdownItem
12
12
  # Library configuration
13
13
  LIB_NAME = "django-cfg"
14
14
  LIB_SITE_URL = "https://djangocfg.com"
15
- LIB_DOCS_URL = "https://docs.djangocfg.com"
16
15
  LIB_GITHUB_URL = "https://github.com/django-cfg/django-cfg"
17
- LIB_SUPPORT_URL = "https://djangocfg.com/support"
16
+ LIB_SUPPORT_URL = "https://demo.djangocfg.com"
18
17
  LIB_HEALTH_URL = "/cfg/health/"
19
18
 
20
19
  def get_maintenance_url(domain: str) -> str:
21
20
  """Get the maintenance URL for the current site."""
22
21
  # return f"{LIB_SITE_URL}/maintenance/{domain}/"
23
- return f"{LIB_DOCS_URL}/maintenance?site={domain}"
22
+ return f"{LIB_SITE_URL}/maintenance?site={domain}"
24
23
 
25
24
  def get_default_dropdown_items() -> List[SiteDropdownItem]:
26
25
  """Get default dropdown menu items for Unfold admin."""
@@ -28,7 +27,7 @@ def get_default_dropdown_items() -> List[SiteDropdownItem]:
28
27
  SiteDropdownItem(
29
28
  title="Documentation",
30
29
  icon=Icons.HELP_OUTLINE,
31
- link=LIB_DOCS_URL,
30
+ link=LIB_SITE_URL,
32
31
  ),
33
32
  SiteDropdownItem(
34
33
  title="GitHub",
@@ -20,6 +20,7 @@ from ...models import (
20
20
  ApiKeys,
21
21
  AxesConfig,
22
22
  CacheConfig,
23
+ CrontabConfig,
23
24
  CryptoFieldsConfig,
24
25
  DatabaseConfig,
25
26
  DRFConfig,
@@ -318,6 +319,12 @@ class DjangoConfig(BaseModel):
318
319
  description="Background task processing configuration (ReArq)",
319
320
  )
320
321
 
322
+ # === Crontab Scheduling ===
323
+ crontab: Optional[CrontabConfig] = Field(
324
+ default=None,
325
+ description="Crontab scheduling configuration (django-crontab integration)",
326
+ )
327
+
321
328
  # === Centrifugo Configuration ===
322
329
  centrifugo: Optional[DjangoCfgCentrifugoConfig] = Field(
323
330
  default=None,
@@ -163,6 +163,10 @@ class InstalledAppsBuilder:
163
163
  # No external app needed - ReArq is embedded
164
164
  apps.append("django_cfg.apps.tasks")
165
165
 
166
+ # Add django-crontab if enabled
167
+ if hasattr(self.config, "crontab") and self.config.crontab and self.config.crontab.enabled:
168
+ apps.append("django_crontab")
169
+
166
170
  # Add DRF Tailwind theme module (uses Tailwind via CDN)
167
171
  if self.config.enable_drf_tailwind:
168
172
  apps.append("django_cfg.modules.django_drf_theme.apps.DjangoDRFThemeConfig")