django-cfg 1.4.111__py3-none-any.whl → 1.4.114__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/dashboard/serializers/__init__.py +12 -0
- django_cfg/apps/dashboard/serializers/django_q2.py +50 -0
- django_cfg/apps/dashboard/serializers/overview.py +22 -11
- django_cfg/apps/dashboard/services/__init__.py +2 -0
- django_cfg/apps/dashboard/services/django_q2_service.py +159 -0
- django_cfg/apps/dashboard/services/system_health_service.py +85 -0
- django_cfg/apps/dashboard/urls.py +2 -0
- django_cfg/apps/dashboard/views/__init__.py +2 -0
- django_cfg/apps/dashboard/views/django_q2_views.py +79 -0
- django_cfg/apps/dashboard/views/overview_views.py +16 -2
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +16 -0
- django_cfg/config.py +3 -4
- django_cfg/core/base/config_model.py +18 -1
- django_cfg/core/builders/apps_builder.py +4 -0
- django_cfg/core/generation/data_generators/cache.py +28 -2
- django_cfg/core/generation/integration_generators/__init__.py +4 -0
- django_cfg/core/generation/integration_generators/django_q2.py +133 -0
- django_cfg/core/generation/orchestrator.py +13 -0
- django_cfg/core/integration/display/startup.py +2 -2
- django_cfg/core/integration/url_integration.py +2 -2
- django_cfg/models/__init__.py +3 -0
- django_cfg/models/django/__init__.py +3 -0
- django_cfg/models/django/django_q2.py +491 -0
- django_cfg/modules/django_admin/utils/html_builder.py +50 -2
- django_cfg/pyproject.toml +2 -2
- django_cfg/registry/core.py +4 -0
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +389 -166
- django_cfg/templatetags/django_cfg.py +8 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/METADATA +3 -1
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/RECORD +35 -29
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -22,6 +22,12 @@ from .commands import (
|
|
|
22
22
|
CommandHelpResponseSerializer,
|
|
23
23
|
)
|
|
24
24
|
from .apizones import APIZoneSerializer, APIZonesSummarySerializer
|
|
25
|
+
from .django_q2 import (
|
|
26
|
+
DjangoQ2ScheduleSerializer,
|
|
27
|
+
DjangoQ2TaskSerializer,
|
|
28
|
+
DjangoQ2StatusSerializer,
|
|
29
|
+
DjangoQ2SummarySerializer,
|
|
30
|
+
)
|
|
25
31
|
|
|
26
32
|
__all__ = [
|
|
27
33
|
# Base
|
|
@@ -59,4 +65,10 @@ __all__ = [
|
|
|
59
65
|
# API Zones
|
|
60
66
|
'APIZoneSerializer',
|
|
61
67
|
'APIZonesSummarySerializer',
|
|
68
|
+
|
|
69
|
+
# Django-Q2
|
|
70
|
+
'DjangoQ2ScheduleSerializer',
|
|
71
|
+
'DjangoQ2TaskSerializer',
|
|
72
|
+
'DjangoQ2StatusSerializer',
|
|
73
|
+
'DjangoQ2SummarySerializer',
|
|
62
74
|
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django-Q2 Task Serializers
|
|
3
|
+
|
|
4
|
+
Serializers for displaying Django-Q2 scheduled tasks and task history.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from rest_framework import serializers
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DjangoQ2ScheduleSerializer(serializers.Serializer):
|
|
11
|
+
"""Serializer for Django-Q2 scheduled tasks."""
|
|
12
|
+
|
|
13
|
+
id = serializers.IntegerField(read_only=True)
|
|
14
|
+
name = serializers.CharField(max_length=255)
|
|
15
|
+
func = serializers.CharField(max_length=512)
|
|
16
|
+
schedule_type = serializers.CharField(max_length=24)
|
|
17
|
+
repeats = serializers.IntegerField()
|
|
18
|
+
next_run = serializers.DateTimeField(allow_null=True)
|
|
19
|
+
last_run = serializers.DateTimeField(allow_null=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DjangoQ2TaskSerializer(serializers.Serializer):
|
|
23
|
+
"""Serializer for Django-Q2 task execution history."""
|
|
24
|
+
|
|
25
|
+
id = serializers.CharField(max_length=32)
|
|
26
|
+
name = serializers.CharField(max_length=255)
|
|
27
|
+
func = serializers.CharField(max_length=512)
|
|
28
|
+
started = serializers.DateTimeField()
|
|
29
|
+
stopped = serializers.DateTimeField(allow_null=True)
|
|
30
|
+
success = serializers.BooleanField()
|
|
31
|
+
result = serializers.CharField(allow_null=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DjangoQ2StatusSerializer(serializers.Serializer):
|
|
35
|
+
"""Serializer for Django-Q2 cluster status."""
|
|
36
|
+
|
|
37
|
+
cluster_running = serializers.BooleanField()
|
|
38
|
+
total_schedules = serializers.IntegerField()
|
|
39
|
+
active_schedules = serializers.IntegerField()
|
|
40
|
+
recent_tasks = serializers.IntegerField()
|
|
41
|
+
successful_tasks = serializers.IntegerField()
|
|
42
|
+
failed_tasks = serializers.IntegerField()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DjangoQ2SummarySerializer(serializers.Serializer):
|
|
46
|
+
"""Summary serializer for Django-Q2 dashboard."""
|
|
47
|
+
|
|
48
|
+
status = DjangoQ2StatusSerializer()
|
|
49
|
+
schedules = DjangoQ2ScheduleSerializer(many=True)
|
|
50
|
+
recent_tasks = DjangoQ2TaskSerializer(many=True)
|
|
@@ -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
|
|
16
|
+
Uses typed serializers for proper OpenAPI schema generation.
|
|
14
17
|
"""
|
|
15
18
|
|
|
16
|
-
stat_cards =
|
|
17
|
-
|
|
19
|
+
stat_cards = StatCardSerializer(
|
|
20
|
+
many=True,
|
|
18
21
|
help_text="Dashboard statistics cards"
|
|
19
22
|
)
|
|
20
|
-
system_health =
|
|
21
|
-
child=serializers.DictField(),
|
|
23
|
+
system_health = SystemHealthSerializer(
|
|
22
24
|
help_text="System health status"
|
|
23
25
|
)
|
|
24
|
-
quick_actions =
|
|
25
|
-
|
|
26
|
+
quick_actions = QuickActionSerializer(
|
|
27
|
+
many=True,
|
|
26
28
|
help_text="Quick action buttons"
|
|
27
29
|
)
|
|
28
|
-
recent_activity =
|
|
29
|
-
|
|
30
|
+
recent_activity = ActivityEntrySerializer(
|
|
31
|
+
many=True,
|
|
30
32
|
help_text="Recent activity entries"
|
|
31
33
|
)
|
|
32
|
-
system_metrics =
|
|
33
|
-
|
|
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 .django_q2_service import DjangoQ2Service
|
|
14
15
|
|
|
15
16
|
__all__ = [
|
|
16
17
|
'StatisticsService',
|
|
@@ -19,4 +20,5 @@ __all__ = [
|
|
|
19
20
|
'CommandsService',
|
|
20
21
|
'APIZonesService',
|
|
21
22
|
'OverviewService',
|
|
23
|
+
'DjangoQ2Service',
|
|
22
24
|
]
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django-Q2 Service
|
|
3
|
+
|
|
4
|
+
Business logic for collecting Django-Q2 task data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List, Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DjangoQ2Service:
|
|
11
|
+
"""Service for aggregating Django-Q2 task information."""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def get_schedules() -> List[Dict[str, Any]]:
|
|
15
|
+
"""
|
|
16
|
+
Get all scheduled tasks from Django-Q2.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
List of scheduled tasks with their configurations
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
from django_q.models import Schedule
|
|
23
|
+
|
|
24
|
+
schedules = Schedule.objects.all().order_by('-next_run')
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
'id': schedule.id,
|
|
28
|
+
'name': schedule.name,
|
|
29
|
+
'func': schedule.func,
|
|
30
|
+
'schedule_type': schedule.schedule_type,
|
|
31
|
+
'repeats': schedule.repeats,
|
|
32
|
+
'next_run': schedule.next_run,
|
|
33
|
+
'last_run': getattr(schedule, 'last_run', None),
|
|
34
|
+
}
|
|
35
|
+
for schedule in schedules
|
|
36
|
+
]
|
|
37
|
+
except ImportError:
|
|
38
|
+
# django-q2 not installed
|
|
39
|
+
return []
|
|
40
|
+
except Exception:
|
|
41
|
+
# Database error or table doesn't exist yet
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def get_recent_tasks(limit: int = 20) -> List[Dict[str, Any]]:
|
|
46
|
+
"""
|
|
47
|
+
Get recent task executions from Django-Q2.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
limit: Maximum number of tasks to return
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List of recent task executions with their results
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
from django_q.models import Task
|
|
57
|
+
|
|
58
|
+
tasks = Task.objects.all().order_by('-started')[:limit]
|
|
59
|
+
return [
|
|
60
|
+
{
|
|
61
|
+
'id': task.id,
|
|
62
|
+
'name': task.name,
|
|
63
|
+
'func': task.func,
|
|
64
|
+
'started': task.started,
|
|
65
|
+
'stopped': task.stopped,
|
|
66
|
+
'success': task.success,
|
|
67
|
+
'result': str(task.result) if task.result else None,
|
|
68
|
+
}
|
|
69
|
+
for task in tasks
|
|
70
|
+
]
|
|
71
|
+
except ImportError:
|
|
72
|
+
# django-q2 not installed
|
|
73
|
+
return []
|
|
74
|
+
except Exception:
|
|
75
|
+
# Database error or table doesn't exist yet
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def get_cluster_status() -> Dict[str, Any]:
|
|
80
|
+
"""
|
|
81
|
+
Get Django-Q2 cluster status.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dictionary with cluster status information
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
from django_q.models import Schedule, Task
|
|
88
|
+
from django_q.cluster import Cluster
|
|
89
|
+
|
|
90
|
+
# Count schedules
|
|
91
|
+
total_schedules = Schedule.objects.count()
|
|
92
|
+
active_schedules = Schedule.objects.filter(repeats__gt=0).count()
|
|
93
|
+
|
|
94
|
+
# Count recent tasks (last 24 hours)
|
|
95
|
+
from django.utils import timezone
|
|
96
|
+
from datetime import timedelta
|
|
97
|
+
|
|
98
|
+
last_24h = timezone.now() - timedelta(hours=24)
|
|
99
|
+
recent_tasks = Task.objects.filter(started__gte=last_24h).count()
|
|
100
|
+
successful_tasks = Task.objects.filter(
|
|
101
|
+
started__gte=last_24h, success=True
|
|
102
|
+
).count()
|
|
103
|
+
failed_tasks = Task.objects.filter(
|
|
104
|
+
started__gte=last_24h, success=False
|
|
105
|
+
).count()
|
|
106
|
+
|
|
107
|
+
# Check if cluster is running
|
|
108
|
+
cluster_running = False
|
|
109
|
+
try:
|
|
110
|
+
# Check for recent task activity as proxy for cluster status
|
|
111
|
+
recent_task = Task.objects.filter(
|
|
112
|
+
started__gte=timezone.now() - timedelta(minutes=5)
|
|
113
|
+
).exists()
|
|
114
|
+
cluster_running = recent_task
|
|
115
|
+
except Exception:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
'cluster_running': cluster_running,
|
|
120
|
+
'total_schedules': total_schedules,
|
|
121
|
+
'active_schedules': active_schedules,
|
|
122
|
+
'recent_tasks': recent_tasks,
|
|
123
|
+
'successful_tasks': successful_tasks,
|
|
124
|
+
'failed_tasks': failed_tasks,
|
|
125
|
+
}
|
|
126
|
+
except ImportError:
|
|
127
|
+
# django-q2 not installed
|
|
128
|
+
return {
|
|
129
|
+
'cluster_running': False,
|
|
130
|
+
'total_schedules': 0,
|
|
131
|
+
'active_schedules': 0,
|
|
132
|
+
'recent_tasks': 0,
|
|
133
|
+
'successful_tasks': 0,
|
|
134
|
+
'failed_tasks': 0,
|
|
135
|
+
}
|
|
136
|
+
except Exception:
|
|
137
|
+
# Database error or table doesn't exist yet
|
|
138
|
+
return {
|
|
139
|
+
'cluster_running': False,
|
|
140
|
+
'total_schedules': 0,
|
|
141
|
+
'active_schedules': 0,
|
|
142
|
+
'recent_tasks': 0,
|
|
143
|
+
'successful_tasks': 0,
|
|
144
|
+
'failed_tasks': 0,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def get_summary(cls) -> Dict[str, Any]:
|
|
149
|
+
"""
|
|
150
|
+
Get complete Django-Q2 summary for dashboard.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Dictionary with status, schedules, and recent tasks
|
|
154
|
+
"""
|
|
155
|
+
return {
|
|
156
|
+
'status': cls.get_cluster_status(),
|
|
157
|
+
'schedules': cls.get_schedules(),
|
|
158
|
+
'recent_tasks': cls.get_recent_tasks(limit=10),
|
|
159
|
+
}
|
|
@@ -191,6 +191,90 @@ class SystemHealthService:
|
|
|
191
191
|
'health_percentage': 0,
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
def check_django_q2_health(self) -> Dict[str, Any]:
|
|
195
|
+
"""
|
|
196
|
+
Check Django-Q2 task scheduling configuration and status.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Health status dictionary with schedule count and cluster status
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
from django_cfg.core.config import get_current_config
|
|
203
|
+
|
|
204
|
+
config = get_current_config()
|
|
205
|
+
|
|
206
|
+
# Check if django_q2 is configured
|
|
207
|
+
if not hasattr(config, 'django_q2') or not config.django_q2:
|
|
208
|
+
return {
|
|
209
|
+
'component': 'django_q2',
|
|
210
|
+
'status': 'info',
|
|
211
|
+
'description': 'Django-Q2 scheduling not configured',
|
|
212
|
+
'last_check': datetime.now().isoformat(),
|
|
213
|
+
'health_percentage': 100,
|
|
214
|
+
'details': {
|
|
215
|
+
'enabled': False,
|
|
216
|
+
'schedules_count': 0,
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
django_q2_config = config.django_q2
|
|
221
|
+
|
|
222
|
+
# Check if enabled
|
|
223
|
+
if not django_q2_config.enabled:
|
|
224
|
+
return {
|
|
225
|
+
'component': 'django_q2',
|
|
226
|
+
'status': 'warning',
|
|
227
|
+
'description': 'Django-Q2 scheduling is disabled',
|
|
228
|
+
'last_check': datetime.now().isoformat(),
|
|
229
|
+
'health_percentage': 50,
|
|
230
|
+
'details': {
|
|
231
|
+
'enabled': False,
|
|
232
|
+
'schedules_count': len(django_q2_config.schedules) if django_q2_config.schedules else 0,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Count schedules
|
|
237
|
+
schedules_count = len(django_q2_config.schedules) if django_q2_config.schedules else 0
|
|
238
|
+
|
|
239
|
+
# Try to check cluster status from database
|
|
240
|
+
cluster_running = False
|
|
241
|
+
try:
|
|
242
|
+
from django_q.models import Schedule, Task
|
|
243
|
+
from django.utils import timezone
|
|
244
|
+
from datetime import timedelta
|
|
245
|
+
|
|
246
|
+
# Check for recent task activity
|
|
247
|
+
recent_task = Task.objects.filter(
|
|
248
|
+
started__gte=timezone.now() - timedelta(minutes=5)
|
|
249
|
+
).exists()
|
|
250
|
+
cluster_running = recent_task
|
|
251
|
+
except Exception:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
'component': 'django_q2',
|
|
256
|
+
'status': 'healthy',
|
|
257
|
+
'description': f'{schedules_count} schedule(s) configured, cluster {"running" if cluster_running else "idle"}',
|
|
258
|
+
'last_check': datetime.now().isoformat(),
|
|
259
|
+
'health_percentage': 100,
|
|
260
|
+
'details': {
|
|
261
|
+
'enabled': True,
|
|
262
|
+
'schedules_count': schedules_count,
|
|
263
|
+
'cluster_running': cluster_running,
|
|
264
|
+
'workers': django_q2_config.workers,
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
self.logger.error(f"Django-Q2 health check failed: {e}")
|
|
270
|
+
return {
|
|
271
|
+
'component': 'django_q2',
|
|
272
|
+
'status': 'error',
|
|
273
|
+
'description': f'Django-Q2 check error: {str(e)}',
|
|
274
|
+
'last_check': datetime.now().isoformat(),
|
|
275
|
+
'health_percentage': 0,
|
|
276
|
+
}
|
|
277
|
+
|
|
194
278
|
def get_all_health_checks(self) -> List[Dict[str, Any]]:
|
|
195
279
|
"""
|
|
196
280
|
Run all health checks and return aggregated results.
|
|
@@ -205,6 +289,7 @@ class SystemHealthService:
|
|
|
205
289
|
self.check_cache_health(),
|
|
206
290
|
self.check_queue_health(),
|
|
207
291
|
self.check_storage_health(),
|
|
292
|
+
self.check_django_q2_health(),
|
|
208
293
|
]
|
|
209
294
|
|
|
210
295
|
return checks
|
|
@@ -21,6 +21,7 @@ from .views import (
|
|
|
21
21
|
ChartsViewSet,
|
|
22
22
|
CommandsViewSet,
|
|
23
23
|
APIZonesViewSet,
|
|
24
|
+
DjangoQ2ViewSet,
|
|
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'django_q2', DjangoQ2ViewSet, basename='django_q2')
|
|
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 .django_q2_views import DjangoQ2ViewSet
|
|
14
15
|
|
|
15
16
|
__all__ = [
|
|
16
17
|
'OverviewViewSet',
|
|
@@ -20,4 +21,5 @@ __all__ = [
|
|
|
20
21
|
'ChartsViewSet',
|
|
21
22
|
'CommandsViewSet',
|
|
22
23
|
'APIZonesViewSet',
|
|
24
|
+
'DjangoQ2ViewSet',
|
|
23
25
|
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django-Q2 ViewSet
|
|
3
|
+
|
|
4
|
+
API endpoints for Django-Q2 task monitoring.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from rest_framework import viewsets, status
|
|
8
|
+
from rest_framework.decorators import action
|
|
9
|
+
from rest_framework.response import Response
|
|
10
|
+
from rest_framework.permissions import IsAuthenticated
|
|
11
|
+
|
|
12
|
+
from ..services.django_q2_service import DjangoQ2Service
|
|
13
|
+
from ..serializers.django_q2 import (
|
|
14
|
+
DjangoQ2ScheduleSerializer,
|
|
15
|
+
DjangoQ2TaskSerializer,
|
|
16
|
+
DjangoQ2StatusSerializer,
|
|
17
|
+
DjangoQ2SummarySerializer,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DjangoQ2ViewSet(viewsets.ViewSet):
|
|
22
|
+
"""
|
|
23
|
+
ViewSet for Django-Q2 task monitoring.
|
|
24
|
+
|
|
25
|
+
Provides endpoints for:
|
|
26
|
+
- Scheduled tasks list
|
|
27
|
+
- Recent task executions
|
|
28
|
+
- Cluster status
|
|
29
|
+
- Complete summary
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
permission_classes = [IsAuthenticated]
|
|
33
|
+
|
|
34
|
+
@action(detail=False, methods=['get'])
|
|
35
|
+
def schedules(self, request):
|
|
36
|
+
"""
|
|
37
|
+
Get all scheduled tasks.
|
|
38
|
+
|
|
39
|
+
GET /api/django_q2/schedules/
|
|
40
|
+
"""
|
|
41
|
+
schedules = DjangoQ2Service.get_schedules()
|
|
42
|
+
serializer = DjangoQ2ScheduleSerializer(schedules, many=True)
|
|
43
|
+
return Response(serializer.data)
|
|
44
|
+
|
|
45
|
+
@action(detail=False, methods=['get'])
|
|
46
|
+
def tasks(self, request):
|
|
47
|
+
"""
|
|
48
|
+
Get recent task executions.
|
|
49
|
+
|
|
50
|
+
GET /api/django_q2/tasks/
|
|
51
|
+
Query params:
|
|
52
|
+
- limit: Number of tasks to return (default: 20)
|
|
53
|
+
"""
|
|
54
|
+
limit = int(request.query_params.get('limit', 20))
|
|
55
|
+
tasks = DjangoQ2Service.get_recent_tasks(limit=limit)
|
|
56
|
+
serializer = DjangoQ2TaskSerializer(tasks, many=True)
|
|
57
|
+
return Response(serializer.data)
|
|
58
|
+
|
|
59
|
+
@action(detail=False, methods=['get'])
|
|
60
|
+
def status(self, request):
|
|
61
|
+
"""
|
|
62
|
+
Get Django-Q2 cluster status.
|
|
63
|
+
|
|
64
|
+
GET /api/django_q2/status/
|
|
65
|
+
"""
|
|
66
|
+
status_data = DjangoQ2Service.get_cluster_status()
|
|
67
|
+
serializer = DjangoQ2StatusSerializer(status_data)
|
|
68
|
+
return Response(serializer.data)
|
|
69
|
+
|
|
70
|
+
def list(self, request):
|
|
71
|
+
"""
|
|
72
|
+
Get complete Django-Q2 summary.
|
|
73
|
+
|
|
74
|
+
GET /api/django_q2/
|
|
75
|
+
Returns status, schedules, and recent tasks.
|
|
76
|
+
"""
|
|
77
|
+
summary = DjangoQ2Service.get_summary()
|
|
78
|
+
serializer = DjangoQ2SummarySerializer(summary)
|
|
79
|
+
return Response(serializer.data)
|
|
@@ -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':
|
|
77
|
+
'app_statistics': app_statistics_list,
|
|
64
78
|
|
|
65
79
|
# System
|
|
66
|
-
'system_health': health_service.
|
|
80
|
+
'system_health': health_service.get_overall_health_status(),
|
|
67
81
|
'system_metrics': stats_service.get_system_metrics(),
|
|
68
82
|
|
|
69
83
|
# Activity
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-10-31 05:54
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("tasks", "0001_initial"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.DeleteModel(
|
|
14
|
+
name="TaskLog",
|
|
15
|
+
),
|
|
16
|
+
]
|
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
|
|
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"{
|
|
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=
|
|
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
|
+
DjangoQ2Config,
|
|
23
24
|
CryptoFieldsConfig,
|
|
24
25
|
DatabaseConfig,
|
|
25
26
|
DRFConfig,
|
|
@@ -216,9 +217,19 @@ class DjangoConfig(BaseModel):
|
|
|
216
217
|
)
|
|
217
218
|
|
|
218
219
|
# === Cache Configuration ===
|
|
220
|
+
# Redis URL - used for automatic cache configuration if cache_default is not set
|
|
221
|
+
redis_url: Optional[str] = Field(
|
|
222
|
+
default=None,
|
|
223
|
+
description=(
|
|
224
|
+
"Redis connection URL (redis://host:port/db). "
|
|
225
|
+
"If set and cache_default is None, automatically creates RedisCache backend. "
|
|
226
|
+
"Also used by tasks (ReArq, Django-Q2) if they don't specify broker_url."
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
|
|
219
230
|
cache_default: Optional[CacheConfig] = Field(
|
|
220
231
|
default=None,
|
|
221
|
-
description="Default cache backend",
|
|
232
|
+
description="Default cache backend (auto-created from redis_url if not set)",
|
|
222
233
|
)
|
|
223
234
|
|
|
224
235
|
cache_sessions: Optional[CacheConfig] = Field(
|
|
@@ -318,6 +329,12 @@ class DjangoConfig(BaseModel):
|
|
|
318
329
|
description="Background task processing configuration (ReArq)",
|
|
319
330
|
)
|
|
320
331
|
|
|
332
|
+
# === Django-Q2 Task Scheduling ===
|
|
333
|
+
django_q2: Optional[DjangoQ2Config] = Field(
|
|
334
|
+
default=None,
|
|
335
|
+
description="Django-Q2 task scheduling and queue configuration",
|
|
336
|
+
)
|
|
337
|
+
|
|
321
338
|
# === Centrifugo Configuration ===
|
|
322
339
|
centrifugo: Optional[DjangoCfgCentrifugoConfig] = Field(
|
|
323
340
|
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-q2 if enabled
|
|
167
|
+
if hasattr(self.config, "django_q2") and self.config.django_q2 and self.config.django_q2.enabled:
|
|
168
|
+
apps.append("django_q")
|
|
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")
|