Fix DNS: add A/AAAA for all panel domains and subdomains (no duplicates)

This commit is contained in:
master3395
2026-02-17 01:50:00 +01:00
parent 90dab2caf1
commit 971ea6badc
7 changed files with 170 additions and 0 deletions

View File

@@ -1199,6 +1199,91 @@ class DNSManager:
final_json = json.dumps({'status': 0, 'delete_status': 0, 'error_message': str(msg), 'deleted_records': []})
return HttpResponse(final_json)
def fixDNSRecordsCloudFlare(self, userID=None, data=None):
"""Ensure all panel domains/subdomains for the zone have A (and AAAA if available) in CloudFlare. No duplicates."""
try:
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'addDeleteRecords') == 0:
return ACLManager.loadErrorJson('fix_status', 0)
zone_domain = data.get('selectedZone', '').strip()
if not zone_domain:
final_json = json.dumps({'status': 0, 'fix_status': 0, 'error_message': 'Zone is required.', 'added': 0, 'skipped': 0})
return HttpResponse(final_json)
admin = Administrator.objects.get(pk=userID)
self.admin = admin
if ACLManager.checkOwnershipZone(zone_domain, admin, currentACL) != 1:
return ACLManager.loadErrorJson()
valid_hostnames = self._get_valid_hostnames_for_zone(zone_domain)
if not valid_hostnames:
final_json = json.dumps({'status': 1, 'fix_status': 1, 'error_message': '', 'added': 0, 'skipped': 0})
return HttpResponse(final_json)
self.loadCFKeys()
params = {'name': zone_domain, 'per_page': 50}
cf = CloudFlare.CloudFlare(email=self.email, token=self.key)
zones = cf.zones.get(params=params)
if not zones:
final_json = json.dumps({'status': 0, 'fix_status': 0, 'error_message': 'Zone not found.', 'added': 0, 'skipped': 0})
return HttpResponse(final_json)
zone_id = sorted(zones, key=lambda v: v['name'])[0]['id']
existing = set()
page = 1
per_page = 100
while True:
try:
dns_records = cf.zones.dns_records.get(zone_id, params={'per_page': per_page, 'page': page})
except BaseException as e:
final_json = json.dumps({'status': 0, 'fix_status': 0, 'error_message': str(e), 'added': 0, 'skipped': 0})
return HttpResponse(final_json)
if not dns_records:
break
for rec in dns_records:
n = (rec.get('name') or '').lower().rstrip('.')
t = (rec.get('type') or '').strip().upper()
if n and t in ('A', 'AAAA', 'CNAME'):
existing.add((n, t))
if len(dns_records) < per_page:
break
page += 1
server_ip = None
try:
server_ip = ACLManager.GetServerIP()
except Exception:
pass
server_ipv6 = None
try:
server_ipv6 = ACLManager.GetServerIPv6()
except Exception:
pass
ttl = 3600
added = 0
skipped = 0
for hostname in valid_hostnames:
name_lower = hostname.lower().rstrip('.')
if (name_lower, 'A') not in existing and server_ip:
try:
DNS.createDNSRecordCloudFlare(cf, zone_id, hostname, 'A', server_ip, 0, ttl)
existing.add((name_lower, 'A'))
added += 1
except BaseException as e:
final_json = json.dumps({'status': 0, 'fix_status': 0, 'error_message': str(e), 'added': added, 'skipped': skipped})
return HttpResponse(final_json)
elif (name_lower, 'A') in existing:
skipped += 1
if server_ipv6 and (name_lower, 'AAAA') not in existing:
try:
DNS.createDNSRecordCloudFlare(cf, zone_id, hostname, 'AAAA', server_ipv6, 0, ttl)
existing.add((name_lower, 'AAAA'))
added += 1
except BaseException as e:
pass
elif (name_lower, 'AAAA') in existing:
skipped += 1
final_json = json.dumps({'status': 1, 'fix_status': 1, 'error_message': '', 'added': added, 'skipped': skipped}, default=str)
return HttpResponse(final_json)
except BaseException as msg:
final_json = json.dumps({'status': 0, 'fix_status': 0, 'error_message': str(msg), 'added': 0, 'skipped': 0})
return HttpResponse(final_json)
def updateDNSRecordCloudFlare(self, userID=None, data=None):
"""Update an existing CloudFlare DNS record (name, type, ttl, content, priority, proxied)."""
try:

View File

