File manager - Edit - /usr/local/cpanel/whostmgr/docroot/templates/backup_configuration/views/destinations.js
Back
/* # backup_configuration/views/destinations.js Copyright 2022 cPanel, L.L.C. # All rights reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited */ /* global define */ define( [ "angular", "lodash", "cjt/util/locale", "cjt/util/table", "cjt/services/alertService", "cjt/directives/alertList", "cjt/directives/toggleSortDirective", "cjt/directives/actionButtonDirective", "cjt/directives/validationContainerDirective", "cjt/directives/validationItemDirective", "cjt/validator/datatype-validators", "cjt/validator/username-validators", "cjt/validator/compare-validators", "app/services/backupConfigurationServices", "app/services/validationLog" ], function(angular, _, LOCALE, Table) { "use strict"; var app = angular.module("whm.backupConfiguration"); var table = new Table(); var controller = app.controller( "destinations", [ "$scope", "$location", "$q", "$window", "backupConfigurationServices", "alertService", "$timeout", "validationLog", function( $scope, $location, $q, $window, backupConfigurationServices, alertService, $timeout, validationLog) { /** * Sets delete confirmation callout element to show and assigns * destination properties to the $scope to be used when deleting destination * * @scope * @method setupDeleteConfirmation * @param {String} name - name of specific destination * @param {String} id - unique identification string */ $scope.setupDeleteConfirmation = function(name, id, index) { $scope.index = index; $scope.showDeleteConfirmation = !$scope.showDeleteConfirmation; $scope.updating = !$scope.updating; $scope.destinationName = name; $scope.destinationId = id; }; /** * Handles backup enable toggle and sets scope property to remove * focus from all inputs if backup is not enabled * * @scope * @method enableBackupConfig */ $scope.enableBackupConfig = function() { if (!$scope.formData.backupenable) { $scope.formEnabled = false; } else { $scope.formEnabled = true; } }; /** * Is validation in progress for given destination. * * @scope * @method isValidationInProgressFor * @param {String} id - id of specific destination to test * @returns {Boolean} is destination being validated */ $scope.isValidationInProgressFor = function(destination) { return validationLog.isValidationInProgressFor(destination); }; /** * Determine whether a validation process is current running. * * @scope * @method isValidationRunning * @returns {Boolean} is validation (multiple or single) process running */ $scope.isValidationRunning = function() { return validationLog.isValidationRunning(); }; /** * Validates destination via API * * @scope * @method validateDestination * @param {String} id - id of specific destination to send to API * @param {String} name - name of specific destination * @param {Object} opts - options * @param {Boolean} opts.all - if true dont clear the alert list since we are validating each destination. * @returns {Promise<String>} - string indicating success * @throws {Promise<String>} - string indicating error */ $scope.validateDestination = function(id, name, opts) { if (!opts) { opts = {}; } if (!opts.all) { $scope.clearAlerts(); } $scope.destinationState.validatingDestination = true; $scope.displayAlertRows = []; $scope.displayAlertRows.push(id); var theDestination = _.find($scope.destinationState.destinationList, function(item) { return item.id === id; }); validationLog.add(theDestination); $scope.currentlyValidating = validationLog.getLogEntries(); return backupConfigurationServices.validateDestination(id) .then(function(success) { var alertOptions = { type: "success", id: "validate-destination-succeeded-" + id, message: LOCALE.maketext("The validation for the “[_1]” destination succeeded.", _.escape(name)), closeable: true, autoClose: 10000, }; if (opts.all) { alertOptions.replace = false; } validationLog.markAsComplete(id, alertOptions); if (!$scope.destinationState.validatingAllDestinations) { alertService.add(alertOptions); } }) .catch(function(error) { var alertOptions = { type: "danger", message: _.escape(error), closeable: true, replace: false, id: "validate-destination-failed-" + id, }; if (opts.all) { alertOptions.replace = false; } // validation has failed, so mark existing entry in // destinations list as disabled theDestination.disabled = true; validationLog.markAsComplete(id, alertOptions); if (!$scope.destinationState.validatingAllDestinations) { alertService.add(alertOptions); } }) .finally(function() { $scope.destinationState.validatingDestination = $scope.isValidationRunning(); $scope.destinationState.showValidationIconHint = true; }); }; /** * Retrieves all current destinations via API * * @scope * @method getDestinations */ $scope.getDestinations = function() { $scope.destinationState.destinationListLoaded = false; return backupConfigurationServices.getDestinationList() .then(function(destinationsData) { $scope.destinationState.destinationList = destinationsData; $scope.destinationState.destinationListLoaded = true; $scope.updating = false; $scope.currentlyValidating = validationLog.getLogEntries(); $scope.setPagination(destinationsData); }, function(error) { $scope.destinationState.destinationListLoaded = true; $scope.updating = false; alertService.add({ type: "danger", closeable: true, message: error, id: "fetch-destinations-failed" }); }); }; /** * Load data and get metadata for table of transports * * @scope * @method setPagination * @param {Array.<TransportType>} transportData - array of transport objects */ $scope.setPagination = function(transportData) { if (transportData) { table.load(transportData); table.setSort("name,type", "asc"); // the next two lines should be removed if // pagination for the table is implemented table.meta.limit = transportData.length; table.meta.pageSize = transportData.length; } table.update(); $scope.meta = table.getMetadata(); $scope.filteredDestinationList = table.getList(); }; $scope.updateTable = function() { $scope.setPagination(); }; /** * Sets path of destination template to retrieve * * @scope * @method setTemplatePath * @param {String} type - string indicating destination type selected */ $scope.setTemplatePath = function(type) { if (type === "Custom") { $scope.templatePath = "views/customTransport.ptt"; } else if (type === "FTP") { $scope.templatePath = "views/FTPTransport.ptt"; } else if (type === "GoogleDrive") { $scope.templatePath = "views/GoogleTransport.ptt"; } else if (type === "Local" || type === "Additional Local Directory") { $scope.templatePath = "views/LocalTransport.ptt"; } else if (type === "SFTP") { $scope.templatePath = "views/SFTPTransport.ptt"; } else if (type === "Amazon S3" || type === "AmazonS3") { $scope.templatePath = "views/AmazonS3Transport.ptt"; } else if (type === "Rsync") { $scope.templatePath = "views/RsyncTransport.ptt"; } else if (type === "WebDAV") { $scope.templatePath = "views/WebDAVTransport.ptt"; } else if (type === "S3Compatible") { $scope.templatePath = "views/S3CompatibleTransport.ptt"; } else if (type === "Backblaze") { $scope.templatePath = "views/B2.ptt"; } }; /** * Returns custom transport type where required. * * @scope * @method getTransportType * @param {String} type - string indicating destination type * @returns {String} - type formatted for display */ $scope.formattedTransportType = function(type) { if (type === "Backblaze") { return "Backblaze B2"; } else if (type === "GoogleDrive") { return "Google Drive™"; } return type; }; /** * Retrieves selected destination via API * * @scope * @method getDestination * @param {String} id - id of selected destination * @param {String} type - type of selected destination */ $scope.getDestination = function(id, type) { $scope.destinationState.fetchingDestination = true; $scope.destinationState.newMode = false; $scope.setTemplatePath(type); return backupConfigurationServices.getDestination(id) .then(function(destinationData) { $scope.destinationState.destination = destinationData; $scope.destinationState.destinationMode = true; $scope.destinationState.fetchingDestination = false; $scope.destinationState.editMode = true; if (type === "SFTP" || type === "Rsync") { $scope.getSSHKeyList(); } if (type === "GoogleDrive") { $scope.checkCredentials(destinationData.googledrive.client_id, destinationData.googledrive.client_secret); } }, function(error) { $scope.destinationState.fetchingDestination = false; alertService.add({ type: "danger", closeable: true, message: error, id: "fetch-destination-error" }); }); }; /** * Sets template path and creates new destination object * * @scope * @method createNewDestination * @param {String} type - destination type */ $scope.createNewDestination = function(type) { $scope.destinationState.destination = {}; $scope.destinationState.editMode = false; $scope.destinationState.newMode = true; $scope.setTemplatePath(type); /** * New destination object created with default values per * https://confluence0.cpanel.net/display/public/SDK/WHM+API+1+Functions+-+backup_destination_add */ if (type === "Custom") { $scope.destinationState.destination.custom = { type: type, timeout: 30 }; } else if (type === "FTP") { $scope.destinationState.destination.ftp = { type: type, port: 21, passive: true, timeout: 30 }; } else if (type === "GoogleDrive") { $scope.destinationState.destination.googledrive = { type: type, timeout: 30 }; } else if (type === "Local" || type === "Additional Local Directory") { $scope.destinationState.destination.local = { type: "Local", mount: false }; } else if (type === "SFTP") { $scope.destinationState.destination.sftp = { type: type, authtype: "key", port: 22, timeout: 30 }; $scope.getSSHKeyList(); } else if (type === "AmazonS3") { $scope.destinationState.destination.amazons3 = { type: "AmazonS3", timeout: 30, region: '' }; } else if (type === "S3Compatible") { $scope.destinationState.destination.s3compatible = { type: "S3Compatible", timeout: 30 }; } else if (type === "Rsync") { $scope.destinationState.destination.rsync = { type: type, authtype: "key", timeout: 30, port: 22 }; $scope.getSSHKeyList(); } else if (type === "WebDAV") { $scope.destinationState.destination.webdav = { type: type }; } else if (type === "Backblaze") { $scope.destinationState.destination.backblaze = { type: type, timeout: 180 }; } $scope.destinationState.destinationMode = true; }; /** * Saves new destination via API * * @scope * @param {<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType>} destination - object representing destination config * @param {Boolean} [shouldValidate] - whether the destination should also be validated as well as saved * @method saveDestination * @returns {Promise<String>} - id indicating success in case destination also needs to be validated * @throws {Promise<String>} - string indicating error */ $scope.saveDestination = function(destination, shouldValidate) { $scope.clearAlerts(); $scope.destinationState.savingDestination = true; var property = Object.keys(destination); var destinationName = destination[property[0]]["name"]; if ($scope.destinationState.newMode) { return backupConfigurationServices.setNewDestination(destination) .then(function(response) { $scope.destinationId = response.id; $scope.destinationState.googleCredentialsGenerated = false; $scope.destinationState.destinationMode = false; $scope.destinationState.newMode = false; alertService.add({ type: "success", autoClose: 5000, message: LOCALE.maketext("The system successfully saved the “[_1]” destination.", _.escape(destinationName)), id: "save-new-destination-success" }); if (destination[property[0]]["type"] === "GoogleDrive") { $scope.destinationState.checkCredentialsOnSave = true; $scope.checkCredentials(destination[property[0]]["client_id"], destination[property[0]]["client_secret"], $scope.destinationState.checkCredentialsOnSave); } return $scope.getDestinations(); }) .then(function() { if (shouldValidate) { // pass all=true for options so save message not overwritten by validate message $scope.validateDestination($scope.destinationId, _.escape(destinationName), { all: true }); } }) .catch(function(error) { alertService.add({ type: "danger", closeable: true, message: _.escape(error), id: "save-new-destination-error" }); }) .finally(function() { $scope.destinationState.savingDestination = false; }); } else if ($scope.destinationState.editMode) { return backupConfigurationServices.updateCurrentDestination(destination) .then(function(response) { $scope.destinationState.editMode = false; alertService.add({ type: "success", autoClose: 5000, message: LOCALE.maketext("The system successfully saved the “[_1]” destination.", _.escape(destinationName)), id: "edit-destination-success" }); if (destination[property[0]]["type"] === "GoogleDrive") { $scope.destinationState.checkCredentialsOnSave = true; $scope.checkCredentials(destination[property[0]]["client_id"], destination[property[0]]["client_secret"], $scope.destinationState.checkCredentialsOnSave); } $scope.destinationState.destinationMode = false; return $scope.getDestinations(); }) .then(function() { // update any existing entry in validation results table to reflect // potential edits to the name var editedId = destination[property[0]]["id"]; validationLog.updateValidationInfo(editedId, _.escape(destinationName)); if (shouldValidate) { // pass all=true for options so save message not overwritten by validate message $scope.validateDestination(destination[property[0]]["id"], _.escape(destinationName), { all: true }); } }) .catch(function(error) { alertService.add({ type: "danger", closeable: true, message: error, id: "edit-destination-error" }); }) .finally(function() { $scope.destinationState.savingDestination = false; }); } }; /** * Saves and validates destination, saveDestination resolves * into destination id * * @scope * @method saveAndValiidateDestination * @param {<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType>} destination - object representing destination config */ $scope.saveAndValidateDestination = function(destination) { var shouldValidate = true; return $scope.saveDestination(destination, shouldValidate); }; /** * Cancels current destination configuration by clearing alerts * and hiding destination view * * @scope * @method cancelDestination */ $scope.cancelDestination = function(skipScrolling) { $scope.clearAlerts(); $scope.destinationState.destinationMode = false; $scope.destinationState.editMode = false; $scope.destinationState.newMode = false; $scope.destinationState.googleCredentialsGenerated = false; if (!skipScrolling) { document.getElementById("additional_destinations_label").scrollIntoView(); } }; /** * Delete specific destination and then call getDestinations * to display current list * * @scope * @method deleteDestination */ $scope.deleteDestination = function(id) { $scope.deleting = true; $scope.showDeleteConfirmation = false; $scope.displayAlertRows = []; $scope.displayAlertRows.push(id); backupConfigurationServices.deleteDestination(id) .then(function(success) { $scope.deleting = false; // delete existing entry from validation results if one exists validationLog.remove(id); $scope.currentlyValidating = validationLog.getLogEntries(); alertService.add({ type: "success", autoClose: 5000, id: "delete-destination-success", message: LOCALE.maketext("The system successfully deleted the “[_1]” destination.", _.escape($scope.destinationName)) }); $scope.getDestinations(); }, function(error) { $scope.deleting = false; $scope.updating = false; alertService.add({ type: "danger", id: "delete-destination-failed", closeable: true, message: error }); }); }; /** * Toggle status of destination then call getDestinations to show current status * * @scope * @method toggleStatus * @param {<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType>} destination - object representing destination config */ $scope.toggleStatus = function(destination) { $scope.toggled = false; $scope.updating = true; $scope.displayAlertRows = []; $scope.displayAlertRows.push(destination.id); var disable, message; if (!destination.disabled) { message = LOCALE.maketext("You disabled the destination “[_1]”.", _.escape(destination.name)); disable = true; } else if (destination.disabled) { message = LOCALE.maketext("You enabled the destination “[_1]”.", _.escape(destination.name)); disable = false; } return backupConfigurationServices.toggleStatus(destination.id, disable) .then(function(success) { alertService.add({ type: "success", autoClose: 5000, id: "toggle-destination-success", message: message }); destination.disabled = !destination.disabled; }, function(error) { alertService.add({ type: "danger", closeable: true, id: "toggle-destination-failed", message: error }); }) .finally(function() { // toggled set to true so that "in process" message // disappears, error message still visible $scope.toggled = true; $scope.updating = false; }); }; /** * Validate all current destinations via API * * @scope * @method validateAllDestinations */ $scope.validateAllDestinations = function() { $scope.currentlyValidating = []; $scope.destinationState.validatingAllDestinations = true; var promises = []; angular.forEach($scope.destinationState.destinationList, function(destination) { promises.push($scope.validateDestination(destination.id, _.escape(destination.name), { all: true })); }); return $q.all(promises).finally(function() { $scope.destinationState.validatingAllDestinations = false; $scope.destinationState.validatingDestination = $scope.isValidationRunning(); }); }; /** * Checks whether the validation process for a particular * destination succeeded. * * @scope * @method validateAllSuccessFor * @param {String} id - unique identification string */ $scope.validateAllSuccessFor = function(id) { return validationLog.validateAllSuccessFor(id); }; /** * Checks whether the validation process for a particular * destination failed. * * @scope * @method validateAllFailureFor * @param {String} id - unique identification string */ $scope.validateAllFailureFor = function(id) { return validationLog.validateAllFailureFor(id); }; /** * Displays alert message for validation result * * @scope * @method showValidationMessageFor * @param {String} id - unique identification string */ $scope.showValidationMessageFor = function(id) { validationLog.showValidationMessageFor(id); }; /** * Generates Google user credentials * * @scope * @method generateCredentials * @param {String} clientId - unique identifying string of user from Google Drive API * @param {String} clientSecret - unique secret string from Google Drive API */ $scope.generateCredentials = function(clientId, clientSecret) { $scope.destinationState.generatingCredentials = true; return backupConfigurationServices.generateGoogleCredentials(clientId, clientSecret) .then(function(response) { $scope.destinationState.generatingCredentials = false; alertService.add({ type: "info", closeable: true, replace: false, message: LOCALE.maketext("A new window will appear that will allow you to generate Google® credentials."), id: "check-google-credentials-popup-" + clientId.substring(0, 6) }); $timeout(function() { $window.open(response.uri, "generate_google_credentials"); }, 2000); }, function(error) { $scope.destinationState.generatingCredentials = false; alertService.add({ type: "danger", closeable: true, message: error, replace: false, id: "generate-google-credentials-failed-" + clientId.substring(0, 6) }); }); }; /** * Checks is Google user credentials are generated * * @scope * @method checkCredentials * @param {String} clientId - unique identifying string of user from Google Drive API * @param {String} clientSecret - unique secret string from Google Drive API * @param {Boolean} checkOnSave - if the credentials should be checked from a save event * @returns {Boolean} - returns false if credentials do not exist to alert user on save event */ $scope.checkCredentials = function(clientId, clientSecret, checkOnSave) { return backupConfigurationServices.checkForGoogleCredentials(clientId) .then(function(exists) { if (exists) { $scope.destinationState.googleCredentialsGenerated = true; } else if (checkOnSave && !exists) { $scope.destinationState.googleCredentialsGenerated = false; alertService.add({ type: "warning", closeable: true, replace: false, message: LOCALE.maketext("No [asis,Google Drive™] credentials have been generated for client id, “[_1]” ….", _.escape(clientId.substring(0, 5))) + LOCALE.maketext("You must generate new credentials to access destinations that require this client [asis,ID]."), id: "no-google-credentials-generated-warning-" + clientId.substring(0, 6) }); } else if (!checkOnSave) { $scope.generateCredentials(clientId, clientSecret); } }, function(error) { $scope.destinationState.googleCredentialsGenerated = false; alertService.add({ type: "danger", closeable: true, message: error, group: "failed-during-check-google-credentials-error" }); }); }; /** * Toggles between showing and hiding key generation form * * @scope * @method toggleKeyGenerationForm */ $scope.toggleKeyGenerationForm = function() { $scope.destinationState.showKeyGenerationForm = !$scope.destinationState.showKeyGenerationForm; }; /** * Creates new SSH key for SFTP transport * * @scope * @method generateKey * @param {SSHKeyConfigType} keyConfig - object representing key configuration */ $scope.generateKey = function(keyConfig) { $scope.destinationState.generatingKey = true; var username; if ($scope.destinationState.destination.sftp) { username = $scope.destinationState.destination.sftp.username; } else if ($scope.destinationState.destination.rsync) { username = $scope.destinationState.destination.rsync.username; } backupConfigurationServices.generateSSHKeyPair(keyConfig, username) .then(function() { $scope.destinationState.generatingKey = false; alertService.add({ type: "success", autoClose: 5000, id: "ssh-key-generation-succeeded", message: LOCALE.maketext("The system generated the key successfully.") }); if ($scope.destinationState.destination.sftp) { $scope.destinationState.destination.sftp.privatekey = $scope.setPrivateKey(keyConfig.name); } else if ($scope.destinationState.destination.rsync) { $scope.destinationState.destination.rsync.privatekey = $scope.setPrivateKey(keyConfig.name); } $scope.toggleKeyGenerationForm(); $scope.getSSHKeyList(); }, function(error) { $scope.destinationState.generatingKey = false; alertService.add({ type: "danger", closeable: true, message: error, id: "ssh-key-generation-failed" }); }); }; /** * Gets list of all private SSH keys for root user * * @scope * @method getSSHKeyList */ $scope.getSSHKeyList = function() { $scope.destinationState.sshKeyListLoaded = false; backupConfigurationServices.listSSHKeys() .then(function(response) { $scope.destinationState.sshKeyListLoaded = true; $scope.destinationState.sshKeyList = response; }, function(error) { $scope.destinationState.sshKeyListLoaded = true; alertService.add({ type: "danger", message: error, closeable: true, id: "ssh-keys-fetch-failed" }); }); }; /** * Sets private key path when key is chosen from list of keys that currently exist * * @scope * @method setPrivateKey * @param {String} key - name of private key file */ $scope.setPrivateKey = function(key) { var keyName = "/root/.ssh/" + key; if ($scope.destinationState.destination.sftp) { $scope.destinationState.destination.sftp.privatekey = keyName; } else if ($scope.destinationState.destination.rsync) { $scope.destinationState.destination.rsync.privatekey = keyName; } var privateKeyField = $window.document.getElementById("private_key"); if (typeof privateKeyField !== "undefined") { privateKeyField.select(); privateKeyField.focus(); } return keyName; }; /** * Locks key size at 1024 if algorithm chosen is DSA * * @scope * @method toggleKeyType * @param {String} algorithm - string indicating what algorithm to use for key generation */ $scope.toggleKeyType = function(algorithm) { if (algorithm === "DSA") { this.newSSHKey.bits = "1024"; this.destinationState.keyBitsSet = true; if (!this.newSSHKey.name || this.newSSHKey.name === "") { this.newSSHKey.name = "id_dsa"; } } else if (algorithm === "RSA") { this.newSSHKey.bits = "4096"; this.destinationState.keyBitsSet = false; if (!this.newSSHKey.name || this.newSSHKey.name === "") { this.newSSHKey.name = "id_rsa"; } } }; /** * Clears alerts from all groups * * @scope * @method clearAlerts */ $scope.clearAlerts = function() { alertService.clear(); }; /** * Toggle SSL activation in WebDAV * * @scope * @method toggleSSLWebDAV */ $scope.toggleSSLWebDAV = function() { $scope.destinationState.destination.webdav.ssl = !$scope.destinationState.destination.webdav.ssl; }; /** * Checks to make sure remote host does not loop back * * @scope * @method checkForLoopBack * @param {String} host - name of remote host */ $scope.checkForLoopBack = function(host) { if (host === $scope.remoteHostLoopbackValue) { $scope.destinationState.isLoopback = true; } else { $scope.destinationState.isLoopback = false; } }; /** * Checks backup directory path for invalid characters * * @scope * @method checkForDisallowedChars * @param {String} path - path to backup directory * @param {String} chars -string indicating disallowed characters */ $scope.checkForDisallowedChars = function(path, chars) { // test will always start at beginning of string chars.lastIndex = 1; var result = chars.test(path); $scope.destinationState.isDisallowedChar = result; }; /** * Prevent typing of decimal points (periods) in field * * @scope * @method noDecimalPoints * @param {keyEvent} key event associated with key down */ $scope.noDecimalPoints = function(keyEvent) { // keyEvent is jQuery wrapper for KeyboardEvent // better to look at properties in wrapped event var actualEvent = keyEvent.originalEvent; // future proofing: "key" is better property to use // but is not completely supported if ((actualEvent.hasOwnProperty("key") && actualEvent.key === ".") || (actualEvent.keyCode === 190)) { keyEvent.preventDefault(); } }; /** * Prevent pasting of non-numbers in field * * @scope * @method onlyNumbers * @param {clipboardEvent} clipboard event associated with paste */ $scope.onlyNumbers = function(pasteEvent) { var pastedData = pasteEvent.originalEvent.clipboardData.getData("text"); if (!pastedData.match(/[0-9]+/)) { pasteEvent.preventDefault(); } }; /** * Initialize page with default values * * @scope * @method init */ $scope.init = function() { $scope.absolutePathRegEx = /^\/./; $scope.relativePathRegEx = /^\w./; $scope.remoteHostValidation = /^[a-z0-9.-]{1,}$/i; $scope.remoteHostLoopbackValue = /^(127(\.\d+){1,3}|[0:]+1|localhost)$/i; $scope.disallowedPathChars = /[\\?%*:|"<>]/g; $scope.validating = false; $scope.toggled = true; $scope.saving = false; $scope.deleting = false; $scope.updating = false; $scope.showDeleteConfirmation = false; $scope.destinationName = ""; $scope.destinationId = ""; $scope.activeTab = 1; $scope.currentlyValidating = validationLog.getLogEntries(); $scope.destinationState = { destinationSelected: "Custom", destinationMode: false, savingDestination: false, validatingDestination: $scope.isValidationRunning(), fetchingDestination: false, destinationListLoaded: false, validatingAllDestinations: false, destinationList: [], newMode: false, editMode: false, generatingCredentials: false, googleCredentialsGenerated: false, showKeyGenerationForm: false, generatingKey: false, keyBitsSet: false, sshKeyListLoaded: false, isLoopback: false, isDisallowedChar: false, checkCredentialsOnSave: false, showValidationIconHint: false }; if (validationLog.hasLogEntries()) { $scope.destinationState.showValidationIconHint = true; } $scope.meta = {}; $scope.displayAlertRows = []; $scope.getDestinations(); }; $scope.init(); } ] ); return controller; } );
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Generation time: 0.05 |
proxy
|
phpinfo
|
Settings