django-cfg 1.2.14__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.
Files changed (62) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/maintenance/README.md +305 -0
  3. django_cfg/apps/maintenance/__init__.py +27 -0
  4. django_cfg/apps/maintenance/admin/__init__.py +28 -0
  5. django_cfg/apps/maintenance/admin/deployments_admin.py +251 -0
  6. django_cfg/apps/maintenance/admin/events_admin.py +374 -0
  7. django_cfg/apps/maintenance/admin/monitoring_admin.py +215 -0
  8. django_cfg/apps/maintenance/admin/sites_admin.py +464 -0
  9. django_cfg/apps/maintenance/apps.py +105 -0
  10. django_cfg/apps/maintenance/management/__init__.py +0 -0
  11. django_cfg/apps/maintenance/management/commands/__init__.py +0 -0
  12. django_cfg/apps/maintenance/management/commands/maintenance.py +375 -0
  13. django_cfg/apps/maintenance/management/commands/sync_cloudflare.py +168 -0
  14. django_cfg/apps/maintenance/managers/__init__.py +20 -0
  15. django_cfg/apps/maintenance/managers/deployments.py +287 -0
  16. django_cfg/apps/maintenance/managers/events.py +374 -0
  17. django_cfg/apps/maintenance/managers/monitoring.py +301 -0
  18. django_cfg/apps/maintenance/managers/sites.py +335 -0
  19. django_cfg/apps/maintenance/migrations/0001_initial.py +939 -0
  20. django_cfg/apps/maintenance/migrations/__init__.py +0 -0
  21. django_cfg/apps/maintenance/models/__init__.py +27 -0
  22. django_cfg/apps/maintenance/models/cloudflare.py +316 -0
  23. django_cfg/apps/maintenance/models/maintenance.py +334 -0
  24. django_cfg/apps/maintenance/models/monitoring.py +393 -0
  25. django_cfg/apps/maintenance/models/sites.py +419 -0
  26. django_cfg/apps/maintenance/serializers/__init__.py +60 -0
  27. django_cfg/apps/maintenance/serializers/actions.py +310 -0
  28. django_cfg/apps/maintenance/serializers/base.py +44 -0
  29. django_cfg/apps/maintenance/serializers/deployments.py +209 -0
  30. django_cfg/apps/maintenance/serializers/events.py +210 -0
  31. django_cfg/apps/maintenance/serializers/monitoring.py +278 -0
  32. django_cfg/apps/maintenance/serializers/sites.py +213 -0
  33. django_cfg/apps/maintenance/services/README.md +168 -0
  34. django_cfg/apps/maintenance/services/__init__.py +21 -0
  35. django_cfg/apps/maintenance/services/cloudflare_client.py +441 -0
  36. django_cfg/apps/maintenance/services/dns_manager.py +497 -0
  37. django_cfg/apps/maintenance/services/maintenance_manager.py +504 -0
  38. django_cfg/apps/maintenance/services/site_sync.py +448 -0
  39. django_cfg/apps/maintenance/services/sync_command_service.py +330 -0
  40. django_cfg/apps/maintenance/services/worker_manager.py +264 -0
  41. django_cfg/apps/maintenance/signals.py +38 -0
  42. django_cfg/apps/maintenance/urls.py +36 -0
  43. django_cfg/apps/maintenance/views/__init__.py +18 -0
  44. django_cfg/apps/maintenance/views/base.py +61 -0
  45. django_cfg/apps/maintenance/views/deployments.py +175 -0
  46. django_cfg/apps/maintenance/views/events.py +204 -0
  47. django_cfg/apps/maintenance/views/monitoring.py +213 -0
  48. django_cfg/apps/maintenance/views/sites.py +338 -0
  49. django_cfg/apps/urls.py +5 -1
  50. django_cfg/core/config.py +42 -3
  51. django_cfg/core/generation.py +16 -5
  52. django_cfg/models/cloudflare.py +316 -0
  53. django_cfg/models/revolution.py +1 -1
  54. django_cfg/models/tasks.py +55 -1
  55. django_cfg/modules/base.py +12 -5
  56. django_cfg/modules/django_tasks.py +41 -3
  57. django_cfg/modules/django_unfold/dashboard.py +16 -1
  58. {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/METADATA +2 -1
  59. {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/RECORD +62 -14
  60. {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/WHEEL +0 -0
  61. {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/entry_points.txt +0 -0
  62. {django_cfg-1.2.14.dist-info → django_cfg-1.2.16.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,464 @@
1
+ """
2
+ Sites admin interfaces with Unfold optimization.
3
+ """
4
+
5
+ from django.contrib import admin
6
+ from django.utils.html import format_html
7
+ from django.urls import reverse
8
+ from django.utils.safestring import mark_safe
9
+ from django.db import models
10
+ from django.db.models import Count, Q
11
+ from django.contrib import messages
12
+ from django.shortcuts import redirect
13
+ from unfold.admin import ModelAdmin, TabularInline
14
+ from unfold.decorators import display, action
15
+ from unfold.enums import ActionVariant
16
+ from unfold.contrib.filters.admin import AutocompleteSelectFilter
17
+ from django_cfg import ImportExportModelAdmin, ExportMixin
18
+
19
+ from ..models import CloudflareSite, SiteGroup, MaintenanceEvent, MonitoringTarget
20
+ from ..services import SiteSyncService, MaintenanceManager
21
+
22
+
23
+ class MaintenanceEventInline(TabularInline):
24
+ """Inline for maintenance events with Unfold styling."""
25
+
26
+ model = MaintenanceEvent.sites.through
27
+ verbose_name = "Maintenance Event"
28
+ verbose_name_plural = "🔧 Recent Maintenance Events"
29
+ extra = 0
30
+ max_num = 5
31
+ can_delete = False
32
+ show_change_link = False
33
+ fields = []
34
+ readonly_fields = []
35
+
36
+
37
+ class MonitoringTargetInline(TabularInline):
38
+ """Inline for monitoring targets with Unfold styling."""
39
+
40
+ model = MonitoringTarget
41
+ verbose_name = "Monitoring Target"
42
+ verbose_name_plural = "📊 Monitoring Configuration"
43
+ extra = 0
44
+ max_num = 1
45
+
46
+ fields = ['check_url', 'check_interval', 'status_display', 'last_check_display']
47
+ readonly_fields = ['status_display', 'last_check_display']
48
+
49
+ @display(description="Status")
50
+ def status_display(self, obj):
51
+ """Display monitoring status with badge."""
52
+ if not obj.status:
53
+ return format_html('<span class="badge badge-secondary">Unknown</span>')
54
+
55
+ colors = {
56
+ 'active': 'success',
57
+ 'paused': 'warning',
58
+ 'disabled': 'secondary',
59
+ 'error': 'danger'
60
+ }
61
+ color = colors.get(obj.status, 'secondary')
62
+ return format_html(
63
+ '<span class="badge badge-{}">{}</span>',
64
+ color, obj.get_status_display()
65
+ )
66
+
67
+ @display(description="Last Check")
68
+ def last_check_display(self, obj):
69
+ """Display last check time."""
70
+ if not obj.last_check_at:
71
+ return "Never"
72
+ return obj.last_check_at.strftime("%Y-%m-%d %H:%M")
73
+
74
+
75
+ @admin.register(CloudflareSite)
76
+ class CloudflareSiteAdmin(ModelAdmin, ImportExportModelAdmin):
77
+ """Admin for CloudflareSite with Unfold styling."""
78
+
79
+ # Unfold configuration
80
+ list_display = [
81
+ "name_with_icon",
82
+ "domain",
83
+ "environment_badge",
84
+ "status_badge",
85
+ "zone_info",
86
+ "monitoring_status",
87
+ "events_count",
88
+ "owner",
89
+ "last_maintenance_display"
90
+ ]
91
+ list_display_links = ["name_with_icon", "domain"]
92
+ search_fields = ["name", "domain", "zone_id", "owner__email"]
93
+ list_filter = [
94
+ "environment",
95
+ "current_status",
96
+ "created_at",
97
+ ("owner", AutocompleteSelectFilter),
98
+ ]
99
+ ordering = ["-created_at"]
100
+ readonly_fields = [
101
+ "zone_id", "account_id", "current_status", "maintenance_active",
102
+ "last_status_check", "last_maintenance_at", "created_at", "updated_at"
103
+ ]
104
+
105
+ fieldsets = (
106
+ ("Basic Information", {
107
+ "fields": ("name", "domain", "description", "owner")
108
+ }),
109
+ ("Configuration", {
110
+ "fields": ("environment", "project", "tags")
111
+ }),
112
+ ("Cloudflare Integration", {
113
+ "fields": ("zone_id", "account_id", "api_token"),
114
+ "classes": ("collapse",)
115
+ }),
116
+ ("Maintenance Settings", {
117
+ "fields": ("worker_name", "maintenance_template", "custom_maintenance_message"),
118
+ "classes": ("collapse",)
119
+ }),
120
+ ("Monitoring", {
121
+ "fields": ("monitoring_enabled", "health_check_url", "check_interval"),
122
+ "classes": ("collapse",)
123
+ }),
124
+ ("Status", {
125
+ "fields": ("current_status", "maintenance_active", "last_status_check", "last_maintenance_at"),
126
+ "classes": ("collapse",)
127
+ }),
128
+ ("Access Control", {
129
+ "fields": ("allowed_users",),
130
+ "classes": ("collapse",)
131
+ }),
132
+ ("Timestamps", {
133
+ "fields": ("created_at", "updated_at"),
134
+ "classes": ("collapse",)
135
+ })
136
+ )
137
+
138
+ inlines = [MonitoringTargetInline, MaintenanceEventInline]
139
+
140
+ # Unfold actions
141
+ actions_detail = ["sync_with_cloudflare", "enable_maintenance", "disable_maintenance"]
142
+ actions_list = ["bulk_sync_sites"]
143
+
144
+ @display(description="Site", ordering="name")
145
+ def name_with_icon(self, obj):
146
+ """Display site name with icon."""
147
+ icon = "🌐"
148
+ if obj.environment == "production":
149
+ icon = "🚀"
150
+ elif obj.environment == "staging":
151
+ icon = "🧪"
152
+ elif obj.environment == "development":
153
+ icon = "🔧"
154
+
155
+ return format_html('{} {}', icon, obj.name)
156
+
157
+ @display(description="Environment", ordering="environment")
158
+ def environment_badge(self, obj):
159
+ """Display environment with colored badge."""
160
+ colors = {
161
+ 'production': 'success',
162
+ 'staging': 'warning',
163
+ 'development': 'info',
164
+ 'testing': 'secondary'
165
+ }
166
+ color = colors.get(obj.environment, 'secondary')
167
+ return format_html(
168
+ '<span class="badge badge-{}">{}</span>',
169
+ color, obj.get_environment_display()
170
+ )
171
+
172
+ @display(description="Status", ordering="current_status")
173
+ def status_badge(self, obj):
174
+ """Display status with colored badge."""
175
+ colors = {
176
+ 'active': 'success',
177
+ 'maintenance': 'warning',
178
+ 'offline': 'danger',
179
+ 'unknown': 'secondary'
180
+ }
181
+ color = colors.get(obj.current_status, 'secondary')
182
+ return format_html(
183
+ '<span class="badge badge-{}">{}</span>',
184
+ color, obj.get_current_status_display()
185
+ )
186
+
187
+ @display(description="Zone Info")
188
+ def zone_info(self, obj):
189
+ """Display Cloudflare zone information."""
190
+ if not obj.zone_id:
191
+ return format_html('<span class="text-muted">Not synced</span>')
192
+
193
+ return format_html(
194
+ '<small class="text-muted">Zone: {}</small>',
195
+ obj.zone_id[:8] + "..." if len(obj.zone_id) > 8 else obj.zone_id
196
+ )
197
+
198
+ @display(description="Monitoring")
199
+ def monitoring_status(self, obj):
200
+ """Display monitoring status."""
201
+ try:
202
+ target = obj.monitoring_target
203
+ if target.enabled:
204
+ color = 'success' if target.status == 'healthy' else 'danger'
205
+ return format_html(
206
+ '<span class="badge badge-{}">{}</span>',
207
+ color, target.status or 'Unknown'
208
+ )
209
+ else:
210
+ return format_html('<span class="text-muted">Disabled</span>')
211
+ except:
212
+ return format_html('<span class="text-muted">Not configured</span>')
213
+
214
+ @display(description="Events", ordering="maintenance_events_count")
215
+ def events_count(self, obj):
216
+ """Display maintenance events count."""
217
+ count = obj.maintenance_events.count()
218
+ if count > 0:
219
+ return format_html(
220
+ '<a href="{}?sites__id__exact={}" class="text-decoration-none">{} events</a>',
221
+ reverse('admin:django_cfg_maintenance_maintenanceevent_changelist'),
222
+ obj.id, count
223
+ )
224
+ return "No events"
225
+
226
+ @display(description="Last Maintenance", ordering="last_maintenance_at")
227
+ def last_maintenance_display(self, obj):
228
+ """Display last maintenance time."""
229
+ if not obj.last_maintenance_at:
230
+ return format_html('<span class="text-muted">Never</span>')
231
+ return obj.last_maintenance_at.strftime("%Y-%m-%d %H:%M")
232
+
233
+ def get_queryset(self, request):
234
+ """Optimize queryset with annotations."""
235
+ return super().get_queryset(request).select_related(
236
+ 'owner'
237
+ ).prefetch_related(
238
+ 'maintenance_events'
239
+ ).annotate(
240
+ maintenance_events_count=Count('maintenance_events')
241
+ )
242
+
243
+ @action(
244
+ description="🔄 Sync with Cloudflare",
245
+ icon="refresh",
246
+ variant=ActionVariant.INFO
247
+ )
248
+ def sync_with_cloudflare(self, request, object_id):
249
+ """Sync site with Cloudflare zones."""
250
+ try:
251
+ # Get the site object to get its name for the message
252
+ site = self.get_object(request, object_id)
253
+ if not site:
254
+ messages.error(request, "Site not found.")
255
+ return redirect(request.META.get('HTTP_REFERER', '/admin/'))
256
+
257
+ # TODO: Implement actual sync logic here
258
+ # For now, just update the last_status_check timestamp
259
+ from django.utils import timezone
260
+ site.last_status_check = timezone.now()
261
+ site.save(update_fields=['last_status_check'])
262
+
263
+ messages.success(
264
+ request,
265
+ f"Site '{site.name}' has been queued for synchronization with Cloudflare.",
266
+ )
267
+
268
+ except CloudflareSite.DoesNotExist:
269
+ messages.error(
270
+ request,
271
+ "Site not found.",
272
+ )
273
+ except Exception as e:
274
+ import logging
275
+ logger = logging.getLogger(__name__)
276
+ logger.exception(f"Unexpected error syncing site {object_id}")
277
+ messages.error(
278
+ request,
279
+ f"Unexpected error syncing site: {e}",
280
+ )
281
+
282
+ return redirect(request.META.get('HTTP_REFERER', '/admin/'))
283
+
284
+ @action(
285
+ description="🔧 Enable Maintenance",
286
+ icon="build",
287
+ variant=ActionVariant.WARNING
288
+ )
289
+ def enable_maintenance(self, request, object_id):
290
+ """Enable maintenance mode for a site."""
291
+ try:
292
+ site = self.get_object(request, object_id)
293
+ if not site:
294
+ messages.error(request, "Site not found.")
295
+ return redirect(request.META.get('HTTP_REFERER', '/admin/'))
296
+
297
+ manager = MaintenanceManager(request.user)
298
+ manager.enable_maintenance_mode(site)
299
+ messages.success(request, f"Maintenance mode enabled for {site.name}.")
300
+ except Exception as e:
301
+ messages.error(request, f"Failed to enable maintenance: {str(e)}")
302
+
303
+ return redirect(request.META.get('HTTP_REFERER', '/admin/'))
304
+
305
+ @action(
306
+ description="✅ Disable Maintenance",
307
+ icon="check_circle",
308
+ variant=ActionVariant.SUCCESS
309
+ )
310
+ def disable_maintenance(self, request, object_id):
311
+ """Disable maintenance mode for a site."""
312
+ try:
313
+ site = self.get_object(request, object_id)
314
+ if not site:
315
+ messages.error(request, "Site not found.")
316
+ return redirect(request.META.get('HTTP_REFERER', '/admin/'))
317
+
318
+ manager = MaintenanceManager(request.user)
319
+ manager.disable_maintenance_mode(site)
320
+ messages.success(request, f"Maintenance mode disabled for {site.name}.")
321
+ except Exception as e:
322
+ messages.error(request, f"Failed to disable maintenance: {str(e)}")
323
+
324
+ return redirect(request.META.get('HTTP_REFERER', '/admin/'))
325
+
326
+ def save_model(self, request, obj, form, change):
327
+ """Set owner to current user if not set."""
328
+ if not change:
329
+ obj.owner = request.user
330
+ super().save_model(request, obj, form, change)
331
+
332
+ @action(
333
+ description="🔄 Sync All Sites with Cloudflare",
334
+ icon="sync",
335
+ variant=ActionVariant.INFO,
336
+ url_path="bulk-sync-sites",
337
+ permissions=["bulk_sync_sites"]
338
+ )
339
+ def bulk_sync_sites(self, request):
340
+ """Bulk sync all sites with Cloudflare."""
341
+ try:
342
+ from django.utils import timezone
343
+ from django_cfg.apps.maintenance.services import SiteSyncService
344
+
345
+ # Get all sites for the current user (or all if superuser)
346
+ if request.user.is_superuser:
347
+ sites = CloudflareSite.objects.all()
348
+ else:
349
+ sites = CloudflareSite.objects.filter(owner=request.user)
350
+
351
+ count = sites.count()
352
+ if count == 0:
353
+ messages.warning(request, "No sites found to synchronize.")
354
+ return redirect(request.META.get('HTTP_REFERER', '/admin/'))
355
+
356
+ # Update last_status_check for all sites
357
+ sites.update(last_status_check=timezone.now())
358
+
359
+ # TODO: Implement actual bulk sync logic here using SiteSyncService
360
+ # This would typically queue background tasks for each site
361
+
362
+ messages.success(
363
+ request,
364
+ f"Successfully queued {count} sites for Cloudflare synchronization."
365
+ )
366
+
367
+ except Exception as e:
368
+ import logging
369
+ logger = logging.getLogger(__name__)
370
+ logger.exception("Bulk sync failed")
371
+ messages.error(
372
+ request,
373
+ f"Bulk sync failed: {e}"
374
+ )
375
+
376
+ return redirect(request.META.get('HTTP_REFERER', '/admin/'))
377
+
378
+ def has_bulk_sync_sites_permission(self, request):
379
+ """Check if user has permission to bulk sync sites."""
380
+ return request.user.is_staff
381
+
382
+
383
+ @admin.register(SiteGroup)
384
+ class SiteGroupAdmin(ModelAdmin, ImportExportModelAdmin):
385
+ """Admin for SiteGroup with Unfold styling."""
386
+
387
+ list_display = [
388
+ "name_with_icon",
389
+ "description_short",
390
+ "sites_count",
391
+ "owner",
392
+ "created_at_display"
393
+ ]
394
+ list_display_links = ["name_with_icon"]
395
+ search_fields = ["name", "description", "owner__email"]
396
+ list_filter = [
397
+ "created_at",
398
+ ("owner", AutocompleteSelectFilter),
399
+ ]
400
+ ordering = ["-created_at"]
401
+ readonly_fields = ["created_at", "updated_at"]
402
+
403
+ fieldsets = (
404
+ ("Basic Information", {
405
+ "fields": ("name", "description", "owner")
406
+ }),
407
+ ("Sites", {
408
+ "fields": ("sites",)
409
+ }),
410
+ ("Timestamps", {
411
+ "fields": ("created_at", "updated_at"),
412
+ "classes": ("collapse",)
413
+ })
414
+ )
415
+
416
+ filter_horizontal = ["sites"]
417
+
418
+ @display(description="Group", ordering="name")
419
+ def name_with_icon(self, obj):
420
+ """Display group name with icon."""
421
+ return format_html('📁 {}', obj.name)
422
+
423
+ @display(description="Description")
424
+ def description_short(self, obj):
425
+ """Display truncated description."""
426
+ if not obj.description:
427
+ return format_html('<span class="text-muted">No description</span>')
428
+
429
+ if len(obj.description) > 50:
430
+ return obj.description[:50] + "..."
431
+ return obj.description
432
+
433
+ @display(description="Sites", ordering="sites_count")
434
+ def sites_count(self, obj):
435
+ """Display sites count with link."""
436
+ count = obj.sites.count()
437
+ if count > 0:
438
+ return format_html(
439
+ '<a href="{}?groups__id__exact={}" class="text-decoration-none">{} sites</a>',
440
+ reverse('admin:django_cfg_maintenance_cloudflaresite_changelist'),
441
+ obj.id, count
442
+ )
443
+ return "No sites"
444
+
445
+ @display(description="Created", ordering="created_at")
446
+ def created_at_display(self, obj):
447
+ """Display creation time."""
448
+ return obj.created_at.strftime("%Y-%m-%d %H:%M")
449
+
450
+ def get_queryset(self, request):
451
+ """Optimize queryset with annotations."""
452
+ return super().get_queryset(request).select_related(
453
+ 'owner'
454
+ ).prefetch_related(
455
+ 'sites'
456
+ ).annotate(
457
+ sites_count=Count('sites')
458
+ )
459
+
460
+ def save_model(self, request, obj, form, change):
461
+ """Set owner to current user if not set."""
462
+ if not change:
463
+ obj.owner = request.user
464
+ super().save_model(request, obj, form, change)
@@ -0,0 +1,105 @@
1
+ """
2
+ Maintenance Application Configuration
3
+
4
+ Follows django-cfg patterns for app configuration with automatic setup.
5
+ """
6
+
7
+ from django.apps import AppConfig
8
+ from django.conf import settings
9
+ import logging
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class MaintenanceConfig(AppConfig):
15
+ """Maintenance application configuration."""
16
+
17
+ default_auto_field = "django.db.models.BigAutoField"
18
+ name = "django_cfg.apps.maintenance"
19
+ label = "django_cfg_maintenance"
20
+ verbose_name = "Django CFG Maintenance"
21
+
22
+ def ready(self):
23
+ """Initialize the maintenance application."""
24
+ # Import signal handlers
25
+ try:
26
+ import django_cfg.apps.maintenance.signals # noqa
27
+ except ImportError:
28
+ pass
29
+
30
+ # Auto-setup Cloudflare if configured
31
+ self._setup_cloudflare_auto_config()
32
+
33
+ def _setup_cloudflare_auto_config(self):
34
+ """Auto-setup Cloudflare configuration if API token and domain are provided."""
35
+ try:
36
+ # Check if basic Cloudflare config is available
37
+ api_token = getattr(settings, 'CLOUDFLARE_API_TOKEN', None)
38
+ domain = getattr(settings, 'CLOUDFLARE_DOMAIN', None)
39
+
40
+ if api_token and domain:
41
+ logger.info("Cloudflare maintenance mode auto-configuration detected")
42
+
43
+ # Import here to avoid circular imports
44
+ from django_cfg.apps.maintenance.services.auto_setup import CloudflareAutoSetup
45
+ from django_cfg.models.cloudflare import CloudflareConfig
46
+
47
+ # Create configuration
48
+ config = CloudflareConfig(
49
+ api_token=api_token,
50
+ domain=domain
51
+ )
52
+
53
+ # Run auto-setup in background (non-blocking)
54
+ import asyncio
55
+ try:
56
+ # Try to get existing event loop
57
+ loop = asyncio.get_event_loop()
58
+ if loop.is_running():
59
+ # If loop is running, schedule the task
60
+ asyncio.create_task(self._run_auto_setup(config))
61
+ else:
62
+ # If no loop is running, run in new thread
63
+ import threading
64
+ thread = threading.Thread(
65
+ target=self._run_auto_setup_sync,
66
+ args=(config,)
67
+ )
68
+ thread.daemon = True
69
+ thread.start()
70
+ except RuntimeError:
71
+ # No event loop, run in thread
72
+ import threading
73
+ thread = threading.Thread(
74
+ target=self._run_auto_setup_sync,
75
+ args=(config,)
76
+ )
77
+ thread.daemon = True
78
+ thread.start()
79
+
80
+ except Exception as e:
81
+ logger.warning(f"Cloudflare auto-setup skipped: {e}")
82
+
83
+ async def _run_auto_setup(self, config):
84
+ """Run auto-setup asynchronously."""
85
+ try:
86
+ from django_cfg.apps.maintenance.services.auto_setup import CloudflareAutoSetup
87
+
88
+ setup_service = CloudflareAutoSetup(config)
89
+ result = await setup_service.setup_complete_infrastructure()
90
+
91
+ if result.success:
92
+ logger.info(f"✅ Cloudflare auto-setup completed in {result.get_duration_seconds():.2f}s")
93
+ else:
94
+ logger.warning(f"❌ Cloudflare auto-setup failed: {len(result.get_failed_steps())} errors")
95
+
96
+ except Exception as e:
97
+ logger.error(f"Cloudflare auto-setup error: {e}")
98
+
99
+ def _run_auto_setup_sync(self, config):
100
+ """Run auto-setup synchronously in thread."""
101
+ try:
102
+ import asyncio
103
+ asyncio.run(self._run_auto_setup(config))
104
+ except Exception as e:
105
+ logger.error(f"Cloudflare auto-setup thread error: {e}")
File without changes