/* * Copyright 2018 Comcast Cable Communications Management, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function() { 'use strict'; angular.module('dns-change') .controller('DnsChangeNewController', function($scope, $log, $location, $timeout, $q, dnsChangeService, utilityService, groupsService){ groupsService.getGroups() .then(function (results) { $scope.myGroups = results['data']['groups']; if ($scope.myGroups.length == 1) { $scope.newBatch.ownerGroupId = $scope.myGroups[0]['id'] } }) .catch(function (error) { handleError(error, 'groupsService::getGroups-failure'); }); $scope.batch = {}; var tomorrow = moment().startOf('hour').add(1, 'day'); $scope.newBatch = {comments: "", changes: [{changeType: "Add", type: "A+PTR"}], scheduledTime: tomorrow.format('LL hh:mm A')}; $scope.alerts = []; $scope.batchChangeErrors = false; $scope.ownerGroupError = false; $scope.softErrors = false; $scope.formStatus = "pendingSubmit"; $scope.scheduledOption = false; $scope.allowManualReview = false; $scope.confirmationPrompt = "Are you sure you want to submit this batch change request?"; $scope.manualReviewEnabled; $scope.naptrFlags = ["U", "S", "A", "P"]; // Initialize Bootstrap tooltips $(document).ready(function() { $('[data-toggle="tooltip"]').tooltip(); }); $scope.addSingleChange = function() { $scope.newBatch.changes.push({changeType: "Add", type: "A+PTR"}); var changesLength = $scope.newBatch.changes.length; $timeout(function() {document.getElementsByClassName("changeType")[changesLength - 1].focus()}); }; $scope.cancelSubmit = function() { $scope.formStatus = "pendingSubmit"; $scope.allowManualReview = false; $scope.confirmationPrompt = "Are you sure you want to submit this batch change request?"; }; $scope.confirmSubmit = function(form) { if(form.$invalid){ form.$setSubmitted(); $scope.formStatus = "pendingSubmit"; } }; $scope.clearRecordData = function(changeIndex) { delete $scope.newBatch.changes[changeIndex].record; }; $scope.createBatchChange = function() { //flag to prevent multiple clicks until previous promise has resolved. $scope.processing = true; var payload = $scope.newBatch; function formatData(payload) { if (!$scope.newBatch.ownerGroupId) { delete payload.ownerGroupId } if ($scope.scheduledOption && $scope.newBatch.scheduledTime) { payload.scheduledTime = moment($scope.newBatch.scheduledTime, 'LL hh:mm A').utc().format(); } else { delete payload.scheduledTime; } for (var i = 0; i < payload.changes.length; i++) { var entry = payload.changes[i] if(entry.type == 'A+PTR' || entry.type == 'AAAA+PTR') { entry.type = entry.type.slice(0, -4); var newEntry = {changeType: entry.changeType, type: "PTR", ttl: entry.ttl, inputName: entry.record.address, record: {ptrdname: entry.inputName}} payload.changes.splice(i+1, 0, newEntry) } if(entry.type == 'NAPTR') { // Since regexp can be left empty if(entry.record.regexp == undefined){ var newEntry = {changeType: entry.changeType, type: "NAPTR", ttl: entry.ttl, inputName: entry.inputName, record: {order: entry.record.order, preference: entry.record.preference, flags: entry.record.flags, service: entry.record.service, regexp: '', replacement: entry.record.replacement}} payload.changes[i] = newEntry; } } if(entry.changeType == 'DeleteRecordSet' && entry.record) { var recordDataEmpty = true; for (var attr in entry.record) { if (entry.record[attr] != undefined && entry.record[attr].toString().length > 0) { recordDataEmpty = false } } if (recordDataEmpty) { delete entry.record } } } } function success(response) { var alert = utilityService.success('Successfully created DNS Change', response, 'createBatchChange: createBatchChange successful'); $scope.alerts.push(alert); $timeout(function(){ location.href = "/dnschanges/" + response.data.id; }, 2000); $scope.batch = response.data; } formatData(payload); return dnsChangeService.createBatchChange(payload, true) .then(success) .catch(function (error){ if(payload.scheduledTime) { $scope.newBatch.scheduledTime = moment(payload.scheduledTime).local().format('LL hh:mm A') } if(error.data.errors || error.status !== 400 || typeof error.data == "string"){ handleError(error, 'dnsChangesService::createBatchChange-failure'); } else { $scope.newBatch.changes = error.data; $scope.batchChangeErrors = true; $scope.listOfErrors = error.data.flatMap(d => d.errors) $scope.ownerGroupError = $scope.listOfErrors.some(e => e.includes('owner group ID must be specified for record')); $scope.softErrors = false; $scope.formStatus = "pendingSubmit"; $scope.alerts.push({type: 'danger', content: 'Errors found. Please correct and submit again.'}); } }); }; $scope.deleteSingleChange = function(changeNumber) { $('.batch-change-delete').blur(); $scope.newBatch.changes.splice(changeNumber, 1); }; $scope.submitChange = function(manualReviewEnabled) { $scope.formStatus = "pendingConfirm"; $scope.manualReviewEnabled = manualReviewEnabled; } $scope.getLocalTimeZone = function() { return new Date().toLocaleString('en-us', {timeZoneName:'short'}).split(' ')[3]; } function handleError(error, type) { var alert = utilityService.failure(error, type); $scope.alerts.push(alert); } function resetFileInput() { $scope.csvInput = null; var inputElement = document.getElementById('batchChangeCsv'); if (inputElement) { inputElement.value = null; } if ($scope.createBatchChangeForm && $scope.createBatchChangeForm.batchChangeCsv) { $scope.createBatchChangeForm.batchChangeCsv.$setViewValue(null); $scope.createBatchChangeForm.batchChangeCsv.$render(); } } $scope.uploadCSV = function(file, batchChangeLimit) { parseFile(file, batchChangeLimit).then(function(dataLength){ $scope.alerts.push({type: 'success', content: 'Successfully imported ' + dataLength + ' DNS changes.' }); resetFileInput(); }, function(error) { $scope.alerts.push({type: 'danger', content: error}); }); function parseFile(file, batchChangeLimit) { return $q(function(resolve, reject) { if (!file || !file.name) { $log.debug('No file selected or file has no name property'); } else if (!file.name.endsWith('.csv')) { reject("Import failed. File should be of ‘.csv’ type."); } else { var reader = new FileReader(); reader.onload = function(e) { var rows = e.target.result.split("\n"); if(rows.length - 1 > batchChangeLimit) {reject("Import failed. Cannot add more than " + batchChangeLimit + " records per DNS change."); } else { if (rows[0].trim() == "Change Type,Record Type,Input Name,TTL,Record Data") { $scope.newBatch.changes = []; for(var i = 1; i < rows.length; i++) { var lengthCheck = rows[i].replace(/,+/g, '').trim().length if (lengthCheck == 0) { continue; } parseRow(rows[i]) } $scope.$apply() resolve($scope.newBatch.changes.length); } else { reject("Import failed. CSV header must be: Change Type,Record Type,Input Name,TTL,Record Data"); } }} reader.readAsText(file); } }); } function decode(str) { // regex from: // https://www.bennadel.com/blog/1504-ask-ben-parsing-csv-strings-with-javascript-exec-regular-expression-command.htm // matches[0] is full match text with delimiter if any // matches[1] is delimiter (usually ',') // matches[2] is quoted field or undefined, internal quotes are doubled by convention // matches[3] is standard field or undefined // one of [2] or [3] will be undefined const regex = /(,|\r?\n|\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\r\n]*))/gi; const matches = [...str.matchAll(regex)]; return matches.map(match => match[2] !== undefined ? match[2].replace(/""/g, '"') : match[3]); } function parseRow(row) { var change = {}; var headers = ["changeType", "type", "inputName", "ttl", "record"]; var rowContent = decode(row); for (var j = 0; j < rowContent.length; j++) { if (headers[j] == "changeType") { if (rowContent[j].match(/add/i)) { change[headers[j]] = "Add" } else if (rowContent[j].match(/delete/i)) { change[headers[j]] = "DeleteRecordSet" } } else if (headers[j] == "type") { change[headers[j]] = rowContent[j].trim().toUpperCase() } else if (headers[j] == "ttl") { change[headers[j]] = parseInt(rowContent[j].trim()) } else if (headers[j] == "record"){ if (change["type"] == "A" || change["type"] == "AAAA" || change["type"] == "A+PTR" || change["type"] == "AAAA+PTR"){ change[headers[j]] = {"address": rowContent[j].trim()} } else if (change["type"] == "CNAME") { change[headers[j]] = {"cname": rowContent[j].trim()} } else if (change["type"] == "PTR") { change[headers[j]] = {"ptrdname": rowContent[j].trim()} } else if (change["type"] == "TXT") { change[headers[j]] = {"text": rowContent[j].trim()} } else if (change["type"] == "NS") { change[headers[j]] = {"nsdname": rowContent[j].trim()} } else if (change["type"] == "MX") { var mxData = rowContent[j].trim().split(' '); change[headers[j]] = {"preference": parseInt(mxData[0]), "exchange": mxData[1]} } else if (change["type"] == "NAPTR") { var naptrData = rowContent[j].trim().split(' '); if(naptrData.length == 6){ change[headers[j]] = {"order": parseInt(naptrData[0]), "preference": parseInt(naptrData[1]), "flags": naptrData[2], "service": naptrData[3], "regexp": naptrData[4], "replacement": naptrData[5]} } else { change[headers[j]] = {"order": parseInt(naptrData[0]), "preference": parseInt(naptrData[1]), "flags": naptrData[2], "service": naptrData[3], "regexp": '', "replacement": naptrData[4]} } } else if (change["type"] == "SRV") { var srvData = rowContent[j].trim().split(' '); change[headers[j]] = {"priority": parseInt(srvData[0]), "weight": parseInt(srvData[1]), "port": parseInt(srvData[2]), "target": srvData[3]} } } else { change[headers[j]] = rowContent[j].trim() } } $scope.newBatch.changes.push(change); } } $('input[name="scheduledTime"]').daterangepicker({ singleDatePicker: true, timePicker: true, startDate: tomorrow, locale: { format: 'LL hh:mm A' } }); }); })();