@@ -860,6 +860,7 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.staleRecords = [];
$scope.staleModalVisible = false;
$scope.staleLoading = false;
$scope.fixDNSLoading = false;
// Hide records boxes
$(".aaaaRecord").hide();
@@ -1355,6 +1356,29 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.staleRecords = [];
};
$scope.fixDNS = function () {
var zone = $scope.selectedZone;
if (!zone) return;
$scope.fixDNSLoading = true;
url = '/dns/fixDNSRecordsCloudFlare';
var data = { selectedZone: zone };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.fixDNSLoading = false;
if (response.data.fix_status === 1) {
populateCurrentRecords();
var msg = response.data.added + ' record(s) added.';
if (response.data.skipped) msg += ' ' + response.data.skipped + ' already present.';
new PNotify({ title: 'Fix DNS done', text: msg, type: 'success' });
} else {
new PNotify({ title: 'Error', text: response.data.error_message || 'Fix DNS failed', type: 'error' });
}
}, function () {
$scope.fixDNSLoading = false;
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.removeStaleRecords = function () {
if (!$scope.staleRecords || $scope.staleRecords.length === 0) return;
var zone = $scope.selectedZone;

View File

@@ -976,6 +976,9 @@
<button type="button" ng-click="checkStaleRecords()" class="btn-secondary" ng-disabled="staleLoading" title="{% trans 'Find DNS records for subdomains that no longer exist in the panel' %}">
<i class="fas fa-broom"></i> {% trans "Check orphan DNS" %}
</button>
<button type="button" ng-click="fixDNS()" class="btn-primary" ng-disabled="fixDNSLoading" title="{% trans 'Add A/AAAA records for all domains and subdomains in the panel; skip if already present' %}">
<i class="fas fa-wrench"></i> {% trans "Fix DNS" %}
</button>
</div>
<div class="dns-search-wrap mb-3" ng-if="!loadingRecords && records.length > 0">

View File

@@ -35,4 +35,5 @@ urlpatterns = [
re_path(r'^importDNSRecordsCloudFlare$', views.importDNSRecordsCloudFlare, name='importDNSRecordsCloudFlare'),
re_path(r'^getStaleDNSRecordsCloudFlare$', views.getStaleDNSRecordsCloudFlare, name='getStaleDNSRecordsCloudFlare'),
re_path(r'^removeStaleDNSRecordsCloudFlare$', views.removeStaleDNSRecordsCloudFlare, name='removeStaleDNSRecordsCloudFlare'),
re_path(r'^fixDNSRecordsCloudFlare$', views.fixDNSRecordsCloudFlare, name='fixDNSRecordsCloudFlare'),
]

View File

@@ -422,3 +422,12 @@ def removeStaleDNSRecordsCloudFlare(request):
return dm.removeStaleDNSRecordsCloudFlare(userID, json.loads(request.body or '{}'))
except KeyError:
return redirect(loadLoginPage)
def fixDNSRecordsCloudFlare(request):
try:
userID = request.session['userID']
dm = DNSManager()
return dm.fixDNSRecordsCloudFlare(userID, json.loads(request.body or '{}'))
except KeyError:
return redirect(loadLoginPage)

View File

@@ -850,6 +850,7 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.staleRecords = [];
$scope.staleModalVisible = false;
$scope.staleLoading = false;
$scope.fixDNSLoading = false;
$scope.showEditModal = false;
$scope.editRecord = {};
@@ -1378,6 +1379,29 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.staleRecords = [];
};
$scope.fixDNS = function () {
var zone = $scope.selectedZone;
if (!zone) return;
$scope.fixDNSLoading = true;
url = '/dns/fixDNSRecordsCloudFlare';
var data = { selectedZone: zone };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.fixDNSLoading = false;
if (response.data.fix_status === 1) {
populateCurrentRecords();
var msg = response.data.added + ' record(s) added.';
if (response.data.skipped) msg += ' ' + response.data.skipped + ' already present.';
new PNotify({ title: 'Fix DNS done', text: msg, type: 'success' });
} else {
new PNotify({ title: 'Error', text: response.data.error_message || 'Fix DNS failed', type: 'error' });
}
}, function () {
$scope.fixDNSLoading = false;
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.removeStaleRecords = function () {
if (!$scope.staleRecords || $scope.staleRecords.length === 0) return;
var zone = $scope.selectedZone;

View File

@@ -851,6 +851,7 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.staleRecords = [];
$scope.staleModalVisible = false;
$scope.staleLoading = false;
$scope.fixDNSLoading = false;
$scope.showEditModal = false;
$scope.editRecord = {};
@@ -1383,6 +1384,29 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.staleRecords = [];
};
$scope.fixDNS = function () {
var zone = $scope.selectedZone;
if (!zone) return;
$scope.fixDNSLoading = true;
url = '/dns/fixDNSRecordsCloudFlare';
var data = { selectedZone: zone };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.fixDNSLoading = false;
if (response.data.fix_status === 1) {
populateCurrentRecords();
var msg = response.data.added + ' record(s) added.';
if (response.data.skipped) msg += ' ' + response.data.skipped + ' already present.';
new PNotify({ title: 'Fix DNS done', text: msg, type: 'success' });
} else {
new PNotify({ title: 'Error', text: response.data.error_message || 'Fix DNS failed', type: 'error' });
}
}, function () {
$scope.fixDNSLoading = false;
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.removeStaleRecords = function () {
if (!$scope.staleRecords || $scope.staleRecords.length === 0) return;
var zone = $scope.selectedZone;