django-cfg 1.2.16__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.16.dist-info → django_cfg-1.2.18.dist-info}/METADATA +52 -1
- {django_cfg-1.2.16.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.16.dist-info → django_cfg-1.2.18.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.16.dist-info → django_cfg-1.2.18.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,185 @@
|
|
1
|
+
"""
|
2
|
+
CloudflareApiKey admin with Unfold styling.
|
3
|
+
|
4
|
+
Admin interface for managing Cloudflare API keys.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib import admin
|
8
|
+
from django.utils.html import format_html
|
9
|
+
from django.http import HttpRequest
|
10
|
+
from typing import Any
|
11
|
+
|
12
|
+
from unfold.admin import ModelAdmin
|
13
|
+
from unfold.decorators import display, action
|
14
|
+
|
15
|
+
from ..models import CloudflareApiKey
|
16
|
+
|
17
|
+
|
18
|
+
@admin.register(CloudflareApiKey)
|
19
|
+
class CloudflareApiKeyAdmin(ModelAdmin):
|
20
|
+
"""Admin interface for CloudflareApiKey model."""
|
21
|
+
|
22
|
+
list_display = [
|
23
|
+
'status_display',
|
24
|
+
'name',
|
25
|
+
'description_preview',
|
26
|
+
'active_badge',
|
27
|
+
'default_badge',
|
28
|
+
'sites_count',
|
29
|
+
'last_used_at',
|
30
|
+
'created_at'
|
31
|
+
]
|
32
|
+
|
33
|
+
list_display_links = ['name']
|
34
|
+
|
35
|
+
search_fields = ['name', 'description', 'account_id']
|
36
|
+
|
37
|
+
list_filter = [
|
38
|
+
'is_active',
|
39
|
+
'is_default',
|
40
|
+
'created_at',
|
41
|
+
'last_used_at'
|
42
|
+
]
|
43
|
+
|
44
|
+
readonly_fields = [
|
45
|
+
'created_at',
|
46
|
+
'updated_at',
|
47
|
+
'last_used_at',
|
48
|
+
'sites_using_key'
|
49
|
+
]
|
50
|
+
|
51
|
+
fieldsets = [
|
52
|
+
('Basic Information', {
|
53
|
+
'fields': ['name', 'description']
|
54
|
+
}),
|
55
|
+
('Cloudflare Configuration', {
|
56
|
+
'fields': ['api_token', 'account_id'],
|
57
|
+
'classes': ['collapse']
|
58
|
+
}),
|
59
|
+
('Settings', {
|
60
|
+
'fields': ['is_active', 'is_default']
|
61
|
+
}),
|
62
|
+
('Timestamps', {
|
63
|
+
'fields': ['created_at', 'updated_at', 'last_used_at'],
|
64
|
+
'classes': ['collapse']
|
65
|
+
}),
|
66
|
+
('Usage', {
|
67
|
+
'fields': ['sites_using_key'],
|
68
|
+
'classes': ['collapse']
|
69
|
+
})
|
70
|
+
]
|
71
|
+
|
72
|
+
actions = [
|
73
|
+
'make_default_action',
|
74
|
+
'activate_keys_action',
|
75
|
+
'deactivate_keys_action'
|
76
|
+
]
|
77
|
+
|
78
|
+
# Display methods
|
79
|
+
|
80
|
+
@display(description="Status")
|
81
|
+
def status_display(self, obj: CloudflareApiKey) -> str:
|
82
|
+
"""Display status with emoji."""
|
83
|
+
if obj.is_active:
|
84
|
+
if obj.is_default:
|
85
|
+
return format_html('<span style="color: green;">🔑 {} (Default)</span>', obj.name)
|
86
|
+
else:
|
87
|
+
return format_html('<span style="color: green;">🔑 {}</span>', obj.name)
|
88
|
+
else:
|
89
|
+
return format_html('<span style="color: red;">🔒 {}</span>', obj.name)
|
90
|
+
|
91
|
+
@display(description="Description")
|
92
|
+
def description_preview(self, obj: CloudflareApiKey) -> str:
|
93
|
+
"""Show description preview."""
|
94
|
+
if not obj.description:
|
95
|
+
return "-"
|
96
|
+
|
97
|
+
preview = obj.description[:50]
|
98
|
+
if len(obj.description) > 50:
|
99
|
+
preview += "..."
|
100
|
+
|
101
|
+
return preview
|
102
|
+
|
103
|
+
@display(description="Active")
|
104
|
+
def active_badge(self, obj: CloudflareApiKey) -> str:
|
105
|
+
"""Display active status badge."""
|
106
|
+
if obj.is_active:
|
107
|
+
return format_html('<span class="badge badge-success">Active</span>')
|
108
|
+
else:
|
109
|
+
return format_html('<span class="badge badge-secondary">Inactive</span>')
|
110
|
+
|
111
|
+
@display(description="Default")
|
112
|
+
def default_badge(self, obj: CloudflareApiKey) -> str:
|
113
|
+
"""Display default status badge."""
|
114
|
+
if obj.is_default:
|
115
|
+
return format_html('<span class="badge badge-primary">Default</span>')
|
116
|
+
else:
|
117
|
+
return "-"
|
118
|
+
|
119
|
+
@display(description="Sites")
|
120
|
+
def sites_count(self, obj: CloudflareApiKey) -> str:
|
121
|
+
"""Display count of sites using this key."""
|
122
|
+
count = obj.cloudflaresite_set.count()
|
123
|
+
if count > 0:
|
124
|
+
return f"{count} sites"
|
125
|
+
return "No sites"
|
126
|
+
|
127
|
+
def sites_using_key(self, obj: CloudflareApiKey) -> str:
|
128
|
+
"""Show sites using this API key."""
|
129
|
+
sites = obj.cloudflaresite_set.all()[:10] # Limit to 10 for display
|
130
|
+
|
131
|
+
if not sites:
|
132
|
+
return "No sites using this key"
|
133
|
+
|
134
|
+
html = "<ul>"
|
135
|
+
for site in sites:
|
136
|
+
status_emoji = "🔧" if site.maintenance_active else "🟢"
|
137
|
+
html += f"<li>{status_emoji} {site.name} ({site.domain})</li>"
|
138
|
+
|
139
|
+
html += "</ul>"
|
140
|
+
|
141
|
+
total_count = obj.cloudflaresite_set.count()
|
142
|
+
if total_count > 10:
|
143
|
+
html += f"<p><em>... and {total_count - 10} more sites</em></p>"
|
144
|
+
|
145
|
+
return format_html(html)
|
146
|
+
|
147
|
+
sites_using_key.short_description = "Sites Using This Key"
|
148
|
+
|
149
|
+
# Admin Actions
|
150
|
+
|
151
|
+
@action(description="🔑 Make default API key")
|
152
|
+
def make_default_action(self, request: HttpRequest, queryset) -> None:
|
153
|
+
"""Make selected key the default."""
|
154
|
+
if queryset.count() > 1:
|
155
|
+
self.message_user(request, "Please select only one API key to make default.", level='error')
|
156
|
+
return
|
157
|
+
|
158
|
+
key = queryset.first()
|
159
|
+
if key:
|
160
|
+
# This will automatically set others to non-default via the model's save method
|
161
|
+
key.is_default = True
|
162
|
+
key.save()
|
163
|
+
self.message_user(request, f"'{key.name}' is now the default API key.")
|
164
|
+
|
165
|
+
@action(description="✅ Activate API keys")
|
166
|
+
def activate_keys_action(self, request: HttpRequest, queryset) -> None:
|
167
|
+
"""Activate selected API keys."""
|
168
|
+
count = queryset.update(is_active=True)
|
169
|
+
self.message_user(request, f"Successfully activated {count} API keys.")
|
170
|
+
|
171
|
+
@action(description="❌ Deactivate API keys")
|
172
|
+
def deactivate_keys_action(self, request: HttpRequest, queryset) -> None:
|
173
|
+
"""Deactivate selected API keys."""
|
174
|
+
# Don't allow deactivating the default key
|
175
|
+
default_keys = queryset.filter(is_default=True)
|
176
|
+
if default_keys.exists():
|
177
|
+
self.message_user(
|
178
|
+
request,
|
179
|
+
"Cannot deactivate default API key. Please set another key as default first.",
|
180
|
+
level='error'
|
181
|
+
)
|
182
|
+
return
|
183
|
+
|
184
|
+
count = queryset.update(is_active=False)
|
185
|
+
self.message_user(request, f"Successfully deactivated {count} API keys.")
|
@@ -0,0 +1,156 @@
|
|
1
|
+
"""
|
2
|
+
MaintenanceLog admin with Unfold styling.
|
3
|
+
|
4
|
+
Read-only admin interface for viewing maintenance operation logs.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from django.contrib import admin
|
8
|
+
from django.utils.html import format_html
|
9
|
+
from django.http import HttpRequest
|
10
|
+
from typing import Any
|
11
|
+
import json
|
12
|
+
|
13
|
+
from unfold.admin import ModelAdmin
|
14
|
+
from unfold.decorators import display
|
15
|
+
|
16
|
+
from ..models import MaintenanceLog
|
17
|
+
|
18
|
+
|
19
|
+
@admin.register(MaintenanceLog)
|
20
|
+
class MaintenanceLogAdmin(ModelAdmin):
|
21
|
+
"""Admin interface for MaintenanceLog model."""
|
22
|
+
|
23
|
+
list_display = [
|
24
|
+
'status_display',
|
25
|
+
'site',
|
26
|
+
'action_display',
|
27
|
+
'created_at',
|
28
|
+
'duration_display',
|
29
|
+
'error_preview'
|
30
|
+
]
|
31
|
+
|
32
|
+
list_filter = [
|
33
|
+
'action',
|
34
|
+
'status',
|
35
|
+
'created_at',
|
36
|
+
'site'
|
37
|
+
]
|
38
|
+
|
39
|
+
search_fields = [
|
40
|
+
'site__name',
|
41
|
+
'site__domain',
|
42
|
+
'reason',
|
43
|
+
'error_message'
|
44
|
+
]
|
45
|
+
|
46
|
+
readonly_fields = [
|
47
|
+
'site',
|
48
|
+
'action',
|
49
|
+
'status',
|
50
|
+
'reason',
|
51
|
+
'error_message',
|
52
|
+
'cloudflare_response',
|
53
|
+
'created_at',
|
54
|
+
'duration_seconds',
|
55
|
+
'cloudflare_response_formatted'
|
56
|
+
]
|
57
|
+
|
58
|
+
fieldsets = [
|
59
|
+
('Operation Details', {
|
60
|
+
'fields': ['site', 'action', 'status', 'created_at', 'duration_seconds']
|
61
|
+
}),
|
62
|
+
('Additional Information', {
|
63
|
+
'fields': ['reason', 'error_message']
|
64
|
+
}),
|
65
|
+
('Cloudflare Response', {
|
66
|
+
'fields': ['cloudflare_response_formatted'],
|
67
|
+
'classes': ['collapse']
|
68
|
+
})
|
69
|
+
]
|
70
|
+
|
71
|
+
ordering = ['-created_at']
|
72
|
+
|
73
|
+
def has_add_permission(self, request: HttpRequest) -> bool:
|
74
|
+
"""Logs are created automatically, no manual adding."""
|
75
|
+
return False
|
76
|
+
|
77
|
+
def has_change_permission(self, request: HttpRequest, obj: Any = None) -> bool:
|
78
|
+
"""Logs are read-only."""
|
79
|
+
return False
|
80
|
+
|
81
|
+
def has_delete_permission(self, request: HttpRequest, obj: Any = None) -> bool:
|
82
|
+
"""Allow deletion of old logs."""
|
83
|
+
return True
|
84
|
+
|
85
|
+
# Display methods
|
86
|
+
|
87
|
+
@display(description="Status", ordering="status")
|
88
|
+
def status_display(self, obj: MaintenanceLog) -> str:
|
89
|
+
"""Display status with emoji and badge."""
|
90
|
+
status_emoji = {
|
91
|
+
MaintenanceLog.Status.SUCCESS: "✅",
|
92
|
+
MaintenanceLog.Status.FAILED: "❌",
|
93
|
+
MaintenanceLog.Status.PENDING: "⏳"
|
94
|
+
}.get(obj.status, "❓")
|
95
|
+
|
96
|
+
color_class = {
|
97
|
+
MaintenanceLog.Status.SUCCESS: "badge-success",
|
98
|
+
MaintenanceLog.Status.FAILED: "badge-danger",
|
99
|
+
MaintenanceLog.Status.PENDING: "badge-warning"
|
100
|
+
}.get(obj.status, "badge-secondary")
|
101
|
+
|
102
|
+
return format_html(
|
103
|
+
'<span class="badge {}">{} {}</span>',
|
104
|
+
color_class, status_emoji, obj.get_status_display()
|
105
|
+
)
|
106
|
+
|
107
|
+
@display(description="Action", ordering="action")
|
108
|
+
def action_display(self, obj: MaintenanceLog) -> str:
|
109
|
+
"""Display action with icon."""
|
110
|
+
action_icons = {
|
111
|
+
MaintenanceLog.Action.ENABLE: "🔧",
|
112
|
+
MaintenanceLog.Action.DISABLE: "🟢",
|
113
|
+
MaintenanceLog.Action.SYNC: "🔄",
|
114
|
+
MaintenanceLog.Action.ERROR: "❌"
|
115
|
+
}
|
116
|
+
|
117
|
+
icon = action_icons.get(obj.action, "❓")
|
118
|
+
return f"{icon} {obj.get_action_display()}"
|
119
|
+
|
120
|
+
@display(description="Duration")
|
121
|
+
def duration_display(self, obj: MaintenanceLog) -> str:
|
122
|
+
"""Display operation duration."""
|
123
|
+
if not obj.duration_seconds:
|
124
|
+
return "-"
|
125
|
+
|
126
|
+
if obj.duration_seconds < 60:
|
127
|
+
return f"{obj.duration_seconds}s"
|
128
|
+
else:
|
129
|
+
minutes = obj.duration_seconds // 60
|
130
|
+
seconds = obj.duration_seconds % 60
|
131
|
+
return f"{minutes}m {seconds}s"
|
132
|
+
|
133
|
+
@display(description="Error")
|
134
|
+
def error_preview(self, obj: MaintenanceLog) -> str:
|
135
|
+
"""Show error message preview."""
|
136
|
+
if not obj.error_message:
|
137
|
+
return "-"
|
138
|
+
|
139
|
+
preview = obj.error_message[:100]
|
140
|
+
if len(obj.error_message) > 100:
|
141
|
+
preview += "..."
|
142
|
+
|
143
|
+
return format_html('<span style="color: red; font-family: monospace;">{}</span>', preview)
|
144
|
+
|
145
|
+
def cloudflare_response_formatted(self, obj: MaintenanceLog) -> str:
|
146
|
+
"""Format Cloudflare response for display."""
|
147
|
+
if not obj.cloudflare_response:
|
148
|
+
return "No response data"
|
149
|
+
|
150
|
+
try:
|
151
|
+
formatted = json.dumps(obj.cloudflare_response, indent=2)
|
152
|
+
return format_html('<pre style="background: #f8f8f8; padding: 10px; overflow: auto;">{}</pre>', formatted)
|
153
|
+
except Exception:
|
154
|
+
return str(obj.cloudflare_response)
|
155
|
+
|
156
|
+
cloudflare_response_formatted.short_description = "Cloudflare Response (Formatted)"
|