django-cfg 1.2.15__py3-none-any.whl → 1.2.16__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/maintenance/README.md +305 -0
- django_cfg/apps/maintenance/__init__.py +27 -0
- django_cfg/apps/maintenance/admin/__init__.py +28 -0
- django_cfg/apps/maintenance/admin/deployments_admin.py +251 -0
- django_cfg/apps/maintenance/admin/events_admin.py +374 -0
- django_cfg/apps/maintenance/admin/monitoring_admin.py +215 -0
- django_cfg/apps/maintenance/admin/sites_admin.py +464 -0
- django_cfg/apps/maintenance/apps.py +105 -0
- django_cfg/apps/maintenance/management/__init__.py +0 -0
- django_cfg/apps/maintenance/management/commands/__init__.py +0 -0
- django_cfg/apps/maintenance/management/commands/maintenance.py +375 -0
- django_cfg/apps/maintenance/management/commands/sync_cloudflare.py +168 -0
- django_cfg/apps/maintenance/managers/__init__.py +20 -0
- django_cfg/apps/maintenance/managers/deployments.py +287 -0
- django_cfg/apps/maintenance/managers/events.py +374 -0
- django_cfg/apps/maintenance/managers/monitoring.py +301 -0
- django_cfg/apps/maintenance/managers/sites.py +335 -0
- django_cfg/apps/maintenance/migrations/0001_initial.py +939 -0
- django_cfg/apps/maintenance/migrations/__init__.py +0 -0
- django_cfg/apps/maintenance/models/__init__.py +27 -0
- django_cfg/apps/maintenance/models/cloudflare.py +316 -0
- django_cfg/apps/maintenance/models/maintenance.py +334 -0
- django_cfg/apps/maintenance/models/monitoring.py +393 -0
- django_cfg/apps/maintenance/models/sites.py +419 -0
- django_cfg/apps/maintenance/serializers/__init__.py +60 -0
- django_cfg/apps/maintenance/serializers/actions.py +310 -0
- django_cfg/apps/maintenance/serializers/base.py +44 -0
- django_cfg/apps/maintenance/serializers/deployments.py +209 -0
- django_cfg/apps/maintenance/serializers/events.py +210 -0
- django_cfg/apps/maintenance/serializers/monitoring.py +278 -0
- django_cfg/apps/maintenance/serializers/sites.py +213 -0
- django_cfg/apps/maintenance/services/README.md +168 -0
- django_cfg/apps/maintenance/services/__init__.py +21 -0
- django_cfg/apps/maintenance/services/cloudflare_client.py +441 -0
- django_cfg/apps/maintenance/services/dns_manager.py +497 -0
- django_cfg/apps/maintenance/services/maintenance_manager.py +504 -0
- django_cfg/apps/maintenance/services/site_sync.py +448 -0
- django_cfg/apps/maintenance/services/sync_command_service.py +330 -0
- django_cfg/apps/maintenance/services/worker_manager.py +264 -0
- django_cfg/apps/maintenance/signals.py +38 -0
- django_cfg/apps/maintenance/urls.py +36 -0
- django_cfg/apps/maintenance/views/__init__.py +18 -0
- django_cfg/apps/maintenance/views/base.py +61 -0
- django_cfg/apps/maintenance/views/deployments.py +175 -0
- django_cfg/apps/maintenance/views/events.py +204 -0
- django_cfg/apps/maintenance/views/monitoring.py +213 -0
- django_cfg/apps/maintenance/views/sites.py +338 -0
- django_cfg/apps/urls.py +5 -1
- django_cfg/core/config.py +34 -3
- django_cfg/core/generation.py +15 -10
- django_cfg/models/cloudflare.py +316 -0
- django_cfg/models/revolution.py +1 -1
- django_cfg/models/tasks.py +1 -1
- django_cfg/modules/base.py +12 -5
- django_cfg/modules/django_unfold/dashboard.py +16 -1
- {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/METADATA +2 -1
- {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/RECORD +61 -13
- {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.15.dist-info → django_cfg-1.2.16.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,301 @@
|
|
1
|
+
"""
|
2
|
+
Custom managers for monitoring models.
|
3
|
+
|
4
|
+
Provides enhanced querying capabilities for MonitoringTarget model.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.db import models
|
8
|
+
from django.utils import timezone
|
9
|
+
from typing import Optional, Dict, Any
|
10
|
+
from datetime import timedelta
|
11
|
+
|
12
|
+
|
13
|
+
class MonitoringTargetQuerySet(models.QuerySet):
|
14
|
+
"""Custom queryset for MonitoringTarget."""
|
15
|
+
|
16
|
+
def for_user(self, user):
|
17
|
+
"""Filter monitoring targets for sites owned by specific user."""
|
18
|
+
return self.filter(site__owner=user)
|
19
|
+
|
20
|
+
def active(self):
|
21
|
+
"""Get active monitoring targets."""
|
22
|
+
return self.filter(is_active=True)
|
23
|
+
|
24
|
+
def inactive(self):
|
25
|
+
"""Get inactive monitoring targets."""
|
26
|
+
return self.filter(is_active=False)
|
27
|
+
|
28
|
+
def by_check_type(self, check_type: str):
|
29
|
+
"""Filter by check type."""
|
30
|
+
return self.filter(check_type=check_type)
|
31
|
+
|
32
|
+
def http_checks(self):
|
33
|
+
"""Get HTTP monitoring targets."""
|
34
|
+
return self.filter(check_type='http')
|
35
|
+
|
36
|
+
def tcp_checks(self):
|
37
|
+
"""Get TCP monitoring targets."""
|
38
|
+
return self.filter(check_type='tcp')
|
39
|
+
|
40
|
+
def ping_checks(self):
|
41
|
+
"""Get ping monitoring targets."""
|
42
|
+
return self.filter(check_type='ping')
|
43
|
+
|
44
|
+
def healthy(self):
|
45
|
+
"""Get targets with healthy status."""
|
46
|
+
return self.filter(last_status='healthy')
|
47
|
+
|
48
|
+
def unhealthy(self):
|
49
|
+
"""Get targets with unhealthy status."""
|
50
|
+
return self.filter(last_status='unhealthy')
|
51
|
+
|
52
|
+
def unknown_status(self):
|
53
|
+
"""Get targets with unknown status."""
|
54
|
+
return self.filter(last_status='unknown')
|
55
|
+
|
56
|
+
def recently_checked(self, minutes: int = 30):
|
57
|
+
"""Get targets checked recently."""
|
58
|
+
cutoff = timezone.now() - timedelta(minutes=minutes)
|
59
|
+
return self.filter(last_check__gte=cutoff)
|
60
|
+
|
61
|
+
def overdue_check(self, multiplier: float = 2.0):
|
62
|
+
"""Get targets that are overdue for a check."""
|
63
|
+
from django.db.models import F
|
64
|
+
from django.utils import timezone
|
65
|
+
|
66
|
+
# Calculate overdue threshold based on check_interval
|
67
|
+
overdue_threshold = timezone.now() - timedelta(
|
68
|
+
seconds=F('check_interval') * multiplier
|
69
|
+
)
|
70
|
+
|
71
|
+
return self.filter(
|
72
|
+
is_active=True,
|
73
|
+
last_check__lt=overdue_threshold
|
74
|
+
)
|
75
|
+
|
76
|
+
def with_failures(self, min_failures: int = 1):
|
77
|
+
"""Get targets with consecutive failures."""
|
78
|
+
return self.filter(consecutive_failures__gte=min_failures)
|
79
|
+
|
80
|
+
def high_failure_rate(self, min_percentage: float = 10.0):
|
81
|
+
"""Get targets with high failure rate."""
|
82
|
+
from django.db.models import Case, When, F
|
83
|
+
|
84
|
+
return self.annotate(
|
85
|
+
failure_rate=Case(
|
86
|
+
When(total_checks=0, then=0),
|
87
|
+
default=F('total_failures') * 100.0 / F('total_checks')
|
88
|
+
)
|
89
|
+
).filter(failure_rate__gte=min_percentage)
|
90
|
+
|
91
|
+
def by_environment(self, environment: str):
|
92
|
+
"""Filter by site environment."""
|
93
|
+
return self.filter(site__environment=environment)
|
94
|
+
|
95
|
+
def production(self):
|
96
|
+
"""Get monitoring targets for production sites."""
|
97
|
+
return self.filter(site__environment='production')
|
98
|
+
|
99
|
+
def staging(self):
|
100
|
+
"""Get monitoring targets for staging sites."""
|
101
|
+
return self.filter(site__environment='staging')
|
102
|
+
|
103
|
+
def with_site(self):
|
104
|
+
"""Include related site in query."""
|
105
|
+
return self.select_related('site')
|
106
|
+
|
107
|
+
def with_site_owner(self):
|
108
|
+
"""Include site and owner in query."""
|
109
|
+
return self.select_related('site__owner')
|
110
|
+
|
111
|
+
|
112
|
+
class MonitoringTargetManager(models.Manager):
|
113
|
+
"""Custom manager for MonitoringTarget."""
|
114
|
+
|
115
|
+
def get_queryset(self):
|
116
|
+
return MonitoringTargetQuerySet(self.model, using=self._db)
|
117
|
+
|
118
|
+
def for_user(self, user):
|
119
|
+
"""Get monitoring targets for specific user."""
|
120
|
+
return self.get_queryset().for_user(user)
|
121
|
+
|
122
|
+
def active(self):
|
123
|
+
"""Get active monitoring targets."""
|
124
|
+
return self.get_queryset().active()
|
125
|
+
|
126
|
+
def inactive(self):
|
127
|
+
"""Get inactive monitoring targets."""
|
128
|
+
return self.get_queryset().inactive()
|
129
|
+
|
130
|
+
def by_check_type(self, check_type: str):
|
131
|
+
"""Get targets by check type."""
|
132
|
+
return self.get_queryset().by_check_type(check_type)
|
133
|
+
|
134
|
+
def http_checks(self):
|
135
|
+
"""Get HTTP monitoring targets."""
|
136
|
+
return self.get_queryset().http_checks()
|
137
|
+
|
138
|
+
def tcp_checks(self):
|
139
|
+
"""Get TCP monitoring targets."""
|
140
|
+
return self.get_queryset().tcp_checks()
|
141
|
+
|
142
|
+
def ping_checks(self):
|
143
|
+
"""Get ping monitoring targets."""
|
144
|
+
return self.get_queryset().ping_checks()
|
145
|
+
|
146
|
+
def healthy(self):
|
147
|
+
"""Get healthy targets."""
|
148
|
+
return self.get_queryset().healthy()
|
149
|
+
|
150
|
+
def unhealthy(self):
|
151
|
+
"""Get unhealthy targets."""
|
152
|
+
return self.get_queryset().unhealthy()
|
153
|
+
|
154
|
+
def unknown_status(self):
|
155
|
+
"""Get targets with unknown status."""
|
156
|
+
return self.get_queryset().unknown_status()
|
157
|
+
|
158
|
+
def recently_checked(self, minutes: int = 30):
|
159
|
+
"""Get recently checked targets."""
|
160
|
+
return self.get_queryset().recently_checked(minutes)
|
161
|
+
|
162
|
+
def overdue_check(self, multiplier: float = 2.0):
|
163
|
+
"""Get overdue targets."""
|
164
|
+
return self.get_queryset().overdue_check(multiplier)
|
165
|
+
|
166
|
+
def with_failures(self, min_failures: int = 1):
|
167
|
+
"""Get targets with failures."""
|
168
|
+
return self.get_queryset().with_failures(min_failures)
|
169
|
+
|
170
|
+
def high_failure_rate(self, min_percentage: float = 10.0):
|
171
|
+
"""Get targets with high failure rate."""
|
172
|
+
return self.get_queryset().high_failure_rate(min_percentage)
|
173
|
+
|
174
|
+
def by_environment(self, environment: str):
|
175
|
+
"""Get targets by environment."""
|
176
|
+
return self.get_queryset().by_environment(environment)
|
177
|
+
|
178
|
+
def production(self):
|
179
|
+
"""Get production monitoring targets."""
|
180
|
+
return self.get_queryset().production()
|
181
|
+
|
182
|
+
def staging(self):
|
183
|
+
"""Get staging monitoring targets."""
|
184
|
+
return self.get_queryset().staging()
|
185
|
+
|
186
|
+
def with_site(self):
|
187
|
+
"""Get targets with site."""
|
188
|
+
return self.get_queryset().with_site()
|
189
|
+
|
190
|
+
def with_site_owner(self):
|
191
|
+
"""Get targets with site and owner."""
|
192
|
+
return self.get_queryset().with_site_owner()
|
193
|
+
|
194
|
+
def create_target(
|
195
|
+
self,
|
196
|
+
site,
|
197
|
+
check_type: str = 'http',
|
198
|
+
check_interval: int = 300, # 5 minutes
|
199
|
+
timeout: int = 30,
|
200
|
+
retry_count: int = 3,
|
201
|
+
expected_status_code: Optional[int] = None,
|
202
|
+
expected_response_time: Optional[int] = None,
|
203
|
+
custom_headers: Optional[Dict] = None,
|
204
|
+
**kwargs
|
205
|
+
):
|
206
|
+
"""Create a new monitoring target."""
|
207
|
+
return self.create(
|
208
|
+
site=site,
|
209
|
+
check_type=check_type,
|
210
|
+
check_interval=check_interval,
|
211
|
+
timeout=timeout,
|
212
|
+
retry_count=retry_count,
|
213
|
+
expected_status_code=expected_status_code or (200 if check_type == 'http' else None),
|
214
|
+
expected_response_time=expected_response_time,
|
215
|
+
custom_headers=custom_headers or {},
|
216
|
+
**kwargs
|
217
|
+
)
|
218
|
+
|
219
|
+
def get_stats_for_user(self, user) -> Dict[str, Any]:
|
220
|
+
"""Get monitoring statistics for user."""
|
221
|
+
targets = self.for_user(user)
|
222
|
+
|
223
|
+
return {
|
224
|
+
'total': targets.count(),
|
225
|
+
'active': targets.active().count(),
|
226
|
+
'inactive': targets.inactive().count(),
|
227
|
+
'healthy': targets.healthy().count(),
|
228
|
+
'unhealthy': targets.unhealthy().count(),
|
229
|
+
'unknown': targets.unknown_status().count(),
|
230
|
+
'http_checks': targets.http_checks().count(),
|
231
|
+
'tcp_checks': targets.tcp_checks().count(),
|
232
|
+
'ping_checks': targets.ping_checks().count(),
|
233
|
+
'production': targets.production().count(),
|
234
|
+
'staging': targets.staging().count(),
|
235
|
+
'recently_checked': targets.recently_checked().count(),
|
236
|
+
'overdue': targets.overdue_check().count(),
|
237
|
+
'with_failures': targets.with_failures().count(),
|
238
|
+
'high_failure_rate': targets.high_failure_rate().count(),
|
239
|
+
}
|
240
|
+
|
241
|
+
def get_health_summary(self, user) -> Dict[str, Any]:
|
242
|
+
"""Get health summary for user's monitoring targets."""
|
243
|
+
targets = self.for_user(user).active()
|
244
|
+
total = targets.count()
|
245
|
+
|
246
|
+
if total == 0:
|
247
|
+
return {
|
248
|
+
'total_targets': 0,
|
249
|
+
'health_percentage': 0,
|
250
|
+
'unhealthy_count': 0,
|
251
|
+
'overdue_count': 0,
|
252
|
+
'critical_issues': [],
|
253
|
+
}
|
254
|
+
|
255
|
+
healthy = targets.healthy().count()
|
256
|
+
unhealthy = targets.unhealthy().count()
|
257
|
+
overdue = targets.overdue_check().count()
|
258
|
+
|
259
|
+
# Get critical issues
|
260
|
+
critical_issues = []
|
261
|
+
|
262
|
+
# Production sites that are unhealthy
|
263
|
+
prod_unhealthy = targets.production().unhealthy().with_site()
|
264
|
+
for target in prod_unhealthy[:5]: # Limit to 5
|
265
|
+
critical_issues.append({
|
266
|
+
'type': 'production_unhealthy',
|
267
|
+
'site': target.site.domain,
|
268
|
+
'last_check': target.last_check,
|
269
|
+
'consecutive_failures': target.consecutive_failures,
|
270
|
+
})
|
271
|
+
|
272
|
+
# Sites with high failure rates
|
273
|
+
high_failure = targets.high_failure_rate(20.0).with_site()
|
274
|
+
for target in high_failure[:3]: # Limit to 3
|
275
|
+
failure_rate = (target.total_failures / target.total_checks * 100) if target.total_checks > 0 else 0
|
276
|
+
critical_issues.append({
|
277
|
+
'type': 'high_failure_rate',
|
278
|
+
'site': target.site.domain,
|
279
|
+
'failure_rate': round(failure_rate, 1),
|
280
|
+
'total_failures': target.total_failures,
|
281
|
+
'total_checks': target.total_checks,
|
282
|
+
})
|
283
|
+
|
284
|
+
return {
|
285
|
+
'total_targets': total,
|
286
|
+
'health_percentage': (healthy / total * 100) if total > 0 else 0,
|
287
|
+
'unhealthy_count': unhealthy,
|
288
|
+
'overdue_count': overdue,
|
289
|
+
'critical_issues': critical_issues,
|
290
|
+
}
|
291
|
+
|
292
|
+
def get_targets_needing_check(self) -> 'MonitoringTargetQuerySet':
|
293
|
+
"""Get targets that need to be checked now."""
|
294
|
+
now = timezone.now()
|
295
|
+
|
296
|
+
return self.active().filter(
|
297
|
+
models.Q(last_check__isnull=True) |
|
298
|
+
models.Q(
|
299
|
+
last_check__lt=now - timedelta(seconds=models.F('check_interval'))
|
300
|
+
)
|
301
|
+
)
|
@@ -0,0 +1,335 @@
|
|
1
|
+
"""
|
2
|
+
Custom managers for site-related models.
|
3
|
+
|
4
|
+
Provides enhanced querying capabilities for CloudflareSite and SiteGroup models.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.db import models
|
8
|
+
from django.utils import timezone
|
9
|
+
from typing import Optional, Dict, Any, List
|
10
|
+
from datetime import timedelta
|
11
|
+
|
12
|
+
|
13
|
+
class CloudflareSiteQuerySet(models.QuerySet):
|
14
|
+
"""Custom queryset for CloudflareSite."""
|
15
|
+
|
16
|
+
def for_user(self, user):
|
17
|
+
"""Filter sites for specific user."""
|
18
|
+
return self.filter(owner=user)
|
19
|
+
|
20
|
+
def by_environment(self, environment: str):
|
21
|
+
"""Filter by site environment."""
|
22
|
+
return self.filter(environment=environment)
|
23
|
+
|
24
|
+
def by_status(self, status: str):
|
25
|
+
"""Filter by site status."""
|
26
|
+
return self.filter(current_status=status)
|
27
|
+
|
28
|
+
def by_project(self, project: str):
|
29
|
+
"""Filter by project name."""
|
30
|
+
return self.filter(project__icontains=project)
|
31
|
+
|
32
|
+
def by_domain(self, domain: str):
|
33
|
+
"""Filter by domain (exact or contains)."""
|
34
|
+
return self.filter(domain__icontains=domain)
|
35
|
+
|
36
|
+
def with_tags(self, tags: List[str]):
|
37
|
+
"""Filter sites that have any of the specified tags."""
|
38
|
+
from django.contrib.postgres.fields import ArrayField
|
39
|
+
from django.db.models import Q
|
40
|
+
|
41
|
+
query = Q()
|
42
|
+
for tag in tags:
|
43
|
+
query |= Q(tags__contains=[tag])
|
44
|
+
return self.filter(query)
|
45
|
+
|
46
|
+
def production(self):
|
47
|
+
"""Get production sites."""
|
48
|
+
return self.filter(environment='production')
|
49
|
+
|
50
|
+
def staging(self):
|
51
|
+
"""Get staging sites."""
|
52
|
+
return self.filter(environment='staging')
|
53
|
+
|
54
|
+
def development(self):
|
55
|
+
"""Get development sites."""
|
56
|
+
return self.filter(environment='development')
|
57
|
+
|
58
|
+
def active(self):
|
59
|
+
"""Get active sites."""
|
60
|
+
return self.filter(current_status='active')
|
61
|
+
|
62
|
+
def in_maintenance(self):
|
63
|
+
"""Get sites currently in maintenance."""
|
64
|
+
return self.filter(maintenance_active=True)
|
65
|
+
|
66
|
+
def offline(self):
|
67
|
+
"""Get offline sites."""
|
68
|
+
return self.filter(current_status='offline')
|
69
|
+
|
70
|
+
def recent(self, days: int = 7):
|
71
|
+
"""Get recently created sites."""
|
72
|
+
cutoff = timezone.now() - timedelta(days=days)
|
73
|
+
return self.filter(created_at__gte=cutoff)
|
74
|
+
|
75
|
+
def recently_maintained(self, days: int = 7):
|
76
|
+
"""Get sites that had maintenance recently."""
|
77
|
+
cutoff = timezone.now() - timedelta(days=days)
|
78
|
+
return self.filter(last_maintenance_at__gte=cutoff)
|
79
|
+
|
80
|
+
def with_monitoring(self):
|
81
|
+
"""Get sites that have monitoring configured."""
|
82
|
+
return self.filter(monitoring_target__isnull=False)
|
83
|
+
|
84
|
+
def without_monitoring(self):
|
85
|
+
"""Get sites without monitoring."""
|
86
|
+
return self.filter(monitoring_target__isnull=True)
|
87
|
+
|
88
|
+
def with_deployments(self):
|
89
|
+
"""Include related deployments in query."""
|
90
|
+
return self.prefetch_related('deployments')
|
91
|
+
|
92
|
+
def with_maintenance_events(self):
|
93
|
+
"""Include related maintenance events."""
|
94
|
+
return self.prefetch_related('maintenance_events')
|
95
|
+
|
96
|
+
def with_full_relations(self):
|
97
|
+
"""Include all related objects for detailed views."""
|
98
|
+
return self.select_related('owner').prefetch_related(
|
99
|
+
'deployments', 'maintenance_events', 'monitoring_target'
|
100
|
+
)
|
101
|
+
|
102
|
+
|
103
|
+
class CloudflareSiteManager(models.Manager):
|
104
|
+
"""Custom manager for CloudflareSite."""
|
105
|
+
|
106
|
+
def get_queryset(self):
|
107
|
+
return CloudflareSiteQuerySet(self.model, using=self._db)
|
108
|
+
|
109
|
+
def for_user(self, user):
|
110
|
+
"""Get sites for specific user."""
|
111
|
+
return self.get_queryset().for_user(user)
|
112
|
+
|
113
|
+
def by_environment(self, environment: str):
|
114
|
+
"""Get sites by environment."""
|
115
|
+
return self.get_queryset().by_environment(environment)
|
116
|
+
|
117
|
+
def by_status(self, status: str):
|
118
|
+
"""Get sites by status."""
|
119
|
+
return self.get_queryset().by_status(status)
|
120
|
+
|
121
|
+
def by_project(self, project: str):
|
122
|
+
"""Get sites by project."""
|
123
|
+
return self.get_queryset().by_project(project)
|
124
|
+
|
125
|
+
def by_domain(self, domain: str):
|
126
|
+
"""Get sites by domain."""
|
127
|
+
return self.get_queryset().by_domain(domain)
|
128
|
+
|
129
|
+
def with_tags(self, tags: List[str]):
|
130
|
+
"""Get sites with specific tags."""
|
131
|
+
return self.get_queryset().with_tags(tags)
|
132
|
+
|
133
|
+
def production(self):
|
134
|
+
"""Get production sites."""
|
135
|
+
return self.get_queryset().production()
|
136
|
+
|
137
|
+
def staging(self):
|
138
|
+
"""Get staging sites."""
|
139
|
+
return self.get_queryset().staging()
|
140
|
+
|
141
|
+
def development(self):
|
142
|
+
"""Get development sites."""
|
143
|
+
return self.get_queryset().development()
|
144
|
+
|
145
|
+
def active(self):
|
146
|
+
"""Get active sites."""
|
147
|
+
return self.get_queryset().active()
|
148
|
+
|
149
|
+
def in_maintenance(self):
|
150
|
+
"""Get sites in maintenance."""
|
151
|
+
return self.get_queryset().in_maintenance()
|
152
|
+
|
153
|
+
def offline(self):
|
154
|
+
"""Get offline sites."""
|
155
|
+
return self.get_queryset().offline()
|
156
|
+
|
157
|
+
def recent(self, days: int = 7):
|
158
|
+
"""Get recent sites."""
|
159
|
+
return self.get_queryset().recent(days)
|
160
|
+
|
161
|
+
def recently_maintained(self, days: int = 7):
|
162
|
+
"""Get recently maintained sites."""
|
163
|
+
return self.get_queryset().recently_maintained(days)
|
164
|
+
|
165
|
+
def with_monitoring(self):
|
166
|
+
"""Get sites with monitoring."""
|
167
|
+
return self.get_queryset().with_monitoring()
|
168
|
+
|
169
|
+
def without_monitoring(self):
|
170
|
+
"""Get sites without monitoring."""
|
171
|
+
return self.get_queryset().without_monitoring()
|
172
|
+
|
173
|
+
def with_deployments(self):
|
174
|
+
"""Get sites with deployments."""
|
175
|
+
return self.get_queryset().with_deployments()
|
176
|
+
|
177
|
+
def with_maintenance_events(self):
|
178
|
+
"""Get sites with maintenance events."""
|
179
|
+
return self.get_queryset().with_maintenance_events()
|
180
|
+
|
181
|
+
def with_full_relations(self):
|
182
|
+
"""Get sites with all relations."""
|
183
|
+
return self.get_queryset().with_full_relations()
|
184
|
+
|
185
|
+
def create_site(
|
186
|
+
self,
|
187
|
+
name: str,
|
188
|
+
domain: str,
|
189
|
+
owner,
|
190
|
+
environment: str = 'production',
|
191
|
+
project: Optional[str] = None,
|
192
|
+
tags: Optional[List[str]] = None,
|
193
|
+
**kwargs
|
194
|
+
):
|
195
|
+
"""Create a new Cloudflare site."""
|
196
|
+
return self.create(
|
197
|
+
name=name,
|
198
|
+
domain=domain,
|
199
|
+
owner=owner,
|
200
|
+
environment=environment,
|
201
|
+
project=project or '',
|
202
|
+
tags=tags or [],
|
203
|
+
**kwargs
|
204
|
+
)
|
205
|
+
|
206
|
+
def get_stats_for_user(self, user) -> Dict[str, Any]:
|
207
|
+
"""Get site statistics for user."""
|
208
|
+
sites = self.for_user(user)
|
209
|
+
|
210
|
+
return {
|
211
|
+
'total': sites.count(),
|
212
|
+
'production': sites.production().count(),
|
213
|
+
'staging': sites.staging().count(),
|
214
|
+
'development': sites.development().count(),
|
215
|
+
'active': sites.active().count(),
|
216
|
+
'in_maintenance': sites.in_maintenance().count(),
|
217
|
+
'offline': sites.offline().count(),
|
218
|
+
'with_monitoring': sites.with_monitoring().count(),
|
219
|
+
'recent': sites.recent().count(),
|
220
|
+
'recently_maintained': sites.recently_maintained().count(),
|
221
|
+
}
|
222
|
+
|
223
|
+
def get_maintenance_summary(self, user) -> Dict[str, Any]:
|
224
|
+
"""Get maintenance summary for user's sites."""
|
225
|
+
sites = self.for_user(user)
|
226
|
+
in_maintenance = sites.in_maintenance()
|
227
|
+
|
228
|
+
return {
|
229
|
+
'total_sites': sites.count(),
|
230
|
+
'in_maintenance': in_maintenance.count(),
|
231
|
+
'maintenance_percentage': (
|
232
|
+
(in_maintenance.count() / sites.count() * 100)
|
233
|
+
if sites.count() > 0 else 0
|
234
|
+
),
|
235
|
+
'production_in_maintenance': in_maintenance.production().count(),
|
236
|
+
'staging_in_maintenance': in_maintenance.staging().count(),
|
237
|
+
'recently_maintained': sites.recently_maintained().count(),
|
238
|
+
}
|
239
|
+
|
240
|
+
|
241
|
+
class SiteGroupQuerySet(models.QuerySet):
|
242
|
+
"""Custom queryset for SiteGroup."""
|
243
|
+
|
244
|
+
def for_user(self, user):
|
245
|
+
"""Filter groups for specific user."""
|
246
|
+
return self.filter(owner=user)
|
247
|
+
|
248
|
+
def by_environment(self, environment: str):
|
249
|
+
"""Filter groups by primary environment of their sites."""
|
250
|
+
return self.filter(sites__environment=environment).distinct()
|
251
|
+
|
252
|
+
def with_sites(self):
|
253
|
+
"""Include related sites in query."""
|
254
|
+
return self.prefetch_related('sites')
|
255
|
+
|
256
|
+
def with_active_sites(self):
|
257
|
+
"""Include only active sites."""
|
258
|
+
return self.prefetch_related(
|
259
|
+
models.Prefetch(
|
260
|
+
'sites',
|
261
|
+
queryset=CloudflareSiteQuerySet(self.model._meta.get_field('sites').related_model).active()
|
262
|
+
)
|
263
|
+
)
|
264
|
+
|
265
|
+
def recent(self, days: int = 7):
|
266
|
+
"""Get recently created groups."""
|
267
|
+
cutoff = timezone.now() - timedelta(days=days)
|
268
|
+
return self.filter(created_at__gte=cutoff)
|
269
|
+
|
270
|
+
|
271
|
+
class SiteGroupManager(models.Manager):
|
272
|
+
"""Custom manager for SiteGroup."""
|
273
|
+
|
274
|
+
def get_queryset(self):
|
275
|
+
return SiteGroupQuerySet(self.model, using=self._db)
|
276
|
+
|
277
|
+
def for_user(self, user):
|
278
|
+
"""Get groups for specific user."""
|
279
|
+
return self.get_queryset().for_user(user)
|
280
|
+
|
281
|
+
def by_environment(self, environment: str):
|
282
|
+
"""Get groups by environment."""
|
283
|
+
return self.get_queryset().by_environment(environment)
|
284
|
+
|
285
|
+
def with_sites(self):
|
286
|
+
"""Get groups with sites."""
|
287
|
+
return self.get_queryset().with_sites()
|
288
|
+
|
289
|
+
def with_active_sites(self):
|
290
|
+
"""Get groups with active sites."""
|
291
|
+
return self.get_queryset().with_active_sites()
|
292
|
+
|
293
|
+
def recent(self, days: int = 7):
|
294
|
+
"""Get recent groups."""
|
295
|
+
return self.get_queryset().recent(days)
|
296
|
+
|
297
|
+
def create_group(
|
298
|
+
self,
|
299
|
+
name: str,
|
300
|
+
owner,
|
301
|
+
description: Optional[str] = None,
|
302
|
+
site_ids: Optional[List[int]] = None,
|
303
|
+
**kwargs
|
304
|
+
):
|
305
|
+
"""Create a new site group."""
|
306
|
+
group = self.create(
|
307
|
+
name=name,
|
308
|
+
owner=owner,
|
309
|
+
description=description or '',
|
310
|
+
**kwargs
|
311
|
+
)
|
312
|
+
|
313
|
+
if site_ids:
|
314
|
+
# Add sites to the group
|
315
|
+
from ..models import CloudflareSite
|
316
|
+
sites = CloudflareSite.objects.filter(id__in=site_ids, owner=owner)
|
317
|
+
group.sites.add(*sites)
|
318
|
+
|
319
|
+
return group
|
320
|
+
|
321
|
+
def get_stats_for_user(self, user) -> Dict[str, Any]:
|
322
|
+
"""Get group statistics for user."""
|
323
|
+
groups = self.for_user(user)
|
324
|
+
|
325
|
+
return {
|
326
|
+
'total': groups.count(),
|
327
|
+
'recent': groups.recent().count(),
|
328
|
+
'total_sites_in_groups': sum(
|
329
|
+
group.sites.count() for group in groups.with_sites()
|
330
|
+
),
|
331
|
+
'avg_sites_per_group': (
|
332
|
+
sum(group.sites.count() for group in groups.with_sites()) / groups.count()
|
333
|
+
if groups.count() > 0 else 0
|
334
|
+
),
|
335
|
+
}
|