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,393 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Monitoring models for external health checks.
|
3
|
-
|
4
|
-
Models for tracking health checks and automatic maintenance triggers.
|
5
|
-
Following CRITICAL_REQUIREMENTS - proper typing, no raw Dict usage.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from django.db import models
|
9
|
-
from django.utils import timezone
|
10
|
-
from django.core.exceptions import ValidationError
|
11
|
-
from typing import Optional, Dict, Any
|
12
|
-
from datetime import timedelta
|
13
|
-
import json
|
14
|
-
|
15
|
-
|
16
|
-
class MonitoringTarget(models.Model):
|
17
|
-
"""
|
18
|
-
External monitoring target configuration.
|
19
|
-
|
20
|
-
Defines what to monitor and how to trigger maintenance mode
|
21
|
-
when health checks fail.
|
22
|
-
"""
|
23
|
-
|
24
|
-
class Status(models.TextChoices):
|
25
|
-
"""Monitoring status choices."""
|
26
|
-
ACTIVE = "active", "Active"
|
27
|
-
PAUSED = "paused", "Paused"
|
28
|
-
DISABLED = "disabled", "Disabled"
|
29
|
-
ERROR = "error", "Error"
|
30
|
-
|
31
|
-
# === Target Configuration ===
|
32
|
-
site = models.OneToOneField(
|
33
|
-
'django_cfg_maintenance.CloudflareSite',
|
34
|
-
on_delete=models.CASCADE,
|
35
|
-
related_name='monitoring_target',
|
36
|
-
help_text="Site being monitored"
|
37
|
-
)
|
38
|
-
|
39
|
-
# === Health Check Settings ===
|
40
|
-
check_url = models.URLField(
|
41
|
-
help_text="URL to check for health status"
|
42
|
-
)
|
43
|
-
check_interval = models.PositiveIntegerField(
|
44
|
-
default=60,
|
45
|
-
help_text="Check interval in seconds"
|
46
|
-
)
|
47
|
-
timeout = models.PositiveIntegerField(
|
48
|
-
default=10,
|
49
|
-
help_text="Request timeout in seconds"
|
50
|
-
)
|
51
|
-
|
52
|
-
# === Expected Response ===
|
53
|
-
expected_status_codes = models.JSONField(
|
54
|
-
default=list,
|
55
|
-
help_text="Expected HTTP status codes (e.g., [200, 201])"
|
56
|
-
)
|
57
|
-
expected_response_time_ms = models.PositiveIntegerField(
|
58
|
-
null=True,
|
59
|
-
blank=True,
|
60
|
-
help_text="Maximum expected response time in milliseconds"
|
61
|
-
)
|
62
|
-
expected_content = models.TextField(
|
63
|
-
blank=True,
|
64
|
-
help_text="Expected content in response body (substring match)"
|
65
|
-
)
|
66
|
-
|
67
|
-
# === Failure Detection ===
|
68
|
-
failure_threshold = models.PositiveIntegerField(
|
69
|
-
default=3,
|
70
|
-
help_text="Consecutive failures before triggering maintenance"
|
71
|
-
)
|
72
|
-
recovery_threshold = models.PositiveIntegerField(
|
73
|
-
default=2,
|
74
|
-
help_text="Consecutive successes before disabling maintenance"
|
75
|
-
)
|
76
|
-
|
77
|
-
# === Current State ===
|
78
|
-
status = models.CharField(
|
79
|
-
max_length=20,
|
80
|
-
choices=Status.choices,
|
81
|
-
default=Status.ACTIVE,
|
82
|
-
help_text="Current monitoring status"
|
83
|
-
)
|
84
|
-
consecutive_failures = models.PositiveIntegerField(
|
85
|
-
default=0,
|
86
|
-
help_text="Current consecutive failure count"
|
87
|
-
)
|
88
|
-
consecutive_successes = models.PositiveIntegerField(
|
89
|
-
default=0,
|
90
|
-
help_text="Current consecutive success count"
|
91
|
-
)
|
92
|
-
|
93
|
-
# === Last Check Results ===
|
94
|
-
last_check_at = models.DateTimeField(
|
95
|
-
null=True,
|
96
|
-
blank=True,
|
97
|
-
help_text="When last health check was performed"
|
98
|
-
)
|
99
|
-
last_check_success = models.BooleanField(
|
100
|
-
default=True,
|
101
|
-
help_text="Whether last check was successful"
|
102
|
-
)
|
103
|
-
last_response_time_ms = models.PositiveIntegerField(
|
104
|
-
null=True,
|
105
|
-
blank=True,
|
106
|
-
help_text="Response time of last check in milliseconds"
|
107
|
-
)
|
108
|
-
|
109
|
-
# === Maintenance Triggers ===
|
110
|
-
auto_enable_maintenance = models.BooleanField(
|
111
|
-
default=True,
|
112
|
-
help_text="Automatically enable maintenance on failure threshold"
|
113
|
-
)
|
114
|
-
auto_disable_maintenance = models.BooleanField(
|
115
|
-
default=True,
|
116
|
-
help_text="Automatically disable maintenance on recovery threshold"
|
117
|
-
)
|
118
|
-
maintenance_triggered_at = models.DateTimeField(
|
119
|
-
null=True,
|
120
|
-
blank=True,
|
121
|
-
help_text="When maintenance was last auto-triggered"
|
122
|
-
)
|
123
|
-
|
124
|
-
# === Advanced Settings ===
|
125
|
-
user_agent = models.CharField(
|
126
|
-
max_length=200,
|
127
|
-
default="Django-CFG-Monitor/1.0",
|
128
|
-
help_text="User agent for health checks"
|
129
|
-
)
|
130
|
-
follow_redirects = models.BooleanField(
|
131
|
-
default=True,
|
132
|
-
help_text="Follow HTTP redirects during checks"
|
133
|
-
)
|
134
|
-
verify_ssl = models.BooleanField(
|
135
|
-
default=True,
|
136
|
-
help_text="Verify SSL certificates"
|
137
|
-
)
|
138
|
-
custom_headers = models.JSONField(
|
139
|
-
default=dict,
|
140
|
-
blank=True,
|
141
|
-
help_text="Custom headers for health check requests"
|
142
|
-
)
|
143
|
-
|
144
|
-
# === Metadata ===
|
145
|
-
created_at = models.DateTimeField(auto_now_add=True)
|
146
|
-
updated_at = models.DateTimeField(auto_now=True)
|
147
|
-
|
148
|
-
# === Custom Manager ===
|
149
|
-
from ..managers.monitoring import MonitoringTargetManager
|
150
|
-
objects = MonitoringTargetManager()
|
151
|
-
|
152
|
-
class Meta:
|
153
|
-
verbose_name = "Monitoring Target"
|
154
|
-
verbose_name_plural = "Monitoring Targets"
|
155
|
-
indexes = [
|
156
|
-
models.Index(fields=['status', 'last_check_at']),
|
157
|
-
models.Index(fields=['site', '-last_check_at']),
|
158
|
-
]
|
159
|
-
|
160
|
-
def __str__(self) -> str:
|
161
|
-
return f"Monitor: {self.site.name} ({self.get_status_display()})"
|
162
|
-
|
163
|
-
@property
|
164
|
-
def is_healthy(self) -> bool:
|
165
|
-
"""Check if target is currently healthy."""
|
166
|
-
return self.last_check_success and self.consecutive_failures == 0
|
167
|
-
|
168
|
-
@property
|
169
|
-
def should_trigger_maintenance(self) -> bool:
|
170
|
-
"""Check if maintenance should be triggered."""
|
171
|
-
return (
|
172
|
-
self.auto_enable_maintenance and
|
173
|
-
self.consecutive_failures >= self.failure_threshold and
|
174
|
-
not self.site.maintenance_active
|
175
|
-
)
|
176
|
-
|
177
|
-
@property
|
178
|
-
def should_disable_maintenance(self) -> bool:
|
179
|
-
"""Check if maintenance should be disabled."""
|
180
|
-
return (
|
181
|
-
self.auto_disable_maintenance and
|
182
|
-
self.consecutive_successes >= self.recovery_threshold and
|
183
|
-
self.site.maintenance_active
|
184
|
-
)
|
185
|
-
|
186
|
-
@property
|
187
|
-
def next_check_at(self) -> Optional[timezone.datetime]:
|
188
|
-
"""Calculate when next check should occur."""
|
189
|
-
if self.last_check_at:
|
190
|
-
return self.last_check_at + timedelta(seconds=self.check_interval)
|
191
|
-
return timezone.now()
|
192
|
-
|
193
|
-
def record_success(self, response_time_ms: int) -> None:
|
194
|
-
"""Record successful health check."""
|
195
|
-
self.last_check_at = timezone.now()
|
196
|
-
self.last_check_success = True
|
197
|
-
self.last_response_time_ms = response_time_ms
|
198
|
-
self.consecutive_failures = 0
|
199
|
-
self.consecutive_successes += 1
|
200
|
-
|
201
|
-
self.save(update_fields=[
|
202
|
-
'last_check_at',
|
203
|
-
'last_check_success',
|
204
|
-
'last_response_time_ms',
|
205
|
-
'consecutive_failures',
|
206
|
-
'consecutive_successes',
|
207
|
-
'updated_at'
|
208
|
-
])
|
209
|
-
|
210
|
-
def record_failure(self, error_message: str = "") -> None:
|
211
|
-
"""Record failed health check."""
|
212
|
-
self.last_check_at = timezone.now()
|
213
|
-
self.last_check_success = False
|
214
|
-
self.consecutive_successes = 0
|
215
|
-
self.consecutive_failures += 1
|
216
|
-
|
217
|
-
self.save(update_fields=[
|
218
|
-
'last_check_at',
|
219
|
-
'last_check_success',
|
220
|
-
'consecutive_successes',
|
221
|
-
'consecutive_failures',
|
222
|
-
'updated_at'
|
223
|
-
])
|
224
|
-
|
225
|
-
# Create health check result record
|
226
|
-
HealthCheckResult.objects.create(
|
227
|
-
target=self,
|
228
|
-
success=False,
|
229
|
-
error_message=error_message
|
230
|
-
)
|
231
|
-
|
232
|
-
def trigger_maintenance(self) -> bool:
|
233
|
-
"""Trigger maintenance mode for this target."""
|
234
|
-
if self.should_trigger_maintenance:
|
235
|
-
self.site.enable_maintenance()
|
236
|
-
self.maintenance_triggered_at = timezone.now()
|
237
|
-
self.save(update_fields=['maintenance_triggered_at', 'updated_at'])
|
238
|
-
return True
|
239
|
-
return False
|
240
|
-
|
241
|
-
def disable_maintenance_if_recovered(self) -> bool:
|
242
|
-
"""Disable maintenance if recovery threshold is met."""
|
243
|
-
if self.should_disable_maintenance:
|
244
|
-
self.site.disable_maintenance()
|
245
|
-
return True
|
246
|
-
return False
|
247
|
-
|
248
|
-
def clean(self) -> None:
|
249
|
-
"""Validate model data."""
|
250
|
-
super().clean()
|
251
|
-
|
252
|
-
# Validate thresholds
|
253
|
-
if self.failure_threshold < 1:
|
254
|
-
raise ValidationError({'failure_threshold': 'Must be at least 1'})
|
255
|
-
|
256
|
-
if self.recovery_threshold < 1:
|
257
|
-
raise ValidationError({'recovery_threshold': 'Must be at least 1'})
|
258
|
-
|
259
|
-
# Validate intervals
|
260
|
-
if self.check_interval < 10:
|
261
|
-
raise ValidationError({'check_interval': 'Must be at least 10 seconds'})
|
262
|
-
|
263
|
-
if self.timeout >= self.check_interval:
|
264
|
-
raise ValidationError({'timeout': 'Timeout must be less than check interval'})
|
265
|
-
|
266
|
-
# Validate expected status codes
|
267
|
-
if self.expected_status_codes:
|
268
|
-
for code in self.expected_status_codes:
|
269
|
-
if not isinstance(code, int) or code < 100 or code > 599:
|
270
|
-
raise ValidationError({
|
271
|
-
'expected_status_codes': 'Must contain valid HTTP status codes (100-599)'
|
272
|
-
})
|
273
|
-
|
274
|
-
|
275
|
-
class HealthCheckResult(models.Model):
|
276
|
-
"""
|
277
|
-
Individual health check result record.
|
278
|
-
|
279
|
-
Stores detailed results of each health check for analysis and debugging.
|
280
|
-
"""
|
281
|
-
|
282
|
-
# === Relationships ===
|
283
|
-
target = models.ForeignKey(
|
284
|
-
MonitoringTarget,
|
285
|
-
on_delete=models.CASCADE,
|
286
|
-
related_name='results',
|
287
|
-
help_text="Monitoring target this result belongs to"
|
288
|
-
)
|
289
|
-
|
290
|
-
# === Check Results ===
|
291
|
-
timestamp = models.DateTimeField(
|
292
|
-
default=timezone.now,
|
293
|
-
help_text="When the check was performed"
|
294
|
-
)
|
295
|
-
success = models.BooleanField(
|
296
|
-
help_text="Whether the check was successful"
|
297
|
-
)
|
298
|
-
|
299
|
-
# === Response Details ===
|
300
|
-
status_code = models.PositiveIntegerField(
|
301
|
-
null=True,
|
302
|
-
blank=True,
|
303
|
-
help_text="HTTP response status code"
|
304
|
-
)
|
305
|
-
response_time_ms = models.PositiveIntegerField(
|
306
|
-
null=True,
|
307
|
-
blank=True,
|
308
|
-
help_text="Response time in milliseconds"
|
309
|
-
)
|
310
|
-
response_size_bytes = models.PositiveIntegerField(
|
311
|
-
null=True,
|
312
|
-
blank=True,
|
313
|
-
help_text="Response size in bytes"
|
314
|
-
)
|
315
|
-
|
316
|
-
# === Error Information ===
|
317
|
-
error_message = models.TextField(
|
318
|
-
blank=True,
|
319
|
-
help_text="Error message if check failed"
|
320
|
-
)
|
321
|
-
error_type = models.CharField(
|
322
|
-
max_length=100,
|
323
|
-
blank=True,
|
324
|
-
help_text="Type of error (timeout, connection, etc.)"
|
325
|
-
)
|
326
|
-
|
327
|
-
# === Additional Data ===
|
328
|
-
details = models.JSONField(
|
329
|
-
default=dict,
|
330
|
-
blank=True,
|
331
|
-
help_text="Additional check details (headers, content, etc.)"
|
332
|
-
)
|
333
|
-
|
334
|
-
class Meta:
|
335
|
-
ordering = ['-timestamp']
|
336
|
-
verbose_name = "Health Check Result"
|
337
|
-
verbose_name_plural = "Health Check Results"
|
338
|
-
indexes = [
|
339
|
-
models.Index(fields=['target', '-timestamp']),
|
340
|
-
models.Index(fields=['success', '-timestamp']),
|
341
|
-
models.Index(fields=['-timestamp']),
|
342
|
-
]
|
343
|
-
|
344
|
-
def __str__(self) -> str:
|
345
|
-
status = "✅ Success" if self.success else "❌ Failed"
|
346
|
-
return f"{status}: {self.target.site.name} at {self.timestamp}"
|
347
|
-
|
348
|
-
@property
|
349
|
-
def is_slow_response(self) -> bool:
|
350
|
-
"""Check if response was slower than expected."""
|
351
|
-
if not self.response_time_ms or not self.target.expected_response_time_ms:
|
352
|
-
return False
|
353
|
-
return self.response_time_ms > self.target.expected_response_time_ms
|
354
|
-
|
355
|
-
@property
|
356
|
-
def duration_seconds(self) -> Optional[float]:
|
357
|
-
"""Get response time in seconds."""
|
358
|
-
if self.response_time_ms is not None:
|
359
|
-
return self.response_time_ms / 1000.0
|
360
|
-
return None
|
361
|
-
|
362
|
-
def get_error_summary(self) -> str:
|
363
|
-
"""Get concise error summary."""
|
364
|
-
if self.success:
|
365
|
-
return "Success"
|
366
|
-
|
367
|
-
if self.error_type and self.error_message:
|
368
|
-
return f"{self.error_type}: {self.error_message[:100]}"
|
369
|
-
elif self.error_message:
|
370
|
-
return self.error_message[:100]
|
371
|
-
elif self.status_code:
|
372
|
-
return f"HTTP {self.status_code}"
|
373
|
-
else:
|
374
|
-
return "Unknown error"
|
375
|
-
|
376
|
-
@classmethod
|
377
|
-
def create_success(cls, target: MonitoringTarget, **kwargs) -> 'HealthCheckResult':
|
378
|
-
"""Create successful health check result."""
|
379
|
-
return cls.objects.create(
|
380
|
-
target=target,
|
381
|
-
success=True,
|
382
|
-
**kwargs
|
383
|
-
)
|
384
|
-
|
385
|
-
@classmethod
|
386
|
-
def create_failure(cls, target: MonitoringTarget, error_message: str, **kwargs) -> 'HealthCheckResult':
|
387
|
-
"""Create failed health check result."""
|
388
|
-
return cls.objects.create(
|
389
|
-
target=target,
|
390
|
-
success=False,
|
391
|
-
error_message=error_message,
|
392
|
-
**kwargs
|
393
|
-
)
|