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
@@ -0,0 +1,448 @@
|
|
1
|
+
"""
|
2
|
+
CloudflareSite admin with Unfold styling and action buttons.
|
3
|
+
|
4
|
+
Beautiful admin interface inspired by the old complex system but simplified.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib import admin
|
8
|
+
from django.utils.html import format_html
|
9
|
+
from django.urls import reverse
|
10
|
+
from django.utils.safestring import mark_safe
|
11
|
+
from django.contrib import messages
|
12
|
+
from django.http import HttpRequest
|
13
|
+
from django.shortcuts import redirect
|
14
|
+
from typing import Any
|
15
|
+
|
16
|
+
from unfold.admin import ModelAdmin, TabularInline
|
17
|
+
from unfold.decorators import display, action
|
18
|
+
from unfold.enums import ActionVariant
|
19
|
+
|
20
|
+
from ..models import CloudflareSite, MaintenanceLog
|
21
|
+
from ..services import MaintenanceService
|
22
|
+
|
23
|
+
|
24
|
+
class MaintenanceLogInline(TabularInline):
|
25
|
+
"""Inline for recent maintenance logs."""
|
26
|
+
|
27
|
+
model = MaintenanceLog
|
28
|
+
verbose_name = "Recent Log"
|
29
|
+
verbose_name_plural = "📋 Recent Maintenance Logs"
|
30
|
+
extra = 0
|
31
|
+
max_num = 5
|
32
|
+
can_delete = False
|
33
|
+
show_change_link = True
|
34
|
+
|
35
|
+
fields = ['status_display', 'action', 'created_at', 'duration_seconds', 'error_preview']
|
36
|
+
readonly_fields = ['status_display', 'action', 'created_at', 'duration_seconds', 'error_preview']
|
37
|
+
|
38
|
+
def has_add_permission(self, request, obj=None):
|
39
|
+
return False
|
40
|
+
|
41
|
+
def has_change_permission(self, request, obj=None):
|
42
|
+
return False
|
43
|
+
|
44
|
+
@display(description="Status")
|
45
|
+
def status_display(self, obj):
|
46
|
+
"""Display status with emoji."""
|
47
|
+
status_emoji = {
|
48
|
+
MaintenanceLog.Status.SUCCESS: "✅",
|
49
|
+
MaintenanceLog.Status.FAILED: "❌",
|
50
|
+
MaintenanceLog.Status.PENDING: "⏳"
|
51
|
+
}.get(obj.status, "❓")
|
52
|
+
|
53
|
+
return format_html('{} {}', status_emoji, obj.get_status_display())
|
54
|
+
|
55
|
+
@display(description="Error")
|
56
|
+
def error_preview(self, obj):
|
57
|
+
"""Show error message preview."""
|
58
|
+
if not obj.error_message:
|
59
|
+
return "-"
|
60
|
+
|
61
|
+
preview = obj.error_message[:50]
|
62
|
+
if len(obj.error_message) > 50:
|
63
|
+
preview += "..."
|
64
|
+
|
65
|
+
return format_html('<span style="color: red; font-family: monospace;">{}</span>', preview)
|
66
|
+
|
67
|
+
|
68
|
+
@admin.register(CloudflareSite)
|
69
|
+
class CloudflareSiteAdmin(ModelAdmin):
|
70
|
+
"""Admin for CloudflareSite with Unfold styling and action buttons."""
|
71
|
+
|
72
|
+
list_display = [
|
73
|
+
'status_display',
|
74
|
+
'name',
|
75
|
+
'domain',
|
76
|
+
'maintenance_badge',
|
77
|
+
'active_badge',
|
78
|
+
'last_maintenance_at',
|
79
|
+
'logs_count',
|
80
|
+
'action_buttons'
|
81
|
+
]
|
82
|
+
|
83
|
+
list_display_links = ['name', 'domain']
|
84
|
+
|
85
|
+
search_fields = ['name', 'domain', 'zone_id']
|
86
|
+
|
87
|
+
list_filter = [
|
88
|
+
'maintenance_active',
|
89
|
+
'is_active',
|
90
|
+
'created_at',
|
91
|
+
'last_maintenance_at'
|
92
|
+
]
|
93
|
+
|
94
|
+
readonly_fields = [
|
95
|
+
'created_at',
|
96
|
+
'updated_at',
|
97
|
+
'last_maintenance_at',
|
98
|
+
'logs_preview'
|
99
|
+
]
|
100
|
+
|
101
|
+
fieldsets = [
|
102
|
+
('Basic Information', {
|
103
|
+
'fields': ['name', 'domain']
|
104
|
+
}),
|
105
|
+
('Cloudflare Configuration', {
|
106
|
+
'fields': ['zone_id', 'account_id', 'api_key'],
|
107
|
+
'classes': ['collapse']
|
108
|
+
}),
|
109
|
+
('Status', {
|
110
|
+
'fields': ['maintenance_active', 'maintenance_url', 'is_active']
|
111
|
+
}),
|
112
|
+
('Timestamps', {
|
113
|
+
'fields': ['created_at', 'updated_at', 'last_maintenance_at'],
|
114
|
+
'classes': ['collapse']
|
115
|
+
}),
|
116
|
+
('Recent Activity', {
|
117
|
+
'fields': ['logs_preview'],
|
118
|
+
'classes': ['collapse']
|
119
|
+
})
|
120
|
+
]
|
121
|
+
|
122
|
+
inlines = [MaintenanceLogInline]
|
123
|
+
|
124
|
+
actions = [
|
125
|
+
'enable_maintenance_action',
|
126
|
+
'disable_maintenance_action',
|
127
|
+
'sync_from_cloudflare_action'
|
128
|
+
]
|
129
|
+
|
130
|
+
# Unfold action buttons
|
131
|
+
actions_detail = [
|
132
|
+
'sync_with_cloudflare_detail',
|
133
|
+
'enable_maintenance_detail',
|
134
|
+
'disable_maintenance_detail'
|
135
|
+
]
|
136
|
+
actions_list = [
|
137
|
+
'bulk_sync_sites',
|
138
|
+
'bulk_discover_sites'
|
139
|
+
]
|
140
|
+
|
141
|
+
# Display methods
|
142
|
+
|
143
|
+
@display(description="Status")
|
144
|
+
def status_display(self, obj: CloudflareSite) -> str:
|
145
|
+
"""Display status with emoji."""
|
146
|
+
if obj.maintenance_active:
|
147
|
+
return format_html('<span style="color: orange;">🔧 {}</span>', obj.name)
|
148
|
+
elif obj.is_active:
|
149
|
+
return format_html('<span style="color: green;">🟢 {}</span>', obj.name)
|
150
|
+
else:
|
151
|
+
return format_html('<span style="color: red;">🔴 {}</span>', obj.name)
|
152
|
+
|
153
|
+
@display(description="Maintenance")
|
154
|
+
def maintenance_badge(self, obj: CloudflareSite) -> str:
|
155
|
+
"""Display maintenance status badge."""
|
156
|
+
if obj.maintenance_active:
|
157
|
+
return format_html('<span class="badge badge-warning">🔧 Active</span>')
|
158
|
+
else:
|
159
|
+
return format_html('<span class="badge badge-success">✅ Normal</span>')
|
160
|
+
|
161
|
+
@display(description="Site Active")
|
162
|
+
def active_badge(self, obj: CloudflareSite) -> str:
|
163
|
+
"""Display active status badge."""
|
164
|
+
if obj.is_active:
|
165
|
+
return format_html('<span class="badge badge-success">Active</span>')
|
166
|
+
else:
|
167
|
+
return format_html('<span class="badge badge-secondary">Inactive</span>')
|
168
|
+
|
169
|
+
@display(description="Logs")
|
170
|
+
def logs_count(self, obj: CloudflareSite) -> str:
|
171
|
+
"""Display count of logs with link."""
|
172
|
+
count = obj.logs.count()
|
173
|
+
if count > 0:
|
174
|
+
url = reverse('admin:maintenance_maintenancelog_changelist')
|
175
|
+
return format_html(
|
176
|
+
'<a href="{}?site__id__exact={}">{} logs</a>',
|
177
|
+
url, obj.id, count
|
178
|
+
)
|
179
|
+
return "No logs"
|
180
|
+
|
181
|
+
@display(description="Actions")
|
182
|
+
def action_buttons(self, obj: CloudflareSite) -> str:
|
183
|
+
"""Display action buttons."""
|
184
|
+
buttons = []
|
185
|
+
|
186
|
+
if obj.maintenance_active:
|
187
|
+
buttons.append(
|
188
|
+
f'<a href="#" onclick="disableMaintenance({obj.id}, \'{obj.domain}\')" '
|
189
|
+
f'class="button" style="background: green; color: white; margin: 2px;">Disable</a>'
|
190
|
+
)
|
191
|
+
else:
|
192
|
+
buttons.append(
|
193
|
+
f'<a href="#" onclick="enableMaintenance({obj.id}, \'{obj.domain}\')" '
|
194
|
+
f'class="button" style="background: orange; color: white; margin: 2px;">Enable</a>'
|
195
|
+
)
|
196
|
+
|
197
|
+
buttons.append(
|
198
|
+
f'<a href="#" onclick="syncSite({obj.id}, \'{obj.domain}\')" '
|
199
|
+
f'class="button" style="background: blue; color: white; margin: 2px;">Sync</a>'
|
200
|
+
)
|
201
|
+
|
202
|
+
return mark_safe(' '.join(buttons))
|
203
|
+
|
204
|
+
def logs_preview(self, obj: CloudflareSite) -> str:
|
205
|
+
"""Show recent logs preview."""
|
206
|
+
recent_logs = obj.logs.all()[:5]
|
207
|
+
if not recent_logs:
|
208
|
+
return "No logs yet"
|
209
|
+
|
210
|
+
html = "<ul>"
|
211
|
+
for log in recent_logs:
|
212
|
+
status_emoji = {
|
213
|
+
MaintenanceLog.Status.SUCCESS: "✅",
|
214
|
+
MaintenanceLog.Status.FAILED: "❌",
|
215
|
+
MaintenanceLog.Status.PENDING: "⏳"
|
216
|
+
}.get(log.status, "❓")
|
217
|
+
|
218
|
+
html += f"<li>{status_emoji} {log.get_action_display()} - {log.created_at.strftime('%Y-%m-%d %H:%M')}"
|
219
|
+
if log.error_message:
|
220
|
+
html += f" <em>({log.error_message[:50]}...)</em>"
|
221
|
+
html += "</li>"
|
222
|
+
|
223
|
+
html += "</ul>"
|
224
|
+
|
225
|
+
if obj.logs.count() > 5:
|
226
|
+
url = reverse('admin:maintenance_maintenancelog_changelist')
|
227
|
+
html += f'<a href="{url}?site__id__exact={obj.id}">View all logs →</a>'
|
228
|
+
|
229
|
+
return mark_safe(html)
|
230
|
+
|
231
|
+
logs_preview.short_description = "Recent Activity"
|
232
|
+
|
233
|
+
# Admin Actions
|
234
|
+
|
235
|
+
@action(description="🔧 Enable maintenance mode")
|
236
|
+
def enable_maintenance_action(self, request: HttpRequest, queryset) -> None:
|
237
|
+
"""Enable maintenance for selected sites."""
|
238
|
+
success_count = 0
|
239
|
+
error_count = 0
|
240
|
+
|
241
|
+
for site in queryset:
|
242
|
+
try:
|
243
|
+
service = MaintenanceService(site)
|
244
|
+
service.enable_maintenance("Enabled via admin interface")
|
245
|
+
success_count += 1
|
246
|
+
except Exception as e:
|
247
|
+
error_count += 1
|
248
|
+
messages.error(request, f"Failed to enable maintenance for {site.domain}: {str(e)}")
|
249
|
+
|
250
|
+
if success_count:
|
251
|
+
messages.success(request, f"Successfully enabled maintenance for {success_count} sites")
|
252
|
+
|
253
|
+
if error_count:
|
254
|
+
messages.error(request, f"Failed to enable maintenance for {error_count} sites")
|
255
|
+
|
256
|
+
@action(description="🟢 Disable maintenance mode")
|
257
|
+
def disable_maintenance_action(self, request: HttpRequest, queryset) -> None:
|
258
|
+
"""Disable maintenance for selected sites."""
|
259
|
+
success_count = 0
|
260
|
+
error_count = 0
|
261
|
+
|
262
|
+
for site in queryset:
|
263
|
+
try:
|
264
|
+
service = MaintenanceService(site)
|
265
|
+
service.disable_maintenance()
|
266
|
+
success_count += 1
|
267
|
+
except Exception as e:
|
268
|
+
error_count += 1
|
269
|
+
messages.error(request, f"Failed to disable maintenance for {site.domain}: {str(e)}")
|
270
|
+
|
271
|
+
if success_count:
|
272
|
+
messages.success(request, f"Successfully disabled maintenance for {success_count} sites")
|
273
|
+
|
274
|
+
if error_count:
|
275
|
+
messages.error(request, f"Failed to disable maintenance for {error_count} sites")
|
276
|
+
|
277
|
+
@action(description="🔄 Sync from Cloudflare")
|
278
|
+
def sync_from_cloudflare_action(self, request: HttpRequest, queryset) -> None:
|
279
|
+
"""Sync selected sites from Cloudflare."""
|
280
|
+
success_count = 0
|
281
|
+
error_count = 0
|
282
|
+
|
283
|
+
for site in queryset:
|
284
|
+
try:
|
285
|
+
service = MaintenanceService(site)
|
286
|
+
service.sync_site_from_cloudflare()
|
287
|
+
success_count += 1
|
288
|
+
except Exception as e:
|
289
|
+
error_count += 1
|
290
|
+
messages.error(request, f"Failed to sync {site.domain}: {str(e)}")
|
291
|
+
|
292
|
+
if success_count:
|
293
|
+
messages.success(request, f"Successfully synced {success_count} sites from Cloudflare")
|
294
|
+
|
295
|
+
if error_count:
|
296
|
+
messages.error(request, f"Failed to sync {error_count} sites")
|
297
|
+
|
298
|
+
# Unfold detail actions (кнопки на странице отдельного объекта)
|
299
|
+
|
300
|
+
@action(
|
301
|
+
description="🔄 Sync with Cloudflare",
|
302
|
+
icon="refresh",
|
303
|
+
variant=ActionVariant.INFO
|
304
|
+
)
|
305
|
+
def sync_with_cloudflare_detail(self, request, object_id):
|
306
|
+
"""Sync site with Cloudflare zones."""
|
307
|
+
try:
|
308
|
+
site = self.get_object(request, object_id)
|
309
|
+
if not site:
|
310
|
+
messages.error(request, "Site not found.")
|
311
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
312
|
+
|
313
|
+
# Use SiteSyncService for site synchronization
|
314
|
+
from ..services import SiteSyncService
|
315
|
+
sync_service = SiteSyncService(site.api_key)
|
316
|
+
sync_service.sync_zones()
|
317
|
+
|
318
|
+
messages.success(
|
319
|
+
request,
|
320
|
+
f"Site '{site.name}' has been synchronized with Cloudflare."
|
321
|
+
)
|
322
|
+
|
323
|
+
except Exception as e:
|
324
|
+
messages.error(request, f"Failed to sync site: {str(e)}")
|
325
|
+
|
326
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
327
|
+
|
328
|
+
@action(
|
329
|
+
description="🔧 Enable Maintenance",
|
330
|
+
icon="build",
|
331
|
+
variant=ActionVariant.WARNING
|
332
|
+
)
|
333
|
+
def enable_maintenance_detail(self, request, object_id):
|
334
|
+
"""Enable maintenance mode for a site."""
|
335
|
+
try:
|
336
|
+
site = self.get_object(request, object_id)
|
337
|
+
if not site:
|
338
|
+
messages.error(request, "Site not found.")
|
339
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
340
|
+
|
341
|
+
service = MaintenanceService(site)
|
342
|
+
service.enable_maintenance("Enabled via admin interface")
|
343
|
+
|
344
|
+
messages.success(request, f"Maintenance mode enabled for {site.name}.")
|
345
|
+
|
346
|
+
except Exception as e:
|
347
|
+
messages.error(request, f"Failed to enable maintenance: {str(e)}")
|
348
|
+
|
349
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
350
|
+
|
351
|
+
@action(
|
352
|
+
description="✅ Disable Maintenance",
|
353
|
+
icon="check_circle",
|
354
|
+
variant=ActionVariant.SUCCESS
|
355
|
+
)
|
356
|
+
def disable_maintenance_detail(self, request, object_id):
|
357
|
+
"""Disable maintenance mode for a site."""
|
358
|
+
try:
|
359
|
+
site = self.get_object(request, object_id)
|
360
|
+
if not site:
|
361
|
+
messages.error(request, "Site not found.")
|
362
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
363
|
+
|
364
|
+
service = MaintenanceService(site)
|
365
|
+
service.disable_maintenance()
|
366
|
+
|
367
|
+
messages.success(request, f"Maintenance mode disabled for {site.name}.")
|
368
|
+
|
369
|
+
except Exception as e:
|
370
|
+
messages.error(request, f"Failed to disable maintenance: {str(e)}")
|
371
|
+
|
372
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
373
|
+
|
374
|
+
# Unfold list actions (кнопки над списком)
|
375
|
+
|
376
|
+
@action(
|
377
|
+
description="🔄 Sync All Sites with Cloudflare",
|
378
|
+
icon="sync",
|
379
|
+
variant=ActionVariant.INFO,
|
380
|
+
url_path="bulk-sync-sites"
|
381
|
+
)
|
382
|
+
def bulk_sync_sites(self, request):
|
383
|
+
"""Bulk sync all sites with Cloudflare."""
|
384
|
+
try:
|
385
|
+
from ..models import CloudflareSite
|
386
|
+
|
387
|
+
# Use manager method for bulk sync
|
388
|
+
result = CloudflareSite.objects.bulk_sync_all()
|
389
|
+
|
390
|
+
if result.get('synced', 0) > 0:
|
391
|
+
messages.success(
|
392
|
+
request,
|
393
|
+
f"Successfully synchronized {result['synced']} sites with Cloudflare."
|
394
|
+
)
|
395
|
+
|
396
|
+
if result.get('errors', 0) > 0:
|
397
|
+
# Show detailed error messages
|
398
|
+
error_details = result.get('error_details', [])
|
399
|
+
for error in error_details:
|
400
|
+
messages.error(request, f"Sync error: {error}")
|
401
|
+
|
402
|
+
messages.warning(
|
403
|
+
request,
|
404
|
+
f"Synchronization completed with {result['errors']} errors."
|
405
|
+
)
|
406
|
+
|
407
|
+
if result.get('synced', 0) == 0 and result.get('errors', 0) == 0:
|
408
|
+
messages.info(request, "No sites to synchronize.")
|
409
|
+
|
410
|
+
except Exception as e:
|
411
|
+
messages.error(request, f"Bulk sync failed: {str(e)}")
|
412
|
+
|
413
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
414
|
+
|
415
|
+
@action(
|
416
|
+
description="🔍 Discover New Sites",
|
417
|
+
icon="search",
|
418
|
+
variant=ActionVariant.SUCCESS,
|
419
|
+
url_path="bulk-discover-sites"
|
420
|
+
)
|
421
|
+
def bulk_discover_sites(self, request):
|
422
|
+
"""Discover new sites from Cloudflare."""
|
423
|
+
try:
|
424
|
+
from ..models import CloudflareSite, CloudflareApiKey
|
425
|
+
|
426
|
+
# Check if we have active API keys
|
427
|
+
if not CloudflareApiKey.objects.filter(is_active=True).exists():
|
428
|
+
messages.error(request, "No active API keys found. Please add Cloudflare API keys first.")
|
429
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
430
|
+
|
431
|
+
# Use manager method for discovery
|
432
|
+
result = CloudflareSite.objects.discover_all_sites()
|
433
|
+
|
434
|
+
if result.get('discovered', 0) > 0:
|
435
|
+
messages.success(
|
436
|
+
request,
|
437
|
+
f"Successfully discovered {result['discovered']} new sites from Cloudflare."
|
438
|
+
)
|
439
|
+
else:
|
440
|
+
messages.info(request, "No new sites discovered.")
|
441
|
+
|
442
|
+
if result.get('errors', 0) > 0:
|
443
|
+
messages.warning(request, f"Discovery completed with {result['errors']} API key errors.")
|
444
|
+
|
445
|
+
except Exception as e:
|
446
|
+
messages.error(request, f"Site discovery failed: {str(e)}")
|
447
|
+
|
448
|
+
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
@@ -1,105 +1,18 @@
|
|
1
1
|
"""
|
2
|
-
Maintenance
|
3
|
-
|
4
|
-
Follows django-cfg patterns for app configuration with automatic setup.
|
2
|
+
Maintenance app configuration.
|
5
3
|
"""
|
6
4
|
|
7
5
|
from django.apps import AppConfig
|
8
|
-
from django.conf import settings
|
9
|
-
import logging
|
10
|
-
|
11
|
-
logger = logging.getLogger(__name__)
|
12
6
|
|
13
7
|
|
14
8
|
class MaintenanceConfig(AppConfig):
|
15
|
-
"""
|
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}")
|
9
|
+
"""Configuration for the simplified maintenance app."""
|
82
10
|
|
83
|
-
|
84
|
-
|
85
|
-
|
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}")
|
11
|
+
default_auto_field = 'django.db.models.BigAutoField'
|
12
|
+
name = 'django_cfg.apps.maintenance'
|
13
|
+
verbose_name = 'Maintenance Mode'
|
98
14
|
|
99
|
-
def
|
100
|
-
"""
|
101
|
-
|
102
|
-
|
103
|
-
asyncio.run(self._run_auto_setup(config))
|
104
|
-
except Exception as e:
|
105
|
-
logger.error(f"Cloudflare auto-setup thread error: {e}")
|
15
|
+
def ready(self):
|
16
|
+
"""Initialize app when Django starts."""
|
17
|
+
# No complex signals or initialization needed
|
18
|
+
pass
|