diff --git a/dns/dnsManager.py b/dns/dnsManager.py
index 379a8ac43..83d17706b 100644
--- a/dns/dnsManager.py
+++ b/dns/dnsManager.py
@@ -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,
diff --git a/dns/static/dns/dns.js b/dns/static/dns/dns.js
index 0767bb30d..b7a36b268 100644
--- a/dns/static/dns/dns.js
+++ b/dns/static/dns/dns.js
@@ -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 = [];
}
diff --git a/dns/templates/dns/addDeleteDNSRecordsCloudFlare.html b/dns/templates/dns/addDeleteDNSRecordsCloudFlare.html
index a41c88b04..81c335c72 100644
--- a/dns/templates/dns/addDeleteDNSRecordsCloudFlare.html
+++ b/dns/templates/dns/addDeleteDNSRecordsCloudFlare.html
@@ -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 @@
-
-
+
{% trans "DNS Records" %}
-
+
+ Loading DNS records...
+
+
+
+ No DNS records found.
+
+
+
| {% trans "Name" %} |
@@ -788,7 +863,7 @@
- |
+ |
{{ record.type }}
@@ -806,18 +881,41 @@
|
|
+
+
+
+
+
+ | {% trans "Name" %} |
+ {% trans "Type" %} |
+ {% trans "TTL" %} |
+ {% trans "Value" %} |
+ {% trans "Priority" %} |
+ {% trans "Proxy" %} |
+ {% trans "Actions" %} |
+
+
+
+
+ | Loading... |
+ Loading... |
+ Loading... |
+ Loading... |
+ Loading... |
+ Loading... |
+ Loading... |
+
+
+
-
-
-
diff --git a/plogical/dnsUtilities.py b/plogical/dnsUtilities.py
index a7924982a..78db0bc59 100644
--- a/plogical/dnsUtilities.py
+++ b/plogical/dnsUtilities.py
@@ -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:
diff --git a/plogical/vhost.py b/plogical/vhost.py
index 7d952f590..540d72ec2 100644
--- a/plogical/vhost.py
+++ b/plogical/vhost.py
@@ -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.
diff --git a/plogical/virtualHostUtilities.py b/plogical/virtualHostUtilities.py
index 78a4d2847..fbf6c58e2 100644
--- a/plogical/virtualHostUtilities.py
+++ b/plogical/virtualHostUtilities.py
@@ -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)