Viewing File: /usr/local/cpanel/whostmgr/docroot/templates/backup_configuration/index.cmb.js
/*
* backup_configuration/services/backupConfigurationServices.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, PAGE */
define(
'app/services/backupConfigurationServices',[
"angular",
"lodash",
"cjt/util/parse",
"cjt/io/whm-v1-request",
"cjt/io/whm-v1", // IMPORTANT: Load the driver so it's ready
"cjt/services/APIService",
],
function(
angular,
_,
PARSE,
APIREQUEST) {
"use strict";
var NO_MODULE = "";
var app = angular.module("whm.backupConfiguration.backupConfigurationServices.service", []);
app.value("PAGE", PAGE);
/**
* @constant {Array} BOOLEAN_PROPERTIES
* @description Perl boolean properties from backup configuration to be parsed into JavaScript boolean
**/
var BOOLEAN_PROPERTIES = [
"backup_daily_enable",
"backup_monthly_enable",
"backup_weekly_enable",
"backupaccts",
"backupbwdata",
"backupenable",
"backupfiles",
"backuplogs",
"backupmount",
"backupsuspendedaccts",
"check_min_free_space",
"force_prune_daily",
"force_prune_monthly",
"force_prune_weekly",
"keeplocal",
"linkdest",
"localzonesonly",
"postbackup",
"psqlbackup",
];
/**
* @constant {Array} NUMBER_PROPERTIES
* @description properties to be parsed into numbers
*/
var NUMBER_PROPERTIES = [
"backup_daily_retention",
"backup_monthly_retention",
"backup_weekly_day",
"backup_weekly_retention",
"errorthreshhold",
"maximum_restore_timeout",
"maximum_timeout",
"min_free_space",
];
/**
* @constant {Array} NUM_STRING_PROPERTIES
* @description properties where the value is a string of numbers that will be parsed into JavaScript object
*/
var NUM_STRING_PROPERTIES = [
"backup_monthly_dates",
"backupdays",
];
app.factory("backupConfigurationServices", [
"$q",
"APIService",
"PAGE",
function($q, APIService, PAGE ) {
var configService = this;
configService.settings = null;
configService.destinations = [];
/**
* @typedef BackupConfiguration
* @property {String | Boolean} [backup_daily_enable = 1] - whether to enable daily backups
* @property {String | Boolean} [postbackup = 0] - whether to run `postcpbackup` script after backup finishes
* @property {String | Boolean} [backupenable = 0] - whether to enable backups
* @property {String | Boolean} [backup_monthly_enable = 0] - whether to enable monthly backups
* @property {String} [backuptype = compressed] - type of backup to create
* @property {Number} [backup_daily_retention = 5] - number of daily backups to retain
* @property {String} [backupdays = 0,2,4,6] - which days of the week to run daily backups
* @property {String} [backup_monthly_dates = 1] - which days of the month to run backups
* @property {Number} [backup_weekly_day = 1] - which day of the week to run weekly backups
* @property {String | Boolean} [backup_weekly_enable = 0] - whether to enable weekly backups
* @property {Number} [backup_weekly_retention = 1] - the number of weekly backups to retain
* @property {Number} [maximum_restore_timeout = 21600] - how long a restoration will attempt to run, in seconds
* @property {Number} [maximum_timeout = 7200] - how long a backup will atempt to run, in seconds
* @property {String | Boolean} [backupfiles = 1] - whether to back up system files
* @property {String | Boolean} [backupaccts = 1] - whether to back up accounts
* @property {String | Boolean} [keeplocal = 1] - whether to delete backups from the local directory
* @property {String | Boolean} [localzonesonly = 0] - whether to use `/var/named/domain.tld` or dnsadmin (1 = file, 0 = dnsadmin)
* @property {String | Boolean} [backupbwdata = 1] - whether to backup bandwidth tracking data
* @property {String | Boolean} [backuplogs = 0] - whether to back up the error logs
* @property {String | Boolean} [backupsuspendedaccts = 0] - whether to back up suspended accounts
* @property {String} [backupdir = /backup] - primary backup directory
* @property {String | Boolean} [backupmount = 0] - whether to mount a backup partition
* @property {String} [mysqlbackup = accounts] - backup method to use for MySQL databases
* @property {Number} [backup_monthly_retention = 1] - number of monthly backups to keep
* @property {String | Boolean} [check_min_free_space = 0] - check whether destination server has minimum space required
* @property {String | Number} [min_free_space = 5] - minimum amount of free space to check for on destination server
* @property {String} [min_free_space_unit = percent] - unit of measure of disk space
* @property {String | Boolean} [force_prune_daily = 0] - whether to strictly enforce daily retention
* @property {String | Boolean} [force_prune_weekly = 0] - whether to strictly enforce weekly retention
* @property {String | Boolean} [force_prune_monthly = 0] - whether to strictly enforce monthly retention
*/
/**
* @typedef TransportType
* @description common properties for all transport types
* @property {String} name - backup transport's name
* @property {String} type - backup transport type
* @property {String | Boolean} disabled = 0 - whether to disable backup transport (1 = disabled, 0 = enabled)
* @property {String} upload_system_backup = off - whether to upload system backups
* @property {String | Boolean} only_used_for_logs = 0 - whether to use this destination for only logs (0 = used for all backups, 1 = only logs are backed up here)
*/
/**
* @typedef CustomTransportType
* @augments TransportType
* @property {String} script - valid absolute path to transport solution script
* @property {String} host - valid remote server hostname
* @property {String} path - valid file path to backup directoy on remote server
* @property {String | Number} [timeout = 30] - session timeout
* @property {String} username - remote server account's username
* @property {String} åpassword - remote server account's password
*/
/**
* @typedef FTPTransportType
* @augments TransportType
* @property {String} host - a remote server's hostname
* @property {Number} [port = 21] - remote server's FTP port
* @property {String} path - path to backups directory on remote server
* @property {String | Boolean} [passive = 1] - whether to use passive FTP
* @property {String | Number} [timeout = 30] - session timeout
* @property {String} username - remote server account's username
* @property {String} password - remote server account's password
*/
/**
* @typedef GoogleTransportType
* @augments TransportType
* @property {String} client_id - unique user is provided by Google API
* @property {String} cliend_secret - unique secret provided by Google API
* @property {String} folder - backups folder in Google Drive
* @property {String | Number} [timeout = 30] - session timeout
*/
/**
* @typedef LocalTransportType
* @augments TransportType
* @property {String | Boolean} [mount = 0] - whether the path is mounted
* @property {String} path - valid path to backups directory
*/
/**
* @typedef SFTPTransportType
* @augments TransportType
* @property {String} host - emote server's hostname
* @property {Number} [port = 22] - remote server's SFTP port
* @property {String} path - path to backup directory on remote server
* @property {String | Number} [timeout = 30] - session timeout
* @property {String} username - remote server account's username
* @property {String} authtype - authorization type
* @property {String} password - remote server account's password (if authtype is password)
* @property {String} privatekey - path to private key file (if authtype is key)
* @property {String} passphrase - private key file's passphrase (if authtype is key)
*/
/**
* @typedef AmazonS3TransportType
* @augments TransportType
* @property {String} [folder] - valid file path, relative to the root directory
* @property {String} bucket - AmazonS3 bucket
* @property {String} aws_access_key_id - AmazonS3 acces key id
* @property {String | Number} [timeout = 30] - session timeout
* @property {String} password - AmazonS3 access key password
*/
/**
* @typedef S3CompatibleTransportType
* @augments TransportType
* @property {String} [folder] - valid file path, relative to the root directory
* @property {String} bucket - AmazonS3 bucket
* @property {String} aws_access_key_id - AmazonS3 acces key id
* @property {String | Number} [timeout = 30] - session timeout
* @property {String} password - AmazonS3 access key password
* @property {String} host - remote server's hostname
*/
/**
* @typedef RsyncTransportType
* @augments TransportType
* @property {String} host - emote server's hostname
* @property {Number} [port = 22] - remote server's SFTP port
* @property {String} path - path to backup directory on remote server
* @property {String} username - remote server account's username
* @property {String} authtype - authorization type
* @property {String} password - remote server account's password (if authtype is password)
* @property {String} privatekey - path to private key file (if authtype is key)
* @property {String} passphrase - private key file's passphrase (if authtype is key)
*/
/**
* @typedef WebDAVTransportType
* @augments TransportType
* @property {String} host - remote server's host name
* @property {Number} [port = 21] - remote server's FTP port
* @property {String} path - the path to the backups directory on remote server
* @property {String | Boolean} [ssl = 1] - whether to use SSL
* @property {String | Number} [timeout = 30] - session timeout
* @property {String} username - remote server account's username
* @property {String} password - remote server account's password
*/
/**
* @typedef BackblazeB2TransportType
* @augments TransportType
* @property {String} path - the path to the backups directory on remote server
* @property {String | Number} [timeout = 30] - session timeout
* @property {String} bucket_id - unique identifier for named bucket
* @property {String} bucket_name - unique name for bucket on Backblaze server
* @property {String} application_key_id - unique identifier for Backblaze application
* @property {String} application_key - secret key for Backblaze application
*/
/**
* @typedef SSHKeyConfigType
* @property {String} [user = root] - key's owner
* @property {String} [passphrase] - key's passphrase
* @property {String} [name = id_rsa | id_dsa] - key's file name, default depends on algorithm chosen
* @property {Number} [bits = 4096 | 1024] - key's bits, default depends on algorithm chosen (RSA = 4096, DSA = 1024)
* @property {String} [algorithm = system default] - key's encryption algorithm, defaults to system default
* @property {String | Boolean} abort_on_existing_key = 1 - whether to abort the function if user already has key, always set to 1 (true)
* @property {String} [comment] - a comment
*/
/**
* Parses raw response for consumption by front end
*
* @private
* @method parseConfigData
* @param {BackupConfiguration} response - raw response from API
* @returns {BackupConfiguration} parsed data for consumption by front end
*/
function parseConfigData(response) {
var data = response.data.backup_config;
for (var i = 0, len = BOOLEAN_PROPERTIES.length; i < len; i++) {
var boolProperty = BOOLEAN_PROPERTIES[i];
data[boolProperty] = PARSE.parsePerlBoolean(data[boolProperty]);
}
for (var j = 0, len = NUMBER_PROPERTIES.length; j < len; j++) { // eslint-disable-line no-redeclare
var numProperty = NUMBER_PROPERTIES[j];
data[numProperty] = parseInt(data[numProperty], 10);
}
for (var k = 0, len = NUM_STRING_PROPERTIES.length; k < len; k++) { // eslint-disable-line no-redeclare
var property = NUM_STRING_PROPERTIES[k];
var numbers = data[property].split(",");
data[property] = {};
numbers.forEach(function(number) {
data[property][number] = number;
});
}
configService.settings = data;
return configService.settings;
}
/**
* Parses raw transports response for consumption by front end
*
* @private
* @method parseDestinations
* @param {Array.<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType | BackblazeB2TransportType>} response - array of raw transport objects
* @property {String | Boolean} destination.disabled - Perl boolean, which is a string, to be parsed into JavaScript boolean (enabled = true, disabled = false)
* @property {String | Boolean} destination.upload_system_backup - String to be parsed into JavaScript Boolean
* @property {String | Boolean} destination.only_used_for_logs - String to be parsed into JavaScript Boolean
* @returns {Array.<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType>} array of parsed transport objects for consumption by front end
*/
function parseDestinations(response) {
var destinations = response.data;
var parsedDestinations = [];
destinations.forEach(function(destination) {
destination.disabled = PARSE.parsePerlBoolean(destination.disabled);
destination.upload_system_backup = PARSE.parsePerlBoolean(destination.upload_system_backup);
destination.only_used_for_logs = PARSE.parsePerlBoolean(destination.only_used_for_logs);
parsedDestinations.push(destination);
});
configService.destinations = parsedDestinations;
return configService.destinations;
}
/**
* Parses raw transport response for consumption by front end
*
* @private
* @method parseDestination
* @param {<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType | BackblazeB2TransportType>} response - raw destination response object
* @returns {<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType>} array of parsed transport objects for consumption by front end
*/
function parseDestination(response) {
var destination = response.data;
var type = destination.type.toLowerCase();
var parsedDestination = {};
parsedDestination[type] = {};
destination.disabled = PARSE.parsePerlBoolean(destination.disabled);
destination.upload_system_backup = PARSE.parsePerlBoolean(destination.upload_system_backup);
destination.only_used_for_logs = PARSE.parsePerlBoolean(destination.only_used_for_logs);
if (destination.timeout) {
destination.timeout = parseInt(destination.timeout, 10);
}
if (destination.passive) {
destination.passive = PARSE.parsePerlBoolean(destination.passive);
}
if (destination.port) {
destination.port = parseInt(destination.port, 10);
}
if (destination.mount) {
destination.mount = PARSE.parsePerlBoolean(destination.mount);
}
if (destination.ssl) {
destination.ssl = PARSE.parsePerlBoolean(destination.ssl);
}
for (var prop in destination) {
if (destination.hasOwnProperty(prop)) {
parsedDestination[type][prop] = destination[prop];
}
}
return parsedDestination;
}
/**
* Transforms data structure of transport
*
* @private
* @method serializeDestinations
* @param {CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType | BackblazeB2TransportType} data parsed data object
* @returns {CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType | BackblazeB2TransportType} serialized object for API consumption
*/
function serializeDestination(data) {
var serializedDestination;
for (var prop in data) {
if (data.hasOwnProperty(prop)) {
serializedDestination = data[prop];
}
}
return serializedDestination;
}
/**
* Transforms objects into strings
*
* @private
* @method serializeProperty
* @param {Object} parsed configuration property that must be formatted as string
* @returns {String} serialized property for consumption by API
*/
function serializeProperty(data) {
var propertyString = "";
for (var prop in data) {
if (data.hasOwnProperty(prop)) {
propertyString += data[prop] + ",";
}
}
return propertyString.substring(0, propertyString.length - 1);
}
/**
* Add parameters to API request for adding or updating an
* additional destination.
*
* @private
* @method buildDestinationAPICall
* @param {Object} encapsulates destination info and APIRequest instance
*/
function buildDestinationAPICall(requestInfo) {
var serializedDestination = requestInfo.destination;
var apiRequest = requestInfo.apiCall;
apiRequest.addArgument("id", serializedDestination.id);
apiRequest.addArgument("name", serializedDestination.name);
apiRequest.addArgument("type", serializedDestination.type);
apiRequest.addArgument("disabled", serializedDestination.disabled ? 1 : 0);
apiRequest.addArgument("upload_system_backup", serializedDestination.upload_system_backup ? 1 : 0);
apiRequest.addArgument("only_used_for_logs", serializedDestination.only_used_for_logs ? 1 : 0);
if (serializedDestination.destination !== "Local" && serializedDestination.timeout) {
apiRequest.addArgument("timeout", serializedDestination.timeout);
}
if ((serializedDestination.type === "FTP" ||
serializedDestination.type === "SFTP" ||
serializedDestination.type === "Rsync" ||
serializedDestination.type === "WebDAV") &&
serializedDestination.port) {
apiRequest.addArgument("port", serializedDestination.port);
}
if (serializedDestination.type === "Custom") {
apiRequest.addArgument("script", serializedDestination.script);
apiRequest.addArgument("host", serializedDestination.host);
apiRequest.addArgument("path", serializedDestination.path);
apiRequest.addArgument("username", serializedDestination.username);
apiRequest.addArgument("password", serializedDestination.password);
}
if (serializedDestination.type === "FTP") {
apiRequest.addArgument("host", serializedDestination.host);
apiRequest.addArgument("path", serializedDestination.path);
apiRequest.addArgument("passive", serializedDestination.passive ? 1 : 0);
apiRequest.addArgument("username", serializedDestination.username);
apiRequest.addArgument("password", serializedDestination.password);
}
if (serializedDestination.type === "GoogleDrive") {
apiRequest.addArgument("client_id", serializedDestination.client_id);
apiRequest.addArgument("client_secret", serializedDestination.client_secret);
apiRequest.addArgument("folder", serializedDestination.folder);
}
if (serializedDestination.type === "Local") {
apiRequest.addArgument("mount", serializedDestination.mount ? 1 : 0);
apiRequest.addArgument("path", serializedDestination.path);
apiRequest.addArgument("no_mount_fail", serializedDestination.no_mount_fail);
}
if (serializedDestination.type === "AmazonS3" || serializedDestination.type === "S3Compatible") {
apiRequest.addArgument("folder", serializedDestination.folder);
apiRequest.addArgument("bucket", serializedDestination.bucket);
apiRequest.addArgument("aws_access_key_id", serializedDestination.aws_access_key_id);
apiRequest.addArgument("password", serializedDestination.password);
if (serializedDestination.type === "S3Compatible") {
apiRequest.addArgument("host", serializedDestination.host);
}
}
if (serializedDestination.type === "SFTP") {
apiRequest.addArgument("host", serializedDestination.host);
apiRequest.addArgument("path", serializedDestination.path);
apiRequest.addArgument("username", serializedDestination.username);
apiRequest.addArgument("authtype", serializedDestination.authtype);
apiRequest.addArgument("password", serializedDestination.password);
apiRequest.addArgument("privatekey", serializedDestination.privatekey);
apiRequest.addArgument("passphrase", serializedDestination.passphrase);
}
if (serializedDestination.type === "Rsync") {
apiRequest.addArgument("host", serializedDestination.host);
apiRequest.addArgument("path", serializedDestination.path);
apiRequest.addArgument("username", serializedDestination.username);
apiRequest.addArgument("authtype", serializedDestination.authtype);
apiRequest.addArgument("password", serializedDestination.password);
apiRequest.addArgument("privatekey", serializedDestination.privatekey);
apiRequest.addArgument("passphrase", serializedDestination.passphrase);
}
if (serializedDestination.type === "WebDAV") {
apiRequest.addArgument("host", serializedDestination.host);
apiRequest.addArgument("path", serializedDestination.path);
apiRequest.addArgument("ssl", serializedDestination.ssl ? 1 : 0);
apiRequest.addArgument("username", serializedDestination.username);
apiRequest.addArgument("password", serializedDestination.password);
}
if (serializedDestination.type === "Backblaze") {
apiRequest.addArgument("application_key", serializedDestination.application_key);
apiRequest.addArgument("application_key_id", serializedDestination.application_key_id);
apiRequest.addArgument("bucket_id", serializedDestination.bucket_id);
apiRequest.addArgument("bucket_name", serializedDestination.bucket_name);
apiRequest.addArgument("path", serializedDestination.path);
}
return apiRequest;
}
var BackupConfigurationServices = function() {};
BackupConfigurationServices.prototype = new APIService();
angular.extend(BackupConfigurationServices.prototype, {
/**
* Access point for testing private utility functions.
*
* @private
* @method _testHarness
* @param {String} name of utility
* @param {Object} data to be massaged or checked
**/
_testHarness: function(utility, data) {
switch (utility) {
case "parseDestination":
return parseDestination(data);
case "parseDestinations":
return parseDestinations(data);
case "serializeProperty":
return serializeProperty(data);
case "parseConfigData":
return parseConfigData(data);
case "serializeDestination":
return serializeDestination(data);
case "buildDestinationAPICall":
return buildDestinationAPICall(data);
default:
return null;
}
},
/**
* Calls WHM API to request current backup configuration
*
* @async
* @method getBackupConfig
* @returns {Promise<BackupConfiguration>} deferred.promise - backup configuration object
* @throws {Promise<String>} error message on failure
*/
getBackupConfig: function() {
if (configService.settings) {
return $q.resolve(configService.settings);
} else {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_config_get");
var deferred = this.deferred(apiRequest, {
transformAPISuccess: parseConfigData,
});
return deferred.promise;
}
},
/**
* Calls WHM API to set new backup config
*
* @async
* @method setBackupConfig
* @param {BackupConfiguration} config - An object that has the configuration settings as properties
* @param {Boolean} config.backup_daily_enable - daily backups enabled, converted to Perl boolean
* @param {Boolean} config.backupenable - backups enabled, converted to Perl boolean
* @param {Boolean} config.backup_monthly_enable - monthly backups enabled, converted to Perl boolean
* @param {Boolean} config.backupfiles - backup system files, converted to Perl boolean
* @param {Boolean} config.backupaccts - backup accounts, converted to Perl boolean
* @param {Boolean} config.keeplocal - whether to delete backups from loca directory, converted to Perl boolean
* @param {Boolean} config.localzonesonly - whether to use `/var/named/domain.tld` or dnsadmin, converted to Perl boolean (1 = file, 0 = dnsadmin)
* @param {Boolean} config.backupbwdata - backup bandwidth tracking data, converted to Perl boolean
* @param {Boolean} config.backuplogs - backup error logs, converted to Perl boolean
* @param {Boolean} config.backupsuspendedaccts - backup suspended accounts, converted to Perl boolean
* @param {Boolean} config.backupmount - mount a backup partition, converted to Perl boolean
* @param {Boolean} config.check_min_free_space - check server to ensure minimum space is available, converted to Perl boolean
* @param {Boolean} config.force_prune_daily - prune retained daily backups, converted to Perl boolean
* @param {Boolean} config.force_prune_Weekly - prune retained weekly backups, converted to Perl boolean
* @param {Boolean} config.force_prune_monthly - prune retained monthly backups, converted to Perl boolean
* @param {Boolean} config.backup_weekly_enable - weekly backups enabled, converted to Perl boolean
* @param {Object} config.backupdays - An object to be converted into a string for API consumption
* @param {Object} config.backup_monthly_dates - An object to be converted into a string for API consumption
* @returns {Promise<String>} success message
* @throws {Promise<String>} error message on failure
*/
setBackupConfig: function(config) {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_config_set");
apiRequest.addArgument("backup_daily_enable", config.backup_daily_enable ? 1 : 0);
apiRequest.addArgument("backupenable", config.backupenable ? 1 : 0);
apiRequest.addArgument("backup_monthly_enable", config.backup_monthly_enable ? 1 : 0);
apiRequest.addArgument("backuptype", config.backuptype);
apiRequest.addArgument("backup_daily_retention", config.backup_daily_retention);
if (config.backup_daily_enable) {
apiRequest.addArgument("backupdays", serializeProperty(config.backupdays));
}
apiRequest.addArgument("backupfiles", config.backupfiles ? 1 : 0);
apiRequest.addArgument("backupaccts", config.backupaccts ? 1 : 0);
apiRequest.addArgument("keeplocal", config.keeplocal ? 1 : 0);
apiRequest.addArgument("localzonesonly", config.localzonesonly ? 1 : 0);
apiRequest.addArgument("backupbwdata", config.backupbwdata ? 1 : 0);
apiRequest.addArgument("backuplogs", config.backuplogs ? 1 : 0);
apiRequest.addArgument("backupsuspendedaccts", config.backupsuspendedaccts ? 1 : 0);
apiRequest.addArgument("backupdir", config.backupdir);
apiRequest.addArgument("remote_restore_staging_dir", config.remote_restore_staging_dir);
apiRequest.addArgument("backupmount", config.backupmount ? 1 : 0);
apiRequest.addArgument("mysqlbackup", config.mysqlbackup);
apiRequest.addArgument("backup_monthly_retention", config.backup_monthly_retention);
apiRequest.addArgument("check_min_free_space", config.check_min_free_space ? 1 : 0);
apiRequest.addArgument("min_free_space", config.min_free_space);
apiRequest.addArgument("min_free_space_unit", config.min_free_space_unit);
apiRequest.addArgument("force_prune_daily", config.force_prune_daily ? 1 : 0);
apiRequest.addArgument("force_prune_weekly", config.force_prune_weekly ? 1 : 0);
apiRequest.addArgument("force_prune_monthly", config.force_prune_monthly ? 1 : 0);
apiRequest.addArgument("maximum_timeout", config.maximum_timeout);
apiRequest.addArgument("maximum_restore_timeout", config.maximum_restore_timeout);
apiRequest.addArgument("backup_weekly_enable", config.backup_weekly_enable ? 1 : 0);
apiRequest.addArgument("backup_weekly_day", config.backup_weekly_day);
apiRequest.addArgument("backup_weekly_retention", config.backup_weekly_retention);
if (config.backup_monthly_dates) {
apiRequest.addArgument("backup_monthly_dates", serializeProperty(config.backup_monthly_dates));
}
var deferred = this.deferred(apiRequest);
return deferred.promise;
},
/**
* Calls WHM API to request current list of backup destinations
*
* @async
* @method getDestinationList
* @returns {Promise<Array.<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType | BackblazeB2TransportType>>} - array of parsed transport object
* @throws {Promise<String>} error message on failure
*/
getDestinationList: function() {
if (configService.destinations.length > 0) {
return $q.resolve(configService.destinations);
} else {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_destination_list");
var deferred = this.deferred(apiRequest, {
transformAPISuccess: parseDestinations,
});
return deferred.promise;
}
},
/**
* Calls WHM API to validate current backup destination, sets destination to always be disabled if validation fails
*
* @async
* @method validateDestination
* @param {String} id - unique id for destination
* @returns {Promise<String>} success message
* @throws {Promise<String>} error message on failure
*/
validateDestination: function(id) {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_destination_validate");
apiRequest.addArgument("id", id);
apiRequest.addArgument("disableonfail", 1);
var deferred = this.deferred(apiRequest);
return deferred.promise;
},
/**
* Calls WHM API to delete backup destination
*
* @async
* @method deleteDestination
* @param {String} id - unique id for destination
* @returns {Promise<String>} success message
* @throws {Promise<String>} error message on failure
*/
deleteDestination: function(id) {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_destination_delete");
apiRequest.addArgument("id", id);
var deferred = this.deferred(apiRequest, {
transformAPISuccess: function(response) {
// delete from cached version of destination list
if (configService.destinations.length > 0) {
_.remove(configService.destinations, function(cachedDest) {
return cachedDest.id === id;
});
}
return response;
},
});
return deferred.promise;
},
/**
* Calls WHM API to toggle destination enable/disable status
*
* @async
* @method toggleStatus
* @param {String} id - unique id for destination
* @param {Boolean} disabled - whether destination is enabled or disabled
* @returns {Promise<String>} success message
* @throws {Promise<String>} error message on failure
*/
toggleStatus: function(id, disabled) {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_destination_set");
apiRequest.addArgument("id", id);
apiRequest.addArgument("disabled", disabled ? 1 : 0); // convert to Perl boolean
var deferred = this.deferred(apiRequest);
return deferred.promise;
},
/**
* Calls WHM API to return destination configuration
*
* @async
* @method getDestination
* @param {String} id - unique id for destination
* @returns {Promise<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType | BackblazeB2TransportType>}
* @throws {Promise<String>} error message on failure
*/
getDestination: function(id) {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_destination_get");
apiRequest.addArgument("id", id);
var deferred = this.deferred(apiRequest, {
transformAPISuccess: parseDestination,
});
return deferred.promise;
},
/**
* Calls WHM API to update existing destination configuration
*
* @async
* @method updateCurrentDestination
* @param {<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType | BackblazeB2TransportType>} destination - transport object
* @returns {Promise<String>} success message
* @throws {Promise<String>} error message on failure
*/
updateCurrentDestination: function(destination) {
var serializedDestination = serializeDestination(destination);
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_destination_set");
buildDestinationAPICall({ destination: serializedDestination, apiCall: apiRequest });
var deferred = this.deferred(apiRequest, {
transformAPISuccess: function(response) {
// get the destination object from the function parameter
var destinationTypeKey = Object.keys(destination)[0];
var destinationProps = destination[destinationTypeKey];
// find corresponding object in the cache
var cachedObject = _.find(configService.destinations, function(dest) {
return dest.id === destinationProps.id;
});
// update object in cache to reflect changes
if (cachedObject && typeof destinationProps === "object") {
for (var prop in destinationProps) {
if (destinationProps.hasOwnProperty(prop)) {
cachedObject[prop] = destinationProps[prop];
}
}
}
return response.data;
},
});
return deferred.promise;
},
/**
* Calls WHM API to create new destination configuration
*
* @async
* @method setNewDestination
* @param {<CustomTransportType | FTPTransportType | GoogleTransportType | LocalTransportType | SFTPTransportType | AmazonS3TransportType | RsyncTransportType | WebDAVTransportType | S3CompatibleTransportType | BackblazeB2TransportType>} destination - transport object
* @returns {Promise<String>} unique ID for newly created destination
* @throws {Promise<String>} error message on failure
*/
setNewDestination: function(destination) {
var serializedDestination = serializeDestination(destination);
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_destination_add");
buildDestinationAPICall({ destination: serializedDestination, apiCall: apiRequest });
var deferred = this.deferred(apiRequest, {
transformAPISuccess: function(response) {
// add the destination to the cache
var newDestination = _.assign({}, destination[Object.keys(destination)[0]], response.data);
configService.destinations.push(newDestination);
return response.data;
},
});
return deferred.promise;
},
/**
* Generates credentials given a specific client ID and secret
*
* @async
* @method generateGoogleCredentials
* @param {String} clientId - string identifying client, obtained from Google Drive API
* @param {String} clientSecret - unique string, obtained from Google Drive API
* @returns {Promise<String>} URI directing user to OAuth page
* @throws {Promise<String>} error message on failure
*/
generateGoogleCredentials: function(clientId, clientSecret) {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_generate_google_oauth_uri");
apiRequest.addArgument("client_id", clientId);
apiRequest.addArgument("client_secret", clientSecret);
var deferred = this.deferred(apiRequest, {
transformAPISuccess: function(response) {
return response.data;
},
});
return deferred.promise;
},
/**
* Checks if client credentials exists given a specific client ID
*
* @async
* @method checkForGoogleCredentials
* @param {String} clientId - unique identifying string of user
* @returns {Promise<Boolean>} true if credentials exists, false if they do not
* @throws {Promise<String>} error message on failure
*/
checkForGoogleCredentials: function(clientId) {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "backup_does_client_id_have_google_credentials");
apiRequest.addArgument("client_id", clientId);
var deferred = this.deferred(apiRequest, {
transformAPISuccess: function(response) {
if (response.data.exists) {
return true;
} else {
return false;
}
},
});
return deferred.promise;
},
/**
* Creates new SSH key pair
*
* @async
* @method generateSSHKeyPair
* @param {SSHKeyConfigType} keyConfig - object representing SSH key configuration
* @param {String} username - indicates user generating keys, defaults to root
* @returns {Promise<Object>} returns metadata object indicating success
* @throws {Promise<String>} error message on failure
*/
generateSSHKeyPair: function(keyConfig, username) {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "generatesshkeypair");
apiRequest.addArgument("user", username);
apiRequest.addArgument("passphrase", keyConfig.passphrase);
apiRequest.addArgument("name", keyConfig.name);
apiRequest.addArgument("bits", keyConfig.bits);
apiRequest.addArgument("algorithm", keyConfig.algorithm === "RSA" ? "rsa2" : "dsa");
/**
* this parameter always passes as 1, per documentation
* https://confluence0.cpanel.net/display/public/SDK/WHM+API+1+Functions+-+generatesshkeypair
*/
apiRequest.addArgument("abort_on_existing_key", 1);
apiRequest.addArgument("comment", keyConfig.comment ? keyConfig.comment : "");
var deferred = this.deferred(apiRequest);
return deferred.promise;
},
/**
* Lists all private keys for the root user
*
* @async
* @method listSSHKeys
* @returns {Promise<Array.<String>>} array of strings representing all private SSH keys
* @throws {Promise<String>} error message on failure
*/
listSSHKeys: function() {
var apiRequest = new APIREQUEST.Class();
apiRequest.initialize(NO_MODULE, "listsshkeys");
apiRequest.addArgument("user", "root");
apiRequest.addArgument("private", 1);
apiRequest.addArgument("public", 0);
var deferred = this.deferred(apiRequest, {
transformAPISuccess: function(response) {
var names = [];
response.data.forEach(function(key) {
names.push(key.file);
});
return names;
},
});
return deferred.promise;
},
});
return new BackupConfigurationServices();
},
]);
});
/* backup_configuration/services/validationLog.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: false */
define(
'app/services/validationLog',[
// Libraries
"angular",
"lodash",
// CJT
"cjt/core",
"cjt/util/locale",
"cjt/services/alertService",
],
function(angular, _, CJT, LOCALE, alertService) {
"use strict";
var module = angular.module("whm.backupConfiguration.validationLog.service", []);
/**
* Setup validation log service
*/
module.factory("validationLog", ["$window", "alertService", function($window, alertService) {
/** List of validation log items for backup destinations. */
var logEntries = [];
/* Represents basic information used to record the
* current validation state for a remote backup destination.
*
* @param source {Object} generic destination object or ValidationLogItem
*/
function ValidationLogItem(source) {
if (typeof source !== "object") {
return;
}
// if the source object contains the destinationId
// property, it is an existing ValidationLogItem,
// possibly lacking defined methods
if (source.hasOwnProperty("destinationId")) {
this.cloneProperties(source);
} else {
this.name = source.name;
this.destinationId = source.id;
this.transport = source.type;
this.status = "running";
this.updateBeginTime();
}
if (this.status === "running") {
ValidationLogItem.inProgress++;
}
}
/** Static property to allow easy access to log items by destination ID.
*/
ValidationLogItem.quickAccess = {};
/** Static property to maintain count of in progress validations.
* This avoids excessive looping over the list of validations,
* looking for those with a status of "running".
*/
ValidationLogItem.inProgress = 0;
/**
* Update the quick access hash with revised log items (static).
*
* @method updateQuickAccess
* @param newLogItemList {Array} list of log items
*/
ValidationLogItem.updateQuickAccess = function(newLogItemList) {
ValidationLogItem.quickAccess = {};
if (Array.isArray(newLogItemList) && newLogItemList.length > 0) {
newLogItemList.forEach(function(item) {
if (typeof (item) === "object" &&
item.hasOwnProperty("destinationId")) {
ValidationLogItem.quickAccess[item.destinationId] = item;
}
});
}
};
/**
* Retrieve status from quick access hash.
*
* @method getStatusFor
* @param {string} id - destination id for log item of interest
* @return {ValidationLogItem}
*/
ValidationLogItem.getStatusFor = function(id) {
if (ValidationLogItem.quickAccess.hasOwnProperty(id)) {
return ValidationLogItem.quickAccess[id].status;
}
return null;
};
/**
* Given an existing object, copy its properties into this ValidationLogItem.
* This is used to create complete objects from serialized objects stored in
* a JSON data structure. JSON does not support methods.
*
* @method cloneProperties
* @param {Object} noFunctionObject
*/
ValidationLogItem.prototype.cloneProperties = function(noFunctionObject) {
for (var property in noFunctionObject) {
if (noFunctionObject.hasOwnProperty(property)) {
this[property] = noFunctionObject[property];
}
}
};
/**
* Update the begin time stamp for a validation run.
* Also, creates a formatted time for display.
*
* @method updateElapsedTime
*/
ValidationLogItem.prototype.updateBeginTime = function() {
var start = new Date();
this.beginTime = Date.now();
start.setTime(this.beginTime);
this.formattedBeginTime = start.toLocaleTimeString();
};
/**
* Reset the elapsed time validation log item.
*
* @method resetElapsedTime
*/
ValidationLogItem.prototype.resetElapsedTime = function() {
delete this.endTime;
delete this.elapsedTime;
delete this.alert;
delete this.formattedElapsedTime;
this.status = "running";
ValidationLogItem.inProgress++;
};
/**
* Generate the elapsed time for a completed validation run.
* Also, creates a formatted string for display purposes.
*
* @method generateElapsedTime
*/
ValidationLogItem.prototype.generateElapsedTime = function() {
this.endTime = Date.now();
this.elapsedTime = this.endTime - this.beginTime;
this.formattedElapsedTime = LOCALE.maketext("[_1] [numerate,_1,second,seconds]", Math.round(this.elapsedTime / 1000));
};
/**
* Updates a ValidationLogItem to indicate that the in progress
* validation has completed.
*
* @method markAsComplete
*/
ValidationLogItem.prototype.markAsComplete = function(alert) {
this.generateElapsedTime();
if (alert.type === "success") {
this.status = "success";
} else {
this.status = "failure";
}
ValidationLogItem.inProgress--;
this.alert = alert;
};
/**
* Adds a ValidationLogItem to an existing array. If the item
* already exists, it is reset to initial settings.
*
* @method addTo
* @param {Array} itemList - array to which to add log item.
* @return {boolean} true = suceesfully added; false = already there or parameter is not
* an array
*/
ValidationLogItem.prototype.addTo = function(itemList) {
if (Array.isArray(itemList)) {
if (!ValidationLogItem.quickAccess.hasOwnProperty(this.destinationId)) {
itemList.push(this);
ValidationLogItem.quickAccess[this.destinationId] = this;
return true;
} else {
this.status = "running";
this.updateBeginTime();
this.resetElapsedTime();
}
}
return false;
};
// return the factory interface
return {
/**
* Get the list of validation log entries.
*
* @method getLogEntries
* @return {array} - array of validation log entries
*/
getLogEntries: function() {
if (logEntries.length === 0) {
// check to see whether there is there is a session cache of validation log entries
var sessionCache = $window.sessionStorage.getItem("destination_validation_log");
if (sessionCache) {
var cachedLogEntries = JSON.parse(sessionCache);
cachedLogEntries.forEach(function(entry) {
logEntries.push(new ValidationLogItem(entry));
});
}
ValidationLogItem.updateQuickAccess(logEntries);
}
return logEntries;
},
/**
* Create a cache of the validation log items using sessionStorage.
*
* @method cacheLogEntries
*/
cacheLogEntries: function() {
$window.sessionStorage.setItem("destination_validation_log", JSON.stringify(logEntries));
},
/**
* Clear the cache of the validation log items stored in sessionStorage.
*
* @method clearCache
*/
clearCache: function() {
$window.sessionStorage.removeItem("destination_validation_log");
},
/**
* Are there entries in the validation log.
*
* @method hasLogEntries
* @return {boolean} - true if populated; false if not
*/
hasLogEntries: function() {
return logEntries && logEntries.length > 0;
},
/**
* Update name for a given Id. Called when
* updating a destination in case of potential changes affecting
* a validation log record.
*
* @method updateValidationInfo
* @param {string} destinationId - unique id of destination that was changed
* @param {string} newName - potentially updated name
*/
updateValidationInfo: function(destinationId, newName) {
if (ValidationLogItem.quickAccess.hasOwnProperty(destinationId)) {
ValidationLogItem.quickAccess[destinationId].name = _.escape(newName);
}
},
/**
* Add new validation information to the log for a given destination.
*
* @method add
* @param {Object} - validatingDestination the destination being validated
*/
add: function(validatingDestination) {
var validating = null;
if (ValidationLogItem.quickAccess.hasOwnProperty(validatingDestination.id)) {
validating = ValidationLogItem.quickAccess[validatingDestination.id];
validating.resetElapsedTime();
validating.updateBeginTime();
} else {
validating = new ValidationLogItem(validatingDestination);
validating.addTo(logEntries);
ValidationLogItem.quickAccess[validating.destinationId] = validating;
}
this.cacheLogEntries();
},
/**
* Remove validation information from the log for a given destination.
*
* @method remove
* @param {string} - destinationId - the destination id to remove from the log
*/
remove: function(destId) {
if (ValidationLogItem.quickAccess.hasOwnProperty(destId)) {
var itemToRemove = ValidationLogItem.quickAccess[destId];
if (itemToRemove.status === "running") {
ValidationLogItem.inProgress--;
}
delete ValidationLogItem.quickAccess[destId];
_.remove(logEntries, function(item) {
return item.destinationId === destId;
});
this.cacheLogEntries();
}
},
/**
* Is validation in progress for given destination.
*
* @method isValidationInProgressFor
* @param {String} id - id of specific destination to test
* @returns {Boolean} is destination being validated
*/
isValidationInProgressFor: function(destination) {
if (ValidationLogItem.getStatusFor(destination.id) === "running") {
return true;
}
return false;
},
/**
* Determine whether a validation process is current running.
*
* @method isValidationRunning
* @returns {Boolean} is validation (multiple or single) process running
*/
isValidationRunning: function() {
return ValidationLogItem.inProgress > 0;
},
/**
* Get count of inProgress validations.
*
* @method getInProgressCount
* @returns {number} the current count of in progress validations.
*/
getInProgressCount: function() {
return ValidationLogItem.inProgress;
},
/**
* Gets the current status of the validation process
* for a given destination id.
*
* @method validateAllStatus
* @param {String} id - unique identification string
* @return {String} - status string (running | success | failure)
*/
validateAllStatus: function(id) {
if (ValidationLogItem.quickAccess.hasOwnProperty(id)) {
return ValidationLogItem.quickAccess[id].status;
}
return null;
},
/**
* Checks whether the validation process for a particular
* destination succeeded.
*
* @method validateAllSuccessFor
* @param {String} id - unique identification string
*/
validateAllSuccessFor: function(id) {
return this.validateAllStatus(id) === "success";
},
/**
* Checks whether the validation process for a particular
* destination failed.
*
* @method validateAllFailureFor
* @param {String} id - unique identification string
*/
validateAllFailureFor: function(id) {
return this.validateAllStatus(id) === "failure";
},
/**
* Updates the status of an existing Validation Log Item.
*
* @method markAsComplete
* @param {String} id - unique identification string
* @param {Object} alertOptions - details of validation result
*/
markAsComplete: function(id, alertOptions) {
if (ValidationLogItem.quickAccess.hasOwnProperty(id)) {
ValidationLogItem.quickAccess[id].markAsComplete(alertOptions);
this.cacheLogEntries();
}
},
/**
* Displays alert message for validation result
*
* @method showValidationMessageFor
* @param {String} id - unique identification string
*/
showValidationMessageFor: function(id) {
if (ValidationLogItem.quickAccess.hasOwnProperty(id) && ValidationLogItem.quickAccess[id].hasOwnProperty("alert")) {
alertService.add(ValidationLogItem.quickAccess[id].alert);
}
},
};
}]);
}
);
/*
# backup_configuration/directives/formValidator.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(
'app/directives/formValidator',[
"angular",
"cjt/util/locale",
"cjt/validator/validator-utils",
"cjt/validator/domain-validators",
"cjt/validator/path-validators",
"cjt/validator/ip-validators",
"cjt/validator/validateDirectiveFactory"
],
function(angular, LOCALE, validationUtils, domainValidators, pathValidators, ipValidators) {
"use strict";
/* regular expressions used by validation checks */
var loopbackRegex = /^(127(\.\d+){1,3}|[0:]+1|localhost)$/i;
var protocolRegex = /^http(s)*:\/*/;
var portRegex = /:\d*$/;
var bucketRegex = /^[a-z0-9-]*$/;
var bucketRegexAmazon = /^[a-z0-9-.]*$/;
var bucketRegexB2 = /^[A-Za-z0-9-]*$/;
var bucketBeginRegex = /^[-]/;
var bucketEndRegex = /[-]$/;
var bucketBeginRegexAmazon = /^[-.]/;
var bucketEndRegexAmazon = /[-.]$/;
var bucketBeginB2 = /^b2-/i;
/* validation messages */
var relativePathWarning = LOCALE.maketext("You must enter a relative path.");
var subordinatePathWarning = LOCALE.maketext("You must enter a path within the user home directory.");
var loopbackWarning = LOCALE.maketext("You cannot enter a loopback address for the remote address.");
var noSlashesWarning = LOCALE.maketext("You must enter a value without slashes.");
var noSpacesWarning = LOCALE.maketext("You must enter a value without spaces.");
var noProtocolAllowedWarning = LOCALE.maketext("The remote host address must not contain a protocol.");
var noPathWarning = LOCALE.maketext("The remote host address must not contain path information.");
var noPortWarning = LOCALE.maketext("The remote host address must not contain a port number.");
var absolutePathWarning = LOCALE.maketext("You must enter an absolute path.");
var remoteHostWarning = LOCALE.maketext("The remote host address must be a valid hostname or IP address.");
var bucketLengthWarning = LOCALE.maketext("The bucket name must be between [numf,3] and [numf,63] characters.");
var b2BucketLengthWarning = LOCALE.maketext("The bucket name must be between [numf,6] and [numf,50] characters.");
var bucketNameWarning = LOCALE.maketext("The bucket name must not begin or end with a hyphen.");
var bucketNameWarningAmazon = LOCALE.maketext("The bucket name must not begin or end with a hyphen or a period.");
var bucketAllowedCharacters = LOCALE.maketext("The bucket name must only contain numbers, hyphens, and lowercase letters.");
var bucketAllowedCharactersAmazon = LOCALE.maketext("The bucket name must only contain numbers, periods, hyphens, and lowercase letters.");
var bucketAllowedCharactersB2 = LOCALE.maketext("The bucket name must only contain numbers, hyphens, and letters.");
var bucketNameB2Reserved = LOCALE.maketext("The [asis,Backblaze] [asis,B2] bucket name must not begin with “b2-” because [asis,Backblaze] reserves this prefix.");
var validators = {
/*
* Checks to see if a value is a valid backup location.
*
* @param {string} val - form value to be evaluated
* @param {string} arg - optional argument ("absolute") to disable relative path checking
* @return {ValidationResult} results of the validation
*/
backupLocation: function(val, arg) {
var result = validationUtils.initializeValidationResult();
result.isValid = false;
// allow optional field to be empty
if (!val) {
result.isValid = true;
} else if (arg !== "absolute" && val.length > 0 && val[0] === "/") {
result.add("backupConfigIssue", relativePathWarning);
} else if (val.substring(0, 3) === "../") {
result.add("backupConfigIssue", subordinatePathWarning);
} else {
result = pathValidators.methods.validPath(val);
}
return result;
},
/* Checks to see if a value is a valid S3, AmazonS3 or B2 bucket name.
*
* @param {string} val - form value to be evaluated
* @param {string} arg - optional transport type ("amazon" if AmazonS3, "b2" if Backblaze b2)
* @return {ValidationResult} results of the validation
*/
bucket: function(val, arg) {
var result = validationUtils.initializeValidationResult();
result.isValid = false;
if (arg === "b2" && bucketBeginB2.test(val)) {
result.add("backupConfigIssue", bucketNameB2Reserved);
} else if (arg === "b2" && !bucketRegexB2.test(val)) {
result.add("backupConfigIssue", bucketAllowedCharactersB2);
} else if (arg === "amazon" && !bucketRegexAmazon.test(val)) {
result.add("backupConfigIssue", bucketAllowedCharactersAmazon);
} else if (arg !== "amazon" && arg !== "b2" && !bucketRegex.test(val)) {
result.add("backupConfigIssue", bucketAllowedCharacters);
} else if (arg === "amazon" && (bucketBeginRegexAmazon.test(val) || bucketEndRegexAmazon.test(val))) {
result.add("backupConfigIssue", bucketNameWarningAmazon);
} else if (arg !== "amazon" && arg !== "b2" && (bucketBeginRegex.test(val) || bucketEndRegex.test(val))) {
result.add("backupConfigIssue", bucketNameWarning);
} else if (arg === "b2" && (val.length < 6 || val.length > 50)) {
result.add("backupConfigIssue", b2BucketLengthWarning);
} else if (val.length < 3 || val.length > 63) {
result.add("backupConfigIssue", bucketLengthWarning);
} else {
result.isValid = true;
}
return result;
},
/*
* Checks to see if a value is a valid remote host or ip address.
*
* @param {string} val - form value to be evaluated
* @return {ValidationResult} results of the validation
*/
remoteHost: function(val) {
var result = validationUtils.initializeValidationResult();
result.isValid = false;
var ipCheck = ipValidators.methods.ipv4(val);
if (ipCheck.isValid) {
if (loopbackRegex.test(val)) {
// remote destination should not be a loopback
result.add("backupConfigIssue", loopbackWarning);
return result;
}
return ipCheck;
} else {
// if it's not a valid ip address
// check the hostname for special conditions
if (protocolRegex.test(val)) {
result.add("backupConfigIssue", noProtocolAllowedWarning);
return result;
}
if (val.indexOf("/") >= 0 || val.indexOf("\\") >= 0) {
result.add("backupConfigIssue", noPathWarning);
return result;
}
if (portRegex.test(val)) {
result.add("backupConfigIssue", noPortWarning);
return result;
}
}
var fqdnCheck = domainValidators.methods.fqdn(val);
if (!ipCheck.isValid && !fqdnCheck.isValid) {
result.add("backupConfigIssue", remoteHostWarning);
return result;
}
return fqdnCheck;
},
/*
* Checks a value for the existence of slashes.
*
* @param {string} val - form value to be evaluated
* @return {ValidationResult} results of the validation
*/
noslashes: function(val) {
var result = validationUtils.initializeValidationResult();
result.isValid = false;
if (val.indexOf("/") < 0 && val.indexOf("\\") < 0) {
result.isValid = true;
} else {
result.add("backupConfigIssue", noSlashesWarning);
}
return result;
},
/*
* Checks a value for the existence of spaces.
*
* @param {string} val - form value to be evaluated
* @return {ValidationResult} results of the validation
*/
nospaces: function(val) {
var result = validationUtils.initializeValidationResult();
result.isValid = false;
if (val.indexOf(" ") < 0) {
result.isValid = true;
} else {
result.add("backupConfigIssue", noSpacesWarning);
}
return result;
},
/*
* Checks a value for a valid absolute path format.
*
* @param {string} val - form value to be evaluated
* @return {ValidationResult} results of the validation
*/
fullPath: function(val) {
var result = validationUtils.initializeValidationResult();
result.isValid = true;
// allow optional field to be empty
if (!val) {
return result;
} else if (val.indexOf("/") !== 0) {
// value must start with a forward slash (/)
result.isValid = false;
result.add("backupConfigIssue", absolutePathWarning);
} else {
result = pathValidators.methods.validPath(val);
}
return result;
}
};
// Generate a directive for each validation function
var validatorModule = angular.module("cjt2.validate");
validatorModule.run(["validatorFactory",
function(validatorFactory) {
validatorFactory.generate(validators);
}
]);
return {
methods: validators,
name: "backupConfigurationValidators",
description: "Validation library for Backup Configuration.",
version: 1.0,
};
}
);
/*
# backup_configuration/views/config.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(
'app/views/config',[
"angular",
"lodash",
"cjt/util/locale",
"cjt/util/table",
"cjt/services/alertService",
"cjt/directives/alertList",
"cjt/directives/actionButtonDirective",
"cjt/directives/validationContainerDirective",
"cjt/directives/validationItemDirective",
"cjt/validator/datatype-validators",
"cjt/validator/username-validators",
"cjt/validator/compare-validators",
"app/services/backupConfigurationServices"
],
function(angular, _, LOCALE, Table) {
"use strict";
var app = angular.module("whm.backupConfiguration");
// var table = new Table();
var controller = app.controller(
"config", [
"$scope",
"$location",
"$anchorScroll",
"$q",
"$window",
"backupConfigurationServices",
"alertService",
"$timeout",
function(
$scope,
$location,
$anchorScroll,
$q,
$window,
backupConfigurationServices,
alertService,
$timeout) {
/**
* Make a copy of the source backup configuration
* and return a reference to it.
*
* @function cloneConfiguration
* @param {Object} sourceConfig - configuration object to copy
* @return {Object} - reference to new copy
*/
var cloneConfiguration = function(sourceConfig) {
// first do a shallow copy of the sourceConfig
var copyOfConfig = _.clone(sourceConfig);
// special case properties not copied by shallow copy
if (sourceConfig.hasOwnProperty("backupdays")) {
copyOfConfig.backupdays = _.clone(sourceConfig.backupdays);
}
if (sourceConfig.hasOwnProperty("backup_monthly_dates")) {
copyOfConfig.backup_monthly_dates = _.clone(sourceConfig.backup_monthly_dates);
}
return copyOfConfig;
};
/**
* Fetches current backup configuration.
*
* @scope
* @method getBackupConfiguration
*/
$scope.getBackupConfiguration = function() {
alertService.clear();
backupConfigurationServices.getBackupConfig()
.then(function(configuration) {
if (!$scope.initialFormData) {
$scope.formData = cloneConfiguration(configuration);
$scope.initialFormData = cloneConfiguration(configuration);
} else {
$scope.formData = cloneConfiguration(configuration);
}
$scope.backupConfigLoaded = true;
$scope.formEnabled = $scope.formData.backupenable;
$scope.setMonthlyBackupDays();
}, function(error) {
alertService.add({
type: "danger",
message: error,
id: "configuration-loading-error",
closeable: true
});
// set to true on error so loading dialog is removed
$scope.backupConfigLoaded = true;
});
};
/**
* Toggle between percent and MB as units of storage
*
* @scope
* @method handleUnitToggle
* @param {String} value - value to toggle
*/
$scope.handleUnitToggle = function(value) {
if (value === "%") {
$scope.formData.min_free_space_unit = "percent";
} else if (value === "MB") {
$scope.formData.min_free_space_unit = "MB";
} else {
throw "DEVELOPER ERROR: value argument has unexpected value: " + value;
}
};
/**
* Add or remove day from list of active backup days
*
* @scope
* @method handleDaysToggle
* @param {Number} index - index of toggled day in array of days
* @param {FormController} form - the form calling the function
*/
$scope.handleDaysToggle = function(index, form) {
if ($scope.formData.backupdays[index]) {
delete $scope.formData.backupdays[index];
} else {
$scope.formData.backupdays[index] = index.toString();
}
form.$setDirty();
// if length of array is zero no days are selected and
// warning is displayed
$scope.selectedDays = Object.keys($scope.formData.backupdays);
};
/*
* Handles toggle between days when setting weekly backups
*
* @scope
* @method handleDayToggle
* @param {Number} index - index referencing active backup day in
* @param {FormController} form - the form calling the function
* weekly backup settings
*/
$scope.handleDayToggle = function(index, form) {
$scope.formData.backup_weekly_day = index;
form.$setDirty();
};
/**
* Handles toggle of monthly backup schedule options
*
* @scope
* @method handleMonthlyToggle
* @param {String} day - day to toggle
*/
$scope.handleMonthlyToggle = function(day) {
if (!$scope.formData.backup_monthly_dates) {
$scope.formData.backup_monthly_dates = {};
}
if (day === "first") {
if ($scope.formData.backup_monthly_dates[1]) {
delete $scope.formData.backup_monthly_dates[1];
} else {
$scope.formData.backup_monthly_dates[1] = "1";
}
} else if (day === "fifteenth") {
if ($scope.formData.backup_monthly_dates[15]) {
delete $scope.formData.backup_monthly_dates[15];
} else {
$scope.formData.backup_monthly_dates[15] = "15";
}
} else {
throw "DEVELOPER ERROR: value argument has unexpected value: " + day;
}
$scope.setMonthlyBackupDays();
};
/**
* Set boolean values for monthly dates object based on active
* backup days
*
* @scope
* @method setMonthlyBackupDays
*/
$scope.setMonthlyBackupDays = function() {
if (!$scope.monthlyBackupBool) {
$scope.monthlyBackupBool = {};
}
if ($scope.formData.backup_monthly_dates[1]) {
$scope.monthlyBackupBool["first"] = true;
} else {
$scope.monthlyBackupBool["first"] = false;
}
if ($scope.formData.backup_monthly_dates[15]) {
$scope.monthlyBackupBool["fifteenth"] = true;
} else {
$scope.monthlyBackupBool["fifteenth"] = false;
}
if (!$scope.monthlyBackupBool["first"] && !$scope.monthlyBackupBool["fifteenth"]) {
delete $scope.formData.backup_monthly_dates;
}
};
/**
* Opens new tab with select user options
*
* @scope
* @method redirectToSelectUsers
*/
$scope.redirectToSelectUsers = function() {
window.open("../backup_user_selection");
};
/**
* Saves a new backup configuration via API
*
* @scope
* @param {FormController} form - the form calling the function
* @method saveConfiguration
*/
$scope.saveConfiguration = function(form) {
$scope.saving = true;
return backupConfigurationServices.setBackupConfig($scope.formData)
.then(function(success) {
$scope.initialFormData = cloneConfiguration($scope.formData);
alertService.add({
type: "success",
autoClose: 5000,
message: LOCALE.maketext("The system successfully saved the backup configuration."),
id: "save-configuration-succeeded"
});
// on success force form to clean state
form.$setPristine();
})
.catch(function(error) {
alertService.add({
type: "danger",
closeable: true,
message: error,
id: "save-configuration-failed"
});
})
.finally(function() {
$scope.saving = false;
});
};
/**
* Resets config page to initial values and scrolls to top of page
*
* @scope
* @method resetConfiguration
* @param {FormController} form - the form calling the function
*/
$scope.resetConfiguration = function(form) {
$scope.formData = cloneConfiguration($scope.initialFormData);
// disable form controls if backups are disabled
$scope.enableBackupConfig();
// update selectedDays array so validation message is correctly displayed
$scope.selectedDays = Object.keys($scope.formData.backupdays);
// massage monthly backup days checkboxes
$scope.setMonthlyBackupDays();
form.$setPristine();
$location.hash("backup_status");
$anchorScroll();
};
/**
* 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;
}
};
/**
* 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.backupConfigLoaded = false;
$scope.getBackupConfiguration();
$scope.dailyDays = [
LOCALE.maketext("Sunday"),
LOCALE.maketext("Monday"),
LOCALE.maketext("Tuesday"),
LOCALE.maketext("Wednesday"),
LOCALE.maketext("Thursday"),
LOCALE.maketext("Friday"),
LOCALE.maketext("Saturday")
];
$scope.weeklyDays = [
LOCALE.maketext("Sunday"),
LOCALE.maketext("Monday"),
LOCALE.maketext("Tuesday"),
LOCALE.maketext("Wednesday"),
LOCALE.maketext("Thursday"),
LOCALE.maketext("Friday"),
LOCALE.maketext("Saturday")
];
$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 = 0;
};
$scope.init();
}
]
);
return controller;
}
);
/*
# 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(
'app/views/destinations',[
"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
};
} 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;
}
);
/*
# backup_configuration/views/validationResults.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(
'app/views/validationResults',[
"angular",
"cjt/util/locale",
"cjt/util/table",
"cjt/services/alertService",
"cjt/directives/alertList",
"cjt/directives/toggleSortDirective",
"cjt/directives/actionButtonDirective",
"app/services/validationLog"
],
function(angular, LOCALE, Table) {
"use strict";
var app = angular.module("whm.backupConfiguration");
var controller = app.controller(
"validationResults", [
"$scope",
"alertService",
"validationLog",
function(
$scope,
alertService,
validationLog) {
var logTable = new Table();
/**
* Sort ValidationLogItem Objects and update table. Items
* are sorted in place.
*
* @scope
* @method sortValidationEntries
*/
$scope.sortValidationEntries = function() {
$scope.currentlyValidating = logTable.update();
$scope.meta = logTable.getMetadata();
};
/**
* Initialize page with default values
*
* @scope
* @method init
*/
$scope.init = function() {
$scope.currentlyValidating = validationLog.getLogEntries();
logTable.load($scope.currentlyValidating);
logTable.setSort("name,transport", "asc");
// remove if pagination is ever implemented
logTable.meta.limit = $scope.currentlyValidating.length;
logTable.meta.pageSize = $scope.currentlyValidating.length;
$scope.$watch("currentlyValidating", function() {
$scope.sortValidationEntries();
validationLog.cacheLogEntries();
}, true);
};
$scope.init();
}
]
);
return controller;
}
);
/*
# backup_configuration/index.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 require, define, PAGE */
define(
'app/index',[
"angular",
"cjt/core",
"cjt/modules",
"ngRoute",
"app/services/backupConfigurationServices",
"app/services/validationLog"
],
function(angular, CJT) {
"use strict";
return function() {
// First create the application
angular.module("whm.backupConfiguration", [
"cjt2.config.whm.configProvider",
"ngRoute",
"cjt2.whm",
"whm.backupConfiguration.backupConfigurationServices.service",
"whm.backupConfiguration.validationLog.service"
]);
// Then load the application dependencies
var app = require(
[
"cjt/bootstrap",
"app/directives/formValidator",
"app/views/config",
"app/views/destinations",
"app/views/validationResults"
],
function(BOOTSTRAP) {
var app = angular.module("whm.backupConfiguration");
app.value("PAGE", PAGE);
app.controller("BaseController", ["$rootScope", "$scope", "$location",
function($rootScope, $scope, $location) {
$scope.loading = false;
$rootScope.$on("$routeChangeStart", function() {
$scope.loading = true;
$rootScope.currentRoute = $location.path();
});
}
]);
app.config([
"$routeProvider",
function($routeProvider) {
$routeProvider.when("/settings", {
controller: "config",
templateUrl: "views/config.ptt"
});
$routeProvider.when("/destinations", {
controller: "destinations",
templateUrl: "views/destinations.ptt"
});
$routeProvider.when("/validation", {
controller: "validationResults",
templateUrl: "views/validationResults.ptt"
});
$routeProvider.otherwise({
"redirectTo": "/settings"
});
}
]);
var appContent = angular.element("#pageContainer");
if (appContent[0] !== null) {
// apply the app after requirejs loads everything
BOOTSTRAP(appContent[0], "whm.backupConfiguration");
}
});
return app;
};
}
);
Back to Directory
File Manager