Merge pull request #1637 from master3395/v2.5.5-dev
Fix CloudFlare DNS Management: Filter main domains only, auto-delete DNS records on domain removal, improve table display
This commit is contained in:
commit
a818dceced
|
|
@ -649,7 +649,10 @@ class DNSManager:
|
|||
|
||||
if os.path.exists(cfPath):
|
||||
CloudFlare = 1
|
||||
domainsList = ACLManager.findAllDomains(currentACL, userID)
|
||||
allDomains = ACLManager.findAllDomains(currentACL, userID)
|
||||
# Filter to only show main domains (domains with exactly one dot, e.g., "example.com")
|
||||
# Sub-domains have two or more dots (e.g., "subdomain.example.com")
|
||||
domainsList = [domain for domain in allDomains if domain.count('.') == 1]
|
||||
self.admin = admin
|
||||
self.loadCFKeys()
|
||||
data = {"domainsList": domainsList, "status": status, 'CloudFlare': CloudFlare, 'cfEmail': self.email,
|
||||
|
|
|
|||
|
|
@ -807,10 +807,12 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
|
|||
$scope.recordAdded = true;
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.recordsLoading = true;
|
||||
$scope.loadingRecords = true;
|
||||
$scope.recordDeleted = true;
|
||||
$scope.couldNotDeleteRecords = true;
|
||||
$scope.couldNotAddRecord = true;
|
||||
$scope.recordValueDefault = false;
|
||||
$scope.records = [];
|
||||
|
||||
// Hide records boxes
|
||||
$(".aaaaRecord").hide();
|
||||
|
|
@ -981,7 +983,7 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
|
|||
};
|
||||
|
||||
function populateCurrentRecords() {
|
||||
|
||||
$scope.loadingRecords = true;
|
||||
var selectedZone = $scope.selectedZone;
|
||||
|
||||
url = "/dns/getCurrentRecordsForDomainCloudFlare";
|
||||
|
|
@ -1002,6 +1004,7 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
|
|||
|
||||
|
||||
function ListInitialDatas(response) {
|
||||
$scope.loadingRecords = false;
|
||||
if (response.data.fetchStatus === 1) {
|
||||
|
||||
$scope.records = JSON.parse(response.data.data);
|
||||
|
|
@ -1028,6 +1031,7 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
|
|||
$scope.couldNotConnect = true;
|
||||
$scope.recordsLoading = true;
|
||||
$scope.couldNotAddRecord = true;
|
||||
$scope.records = [];
|
||||
|
||||
$scope.errorMessage = response.data.error_message;
|
||||
}
|
||||
|
|
@ -1035,7 +1039,7 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
|
|||
}
|
||||
|
||||
function cantLoadInitialDatas(response) {
|
||||
|
||||
$scope.loadingRecords = false;
|
||||
$scope.addRecordsBox = true;
|
||||
$scope.currentRecords = true;
|
||||
$scope.canNotFetchRecords = true;
|
||||
|
|
@ -1044,6 +1048,7 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
|
|||
$scope.recordAdded = true;
|
||||
$scope.couldNotConnect = false;
|
||||
$scope.couldNotAddRecord = true;
|
||||
$scope.records = [];
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,32 +310,100 @@
|
|||
overflow: hidden;
|
||||
border: 1px solid var(--border-primary, #e8e9ff);
|
||||
margin-top: 2rem;
|
||||
display: table;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.records-table th,
|
||||
.records-table td {
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
||||
.records-table tr {
|
||||
display: table-row !important;
|
||||
}
|
||||
|
||||
.records-table thead {
|
||||
background: var(--bg-secondary, #f8f9ff);
|
||||
display: table-header-group !important;
|
||||
background: linear-gradient(135deg, #5b5fcf 0%, #4a4fc7 100%);
|
||||
}
|
||||
|
||||
.records-table tbody {
|
||||
display: table-row-group !important;
|
||||
}
|
||||
|
||||
.records-table th {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary, #1e293b);
|
||||
font-size: 0.875rem;
|
||||
padding: 14px 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
border-bottom: 1px solid var(--border-primary, #e8e9ff);
|
||||
letter-spacing: 0.8px;
|
||||
border-bottom: 2px solid rgba(91, 95, 207, 0.3);
|
||||
background: linear-gradient(135deg, #5b5fcf 0%, #4a4fc7 100%);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.records-table td {
|
||||
padding: 1rem;
|
||||
color: var(--text-secondary, #64748b);
|
||||
font-size: 0.875rem;
|
||||
border-bottom: 1px solid var(--border-light, #f3f4f6);
|
||||
.records-table tbody tr {
|
||||
border-bottom: 1px solid var(--border-color, #f0f0ff);
|
||||
}
|
||||
|
||||
.records-table tbody tr:hover {
|
||||
background: var(--bg-secondary, #f8f9ff);
|
||||
background: var(--bg-hover, #f8f9ff);
|
||||
}
|
||||
|
||||
.records-table td {
|
||||
padding: 12px 12px;
|
||||
font-size: 13px;
|
||||
color: var(--text-primary, #2f3640);
|
||||
border-bottom: 1px solid var(--border-color, #f0f0ff);
|
||||
vertical-align: middle;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.records-table td:nth-child(1) {
|
||||
min-width: 200px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.records-table td:nth-child(2) {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.records-table td:nth-child(3) {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.records-table td:nth-child(4) {
|
||||
min-width: 150px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.records-table td:nth-child(5) {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.records-table td:nth-child(6) {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.records-table td:nth-child(7) {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
|
|
@ -767,14 +835,21 @@
|
|||
|
||||
|
||||
<!-- DNS Records Table -->
|
||||
|
||||
<div ng-hide="currentRecords" style="margin-top: 3rem;">
|
||||
<div style="margin-top: 3rem;">
|
||||
<h4 class="mb-3" style="color: var(--text-primary, #1e293b); font-weight: 600;">
|
||||
<i class="fas fa-list"></i>
|
||||
{% trans "DNS Records" %}
|
||||
</h4>
|
||||
|
||||
<table class="records-table">
|
||||
<div ng-if="loadingRecords" style="text-align: center; padding: 20px; color: #8893a7;">
|
||||
Loading DNS records...
|
||||
</div>
|
||||
|
||||
<div ng-if="!loadingRecords && records.length === 0" style="text-align: center; padding: 20px; color: #8893a7;">
|
||||
No DNS records found.
|
||||
</div>
|
||||
|
||||
<table class="records-table activity-table" ng-if="!loadingRecords && records.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
|
|
@ -788,7 +863,7 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="record in records track by $index">
|
||||
<td ng-bind="record.name"></td>
|
||||
<td><strong ng-bind="record.name"></strong></td>
|
||||
<td>
|
||||
<span style="padding: 0.25rem 0.75rem; background: var(--bg-hover, #f0f1ff); color: #5b5fcf; border-radius: 4px; font-weight: 500; font-size: 0.75rem;">
|
||||
{{ record.type }}
|
||||
|
|
@ -806,18 +881,41 @@
|
|||
</td>
|
||||
<td style="text-align: center;">
|
||||
<i class="fas fa-trash delete-icon"
|
||||
style="color: #ef4444;"
|
||||
style="color: #ef4444; cursor: pointer;"
|
||||
ng-click="deleteRecord(record.id)"
|
||||
title="{% trans 'Delete Record' %}"></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Loading placeholder table -->
|
||||
<table class="records-table activity-table" ng-if="loadingRecords">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "TTL" %}</th>
|
||||
<th>{% trans "Value" %}</th>
|
||||
<th>{% trans "Priority" %}</th>
|
||||
<th>{% trans "Proxy" %}</th>
|
||||
<th style="text-align: center;">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="i in [1,2,3,4,5]">
|
||||
<td>Loading...</td>
|
||||
<td>Loading...</td>
|
||||
<td>Loading...</td>
|
||||
<td>Loading...</td>
|
||||
<td>Loading...</td>
|
||||
<td>Loading...</td>
|
||||
<td>Loading...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- DNS Records Table -->
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -839,6 +839,102 @@ class DNS:
|
|||
## There does not exist a zone for this domain.
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def deleteCloudFlareDNSRecords(domainName, adminUserName=None):
|
||||
"""
|
||||
Delete all CloudFlare DNS records for a domain when domain is removed from CyberPanel.
|
||||
This function is called automatically when domains/sub-domains are deleted.
|
||||
"""
|
||||
try:
|
||||
# Check if CloudFlare is configured for this admin user
|
||||
if adminUserName:
|
||||
cfFile = '%s%s' % (DNS.CFPath, adminUserName)
|
||||
else:
|
||||
# Try to find admin user from domain
|
||||
try:
|
||||
from loginSystem.models import Administrator
|
||||
from websiteFunctions.models import Websites, ChildDomains
|
||||
try:
|
||||
website = Websites.objects.get(domain=domainName)
|
||||
adminUserName = website.admin.userName
|
||||
except:
|
||||
try:
|
||||
childDomain = ChildDomains.objects.get(domain=domainName)
|
||||
adminUserName = childDomain.master.admin.userName
|
||||
except:
|
||||
return 0, "Could not find admin user for domain"
|
||||
cfFile = '%s%s' % (DNS.CFPath, adminUserName)
|
||||
except:
|
||||
return 0, "Could not determine admin user"
|
||||
|
||||
if not os.path.exists(cfFile):
|
||||
# CloudFlare not configured for this user, skip deletion
|
||||
return 1, "CloudFlare not configured"
|
||||
|
||||
# Load CloudFlare credentials
|
||||
data = open(cfFile, 'r').readlines()
|
||||
email = data[0].rstrip('\n')
|
||||
token = data[1].rstrip('\n')
|
||||
|
||||
# Initialize CloudFlare API
|
||||
cf = CloudFlare.CloudFlare(email=email, token=token)
|
||||
|
||||
try:
|
||||
# Find the zone for this domain
|
||||
params = {'name': domainName, 'per_page': 50}
|
||||
zones = cf.zones.get(params=params)
|
||||
|
||||
for zone in sorted(zones, key=lambda v: v['name']):
|
||||
if zone['name'] == domainName:
|
||||
zone_id = zone['id']
|
||||
|
||||
# Get all DNS records for this zone
|
||||
try:
|
||||
dns_records = cf.zones.dns_records.get(zone_id)
|
||||
|
||||
# Delete all DNS records
|
||||
deleted_count = 0
|
||||
for record in dns_records:
|
||||
try:
|
||||
cf.zones.dns_records.delete(zone_id, record['id'])
|
||||
deleted_count += 1
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'Error deleting CloudFlare DNS record {record["id"]} for {domainName}: {str(e)}')
|
||||
|
||||
if deleted_count > 0:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'Deleted {deleted_count} CloudFlare DNS records for {domainName}')
|
||||
return 1, f"Deleted {deleted_count} DNS records"
|
||||
else:
|
||||
return 1, "No DNS records found to delete"
|
||||
|
||||
except CloudFlare.exceptions.CloudFlareAPIError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'CloudFlare API error deleting DNS records for {domainName}: {str(e)}')
|
||||
return 0, str(e)
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'Error getting CloudFlare DNS records for {domainName}: {str(e)}')
|
||||
return 0, str(e)
|
||||
|
||||
# Zone not found in CloudFlare
|
||||
return 1, "Domain not found in CloudFlare"
|
||||
|
||||
except CloudFlare.exceptions.CloudFlareAPIError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'CloudFlare API error for {domainName}: {str(e)}')
|
||||
return 0, str(e)
|
||||
except Exception as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'Error deleting CloudFlare DNS records for {domainName}: {str(e)}')
|
||||
return 0, str(e)
|
||||
|
||||
except BaseException as msg:
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'Error in deleteCloudFlareDNSRecords for {domainName}: {str(msg)}')
|
||||
return 0, str(msg)
|
||||
|
||||
@staticmethod
|
||||
def createDNSZone(virtualHostName, admin):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -398,6 +398,12 @@ class vhost:
|
|||
delWebsite = Websites.objects.get(domain=virtualHostName)
|
||||
externalApp = delWebsite.externalApp
|
||||
|
||||
# Get admin user name for CloudFlare cleanup
|
||||
adminUserName = None
|
||||
try:
|
||||
adminUserName = delWebsite.admin.userName
|
||||
except:
|
||||
pass
|
||||
|
||||
##
|
||||
|
||||
|
|
@ -411,6 +417,14 @@ class vhost:
|
|||
numberOfSites = Websites.objects.count() + ChildDomains.objects.count()
|
||||
vhost.deleteCoreConf(items.domain, numberOfSites)
|
||||
|
||||
# Delete CloudFlare DNS records for child domain
|
||||
try:
|
||||
DNS.deleteCloudFlareDNSRecords(items.domain, adminUserName)
|
||||
except Exception as cfError:
|
||||
# Log error but don't fail deletion if CloudFlare deletion fails
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'CloudFlare DNS deletion failed for child domain {items.domain}: {str(cfError)}')
|
||||
|
||||
### Delete ACME Folder
|
||||
|
||||
if os.path.exists('/root/.acme.sh/%s' % (items.domain)):
|
||||
|
|
@ -455,6 +469,14 @@ class vhost:
|
|||
for items in databases:
|
||||
mysqlUtilities.deleteDatabase(items.dbName, items.dbUser)
|
||||
|
||||
# Delete CloudFlare DNS records for main domain before deletion
|
||||
try:
|
||||
DNS.deleteCloudFlareDNSRecords(virtualHostName, adminUserName)
|
||||
except Exception as cfError:
|
||||
# Log error but don't fail deletion if CloudFlare deletion fails
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'CloudFlare DNS deletion failed for {virtualHostName}: {str(cfError)}')
|
||||
|
||||
delWebsite.delete()
|
||||
|
||||
## Deleting DNS Zone if there is any.
|
||||
|
|
|
|||
|
|
@ -1834,6 +1834,22 @@ local_name %s {
|
|||
vhost.deleteCoreConf(virtualHostName, numberOfWebsites)
|
||||
delWebsite = ChildDomains.objects.get(domain=virtualHostName)
|
||||
|
||||
# Get admin user name before deletion for CloudFlare cleanup
|
||||
adminUserName = None
|
||||
try:
|
||||
adminUserName = delWebsite.master.admin.userName
|
||||
except:
|
||||
pass
|
||||
|
||||
# Delete CloudFlare DNS records for this domain
|
||||
try:
|
||||
from plogical.dnsUtilities import DNS
|
||||
DNS.deleteCloudFlareDNSRecords(virtualHostName, adminUserName)
|
||||
except Exception as cfError:
|
||||
# Log error but don't fail domain deletion if CloudFlare deletion fails
|
||||
logging.CyberCPLogFileWriter.writeToFile(
|
||||
f'CloudFlare DNS deletion failed for {virtualHostName}: {str(cfError)}')
|
||||
|
||||
if DeleteDocRoot:
|
||||
command = 'rm -rf %s' % (delWebsite.path)
|
||||
ProcessUtilities.executioner(command)
|
||||
|
|
|
|||
Loading…
Reference in New Issue