django-cfg 1.2.17__py3-none-any.whl → 1.2.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/models/__init__.py +68 -0
- django_cfg/apps/accounts/models/activity.py +34 -0
- django_cfg/apps/accounts/models/auth.py +50 -0
- django_cfg/apps/accounts/models/base.py +8 -0
- django_cfg/apps/accounts/models/choices.py +32 -0
- django_cfg/apps/accounts/models/integrations.py +75 -0
- django_cfg/apps/accounts/models/registration.py +52 -0
- django_cfg/apps/accounts/models/user.py +80 -0
- django_cfg/apps/maintenance/__init__.py +53 -24
- django_cfg/apps/maintenance/admin/__init__.py +7 -18
- django_cfg/apps/maintenance/admin/api_key_admin.py +185 -0
- django_cfg/apps/maintenance/admin/log_admin.py +156 -0
- django_cfg/apps/maintenance/admin/scheduled_admin.py +390 -0
- django_cfg/apps/maintenance/admin/site_admin.py +448 -0
- django_cfg/apps/maintenance/apps.py +9 -96
- django_cfg/apps/maintenance/management/commands/maintenance.py +193 -307
- django_cfg/apps/maintenance/management/commands/process_scheduled_maintenance.py +241 -0
- django_cfg/apps/maintenance/management/commands/sync_cloudflare.py +152 -111
- django_cfg/apps/maintenance/managers/__init__.py +7 -12
- django_cfg/apps/maintenance/managers/cloudflare_site_manager.py +192 -0
- django_cfg/apps/maintenance/managers/maintenance_log_manager.py +151 -0
- django_cfg/apps/maintenance/migrations/0001_initial.py +145 -705
- django_cfg/apps/maintenance/migrations/0002_cloudflaresite_maintenance_url.py +21 -0
- django_cfg/apps/maintenance/models/__init__.py +23 -21
- django_cfg/apps/maintenance/models/cloudflare_api_key.py +109 -0
- django_cfg/apps/maintenance/models/cloudflare_site.py +125 -0
- django_cfg/apps/maintenance/models/maintenance_log.py +131 -0
- django_cfg/apps/maintenance/models/scheduled_maintenance.py +307 -0
- django_cfg/apps/maintenance/services/__init__.py +37 -16
- django_cfg/apps/maintenance/services/bulk_operations_service.py +400 -0
- django_cfg/apps/maintenance/services/maintenance_service.py +230 -0
- django_cfg/apps/maintenance/services/scheduled_maintenance_service.py +381 -0
- django_cfg/apps/maintenance/services/site_sync_service.py +390 -0
- django_cfg/apps/maintenance/utils/__init__.py +12 -0
- django_cfg/apps/maintenance/utils/retry_utils.py +109 -0
- django_cfg/config.py +3 -0
- django_cfg/core/config.py +4 -6
- django_cfg/modules/django_unfold/dashboard.py +4 -5
- {django_cfg-1.2.17.dist-info → django_cfg-1.2.18.dist-info}/METADATA +52 -1
- {django_cfg-1.2.17.dist-info → django_cfg-1.2.18.dist-info}/RECORD +45 -55
- django_cfg/apps/maintenance/README.md +0 -305
- django_cfg/apps/maintenance/admin/deployments_admin.py +0 -251
- django_cfg/apps/maintenance/admin/events_admin.py +0 -374
- django_cfg/apps/maintenance/admin/monitoring_admin.py +0 -215
- django_cfg/apps/maintenance/admin/sites_admin.py +0 -464
- django_cfg/apps/maintenance/managers/deployments.py +0 -287
- django_cfg/apps/maintenance/managers/events.py +0 -374
- django_cfg/apps/maintenance/managers/monitoring.py +0 -301
- django_cfg/apps/maintenance/managers/sites.py +0 -335
- django_cfg/apps/maintenance/models/cloudflare.py +0 -316
- django_cfg/apps/maintenance/models/maintenance.py +0 -334
- django_cfg/apps/maintenance/models/monitoring.py +0 -393
- django_cfg/apps/maintenance/models/sites.py +0 -419
- django_cfg/apps/maintenance/serializers/__init__.py +0 -60
- django_cfg/apps/maintenance/serializers/actions.py +0 -310
- django_cfg/apps/maintenance/serializers/base.py +0 -44
- django_cfg/apps/maintenance/serializers/deployments.py +0 -209
- django_cfg/apps/maintenance/serializers/events.py +0 -210
- django_cfg/apps/maintenance/serializers/monitoring.py +0 -278
- django_cfg/apps/maintenance/serializers/sites.py +0 -213
- django_cfg/apps/maintenance/services/README.md +0 -168
- django_cfg/apps/maintenance/services/cloudflare_client.py +0 -441
- django_cfg/apps/maintenance/services/dns_manager.py +0 -497
- django_cfg/apps/maintenance/services/maintenance_manager.py +0 -504
- django_cfg/apps/maintenance/services/site_sync.py +0 -448
- django_cfg/apps/maintenance/services/sync_command_service.py +0 -330
- django_cfg/apps/maintenance/services/worker_manager.py +0 -264
- django_cfg/apps/maintenance/signals.py +0 -38
- django_cfg/apps/maintenance/urls.py +0 -36
- django_cfg/apps/maintenance/views/__init__.py +0 -18
- django_cfg/apps/maintenance/views/base.py +0 -61
- django_cfg/apps/maintenance/views/deployments.py +0 -175
- django_cfg/apps/maintenance/views/events.py +0 -204
- django_cfg/apps/maintenance/views/monitoring.py +0 -213
- django_cfg/apps/maintenance/views/sites.py +0 -338
- django_cfg/models/cloudflare.py +0 -316
- /django_cfg/apps/accounts/{models.py → __models.py} +0 -0
- {django_cfg-1.2.17.dist-info → django_cfg-1.2.18.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.17.dist-info → django_cfg-1.2.18.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.17.dist-info → django_cfg-1.2.18.dist-info}/licenses/LICENSE +0 -0
@@ -1,61 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Base views for maintenance app.
|
3
|
-
|
4
|
-
Common functionality and mixins for maintenance views.
|
5
|
-
"""
|
6
|
-
|
7
|
-
import logging
|
8
|
-
from rest_framework import permissions
|
9
|
-
from rest_framework.response import Response
|
10
|
-
from rest_framework import status
|
11
|
-
|
12
|
-
logger = logging.getLogger(__name__)
|
13
|
-
|
14
|
-
|
15
|
-
class MaintenancePermissionMixin:
|
16
|
-
"""Mixin for maintenance app permissions."""
|
17
|
-
|
18
|
-
permission_classes = [permissions.IsAuthenticated]
|
19
|
-
|
20
|
-
def get_user_queryset(self, model_class, owner_field='owner'):
|
21
|
-
"""Get queryset filtered by user permissions."""
|
22
|
-
if getattr(self, 'swagger_fake_view', False):
|
23
|
-
return model_class.objects.none()
|
24
|
-
|
25
|
-
user = self.request.user
|
26
|
-
if user.is_staff:
|
27
|
-
return model_class.objects.all()
|
28
|
-
|
29
|
-
# Use dynamic field lookup
|
30
|
-
filter_kwargs = {owner_field: user}
|
31
|
-
return model_class.objects.filter(**filter_kwargs)
|
32
|
-
|
33
|
-
|
34
|
-
class MaintenanceResponseMixin:
|
35
|
-
"""Mixin for standardized API responses."""
|
36
|
-
|
37
|
-
def success_response(self, message, data=None):
|
38
|
-
"""Return standardized success response."""
|
39
|
-
response_data = {
|
40
|
-
'success': True,
|
41
|
-
'message': message
|
42
|
-
}
|
43
|
-
if data is not None:
|
44
|
-
response_data['data'] = data
|
45
|
-
return Response(response_data)
|
46
|
-
|
47
|
-
def error_response(self, error, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR):
|
48
|
-
"""Return standardized error response."""
|
49
|
-
logger.error(f"API Error: {error}")
|
50
|
-
return Response({
|
51
|
-
'success': False,
|
52
|
-
'error': str(error)
|
53
|
-
}, status=status_code)
|
54
|
-
|
55
|
-
def validation_error_response(self, errors):
|
56
|
-
"""Return standardized validation error response."""
|
57
|
-
return Response({
|
58
|
-
'success': False,
|
59
|
-
'error': 'Validation failed',
|
60
|
-
'errors': errors
|
61
|
-
}, status=status.HTTP_400_BAD_REQUEST)
|
@@ -1,175 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Cloudflare deployment views.
|
3
|
-
|
4
|
-
ViewSets for CloudflareDeployment management.
|
5
|
-
"""
|
6
|
-
|
7
|
-
import logging
|
8
|
-
from rest_framework import viewsets
|
9
|
-
from rest_framework.decorators import action
|
10
|
-
from drf_spectacular.utils import extend_schema, extend_schema_view
|
11
|
-
|
12
|
-
from ..models import CloudflareDeployment
|
13
|
-
from ..serializers import (
|
14
|
-
CloudflareDeploymentSerializer, CloudflareDeploymentListSerializer
|
15
|
-
)
|
16
|
-
from .base import MaintenancePermissionMixin, MaintenanceResponseMixin
|
17
|
-
|
18
|
-
logger = logging.getLogger(__name__)
|
19
|
-
|
20
|
-
|
21
|
-
@extend_schema_view(
|
22
|
-
list=extend_schema(
|
23
|
-
summary="List Cloudflare deployments",
|
24
|
-
description="Get list of Cloudflare deployments"
|
25
|
-
),
|
26
|
-
retrieve=extend_schema(
|
27
|
-
summary="Get Cloudflare deployment",
|
28
|
-
description="Get detailed information about a Cloudflare deployment"
|
29
|
-
),
|
30
|
-
destroy=extend_schema(
|
31
|
-
summary="Delete Cloudflare deployment",
|
32
|
-
description="Delete a Cloudflare deployment record"
|
33
|
-
)
|
34
|
-
)
|
35
|
-
class CloudflareDeploymentViewSet(MaintenancePermissionMixin, MaintenanceResponseMixin, viewsets.ReadOnlyModelViewSet):
|
36
|
-
"""ViewSet for managing Cloudflare deployments (read-only)."""
|
37
|
-
|
38
|
-
serializer_class = CloudflareDeploymentSerializer
|
39
|
-
lookup_field = 'id'
|
40
|
-
filterset_fields = ['deployment_type', 'status']
|
41
|
-
ordering = ['-deployed_at']
|
42
|
-
|
43
|
-
def get_queryset(self):
|
44
|
-
"""Get queryset filtered by user permissions."""
|
45
|
-
# CloudflareDeployment is related to CloudflareSite via site.owner
|
46
|
-
if getattr(self, 'swagger_fake_view', False):
|
47
|
-
return CloudflareDeployment.objects.none()
|
48
|
-
|
49
|
-
user = self.request.user
|
50
|
-
if user.is_staff:
|
51
|
-
return CloudflareDeployment.objects.all()
|
52
|
-
|
53
|
-
return CloudflareDeployment.objects.filter(site__owner=user)
|
54
|
-
|
55
|
-
def get_serializer_class(self):
|
56
|
-
"""Return appropriate serializer based on action."""
|
57
|
-
if self.action == 'list':
|
58
|
-
return CloudflareDeploymentListSerializer
|
59
|
-
return CloudflareDeploymentSerializer
|
60
|
-
|
61
|
-
@action(detail=True, methods=['post'])
|
62
|
-
@extend_schema(
|
63
|
-
summary="Rollback deployment",
|
64
|
-
description="Rollback this Cloudflare deployment"
|
65
|
-
)
|
66
|
-
def rollback(self, request, id=None):
|
67
|
-
"""Rollback a deployment."""
|
68
|
-
try:
|
69
|
-
deployment = self.get_object()
|
70
|
-
|
71
|
-
if deployment.status != CloudflareDeployment.Status.ACTIVE:
|
72
|
-
return self.error_response(
|
73
|
-
f'Cannot rollback deployment with status: {deployment.status}',
|
74
|
-
status_code=400
|
75
|
-
)
|
76
|
-
|
77
|
-
# Import Cloudflare service
|
78
|
-
from ..services.cloudflare_service import CloudflareService
|
79
|
-
|
80
|
-
cloudflare_service = CloudflareService()
|
81
|
-
success = cloudflare_service.rollback_deployment(deployment)
|
82
|
-
|
83
|
-
if success:
|
84
|
-
deployment.status = CloudflareDeployment.Status.ROLLED_BACK
|
85
|
-
deployment.save()
|
86
|
-
|
87
|
-
return self.success_response(f'Deployment {deployment.id} rolled back successfully')
|
88
|
-
else:
|
89
|
-
return self.error_response('Failed to rollback deployment')
|
90
|
-
|
91
|
-
except Exception as e:
|
92
|
-
return self.error_response(f"Rollback deployment error: {e}")
|
93
|
-
|
94
|
-
@action(detail=True, methods=['get'])
|
95
|
-
@extend_schema(
|
96
|
-
summary="Get deployment logs",
|
97
|
-
description="Get logs for this deployment"
|
98
|
-
)
|
99
|
-
def logs(self, request, id=None):
|
100
|
-
"""Get logs for a deployment."""
|
101
|
-
try:
|
102
|
-
deployment = self.get_object()
|
103
|
-
|
104
|
-
# Import Cloudflare service
|
105
|
-
from ..services.cloudflare_service import CloudflareService
|
106
|
-
|
107
|
-
cloudflare_service = CloudflareService()
|
108
|
-
logs = cloudflare_service.get_deployment_logs(deployment)
|
109
|
-
|
110
|
-
return self.success_response(
|
111
|
-
f'Retrieved logs for deployment {deployment.id}',
|
112
|
-
data={'logs': logs}
|
113
|
-
)
|
114
|
-
|
115
|
-
except Exception as e:
|
116
|
-
return self.error_response(f"Get deployment logs error: {e}")
|
117
|
-
|
118
|
-
@action(detail=True, methods=['get'])
|
119
|
-
@extend_schema(
|
120
|
-
summary="Get deployment status",
|
121
|
-
description="Get current status of this deployment from Cloudflare"
|
122
|
-
)
|
123
|
-
def status(self, request, id=None):
|
124
|
-
"""Get current deployment status from Cloudflare."""
|
125
|
-
try:
|
126
|
-
deployment = self.get_object()
|
127
|
-
|
128
|
-
# Import Cloudflare service
|
129
|
-
from ..services.cloudflare_service import CloudflareService
|
130
|
-
|
131
|
-
cloudflare_service = CloudflareService()
|
132
|
-
status_info = cloudflare_service.get_deployment_status(deployment)
|
133
|
-
|
134
|
-
return self.success_response(
|
135
|
-
f'Retrieved status for deployment {deployment.id}',
|
136
|
-
data=status_info
|
137
|
-
)
|
138
|
-
|
139
|
-
except Exception as e:
|
140
|
-
return self.error_response(f"Get deployment status error: {e}")
|
141
|
-
|
142
|
-
@action(detail=False, methods=['get'])
|
143
|
-
@extend_schema(
|
144
|
-
summary="Get deployment statistics",
|
145
|
-
description="Get deployment statistics for user's sites"
|
146
|
-
)
|
147
|
-
def statistics(self, request):
|
148
|
-
"""Get deployment statistics."""
|
149
|
-
try:
|
150
|
-
deployments = self.get_queryset()
|
151
|
-
|
152
|
-
stats = {
|
153
|
-
'total_deployments': deployments.count(),
|
154
|
-
'active_deployments': deployments.filter(status=CloudflareDeployment.Status.ACTIVE).count(),
|
155
|
-
'failed_deployments': deployments.filter(status=CloudflareDeployment.Status.FAILED).count(),
|
156
|
-
'rolled_back_deployments': deployments.filter(status=CloudflareDeployment.Status.ROLLED_BACK).count(),
|
157
|
-
'by_type': {},
|
158
|
-
'recent_deployments': []
|
159
|
-
}
|
160
|
-
|
161
|
-
# Count by deployment type
|
162
|
-
for deployment_type in CloudflareDeployment.DeploymentType.choices:
|
163
|
-
type_value = deployment_type[0]
|
164
|
-
count = deployments.filter(deployment_type=type_value).count()
|
165
|
-
stats['by_type'][type_value] = count
|
166
|
-
|
167
|
-
# Get recent deployments
|
168
|
-
recent = deployments.order_by('-deployed_at')[:10]
|
169
|
-
serializer = CloudflareDeploymentListSerializer(recent, many=True)
|
170
|
-
stats['recent_deployments'] = serializer.data
|
171
|
-
|
172
|
-
return self.success_response('Deployment statistics retrieved', data=stats)
|
173
|
-
|
174
|
-
except Exception as e:
|
175
|
-
return self.error_response(f"Get deployment statistics error: {e}")
|
@@ -1,204 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Maintenance event views.
|
3
|
-
|
4
|
-
ViewSets for MaintenanceEvent management.
|
5
|
-
"""
|
6
|
-
|
7
|
-
import logging
|
8
|
-
from rest_framework import viewsets
|
9
|
-
from rest_framework.decorators import action
|
10
|
-
from drf_spectacular.utils import extend_schema, extend_schema_view
|
11
|
-
|
12
|
-
from ..models import MaintenanceEvent
|
13
|
-
from ..serializers import (
|
14
|
-
MaintenanceEventSerializer, MaintenanceEventCreateSerializer, MaintenanceEventListSerializer,
|
15
|
-
MaintenanceEventUpdateSerializer
|
16
|
-
)
|
17
|
-
from .base import MaintenancePermissionMixin, MaintenanceResponseMixin
|
18
|
-
|
19
|
-
logger = logging.getLogger(__name__)
|
20
|
-
|
21
|
-
|
22
|
-
@extend_schema_view(
|
23
|
-
list=extend_schema(
|
24
|
-
summary="List maintenance events",
|
25
|
-
description="Get list of maintenance events"
|
26
|
-
),
|
27
|
-
create=extend_schema(
|
28
|
-
summary="Create maintenance event",
|
29
|
-
description="Create a new maintenance event"
|
30
|
-
),
|
31
|
-
retrieve=extend_schema(
|
32
|
-
summary="Get maintenance event",
|
33
|
-
description="Get detailed information about a maintenance event"
|
34
|
-
),
|
35
|
-
update=extend_schema(
|
36
|
-
summary="Update maintenance event",
|
37
|
-
description="Update maintenance event details"
|
38
|
-
),
|
39
|
-
destroy=extend_schema(
|
40
|
-
summary="Delete maintenance event",
|
41
|
-
description="Delete a maintenance event"
|
42
|
-
)
|
43
|
-
)
|
44
|
-
class MaintenanceEventViewSet(MaintenancePermissionMixin, MaintenanceResponseMixin, viewsets.ModelViewSet):
|
45
|
-
"""ViewSet for managing maintenance events."""
|
46
|
-
|
47
|
-
serializer_class = MaintenanceEventSerializer
|
48
|
-
lookup_field = 'id'
|
49
|
-
filterset_fields = ['status', 'reason']
|
50
|
-
ordering = ['-created_at']
|
51
|
-
|
52
|
-
def get_queryset(self):
|
53
|
-
"""Get queryset filtered by user permissions."""
|
54
|
-
return self.get_user_queryset(MaintenanceEvent, 'initiated_by')
|
55
|
-
|
56
|
-
def get_serializer_class(self):
|
57
|
-
"""Return appropriate serializer based on action."""
|
58
|
-
if self.action == 'create':
|
59
|
-
return MaintenanceEventCreateSerializer
|
60
|
-
elif self.action == 'list':
|
61
|
-
return MaintenanceEventListSerializer
|
62
|
-
elif self.action in ['update', 'partial_update']:
|
63
|
-
return MaintenanceEventUpdateSerializer
|
64
|
-
return MaintenanceEventSerializer
|
65
|
-
|
66
|
-
def perform_create(self, serializer):
|
67
|
-
"""Set initiated_by when creating event."""
|
68
|
-
serializer.save(initiated_by=self.request.user)
|
69
|
-
|
70
|
-
@action(detail=True, methods=['post'])
|
71
|
-
@extend_schema(
|
72
|
-
summary="Complete maintenance event",
|
73
|
-
description="Mark maintenance event as completed"
|
74
|
-
)
|
75
|
-
def complete(self, request, id=None):
|
76
|
-
"""Complete a maintenance event."""
|
77
|
-
try:
|
78
|
-
event = self.get_object()
|
79
|
-
|
80
|
-
if event.status != MaintenanceEvent.Status.ACTIVE:
|
81
|
-
return self.error_response(
|
82
|
-
f'Cannot complete event with status: {event.status}',
|
83
|
-
status_code=400
|
84
|
-
)
|
85
|
-
|
86
|
-
event.complete(request.user)
|
87
|
-
|
88
|
-
return self.success_response(f'Maintenance event "{event.title}" completed')
|
89
|
-
|
90
|
-
except Exception as e:
|
91
|
-
return self.error_response(f"Complete maintenance event error: {e}")
|
92
|
-
|
93
|
-
@action(detail=True, methods=['post'])
|
94
|
-
@extend_schema(
|
95
|
-
summary="Cancel maintenance event",
|
96
|
-
description="Cancel maintenance event"
|
97
|
-
)
|
98
|
-
def cancel(self, request, id=None):
|
99
|
-
"""Cancel a maintenance event."""
|
100
|
-
try:
|
101
|
-
event = self.get_object()
|
102
|
-
|
103
|
-
if event.status not in [MaintenanceEvent.Status.ACTIVE, MaintenanceEvent.Status.SCHEDULED]:
|
104
|
-
return self.error_response(
|
105
|
-
f'Cannot cancel event with status: {event.status}',
|
106
|
-
status_code=400
|
107
|
-
)
|
108
|
-
|
109
|
-
event.cancel(request.user)
|
110
|
-
|
111
|
-
return self.success_response(f'Maintenance event "{event.title}" cancelled')
|
112
|
-
|
113
|
-
except Exception as e:
|
114
|
-
return self.error_response(f"Cancel maintenance event error: {e}")
|
115
|
-
|
116
|
-
@action(detail=True, methods=['post'])
|
117
|
-
@extend_schema(
|
118
|
-
summary="Fail maintenance event",
|
119
|
-
description="Mark maintenance event as failed"
|
120
|
-
)
|
121
|
-
def fail(self, request, id=None):
|
122
|
-
"""Mark a maintenance event as failed."""
|
123
|
-
try:
|
124
|
-
event = self.get_object()
|
125
|
-
error_message = request.data.get('error_message', 'Maintenance failed')
|
126
|
-
|
127
|
-
if event.status != MaintenanceEvent.Status.ACTIVE:
|
128
|
-
return self.error_response(
|
129
|
-
f'Cannot fail event with status: {event.status}',
|
130
|
-
status_code=400
|
131
|
-
)
|
132
|
-
|
133
|
-
event.fail(error_message)
|
134
|
-
|
135
|
-
return self.success_response(f'Maintenance event "{event.title}" marked as failed')
|
136
|
-
|
137
|
-
except Exception as e:
|
138
|
-
return self.error_response(f"Fail maintenance event error: {e}")
|
139
|
-
|
140
|
-
@action(detail=True, methods=['get'])
|
141
|
-
@extend_schema(
|
142
|
-
summary="Get event logs",
|
143
|
-
description="Get logs for this maintenance event"
|
144
|
-
)
|
145
|
-
def logs(self, request, id=None):
|
146
|
-
"""Get logs for a maintenance event."""
|
147
|
-
try:
|
148
|
-
event = self.get_object()
|
149
|
-
logs = event.logs.order_by('-timestamp')
|
150
|
-
|
151
|
-
# Simple pagination
|
152
|
-
page_size = int(request.query_params.get('page_size', 50))
|
153
|
-
page = int(request.query_params.get('page', 1))
|
154
|
-
start = (page - 1) * page_size
|
155
|
-
end = start + page_size
|
156
|
-
|
157
|
-
paginated_logs = logs[start:end]
|
158
|
-
|
159
|
-
from ..serializers import MaintenanceLogSerializer
|
160
|
-
serializer = MaintenanceLogSerializer(paginated_logs, many=True)
|
161
|
-
|
162
|
-
return self.success_response(
|
163
|
-
f'Retrieved {len(serializer.data)} logs',
|
164
|
-
data={
|
165
|
-
'logs': serializer.data,
|
166
|
-
'total': logs.count(),
|
167
|
-
'page': page,
|
168
|
-
'page_size': page_size
|
169
|
-
}
|
170
|
-
)
|
171
|
-
|
172
|
-
except Exception as e:
|
173
|
-
return self.error_response(f"Get event logs error: {e}")
|
174
|
-
|
175
|
-
@action(detail=True, methods=['get'])
|
176
|
-
@extend_schema(
|
177
|
-
summary="Get event statistics",
|
178
|
-
description="Get statistics for this maintenance event"
|
179
|
-
)
|
180
|
-
def statistics(self, request, id=None):
|
181
|
-
"""Get statistics for a maintenance event."""
|
182
|
-
try:
|
183
|
-
event = self.get_object()
|
184
|
-
|
185
|
-
stats = {
|
186
|
-
'event_id': event.id,
|
187
|
-
'title': event.title,
|
188
|
-
'status': event.status,
|
189
|
-
'duration': event.duration.total_seconds() if event.duration else None,
|
190
|
-
'affected_sites': event.affected_sites_count,
|
191
|
-
'success_count': event.success_count,
|
192
|
-
'error_count_before': event.error_count_before,
|
193
|
-
'error_count_during': event.error_count_during,
|
194
|
-
'is_active': event.is_active,
|
195
|
-
'is_scheduled': event.is_scheduled,
|
196
|
-
'started_at': event.started_at.isoformat() if event.started_at else None,
|
197
|
-
'ended_at': event.ended_at.isoformat() if event.ended_at else None,
|
198
|
-
'logs_count': event.logs.count()
|
199
|
-
}
|
200
|
-
|
201
|
-
return self.success_response('Event statistics retrieved', data=stats)
|
202
|
-
|
203
|
-
except Exception as e:
|
204
|
-
return self.error_response(f"Get event statistics error: {e}")
|
@@ -1,213 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Monitoring views.
|
3
|
-
|
4
|
-
ViewSets for MonitoringTarget management.
|
5
|
-
"""
|
6
|
-
|
7
|
-
import logging
|
8
|
-
from rest_framework import viewsets
|
9
|
-
from rest_framework.decorators import action
|
10
|
-
from drf_spectacular.utils import extend_schema, extend_schema_view
|
11
|
-
|
12
|
-
from ..models import MonitoringTarget
|
13
|
-
from ..serializers import (
|
14
|
-
MonitoringTargetSerializer, MonitoringTargetCreateSerializer,
|
15
|
-
HealthCheckResultSerializer
|
16
|
-
)
|
17
|
-
from .base import MaintenancePermissionMixin, MaintenanceResponseMixin
|
18
|
-
|
19
|
-
logger = logging.getLogger(__name__)
|
20
|
-
|
21
|
-
|
22
|
-
@extend_schema_view(
|
23
|
-
list=extend_schema(
|
24
|
-
summary="List monitoring targets",
|
25
|
-
description="Get list of monitoring targets"
|
26
|
-
),
|
27
|
-
create=extend_schema(
|
28
|
-
summary="Create monitoring target",
|
29
|
-
description="Create a new monitoring target"
|
30
|
-
),
|
31
|
-
retrieve=extend_schema(
|
32
|
-
summary="Get monitoring target",
|
33
|
-
description="Get detailed information about a monitoring target"
|
34
|
-
),
|
35
|
-
update=extend_schema(
|
36
|
-
summary="Update monitoring target",
|
37
|
-
description="Update monitoring target configuration"
|
38
|
-
),
|
39
|
-
destroy=extend_schema(
|
40
|
-
summary="Delete monitoring target",
|
41
|
-
description="Delete a monitoring target"
|
42
|
-
)
|
43
|
-
)
|
44
|
-
class MonitoringTargetViewSet(MaintenancePermissionMixin, MaintenanceResponseMixin, viewsets.ModelViewSet):
|
45
|
-
"""ViewSet for managing monitoring targets."""
|
46
|
-
|
47
|
-
serializer_class = MonitoringTargetSerializer
|
48
|
-
lookup_field = 'id'
|
49
|
-
filterset_fields = ['is_active', 'check_type']
|
50
|
-
ordering = ['-created_at']
|
51
|
-
|
52
|
-
def get_queryset(self):
|
53
|
-
"""Get queryset filtered by user permissions."""
|
54
|
-
# MonitoringTarget is related to CloudflareSite via site.owner
|
55
|
-
if getattr(self, 'swagger_fake_view', False):
|
56
|
-
return MonitoringTarget.objects.none()
|
57
|
-
|
58
|
-
user = self.request.user
|
59
|
-
if user.is_staff:
|
60
|
-
return MonitoringTarget.objects.all()
|
61
|
-
|
62
|
-
return MonitoringTarget.objects.filter(site__owner=user)
|
63
|
-
|
64
|
-
def get_serializer_class(self):
|
65
|
-
"""Return appropriate serializer based on action."""
|
66
|
-
if self.action == 'create':
|
67
|
-
return MonitoringTargetCreateSerializer
|
68
|
-
return MonitoringTargetSerializer
|
69
|
-
|
70
|
-
@action(detail=True, methods=['post'])
|
71
|
-
@extend_schema(
|
72
|
-
summary="Run health check",
|
73
|
-
description="Run health check for this monitoring target",
|
74
|
-
responses={
|
75
|
-
200: HealthCheckResultSerializer
|
76
|
-
}
|
77
|
-
)
|
78
|
-
def health_check(self, request, id=None):
|
79
|
-
"""Run health check for a monitoring target."""
|
80
|
-
try:
|
81
|
-
target = self.get_object()
|
82
|
-
|
83
|
-
# Import monitoring service
|
84
|
-
from ..services.monitoring_service import MonitoringService
|
85
|
-
|
86
|
-
monitoring_service = MonitoringService()
|
87
|
-
result = monitoring_service.check_target_health(target)
|
88
|
-
|
89
|
-
# Serialize the result
|
90
|
-
serializer = HealthCheckResultSerializer(result)
|
91
|
-
|
92
|
-
return self.success_response(
|
93
|
-
f'Health check completed for {target.site.domain}',
|
94
|
-
data=serializer.data
|
95
|
-
)
|
96
|
-
|
97
|
-
except Exception as e:
|
98
|
-
return self.error_response(f"Health check error: {e}")
|
99
|
-
|
100
|
-
@action(detail=True, methods=['post'])
|
101
|
-
@extend_schema(
|
102
|
-
summary="Enable monitoring",
|
103
|
-
description="Enable monitoring for this target"
|
104
|
-
)
|
105
|
-
def enable(self, request, id=None):
|
106
|
-
"""Enable monitoring for a target."""
|
107
|
-
try:
|
108
|
-
target = self.get_object()
|
109
|
-
target.is_active = True
|
110
|
-
target.save()
|
111
|
-
|
112
|
-
return self.success_response(f'Monitoring enabled for {target.site.domain}')
|
113
|
-
|
114
|
-
except Exception as e:
|
115
|
-
return self.error_response(f"Enable monitoring error: {e}")
|
116
|
-
|
117
|
-
@action(detail=True, methods=['post'])
|
118
|
-
@extend_schema(
|
119
|
-
summary="Disable monitoring",
|
120
|
-
description="Disable monitoring for this target"
|
121
|
-
)
|
122
|
-
def disable(self, request, id=None):
|
123
|
-
"""Disable monitoring for a target."""
|
124
|
-
try:
|
125
|
-
target = self.get_object()
|
126
|
-
target.is_active = False
|
127
|
-
target.save()
|
128
|
-
|
129
|
-
return self.success_response(f'Monitoring disabled for {target.site.domain}')
|
130
|
-
|
131
|
-
except Exception as e:
|
132
|
-
return self.error_response(f"Disable monitoring error: {e}")
|
133
|
-
|
134
|
-
@action(detail=True, methods=['get'])
|
135
|
-
@extend_schema(
|
136
|
-
summary="Get monitoring statistics",
|
137
|
-
description="Get monitoring statistics for this target"
|
138
|
-
)
|
139
|
-
def statistics(self, request, id=None):
|
140
|
-
"""Get monitoring statistics for a target."""
|
141
|
-
try:
|
142
|
-
target = self.get_object()
|
143
|
-
|
144
|
-
stats = {
|
145
|
-
'target_id': target.id,
|
146
|
-
'site_domain': target.site.domain,
|
147
|
-
'is_active': target.is_active,
|
148
|
-
'check_type': target.check_type,
|
149
|
-
'check_interval': target.check_interval,
|
150
|
-
'timeout': target.timeout,
|
151
|
-
'retry_count': target.retry_count,
|
152
|
-
'last_check': target.last_check.isoformat() if target.last_check else None,
|
153
|
-
'last_status': target.last_status,
|
154
|
-
'consecutive_failures': target.consecutive_failures,
|
155
|
-
'total_checks': target.total_checks,
|
156
|
-
'total_failures': target.total_failures,
|
157
|
-
'uptime_percentage': target.uptime_percentage,
|
158
|
-
'created_at': target.created_at.isoformat(),
|
159
|
-
'updated_at': target.updated_at.isoformat()
|
160
|
-
}
|
161
|
-
|
162
|
-
return self.success_response('Monitoring statistics retrieved', data=stats)
|
163
|
-
|
164
|
-
except Exception as e:
|
165
|
-
return self.error_response(f"Get monitoring statistics error: {e}")
|
166
|
-
|
167
|
-
@action(detail=False, methods=['post'])
|
168
|
-
@extend_schema(
|
169
|
-
summary="Run bulk health checks",
|
170
|
-
description="Run health checks for multiple monitoring targets"
|
171
|
-
)
|
172
|
-
def bulk_health_check(self, request):
|
173
|
-
"""Run health checks for multiple targets."""
|
174
|
-
try:
|
175
|
-
target_ids = request.data.get('target_ids', [])
|
176
|
-
|
177
|
-
if not target_ids:
|
178
|
-
return self.error_response('No target IDs provided', status_code=400)
|
179
|
-
|
180
|
-
# Get targets (filtered by user permissions)
|
181
|
-
targets = self.get_queryset().filter(id__in=target_ids)
|
182
|
-
|
183
|
-
# Import monitoring service
|
184
|
-
from ..services.monitoring_service import MonitoringService
|
185
|
-
|
186
|
-
monitoring_service = MonitoringService()
|
187
|
-
results = []
|
188
|
-
|
189
|
-
for target in targets:
|
190
|
-
try:
|
191
|
-
result = monitoring_service.check_target_health(target)
|
192
|
-
serializer = HealthCheckResultSerializer(result)
|
193
|
-
results.append({
|
194
|
-
'target_id': target.id,
|
195
|
-
'domain': target.site.domain,
|
196
|
-
'success': True,
|
197
|
-
'result': serializer.data
|
198
|
-
})
|
199
|
-
except Exception as e:
|
200
|
-
results.append({
|
201
|
-
'target_id': target.id,
|
202
|
-
'domain': target.site.domain,
|
203
|
-
'success': False,
|
204
|
-
'error': str(e)
|
205
|
-
})
|
206
|
-
|
207
|
-
return self.success_response(
|
208
|
-
f'Bulk health check completed for {len(results)} targets',
|
209
|
-
data={'results': results}
|
210
|
-
)
|
211
|
-
|
212
|
-
except Exception as e:
|
213
|
-
return self.error_response(f"Bulk health check error: {e}")
|