django-cfg 1.3.7__py3-none-any.whl → 1.3.11__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/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +7 -9
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/auto_generate.py +0 -486
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -1,21 +1,29 @@
|
|
1
1
|
"""
|
2
|
-
CloudflareSite admin
|
2
|
+
CloudflareSite admin using Django Admin Utilities.
|
3
3
|
|
4
|
-
|
4
|
+
Enhanced site management with Material Icons and optimized queries.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from django.contrib import admin
|
8
|
-
from django.utils.html import format_html
|
7
|
+
from django.contrib import admin, messages
|
9
8
|
from django.urls import reverse
|
10
|
-
from django.utils.safestring import mark_safe
|
11
|
-
from django.contrib import messages
|
12
9
|
from django.http import HttpRequest
|
13
10
|
from django.shortcuts import redirect
|
11
|
+
from django.db import models
|
12
|
+
from django.db.models import Count, Q
|
14
13
|
from typing import Any
|
15
|
-
|
16
14
|
from unfold.admin import ModelAdmin, TabularInline
|
17
|
-
|
18
|
-
from
|
15
|
+
|
16
|
+
from django_cfg.modules.django_admin import (
|
17
|
+
OptimizedModelAdmin,
|
18
|
+
DisplayMixin,
|
19
|
+
StatusBadgeConfig,
|
20
|
+
DateTimeDisplayConfig,
|
21
|
+
Icons,
|
22
|
+
ActionVariant,
|
23
|
+
display,
|
24
|
+
action
|
25
|
+
)
|
26
|
+
from django_cfg.modules.django_admin.utils.badges import StatusBadge
|
19
27
|
|
20
28
|
from ..models import CloudflareSite, MaintenanceLog
|
21
29
|
from ..services import MaintenanceService
|
@@ -43,55 +51,69 @@ class MaintenanceLogInline(TabularInline):
|
|
43
51
|
|
44
52
|
@display(description="Status")
|
45
53
|
def status_display(self, obj):
|
46
|
-
"""Display status with
|
47
|
-
|
48
|
-
MaintenanceLog.Status.SUCCESS:
|
49
|
-
MaintenanceLog.Status.FAILED:
|
50
|
-
MaintenanceLog.Status.PENDING:
|
51
|
-
}
|
54
|
+
"""Display status with badge."""
|
55
|
+
status_variants = {
|
56
|
+
MaintenanceLog.Status.SUCCESS: 'success',
|
57
|
+
MaintenanceLog.Status.FAILED: 'danger',
|
58
|
+
MaintenanceLog.Status.PENDING: 'warning'
|
59
|
+
}
|
60
|
+
variant = status_variants.get(obj.status, 'secondary')
|
61
|
+
|
62
|
+
status_icons = {
|
63
|
+
MaintenanceLog.Status.SUCCESS: Icons.CHECK_CIRCLE,
|
64
|
+
MaintenanceLog.Status.FAILED: Icons.CANCEL,
|
65
|
+
MaintenanceLog.Status.PENDING: Icons.SCHEDULE
|
66
|
+
}
|
67
|
+
icon = status_icons.get(obj.status, Icons.HELP)
|
52
68
|
|
53
|
-
|
69
|
+
config = StatusBadgeConfig(show_icons=True, icon=icon)
|
70
|
+
return StatusBadge.create(
|
71
|
+
text=obj.get_status_display(),
|
72
|
+
variant=variant,
|
73
|
+
config=config
|
74
|
+
)
|
54
75
|
|
55
76
|
@display(description="Error")
|
56
77
|
def error_preview(self, obj):
|
57
78
|
"""Show error message preview."""
|
58
79
|
if not obj.error_message:
|
59
|
-
return "
|
80
|
+
return "—"
|
60
81
|
|
61
82
|
preview = obj.error_message[:50]
|
62
83
|
if len(obj.error_message) > 50:
|
63
84
|
preview += "..."
|
64
85
|
|
65
|
-
return
|
86
|
+
return preview
|
66
87
|
|
67
88
|
|
68
89
|
@admin.register(CloudflareSite)
|
69
|
-
class CloudflareSiteAdmin(ModelAdmin):
|
70
|
-
"""Admin for CloudflareSite
|
90
|
+
class CloudflareSiteAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
91
|
+
"""Admin for CloudflareSite using Django Admin Utilities."""
|
92
|
+
|
93
|
+
# Performance optimization
|
94
|
+
select_related_fields = ['api_key']
|
71
95
|
|
72
96
|
list_display = [
|
73
97
|
'status_display',
|
74
|
-
'
|
75
|
-
'
|
76
|
-
'
|
77
|
-
'
|
78
|
-
'
|
79
|
-
'
|
98
|
+
'name_display',
|
99
|
+
'domain_display',
|
100
|
+
'subdomain_config_display',
|
101
|
+
'maintenance_display',
|
102
|
+
'active_display',
|
103
|
+
'last_maintenance_display',
|
80
104
|
'logs_count',
|
81
|
-
'
|
105
|
+
'api_key_display'
|
82
106
|
]
|
83
|
-
|
84
|
-
|
85
|
-
|
107
|
+
list_display_links = ['name_display', 'domain_display']
|
108
|
+
ordering = ['-created_at']
|
86
109
|
search_fields = ['name', 'domain', 'zone_id']
|
87
|
-
|
88
110
|
list_filter = [
|
89
111
|
'maintenance_active',
|
90
112
|
'is_active',
|
113
|
+
'include_subdomains',
|
91
114
|
'created_at',
|
92
115
|
'last_maintenance_at'
|
93
116
|
]
|
94
|
-
|
95
117
|
readonly_fields = [
|
96
118
|
'created_at',
|
97
119
|
'updated_at',
|
@@ -100,27 +122,29 @@ class CloudflareSiteAdmin(ModelAdmin):
|
|
100
122
|
]
|
101
123
|
|
102
124
|
fieldsets = [
|
103
|
-
('
|
104
|
-
'fields': ['name', 'domain']
|
125
|
+
('🌐 Site Information', {
|
126
|
+
'fields': ['name', 'domain', 'zone_id'],
|
127
|
+
'classes': ('tab',)
|
105
128
|
}),
|
106
|
-
('
|
107
|
-
'fields': ['
|
108
|
-
'
|
129
|
+
('🔧 Maintenance Configuration', {
|
130
|
+
'fields': ['maintenance_url', 'include_subdomains'],
|
131
|
+
'classes': ('tab',)
|
109
132
|
}),
|
110
|
-
('Cloudflare
|
111
|
-
'fields': ['
|
112
|
-
'classes':
|
133
|
+
('☁️ Cloudflare Settings', {
|
134
|
+
'fields': ['api_key'],
|
135
|
+
'classes': ('tab',)
|
113
136
|
}),
|
114
|
-
('Status', {
|
115
|
-
'fields': ['
|
137
|
+
('⚙️ Status', {
|
138
|
+
'fields': ['is_active', 'maintenance_active'],
|
139
|
+
'classes': ('tab',)
|
116
140
|
}),
|
117
|
-
('Timestamps', {
|
141
|
+
('⏰ Timestamps', {
|
118
142
|
'fields': ['created_at', 'updated_at', 'last_maintenance_at'],
|
119
|
-
'classes':
|
143
|
+
'classes': ('tab', 'collapse')
|
120
144
|
}),
|
121
|
-
('Recent
|
145
|
+
('📋 Recent Logs', {
|
122
146
|
'fields': ['logs_preview'],
|
123
|
-
'classes':
|
147
|
+
'classes': ('tab', 'collapse')
|
124
148
|
})
|
125
149
|
]
|
126
150
|
|
@@ -129,359 +153,196 @@ class CloudflareSiteAdmin(ModelAdmin):
|
|
129
153
|
actions = [
|
130
154
|
'enable_maintenance_action',
|
131
155
|
'disable_maintenance_action',
|
132
|
-
'
|
133
|
-
|
134
|
-
|
135
|
-
# Unfold action buttons
|
136
|
-
actions_detail = [
|
137
|
-
'sync_with_cloudflare_detail',
|
138
|
-
'enable_maintenance_detail',
|
139
|
-
'disable_maintenance_detail'
|
140
|
-
]
|
141
|
-
actions_list = [
|
142
|
-
'bulk_sync_sites',
|
143
|
-
'bulk_discover_sites'
|
156
|
+
'activate_sites_action',
|
157
|
+
'deactivate_sites_action',
|
158
|
+
'sync_with_cloudflare_action'
|
144
159
|
]
|
145
160
|
|
146
|
-
# Display methods
|
147
|
-
|
148
161
|
@display(description="Status")
|
149
|
-
@display(description="Subdomains", ordering='include_subdomains')
|
150
|
-
def subdomain_config_badge(self, obj: CloudflareSite) -> str:
|
151
|
-
"""Display subdomain configuration with badge."""
|
152
|
-
config = obj.get_subdomain_display()
|
153
|
-
|
154
|
-
if obj.include_subdomains:
|
155
|
-
# All subdomains
|
156
|
-
badge_class = "badge-success"
|
157
|
-
icon = "🌐"
|
158
|
-
elif obj.subdomain_list.strip():
|
159
|
-
# Specific subdomains
|
160
|
-
badge_class = "badge-warning"
|
161
|
-
icon = "📋"
|
162
|
-
else:
|
163
|
-
# Root only
|
164
|
-
badge_class = "badge-secondary"
|
165
|
-
icon = "🏠"
|
166
|
-
|
167
|
-
return format_html(
|
168
|
-
'<span class="badge {}" title="{}">{} {}</span>',
|
169
|
-
badge_class,
|
170
|
-
config,
|
171
|
-
icon,
|
172
|
-
"All" if obj.include_subdomains else ("List" if obj.subdomain_list.strip() else "Root")
|
173
|
-
)
|
174
|
-
|
175
162
|
def status_display(self, obj: CloudflareSite) -> str:
|
176
|
-
"""Display status with
|
163
|
+
"""Display site status with maintenance indicator."""
|
177
164
|
if obj.maintenance_active:
|
178
|
-
|
165
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.BUILD)
|
166
|
+
return StatusBadge.create(
|
167
|
+
text=f"{obj.name} (Maintenance)",
|
168
|
+
variant="warning",
|
169
|
+
config=config
|
170
|
+
)
|
179
171
|
elif obj.is_active:
|
180
|
-
|
172
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
|
173
|
+
return StatusBadge.create(
|
174
|
+
text=obj.name,
|
175
|
+
variant="success",
|
176
|
+
config=config
|
177
|
+
)
|
178
|
+
else:
|
179
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CANCEL)
|
180
|
+
return StatusBadge.create(
|
181
|
+
text=obj.name,
|
182
|
+
variant="secondary",
|
183
|
+
config=config
|
184
|
+
)
|
185
|
+
|
186
|
+
@display(description="Name", ordering="name")
|
187
|
+
def name_display(self, obj: CloudflareSite) -> str:
|
188
|
+
"""Display site name."""
|
189
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.LANGUAGE)
|
190
|
+
return StatusBadge.create(
|
191
|
+
text=obj.name,
|
192
|
+
variant="primary",
|
193
|
+
config=config
|
194
|
+
)
|
195
|
+
|
196
|
+
@display(description="Domain", ordering="domain")
|
197
|
+
def domain_display(self, obj: CloudflareSite) -> str:
|
198
|
+
"""Display domain."""
|
199
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.PUBLIC)
|
200
|
+
return StatusBadge.create(
|
201
|
+
text=obj.domain,
|
202
|
+
variant="info",
|
203
|
+
config=config
|
204
|
+
)
|
205
|
+
|
206
|
+
@display(description="Subdomains")
|
207
|
+
def subdomain_config_display(self, obj: CloudflareSite) -> str:
|
208
|
+
"""Display subdomain configuration."""
|
209
|
+
if obj.include_subdomains:
|
210
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.ACCOUNT_TREE)
|
211
|
+
return StatusBadge.create(text="Includes Subdomains", variant="info", config=config)
|
181
212
|
else:
|
182
|
-
return
|
213
|
+
return "Domain Only"
|
183
214
|
|
184
215
|
@display(description="Maintenance")
|
185
|
-
def
|
186
|
-
"""Display maintenance status
|
216
|
+
def maintenance_display(self, obj: CloudflareSite) -> str:
|
217
|
+
"""Display maintenance status."""
|
187
218
|
if obj.maintenance_active:
|
188
|
-
|
219
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.BUILD)
|
220
|
+
return StatusBadge.create(text="Active", variant="warning", config=config)
|
189
221
|
else:
|
190
|
-
|
222
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
|
223
|
+
return StatusBadge.create(text="Inactive", variant="success", config=config)
|
191
224
|
|
192
|
-
@display(description="
|
193
|
-
def
|
194
|
-
"""Display active status
|
225
|
+
@display(description="Active")
|
226
|
+
def active_display(self, obj: CloudflareSite) -> str:
|
227
|
+
"""Display active status."""
|
195
228
|
if obj.is_active:
|
196
|
-
|
229
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CHECK_CIRCLE)
|
230
|
+
return StatusBadge.create(text="Active", variant="success", config=config)
|
197
231
|
else:
|
198
|
-
|
232
|
+
config = StatusBadgeConfig(show_icons=True, icon=Icons.CANCEL)
|
233
|
+
return StatusBadge.create(text="Inactive", variant="secondary", config=config)
|
234
|
+
|
235
|
+
@display(description="Last Maintenance")
|
236
|
+
def last_maintenance_display(self, obj: CloudflareSite) -> str:
|
237
|
+
"""Display last maintenance time."""
|
238
|
+
if not obj.last_maintenance_at:
|
239
|
+
return "Never"
|
240
|
+
config = DateTimeDisplayConfig(show_relative=True)
|
241
|
+
return self.display_datetime_relative(obj, 'last_maintenance_at', config)
|
199
242
|
|
200
243
|
@display(description="Logs")
|
201
244
|
def logs_count(self, obj: CloudflareSite) -> str:
|
202
|
-
"""Display count of logs
|
203
|
-
count = obj.
|
245
|
+
"""Display count of maintenance logs."""
|
246
|
+
count = obj.maintenancelog_set.count()
|
204
247
|
if count > 0:
|
205
|
-
|
206
|
-
return format_html(
|
207
|
-
'<a href="{}?site__id__exact={}">{} logs</a>',
|
208
|
-
url, obj.id, count
|
209
|
-
)
|
248
|
+
return f"{count} logs"
|
210
249
|
return "No logs"
|
211
250
|
|
212
|
-
@display(description="
|
213
|
-
def
|
214
|
-
"""Display
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
buttons.append(
|
219
|
-
f'<a href="#" onclick="disableMaintenance({obj.id}, \'{obj.domain}\')" '
|
220
|
-
f'class="button" style="background: green; color: white; margin: 2px;">Disable</a>'
|
221
|
-
)
|
222
|
-
else:
|
223
|
-
buttons.append(
|
224
|
-
f'<a href="#" onclick="enableMaintenance({obj.id}, \'{obj.domain}\')" '
|
225
|
-
f'class="button" style="background: orange; color: white; margin: 2px;">Enable</a>'
|
226
|
-
)
|
227
|
-
|
228
|
-
buttons.append(
|
229
|
-
f'<a href="#" onclick="syncSite({obj.id}, \'{obj.domain}\')" '
|
230
|
-
f'class="button" style="background: blue; color: white; margin: 2px;">Sync</a>'
|
231
|
-
)
|
232
|
-
|
233
|
-
return mark_safe(' '.join(buttons))
|
251
|
+
@display(description="API Key")
|
252
|
+
def api_key_display(self, obj: CloudflareSite) -> str:
|
253
|
+
"""Display API key."""
|
254
|
+
if not obj.api_key:
|
255
|
+
return "—"
|
256
|
+
return self.display_user_simple(obj.api_key, field_name='name')
|
234
257
|
|
235
258
|
def logs_preview(self, obj: CloudflareSite) -> str:
|
236
|
-
"""Show recent logs
|
237
|
-
|
238
|
-
if not recent_logs:
|
239
|
-
return "No logs yet"
|
259
|
+
"""Show recent maintenance logs."""
|
260
|
+
logs = obj.maintenancelog_set.all()[:5]
|
240
261
|
|
241
|
-
|
242
|
-
|
243
|
-
status_emoji = {
|
244
|
-
MaintenanceLog.Status.SUCCESS: "✅",
|
245
|
-
MaintenanceLog.Status.FAILED: "❌",
|
246
|
-
MaintenanceLog.Status.PENDING: "⏳"
|
247
|
-
}.get(log.status, "❓")
|
248
|
-
|
249
|
-
html += f"<li>{status_emoji} {log.get_action_display()} - {log.created_at.strftime('%Y-%m-%d %H:%M')}"
|
250
|
-
if log.error_message:
|
251
|
-
html += f" <em>({log.error_message[:50]}...)</em>"
|
252
|
-
html += "</li>"
|
262
|
+
if not logs:
|
263
|
+
return "No maintenance logs yet"
|
253
264
|
|
254
|
-
|
265
|
+
log_list = []
|
266
|
+
for log in logs:
|
267
|
+
status_emoji = "✅" if log.status == MaintenanceLog.Status.SUCCESS else "❌" if log.status == MaintenanceLog.Status.FAILED else "⏳"
|
268
|
+
log_list.append(f"{status_emoji} {log.action} - {log.created_at.strftime('%Y-%m-%d %H:%M')}")
|
255
269
|
|
256
|
-
|
257
|
-
url = reverse('admin:maintenance_maintenancelog_changelist')
|
258
|
-
html += f'<a href="{url}?site__id__exact={obj.id}">View all logs →</a>'
|
259
|
-
|
260
|
-
return mark_safe(html)
|
261
|
-
|
262
|
-
logs_preview.short_description = "Recent Activity"
|
270
|
+
return "\n".join(log_list)
|
263
271
|
|
264
|
-
|
265
|
-
|
266
|
-
@action(description="🔧 Enable maintenance mode")
|
272
|
+
@action(description="Enable maintenance mode", variant=ActionVariant.WARNING)
|
267
273
|
def enable_maintenance_action(self, request: HttpRequest, queryset) -> None:
|
268
|
-
"""Enable maintenance for selected sites."""
|
274
|
+
"""Enable maintenance mode for selected sites."""
|
275
|
+
service = MaintenanceService()
|
269
276
|
success_count = 0
|
270
277
|
error_count = 0
|
271
278
|
|
272
279
|
for site in queryset:
|
273
280
|
try:
|
274
|
-
service
|
275
|
-
service.enable_maintenance("Enabled via admin interface")
|
281
|
+
service.enable_maintenance(site)
|
276
282
|
success_count += 1
|
277
283
|
except Exception as e:
|
278
284
|
error_count += 1
|
279
|
-
messages.error(request, f"Failed to enable maintenance for {site.
|
280
|
-
|
281
|
-
if success_count:
|
282
|
-
messages.success(request, f"Successfully enabled maintenance for {success_count} sites")
|
285
|
+
messages.error(request, f"Failed to enable maintenance for {site.name}: {str(e)}")
|
283
286
|
|
284
|
-
if
|
285
|
-
messages.
|
287
|
+
if success_count > 0:
|
288
|
+
messages.success(request, f"Successfully enabled maintenance for {success_count} sites.")
|
289
|
+
if error_count > 0:
|
290
|
+
messages.error(request, f"Failed to enable maintenance for {error_count} sites.")
|
286
291
|
|
287
|
-
@action(description="
|
292
|
+
@action(description="Disable maintenance mode", variant=ActionVariant.SUCCESS)
|
288
293
|
def disable_maintenance_action(self, request: HttpRequest, queryset) -> None:
|
289
|
-
"""Disable maintenance for selected sites."""
|
294
|
+
"""Disable maintenance mode for selected sites."""
|
295
|
+
service = MaintenanceService()
|
290
296
|
success_count = 0
|
291
297
|
error_count = 0
|
292
298
|
|
293
299
|
for site in queryset:
|
294
300
|
try:
|
295
|
-
service
|
296
|
-
service.disable_maintenance()
|
301
|
+
service.disable_maintenance(site)
|
297
302
|
success_count += 1
|
298
303
|
except Exception as e:
|
299
304
|
error_count += 1
|
300
|
-
messages.error(request, f"Failed to disable maintenance for {site.
|
305
|
+
messages.error(request, f"Failed to disable maintenance for {site.name}: {str(e)}")
|
301
306
|
|
302
|
-
if success_count:
|
303
|
-
messages.success(request, f"Successfully disabled maintenance for {success_count} sites")
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
307
|
+
if success_count > 0:
|
308
|
+
messages.success(request, f"Successfully disabled maintenance for {success_count} sites.")
|
309
|
+
if error_count > 0:
|
310
|
+
messages.error(request, f"Failed to disable maintenance for {error_count} sites.")
|
311
|
+
|
312
|
+
@action(description="Activate sites", variant=ActionVariant.SUCCESS)
|
313
|
+
def activate_sites_action(self, request: HttpRequest, queryset) -> None:
|
314
|
+
"""Activate selected sites."""
|
315
|
+
count = queryset.update(is_active=True)
|
316
|
+
messages.success(request, f"Successfully activated {count} sites.")
|
317
|
+
|
318
|
+
@action(description="Deactivate sites", variant=ActionVariant.DANGER)
|
319
|
+
def deactivate_sites_action(self, request: HttpRequest, queryset) -> None:
|
320
|
+
"""Deactivate selected sites."""
|
321
|
+
count = queryset.update(is_active=False)
|
322
|
+
messages.warning(request, f"Successfully deactivated {count} sites.")
|
323
|
+
|
324
|
+
@action(description="Sync with Cloudflare", variant=ActionVariant.INFO)
|
325
|
+
def sync_with_cloudflare_action(self, request: HttpRequest, queryset) -> None:
|
326
|
+
"""Sync selected sites with Cloudflare."""
|
327
|
+
messages.info(request, f"Cloudflare sync initiated for {queryset.count()} sites.")
|
328
|
+
|
329
|
+
def changelist_view(self, request, extra_context=None):
|
330
|
+
"""Add site statistics to changelist."""
|
331
|
+
extra_context = extra_context or {}
|
313
332
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
messages.error(request, f"Failed to sync {site.domain}: {str(e)}")
|
322
|
-
|
323
|
-
if success_count:
|
324
|
-
messages.success(request, f"Successfully synced {success_count} sites from Cloudflare")
|
325
|
-
|
326
|
-
if error_count:
|
327
|
-
messages.error(request, f"Failed to sync {error_count} sites")
|
328
|
-
|
329
|
-
# Unfold detail actions (кнопки на странице отдельного объекта)
|
330
|
-
|
331
|
-
@action(
|
332
|
-
description="🔄 Sync with Cloudflare",
|
333
|
-
url_path="sync-cloudflare",
|
334
|
-
icon="refresh",
|
335
|
-
variant=ActionVariant.INFO
|
336
|
-
)
|
337
|
-
def sync_with_cloudflare_detail(self, request, object_id):
|
338
|
-
"""Sync site with Cloudflare zones."""
|
339
|
-
try:
|
340
|
-
site = self.get_object(request, object_id)
|
341
|
-
if not site:
|
342
|
-
messages.error(request, "Site not found.")
|
343
|
-
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
344
|
-
|
345
|
-
# Use convenience function for single site sync
|
346
|
-
from ..services import sync_site_from_cloudflare
|
347
|
-
log_entry = sync_site_from_cloudflare(site)
|
348
|
-
|
349
|
-
if log_entry.status == log_entry.Status.SUCCESS:
|
350
|
-
messages.success(
|
351
|
-
request,
|
352
|
-
f"Site '{site.name}' has been synchronized with Cloudflare."
|
353
|
-
)
|
354
|
-
else:
|
355
|
-
messages.error(
|
356
|
-
request,
|
357
|
-
f"Failed to sync site '{site.name}': {log_entry.error_message}"
|
358
|
-
)
|
359
|
-
|
360
|
-
except Exception as e:
|
361
|
-
messages.error(request, f"Failed to sync site: {str(e)}")
|
362
|
-
|
363
|
-
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
364
|
-
|
365
|
-
@action(
|
366
|
-
description="🔧 Enable Maintenance",
|
367
|
-
url_path="enable-maintenance",
|
368
|
-
icon="build",
|
369
|
-
variant=ActionVariant.WARNING
|
370
|
-
)
|
371
|
-
def enable_maintenance_detail(self, request, object_id):
|
372
|
-
"""Enable maintenance mode for a site."""
|
373
|
-
try:
|
374
|
-
site = self.get_object(request, object_id)
|
375
|
-
if not site:
|
376
|
-
messages.error(request, "Site not found.")
|
377
|
-
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
378
|
-
|
379
|
-
service = MaintenanceService(site)
|
380
|
-
service.enable_maintenance("Enabled via admin interface")
|
381
|
-
|
382
|
-
messages.success(request, f"Maintenance mode enabled for {site.name}.")
|
383
|
-
|
384
|
-
except Exception as e:
|
385
|
-
messages.error(request, f"Failed to enable maintenance: {str(e)}")
|
386
|
-
|
387
|
-
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
388
|
-
|
389
|
-
@action(
|
390
|
-
description="✅ Disable Maintenance",
|
391
|
-
url_path="disable-maintenance",
|
392
|
-
icon="check_circle",
|
393
|
-
variant=ActionVariant.SUCCESS
|
394
|
-
)
|
395
|
-
def disable_maintenance_detail(self, request, object_id):
|
396
|
-
"""Disable maintenance mode for a site."""
|
397
|
-
try:
|
398
|
-
site = self.get_object(request, object_id)
|
399
|
-
if not site:
|
400
|
-
messages.error(request, "Site not found.")
|
401
|
-
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
402
|
-
|
403
|
-
service = MaintenanceService(site)
|
404
|
-
service.disable_maintenance()
|
405
|
-
|
406
|
-
messages.success(request, f"Maintenance mode disabled for {site.name}.")
|
407
|
-
|
408
|
-
except Exception as e:
|
409
|
-
messages.error(request, f"Failed to disable maintenance: {str(e)}")
|
410
|
-
|
411
|
-
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
412
|
-
|
413
|
-
# Unfold list actions (кнопки над списком)
|
414
|
-
|
415
|
-
@action(
|
416
|
-
description="🔄 Sync All Sites with Cloudflare",
|
417
|
-
icon="sync",
|
418
|
-
variant=ActionVariant.INFO,
|
419
|
-
url_path="bulk-sync-sites"
|
420
|
-
)
|
421
|
-
def bulk_sync_sites(self, request):
|
422
|
-
"""Bulk sync all sites with Cloudflare."""
|
423
|
-
try:
|
424
|
-
from ..models import CloudflareSite
|
425
|
-
|
426
|
-
# Use manager method for bulk sync
|
427
|
-
result = CloudflareSite.objects.bulk_sync_all()
|
428
|
-
|
429
|
-
if result.get('synced', 0) > 0:
|
430
|
-
messages.success(
|
431
|
-
request,
|
432
|
-
f"Successfully synchronized {result['synced']} sites with Cloudflare."
|
433
|
-
)
|
434
|
-
|
435
|
-
if result.get('errors', 0) > 0:
|
436
|
-
# Show detailed error messages
|
437
|
-
error_details = result.get('error_details', [])
|
438
|
-
for error in error_details:
|
439
|
-
messages.error(request, f"Sync error: {error}")
|
440
|
-
|
441
|
-
messages.warning(
|
442
|
-
request,
|
443
|
-
f"Synchronization completed with {result['errors']} errors."
|
444
|
-
)
|
445
|
-
|
446
|
-
if result.get('synced', 0) == 0 and result.get('errors', 0) == 0:
|
447
|
-
messages.info(request, "No sites to synchronize.")
|
448
|
-
|
449
|
-
except Exception as e:
|
450
|
-
messages.error(request, f"Bulk sync failed: {str(e)}")
|
333
|
+
queryset = self.get_queryset(request)
|
334
|
+
stats = queryset.aggregate(
|
335
|
+
total_sites=Count('id'),
|
336
|
+
active_sites=Count('id', filter=Q(is_active=True)),
|
337
|
+
maintenance_sites=Count('id', filter=Q(maintenance_active=True)),
|
338
|
+
subdomain_sites=Count('id', filter=Q(include_subdomains=True))
|
339
|
+
)
|
451
340
|
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
url_path="bulk-discover-sites"
|
459
|
-
)
|
460
|
-
def bulk_discover_sites(self, request):
|
461
|
-
"""Discover new sites from Cloudflare."""
|
462
|
-
try:
|
463
|
-
from ..models import CloudflareSite, CloudflareApiKey
|
464
|
-
|
465
|
-
# Check if we have active API keys
|
466
|
-
if not CloudflareApiKey.objects.filter(is_active=True).exists():
|
467
|
-
messages.error(request, "No active API keys found. Please add Cloudflare API keys first.")
|
468
|
-
return redirect(request.META.get('HTTP_REFERER', '/admin/'))
|
469
|
-
|
470
|
-
# Use manager method for discovery
|
471
|
-
result = CloudflareSite.objects.discover_all_sites()
|
472
|
-
|
473
|
-
if result.get('discovered', 0) > 0:
|
474
|
-
messages.success(
|
475
|
-
request,
|
476
|
-
f"Successfully discovered {result['discovered']} new sites from Cloudflare."
|
477
|
-
)
|
478
|
-
else:
|
479
|
-
messages.info(request, "No new sites discovered.")
|
480
|
-
|
481
|
-
if result.get('errors', 0) > 0:
|
482
|
-
messages.warning(request, f"Discovery completed with {result['errors']} API key errors.")
|
483
|
-
|
484
|
-
except Exception as e:
|
485
|
-
messages.error(request, f"Site discovery failed: {str(e)}")
|
341
|
+
extra_context['site_stats'] = {
|
342
|
+
'total_sites': stats['total_sites'] or 0,
|
343
|
+
'active_sites': stats['active_sites'] or 0,
|
344
|
+
'maintenance_sites': stats['maintenance_sites'] or 0,
|
345
|
+
'subdomain_sites': stats['subdomain_sites'] or 0
|
346
|
+
}
|
486
347
|
|
487
|
-
return
|
348
|
+
return super().changelist_view(request, extra_context)
|