CloudFlare DNS: edit records on click (name, type, TTL, value, priority, proxy)

- Add updateDNSRecordCloudFlare backend (dnsManager, view, URL)
- Return real priority in getCurrentRecordsForDomainCloudFlare
- Edit (pencil) button + modal with all fields; Save calls update API
- openEditModal, closeEditModal, saveEditRecord in dns.js
This commit is contained in:
master3395
2026-02-16 14:39:46 +01:00
parent ce5a8f56b6
commit dbc05728a7
6 changed files with 245 additions and 1 deletions

View File

@@ -775,11 +775,14 @@ class DNSManager:
else:
ttl = dns_record['ttl']
prio = dns_record.get('priority', 0)
if prio is None:
prio = 0
dic = {'id': dns_record['id'],
'type': dns_record['type'],
'name': dns_record['name'],
'content': dns_record['content'],
'priority': '1400',
'priority': str(prio),
'ttl': ttl,
'proxy': dns_record['proxied'],
'proxiable': dns_record['proxiable']
@@ -844,6 +847,67 @@ class DNSManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def updateDNSRecordCloudFlare(self, userID=None, data=None):
"""Update an existing CloudFlare DNS record (name, type, ttl, content, priority, proxied)."""
try:
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'addDeleteRecords') == 0:
return ACLManager.loadErrorJson('update_status', 0)
zone_domain = data['selectedZone']
record_id = data['id']
name = (data.get('name') or '').strip()
record_type = (data.get('recordType') or data.get('type') or '').strip()
content = (data.get('content') or '').strip()
ttl_val = data.get('ttl')
priority = data.get('priority', 0)
proxied = data.get('proxied', False)
if not name or not record_type or not content:
final_json = json.dumps({'status': 0, 'update_status': 0, 'error_message': 'Name, type and content are required.'})
return HttpResponse(final_json)
try:
ttl_int = int(ttl_val) if ttl_val not in (None, '', 'AUTO') else 1
except (ValueError, TypeError):
ttl_int = 1
if ttl_int < 0:
ttl_int = 1
elif ttl_int > 86400 and ttl_int != 1:
ttl_int = 86400
try:
priority_int = int(priority) if priority not in (None, '') else 0
except (ValueError, TypeError):
priority_int = 0
admin = Administrator.objects.get(pk=userID)
self.admin = admin
if ACLManager.checkOwnershipZone(zone_domain, admin, currentACL) != 1:
return ACLManager.loadErrorJson()
self.loadCFKeys()
params = {'name': zone_domain, 'per_page': 50}
cf = CloudFlare.CloudFlare(email=self.email, token=self.key)
zones = cf.zones.get(params=params)
zone_list = sorted(zones, key=lambda v: v['name'])
if not zone_list:
final_json = json.dumps({'status': 0, 'update_status': 0, 'error_message': 'Zone not found.'})
return HttpResponse(final_json)
zone_id = zone_list[0]['id']
update_data = {'name': name, 'type': record_type, 'content': content, 'ttl': ttl_int, 'priority': priority_int}
if record_type in ['A', 'CNAME']:
update_data['proxied'] = bool(proxied)
cf.zones.dns_records.put(zone_id, record_id, data=update_data)
final_dic = {'status': 1, 'update_status': 1, 'error_message': 'None'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'update_status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def addDNSRecordCloudFlare(self, userID = None, data = None):
try:

View File

@@ -203,6 +203,27 @@
background: var(--bg-secondary, #f8f9ff);
border-color: #5b5fcf;
}
.edit-record-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.edit-record-modal {
background: var(--bg-primary, white);
border-radius: 12px;
padding: 2rem;
max-width: 480px;
width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.alert {
padding: 1rem 1.5rem;
@@ -899,6 +920,10 @@
type="checkbox">
</td>
<td style="text-align: center;">
<i class="fas fa-edit edit-icon"
style="color: #5b5fcf; cursor: pointer; margin-right: 10px;"
ng-click="openEditModal(record)"
title="{% trans 'Edit Record' %}"></i>
<i class="fas fa-trash delete-icon"
style="color: #ef4444; cursor: pointer;"
ng-click="deleteRecord(record.id)"
@@ -937,6 +962,56 @@
</div>
<!-- Edit DNS Record Modal -->
<div class="edit-record-overlay" ng-show="showEditModal" ng-click="closeEditModal()">
<div class="edit-record-modal" ng-click="$event.stopPropagation()">
<h4 class="mb-4" style="color: var(--text-primary, #1e293b); font-weight: 600;">
<i class="fas fa-edit"></i> {% trans "Edit DNS Record" %}
</h4>
<div class="form-group">
<label class="form-label">{% trans "Name" %}</label>
<input type="text" class="form-control" ng-model="editRecord.name" placeholder="e.g. www or @">
</div>
<div class="form-group">
<label class="form-label">{% trans "Type" %}</label>
<select class="form-control" ng-model="editRecord.type">
<option value="A">A</option>
<option value="AAAA">AAAA</option>
<option value="CNAME">CNAME</option>
<option value="MX">MX</option>
<option value="TXT">TXT</option>
<option value="NS">NS</option>
<option value="SOA">SOA</option>
<option value="SRV">SRV</option>
<option value="CAA">CAA</option>
<option value="SPF">SPF</option>
</select>
</div>
<div class="form-group">
<label class="form-label">{% trans "TTL" %}</label>
<input type="text" class="form-control" ng-model="editRecord.ttl" placeholder="3600 or AUTO">
</div>
<div class="form-group">
<label class="form-label">{% trans "Value / Content" %}</label>
<input type="text" class="form-control" ng-model="editRecord.content" placeholder="Record value">
</div>
<div class="form-group">
<label class="form-label">{% trans "Priority" %}</label>
<input type="number" class="form-control" ng-model="editRecord.priority" placeholder="0">
</div>
<div class="form-group" ng-if="editRecord.proxiable">
<label class="form-label">{% trans "Proxy (orange cloud)" %}</label>
<input type="checkbox" ng-model="editRecord.proxy" style="margin-left: 8px;">
</div>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn-secondary" ng-click="closeEditModal()">{% trans "Cancel" %}</button>
<button type="button" class="btn-primary" ng-click="saveEditRecord()">
<i class="fas fa-save"></i> {% trans "Save" %}
</button>
</div>
</div>
</div>
<!-- Alert Messages -->
<div style="margin-top: 2rem;">

View File

@@ -27,6 +27,7 @@ urlpatterns = [
re_path(r'^getCurrentRecordsForDomainCloudFlare$', views.getCurrentRecordsForDomainCloudFlare, name='getCurrentRecordsForDomainCloudFlare'),
re_path(r'^deleteDNSRecordCloudFlare$', views.deleteDNSRecordCloudFlare, name='deleteDNSRecordCloudFlare'),
re_path(r'^addDNSRecordCloudFlare$', views.addDNSRecordCloudFlare, name='addDNSRecordCloudFlare'),
re_path(r'^updateDNSRecordCloudFlare$', views.updateDNSRecordCloudFlare, name='updateDNSRecordCloudFlare'),
re_path(r'^syncCF$', views.syncCF, name='syncCF'),
re_path(r'^enableProxy$', views.enableProxy, name='enableProxy'),
]

View File

@@ -339,6 +339,15 @@ def addDNSRecordCloudFlare(request):
return redirect(loadLoginPage)
def updateDNSRecordCloudFlare(request):
try:
userID = request.session['userID']
dm = DNSManager()
return dm.updateDNSRecordCloudFlare(userID, json.loads(request.body))
except KeyError:
return redirect(loadLoginPage)
def syncCF(request):
try:
userID = request.session['userID']

View File

@@ -811,6 +811,9 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.couldNotDeleteRecords = true;
$scope.couldNotAddRecord = true;
$scope.recordValueDefault = false;
$scope.records = [];
$scope.showEditModal = false;
$scope.editRecord = {};
// Hide records boxes
$(".aaaaRecord").hide();
@@ -1135,6 +1138,51 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
};
$scope.openEditModal = function (record) {
$scope.editRecord = {
id: record.id,
name: record.name,
type: record.type,
ttl: record.ttl,
content: record.content,
priority: record.priority || 0,
proxy: record.proxy,
proxiable: record.proxiable
};
$scope.showEditModal = true;
};
$scope.closeEditModal = function () {
$scope.showEditModal = false;
};
$scope.saveEditRecord = function () {
var url = "/dns/updateDNSRecordCloudFlare";
var data = {
selectedZone: $scope.selectedZone,
id: $scope.editRecord.id,
name: $scope.editRecord.name,
recordType: $scope.editRecord.type,
content: $scope.editRecord.content,
ttl: $scope.editRecord.ttl === 'AUTO' || $scope.editRecord.ttl === 1 ? 1 : parseInt($scope.editRecord.ttl, 10) || 3600,
priority: parseInt($scope.editRecord.priority, 10) || 0,
proxied: $scope.editRecord.proxy
};
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
if (response.data.update_status === 1) {
$scope.closeEditModal();
populateCurrentRecords();
new PNotify({ title: 'Success', text: 'Record updated.', type: 'success' });
} else {
$scope.errorMessage = response.data.error_message || 'Update failed';
new PNotify({ title: 'Error', text: $scope.errorMessage, type: 'error' });
}
}, function () {
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.syncCF = function () {
$scope.recordsLoading = false;

View File

@@ -813,6 +813,8 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.couldNotAddRecord = true;
$scope.recordValueDefault = false;
$scope.records = [];
$scope.showEditModal = false;
$scope.editRecord = {};
// Hide records boxes
$(".aaaaRecord").hide();
@@ -1140,6 +1142,51 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
};
$scope.openEditModal = function (record) {
$scope.editRecord = {
id: record.id,
name: record.name,
type: record.type,
ttl: record.ttl,
content: record.content,
priority: record.priority || 0,
proxy: record.proxy,
proxiable: record.proxiable
};
$scope.showEditModal = true;
};
$scope.closeEditModal = function () {
$scope.showEditModal = false;
};
$scope.saveEditRecord = function () {
var url = "/dns/updateDNSRecordCloudFlare";
var data = {
selectedZone: $scope.selectedZone,
id: $scope.editRecord.id,
name: $scope.editRecord.name,
recordType: $scope.editRecord.type,
content: $scope.editRecord.content,
ttl: $scope.editRecord.ttl === 'AUTO' || $scope.editRecord.ttl === 1 ? 1 : parseInt($scope.editRecord.ttl, 10) || 3600,
priority: parseInt($scope.editRecord.priority, 10) || 0,
proxied: $scope.editRecord.proxy
};
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
if (response.data.update_status === 1) {
$scope.closeEditModal();
populateCurrentRecords();
new PNotify({ title: 'Success', text: 'Record updated.', type: 'success' });
} else {
$scope.errorMessage = response.data.error_message || 'Update failed';
new PNotify({ title: 'Error', text: $scope.errorMessage, type: 'error' });
}
}, function () {
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.syncCF = function () {
$scope.recordsLoading = false;