DNS CloudFlare: delete confirmation, clear all, restore, export/import, orphan check

- Delete record: confirmation dialog and local backup before delete
- Clear all DNS records: double confirmation (zone name), local backup, Restore button
- Export/Import DNS records (JSON) for zone
- Check orphan DNS: find A/AAAA/CNAME for hostnames no longer in panel, remove with backup
- Backend: getExportRecordsCloudFlare, clearAllDNSRecordsCloudFlare, importDNSRecordsCloudFlare, getStaleDNSRecordsCloudFlare, removeStaleDNSRecordsCloudFlare
This commit is contained in:
master3395
2026-02-17 01:43:01 +01:00
parent bb8454d3f0
commit 90dab2caf1
7 changed files with 1184 additions and 7 deletions

View File

@@ -732,6 +732,22 @@ app.controller('configureDefaultNameservers', function ($scope, $http) {
/* Java script code for CloudFlare */
app.directive('cfImportFile', function () {
return {
link: function (scope, element) {
element.on('change', function (ev) {
var files = ev.target && ev.target.files;
if (files && files.length && scope.onImportFile) {
scope.$apply(function () {
scope.onImportFile(files);
});
}
ev.target.value = '';
});
}
};
});
app.filter('dnsRecordSearch', function () {
return function (records, searchText) {
if (!records || !Array.isArray(records)) return records;
@@ -828,6 +844,13 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
$scope.couldNotAddRecord = true;
$scope.recordValueDefault = false;
$scope.records = [];
$scope.cfDeletedBackup = {};
$scope.exportLoading = false;
$scope.clearAllLoading = false;
$scope.restoreLoading = false;
$scope.staleRecords = [];
$scope.staleModalVisible = false;
$scope.staleLoading = false;
$scope.showEditModal = false;
$scope.editRecord = {};
@@ -1083,6 +1106,30 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
}
$scope.confirmDeleteRecord = function (record) {
var msg = 'Delete DNS record?\n\nName: ' + (record.name || '') + '\nType: ' + (record.type || '') + '\nValue: ' + (record.content || '');
if (!$window.confirm(msg)) {
return;
}
var zone = $scope.selectedZone;
if (!zone) {
return;
}
if (!$scope.cfDeletedBackup[zone]) {
$scope.cfDeletedBackup[zone] = [];
}
$scope.cfDeletedBackup[zone].push({
type: record.type,
name: record.name,
content: record.content,
priority: parseInt(record.priority, 10) || 0,
ttl: record.ttlNum || record.ttl || 3600,
proxy: record.proxy,
proxiable: record.proxiable !== false
});
$scope.deleteRecord(record.id);
};
$scope.deleteRecord = function (id) {
@@ -1168,6 +1215,198 @@ app.controller('addModifyDNSRecordsCloudFlare', function ($scope, $http, $window
};
$scope.hasBackupForZone = function () {
var zone = $scope.selectedZone;
if (!zone) return false;
var list = $scope.cfDeletedBackup[zone];
return list && list.length > 0;
};
$scope.confirmClearAll = function () {
var zone = $scope.selectedZone;
if (!zone) return;
var msg1 = 'This will remove ALL DNS records for this zone in CloudFlare. This action cannot be undone on CloudFlare.\n\nA local copy will be kept so you can use Restore.\n\nContinue?';
if (!$window.confirm(msg1)) return;
var msg2 = 'Type the zone name below to confirm:\n\n' + zone;
var typed = $window.prompt(msg2);
if (typed === null) return;
if (typed.trim() !== zone) {
new PNotify({ title: 'Cancelled', text: 'Zone name did not match. No records were deleted.', type: 'warning' });
return;
}
$scope.clearAllLoading = true;
url = '/dns/clearAllDNSRecordsCloudFlare';
var data = { selectedZone: zone };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.clearAllLoading = false;
if (response.data.delete_status === 1 && response.data.deleted_records) {
$scope.cfDeletedBackup[zone] = response.data.deleted_records;
$scope.canNotFetchRecords = true;
$scope.recordsFetched = false;
$scope.recordDeleted = false;
populateCurrentRecords();
new PNotify({ title: 'Done', text: 'All DNS records were deleted. Use Restore to undo.', type: 'success' });
} else {
$scope.errorMessage = response.data.error_message || 'Clear all failed';
new PNotify({ title: 'Error', text: $scope.errorMessage, type: 'error' });
}
}, function () {
$scope.clearAllLoading = false;
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.restoreFromBackup = function () {
var zone = $scope.selectedZone;
var list = $scope.cfDeletedBackup[zone];
if (!zone || !list || list.length === 0) return;
$scope.restoreLoading = true;
url = '/dns/importDNSRecordsCloudFlare';
var data = { selectedZone: zone, records: list };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.restoreLoading = false;
if (response.data.import_status === 1) {
$scope.cfDeletedBackup[zone] = [];
populateCurrentRecords();
var failed = response.data.failed || [];
var msg = response.data.imported + ' record(s) restored.';
if (failed.length) msg += ' ' + failed.length + ' failed.';
new PNotify({ title: 'Restore done', text: msg, type: failed.length ? 'warning' : 'success' });
} else {
new PNotify({ title: 'Error', text: response.data.error_message || 'Restore failed', type: 'error' });
}
}, function () {
$scope.restoreLoading = false;
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.exportRecords = function () {
var zone = $scope.selectedZone;
if (!zone) return;
$scope.exportLoading = true;
url = '/dns/getExportRecordsCloudFlare';
var data = { selectedZone: zone };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.exportLoading = false;
if (response.data.fetchStatus === 1 && response.data.data) {
var arr = typeof response.data.data === 'string' ? JSON.parse(response.data.data) : response.data.data;
var blob = new Blob([JSON.stringify(arr, null, 2)], { type: 'application/json' });
var a = document.createElement('a');
a.href = (window.URL || window.webkitURL).createObjectURL(blob);
a.download = 'dns-records-' + zone.replace(/\./g, '-') + '.json';
a.click();
if (a.href) (window.URL || window.webkitURL).revokeObjectURL(a.href);
new PNotify({ title: 'Export done', text: 'DNS records downloaded.', type: 'success' });
} else {
new PNotify({ title: 'Error', text: response.data.error_message || 'Export failed', type: 'error' });
}
}, function () {
$scope.exportLoading = false;
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.onImportFile = function (files) {
if (!files || !files.length) return;
var zone = $scope.selectedZone;
if (!zone) {
new PNotify({ title: 'Error', text: 'Select a zone first.', type: 'error' });
return;
}
var file = files[0];
var reader = new FileReader();
reader.onload = function (e) {
var text = e.target && e.target.result;
if (!text) {
new PNotify({ title: 'Error', text: 'Could not read file.', type: 'error' });
return;
}
var arr;
try {
arr = JSON.parse(text);
} catch (err) {
new PNotify({ title: 'Error', text: 'Invalid JSON: ' + (err.message || ''), type: 'error' });
return;
}
if (!Array.isArray(arr)) {
if (arr && Array.isArray(arr.records)) arr = arr.records;
else if (arr && arr.data) arr = Array.isArray(arr.data) ? arr.data : [arr.data];
else arr = [arr];
}
url = '/dns/importDNSRecordsCloudFlare';
var data = { selectedZone: zone, records: arr };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
if (response.data.import_status === 1) {
populateCurrentRecords();
var failed = response.data.failed || [];
var msg = response.data.imported + ' record(s) imported.';
if (failed.length) msg += ' ' + failed.length + ' failed.';
new PNotify({ title: 'Import done', text: msg, type: failed.length ? 'warning' : 'success' });
} else {
new PNotify({ title: 'Error', text: response.data.error_message || 'Import failed', type: 'error' });
}
}, function () {
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
reader.readAsText(file, 'UTF-8');
};
$scope.checkStaleRecords = function () {
var zone = $scope.selectedZone;
if (!zone) return;
$scope.staleLoading = true;
url = '/dns/getStaleDNSRecordsCloudFlare';
var data = { selectedZone: zone };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
$scope.staleLoading = false;
if (response.data.fetchStatus === 1) {
$scope.staleRecords = response.data.stale_records || [];
$scope.staleModalVisible = true;
} else {
new PNotify({ title: 'Error', text: response.data.error_message || 'Could not fetch stale records', type: 'error' });
}
}, function () {
$scope.staleLoading = false;
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.closeStaleModal = function () {
$scope.staleModalVisible = false;
$scope.staleRecords = [];
};
$scope.removeStaleRecords = function () {
if (!$scope.staleRecords || $scope.staleRecords.length === 0) return;
var zone = $scope.selectedZone;
var msg = 'Remove ' + $scope.staleRecords.length + ' orphan DNS record(s)? A local copy will be kept for Restore.';
if (!$window.confirm(msg)) return;
var ids = $scope.staleRecords.map(function (r) { return r.id; });
url = '/dns/removeStaleDNSRecordsCloudFlare';
var data = { selectedZone: zone, ids: ids };
var config = { headers: { 'X-CSRFToken': getCookie('csrftoken') } };
$http.post(url, data, config).then(function (response) {
if (response.data.delete_status === 1 && response.data.deleted_records) {
if (!$scope.cfDeletedBackup[zone]) $scope.cfDeletedBackup[zone] = [];
$scope.cfDeletedBackup[zone] = $scope.cfDeletedBackup[zone].concat(response.data.deleted_records);
$scope.closeStaleModal();
populateCurrentRecords();
new PNotify({ title: 'Done', text: response.data.deleted_records.length + ' orphan record(s) removed. Use Restore to undo.', type: 'success' });
} else {
new PNotify({ title: 'Error', text: response.data.error_message || 'Remove failed', type: 'error' });
}
}, function () {
new PNotify({ title: 'Error', text: 'Could not connect to server.', type: 'error' });
});
};
$scope.dnsTypeList = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'SRV', 'CAA', 'SPF', 'DNSKEY', 'CDNSKEY', 'HTTPS', 'SVCB', 'URI', 'LOC', 'NAPTR', 'SMIMEA', 'SSHFP', 'TLSA', 'PTR'];
$scope.getTypeOptions = function (record) {
var list = angular.copy($scope.dnsTypeList);