django-cfg 1.4.110__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/dashboard/serializers/__init__.py +10 -0
- django_cfg/apps/dashboard/serializers/crontab.py +84 -0
- django_cfg/apps/dashboard/serializers/overview.py +22 -11
- django_cfg/apps/dashboard/services/__init__.py +2 -0
- django_cfg/apps/dashboard/services/crontab_service.py +210 -0
- django_cfg/apps/dashboard/services/system_health_service.py +72 -0
- django_cfg/apps/dashboard/urls.py +2 -0
- django_cfg/apps/dashboard/views/__init__.py +2 -0
- django_cfg/apps/dashboard/views/crontab_views.py +72 -0
- django_cfg/apps/dashboard/views/overview_views.py +16 -2
- django_cfg/config.py +3 -4
- django_cfg/core/base/config_model.py +7 -0
- django_cfg/core/builders/apps_builder.py +4 -0
- django_cfg/core/generation/integration_generators/__init__.py +3 -0
- django_cfg/core/generation/integration_generators/crontab.py +64 -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/crontab.py +303 -0
- django_cfg/modules/django_admin/base/pydantic_admin.py +10 -0
- django_cfg/modules/django_admin/templates/django_admin/documentation_block.html +7 -1
- django_cfg/modules/django_admin/utils/html_builder.py +50 -2
- django_cfg/modules/django_admin/utils/markdown_renderer.py +19 -3
- django_cfg/modules/django_admin/utils/mermaid_plugin.py +288 -0
- 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.110.dist-info → django_cfg-1.4.113.dist-info}/METADATA +2 -1
- {django_cfg-1.4.110.dist-info → django_cfg-1.4.113.dist-info}/RECORD +37 -31
- {django_cfg-1.4.110.dist-info → django_cfg-1.4.113.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.110.dist-info → django_cfg-1.4.113.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.110.dist-info → django_cfg-1.4.113.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -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
|
|
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 .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':
|
|
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
|
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
|
+
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")
|