mirror of
https://github.com/duplicati/duplicati.git
synced 2025-11-28 11:30:24 +08:00
This PR adds a masking function that replaces sensitive parameters with a masking string, such that the passwords are never submitted to the client during edits. For now, the export feature (both as JSON and as commandline) do not mask the sensitive parameters. This fixes #2024
729 lines
28 KiB
JavaScript
729 lines
28 KiB
JavaScript
backupApp.controller('EditBackupController', function ($rootScope, $scope, $routeParams, $location, $timeout, AppService, AppUtils, SystemInfo, DialogService, EditBackupService, gettext, gettextCatalog) {
|
|
|
|
var SMART_RETENTION = '1W:1D,4W:1W,12M:1M';
|
|
|
|
$scope.SystemInfo = SystemInfo.watch($scope);
|
|
$scope.AppUtils = AppUtils;
|
|
|
|
$scope.RepeatPasshrase = null;
|
|
$scope.PasswordStrength = 'unknown';
|
|
$scope.CurrentStep = 0;
|
|
$scope.showhiddenfolders = false;
|
|
$scope.EditSourceAdvanced = false;
|
|
$scope.EditFilterAdvanced = false;
|
|
|
|
$scope.ExcludeAttributes = [];
|
|
$scope.ExcludeLargeFiles = false;
|
|
$scope.ExcludeFileSize = null;
|
|
|
|
$scope.fileAttributes = [
|
|
{'name': gettextCatalog.getString('Hidden files'), 'value': 'hidden'},
|
|
{'name': gettextCatalog.getString('System files'), 'value': 'system'},
|
|
{'name': gettextCatalog.getString('Temporary files'), 'value': 'temporary'}
|
|
];
|
|
|
|
var scope = $scope;
|
|
|
|
function computePassPhraseStrength() {
|
|
|
|
var strengthMap = {
|
|
'x': gettextCatalog.getString("Passphrases do not match"),
|
|
0: gettextCatalog.getString("Useless"),
|
|
1: gettextCatalog.getString("Very weak"),
|
|
2: gettextCatalog.getString("Weak"),
|
|
3: gettextCatalog.getString("Strong"),
|
|
4: gettextCatalog.getString("Very strong")
|
|
};
|
|
|
|
var passphrase = scope.Options == null ? '' : scope.Options['passphrase'];
|
|
|
|
if (scope.RepeatPasshrase != passphrase)
|
|
scope.PassphraseScore = 'x';
|
|
else if ((passphrase || '') == '')
|
|
scope.PassphraseScore = '';
|
|
else
|
|
scope.PassphraseScore = (zxcvbn(passphrase.substring(0, 100)) || {'score': -1}).score;
|
|
|
|
scope.PassphraseScoreString = strengthMap[scope.PassphraseScore];
|
|
}
|
|
|
|
$scope.$watch('Options["passphrase"]', computePassPhraseStrength);
|
|
$scope.$watch('RepeatPasshrase', computePassPhraseStrength);
|
|
$scope.parseSizeString = function(v) {
|
|
return AppUtils.parseSizeString(v);
|
|
}
|
|
|
|
$scope.checkGpgAsymmetric = function() {
|
|
if (!this.Options) {
|
|
return false;
|
|
}
|
|
|
|
if (!('encryption-module' in this.Options)) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.Options['encryption-module']) {
|
|
return false;
|
|
}
|
|
|
|
if (this.Options['encryption-module'].indexOf('gpg') < 0) {
|
|
return false;
|
|
}
|
|
|
|
return this.ExtendedOptions.includes('--gpg-encryption-command=--encrypt');
|
|
}
|
|
|
|
$scope.isMaskedPassphrase = function() {
|
|
return this.Options && this.Options['passphrase'] && this.Options['passphrase'] == $scope.SystemInfo.PasswordPlaceholder;
|
|
};
|
|
|
|
$scope.generatePassphrase = function() {
|
|
this.Options["passphrase"] = this.RepeatPasshrase = AppUtils.generatePassphrase();
|
|
this.ShowPassphrase = true;
|
|
this.HasGeneratedPassphrase = true;
|
|
};
|
|
|
|
$scope.togglePassphraseVisibility = function() {
|
|
this.ShowPassphrase = !this.ShowPassphrase;
|
|
};
|
|
|
|
$scope.nextPage = function() {
|
|
$scope.CurrentStep = Math.min(4, $scope.CurrentStep + 1);
|
|
};
|
|
|
|
$scope.prevPage = function() {
|
|
$scope.CurrentStep = Math.max(0, $scope.CurrentStep - 1);
|
|
};
|
|
|
|
$scope.setBuilduriFn = function(builduriFn) {
|
|
$scope.builduri = builduriFn;
|
|
};
|
|
|
|
$scope.importUrl = function () {
|
|
DialogService.textareaDialog(gettextCatalog.getString('Import URL'), gettextCatalog.getString('Enter a backup destination URL:'), null, gettextCatalog.getString('Enter URL'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('OK')], null, function(btn, input) {
|
|
if (btn == 1)
|
|
scope.Backup.TargetURL = input;
|
|
});
|
|
};
|
|
|
|
$scope.copyUrlToClipboard = function () {
|
|
$scope.builduri(function(res) {
|
|
DialogService.textareaDialog(gettextCatalog.getString('Copy URL'), null, null, res, [gettextCatalog.getString('OK')], 'templates/copy_clipboard_buttons.html');
|
|
});
|
|
};
|
|
|
|
var oldSchedule = null;
|
|
|
|
$scope.toggleSchedule = function() {
|
|
if (scope.Schedule == null) {
|
|
if (oldSchedule == null) {
|
|
oldSchedule = {
|
|
Tags: [],
|
|
Repeat: '1D',
|
|
AllowedDays: []
|
|
};
|
|
}
|
|
|
|
scope.Schedule = oldSchedule;
|
|
oldSchedule = null;
|
|
} else {
|
|
oldSchedule = scope.Schedule;
|
|
scope.Schedule = null;
|
|
}
|
|
};
|
|
|
|
$scope.addManualSourcePath = function() {
|
|
if (scope.validatingSourcePath)
|
|
return;
|
|
|
|
if (scope.manualSourcePath == null || scope.manualSourcePath == '')
|
|
return;
|
|
|
|
var dirsep = scope.SystemInfo.DirectorySeparator || '/';
|
|
|
|
if (dirsep == '/') {
|
|
if (scope.manualSourcePath.substr(0, 1) != '/' && scope.manualSourcePath.substr(0, 1) != '%') {
|
|
DialogService.dialog(gettextCatalog.getString('Relative paths not allowed'), gettextCatalog.getString("The path must be an absolute path, i.e. it must start with a forward slash '/' "));
|
|
return;
|
|
}
|
|
}
|
|
|
|
function continuation() {
|
|
scope.validatingSourcePath = true;
|
|
|
|
AppService.postJson('/filesystem/validate', {path: scope.manualSourcePath}).then(function() {
|
|
scope.validatingSourcePath = false;
|
|
scope.Backup.Sources.push(scope.manualSourcePath);
|
|
scope.manualSourcePath = null;
|
|
}, function() {
|
|
scope.validatingSourcePath = false;
|
|
|
|
DialogService.dialog(gettextCatalog.getString('Path not found'), gettextCatalog.getString('The path does not appear to exist, do you want to add it anyway?'), [gettextCatalog.getString('No'), gettextCatalog.getString('Yes')], function(ix) {
|
|
if (ix == 1) {
|
|
scope.Backup.Sources.push(scope.manualSourcePath);
|
|
scope.manualSourcePath = null;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (scope.manualSourcePath.substr(scope.manualSourcePath.length - 1, 1) != dirsep) {
|
|
DialogService.dialog(gettextCatalog.getString('Include a file?'), gettextCatalog.getString("The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?", {dirsep: dirsep}), [gettextCatalog.getString('No'), gettextCatalog.getString('Yes')], function(ix) {
|
|
if (ix == 1)
|
|
continuation();
|
|
});
|
|
} else {
|
|
continuation();
|
|
}
|
|
};
|
|
|
|
function toggleArraySelection(lst, value) {
|
|
var ix = lst.indexOf(value);
|
|
|
|
if (ix > -1)
|
|
lst.splice(ix, 1);
|
|
else
|
|
lst.push(value);
|
|
};
|
|
|
|
$scope.toggleAllowedDays = function(value) {
|
|
if ($scope.Schedule.AllowedDays == null)
|
|
$scope.Schedule.AllowedDays = [];
|
|
toggleArraySelection($scope.Schedule.AllowedDays, value);
|
|
};
|
|
|
|
$scope.toggleExcludeAttributes = function(value) {
|
|
if ($scope.ExcludeAttributes == null)
|
|
$scope.ExcludeAttributes = [];
|
|
toggleArraySelection($scope.ExcludeAttributes, value);
|
|
};
|
|
|
|
$scope.save = function() {
|
|
|
|
if (!EditBackupService.preValidate($scope))
|
|
return false;
|
|
|
|
var result = {
|
|
Backup: angular.copy($scope.Backup),
|
|
Schedule: angular.copy($scope.Schedule)
|
|
};
|
|
|
|
var opts = angular.copy($scope.Options, opts);
|
|
|
|
if (!$scope.ExcludeLargeFiles)
|
|
delete opts['--skip-files-larger-than'];
|
|
|
|
var encryptionEnabled = true;
|
|
if ((opts['encryption-module'] || '').length == 0) {
|
|
opts['--no-encryption'] = 'true';
|
|
encryptionEnabled = false;
|
|
}
|
|
|
|
if (!AppUtils.parse_extra_options(scope.ExtendedOptions, opts))
|
|
return false;
|
|
|
|
for (var n in $scope.servermodulesettings)
|
|
opts['--' + n] = $scope.servermodulesettings[n];
|
|
|
|
var exclattr = ($scope.ExcludeAttributes || []).concat((opts['--exclude-files-attributes'] || '').split(','));
|
|
var exclmap = { '': true };
|
|
|
|
// Remove duplicates
|
|
for (var i = exclattr.length - 1; i >= 0; i--) {
|
|
exclattr[i] = (exclattr[i] || '').trim();
|
|
var cmp = exclattr[i].toLowerCase();
|
|
if (exclmap[cmp])
|
|
exclattr.splice(i, 1);
|
|
else
|
|
exclmap[cmp] = true;
|
|
}
|
|
|
|
if (exclattr.length == 0)
|
|
delete opts['--exclude-files-attributes'];
|
|
else
|
|
opts['--exclude-files-attributes'] = exclattr.join(',');
|
|
|
|
if (($scope.Backup.Name || '').trim().length == 0) {
|
|
DialogService.dialog(gettextCatalog.getString('Missing name'), gettextCatalog.getString('You must enter a name for the backup'));
|
|
$scope.CurrentStep = 0;
|
|
return;
|
|
}
|
|
|
|
if (encryptionEnabled && !$scope.checkGpgAsymmetric()) {
|
|
if ($scope.PassphraseScore === '') {
|
|
DialogService.dialog(gettextCatalog.getString('Missing passphrase'), gettextCatalog.getString('You must enter a passphrase or disable encryption'));
|
|
$scope.CurrentStep = 0;
|
|
return;
|
|
}
|
|
|
|
if ($scope.PassphraseScore == 'x') {
|
|
DialogService.dialog(gettextCatalog.getString('Non-matching passphrase'), gettextCatalog.getString('Passphrases are not matching'));
|
|
$scope.CurrentStep = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ($scope.Backup.Sources == null || $scope.Backup.Sources.length == 0) {
|
|
DialogService.dialog(gettextCatalog.getString('Missing sources'), gettextCatalog.getString('You must choose at least one source folder'));
|
|
$scope.CurrentStep = 2;
|
|
return;
|
|
}
|
|
|
|
// Retention options are mutual exclusive -> allow only one to be selected at a time
|
|
function resetAllRetentionOptionsExcept(optionToKeep) {
|
|
['keep-versions', 'keep-time', 'retention-policy'].forEach(function(entry) {
|
|
if (entry != optionToKeep) {
|
|
delete opts[entry];
|
|
}
|
|
});
|
|
}
|
|
|
|
if ($scope.KeepType == 'time') {
|
|
resetAllRetentionOptionsExcept('keep-time');
|
|
|
|
} else if ($scope.KeepType == 'versions') {
|
|
resetAllRetentionOptionsExcept('keep-versions');
|
|
|
|
} else if ($scope.KeepType == 'smart' || $scope.KeepType == 'custom') {
|
|
resetAllRetentionOptionsExcept('retention-policy');
|
|
|
|
} else {
|
|
resetAllRetentionOptionsExcept(); // keep none
|
|
}
|
|
|
|
if ($scope.KeepType == 'time' && (opts['keep-time'] || '').trim().length == 0)
|
|
{
|
|
DialogService.dialog(gettextCatalog.getString('Invalid retention time'), gettextCatalog.getString('You must enter a valid duration for the time to keep backups'));
|
|
$scope.CurrentStep = 4;
|
|
return;
|
|
}
|
|
|
|
if ($scope.KeepType == 'versions' && (parseInt(opts['keep-versions']) <= 0 || isNaN(parseInt(opts['keep-versions']))))
|
|
{
|
|
DialogService.dialog(gettextCatalog.getString('Invalid retention time'), gettextCatalog.getString('You must enter a positive number of backups to keep'));
|
|
$scope.CurrentStep = 4;
|
|
return;
|
|
}
|
|
|
|
var retentionPolicy = (opts['retention-policy'] || '');
|
|
var valid_chars = /^((\d+[smhDWMY]|U):(\d+[smhDWMY]|U),?)+$/;
|
|
var valid_commas = /^(\d*\w:\d*\w,)*\d*\w:\d*\w$/;
|
|
if ($scope.KeepType == 'custom' && (retentionPolicy.indexOf(':') <= 0 || valid_chars.test(retentionPolicy) === false || valid_commas.test(retentionPolicy) === false))
|
|
{
|
|
DialogService.dialog(gettextCatalog.getString('Invalid retention time'), gettextCatalog.getString('You must enter a valid retention policy string'));
|
|
$scope.CurrentStep = 4;
|
|
return;
|
|
}
|
|
|
|
if ($scope.KeepType == 'smart')
|
|
opts['retention-policy'] = SMART_RETENTION;
|
|
|
|
|
|
result.Backup.Settings = [];
|
|
for(var k in opts) {
|
|
var origfilter = "";
|
|
var origarg = null;
|
|
for(var i in $scope.rawddata.Backup.Settings)
|
|
if ($scope.rawddata.Backup.Settings[i].Name == k) {
|
|
origfilter = $scope.rawddata.Backup.Settings[i].Filter;
|
|
origarg = $scope.rawddata.Backup.Settings[i].Argument;
|
|
break;
|
|
}
|
|
|
|
result.Backup.Settings.push({
|
|
Name: k,
|
|
Value: opts[k]?.toString(),
|
|
Filter: origfilter,
|
|
Argument: origarg
|
|
});
|
|
}
|
|
|
|
var filterstrings = result.Backup.Filters || [];
|
|
result.Backup.Filters = [];
|
|
for(var f in filterstrings)
|
|
result.Backup.Filters.push({
|
|
Order: result.Backup.Filters.length,
|
|
Include: filterstrings[f].substr(0, 1) == '+',
|
|
Expression: filterstrings[f].substr(1)
|
|
});
|
|
|
|
function warnWeakPassphrase(continuation) {
|
|
if (encryptionEnabled && ($scope.PassphraseScore == 0 || $scope.PassphraseScore == 1 || $scope.PassphraseScore == 2)) {
|
|
DialogService.dialog(gettextCatalog.getString('Weak passphrase'), gettextCatalog.getString('Your passphrase is easy to guess. Consider changing passphrase.'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Use weak passphrase')], function(ix) {
|
|
if (ix == 0)
|
|
$scope.CurrentStep = 0;
|
|
else
|
|
continuation();
|
|
});
|
|
}
|
|
else
|
|
continuation();
|
|
};
|
|
|
|
function checkForGeneratedPassphrase(continuation) {
|
|
if (!$scope.HasGeneratedPassphrase || !encryptionEnabled)
|
|
continuation();
|
|
else
|
|
DialogService.dialog(gettextCatalog.getString('Autogenerated passphrase'), gettextCatalog.getString('You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes, I have stored the passphrase safely')], function(ix) {
|
|
if (ix == 0)
|
|
$scope.CurrentStep = 0;
|
|
else
|
|
{
|
|
// Don't ask again
|
|
$scope.HasGeneratedPassphrase = false;
|
|
continuation();
|
|
}
|
|
});
|
|
};
|
|
|
|
function checkForChangedPassphrase(continuation) {
|
|
function findPrevOpt(key) {
|
|
var sets = $scope.rawddata.Backup.Settings;
|
|
for(var k in sets)
|
|
if (sets[k].Name == key)
|
|
return sets[k];
|
|
|
|
return null;
|
|
};
|
|
|
|
var previousEncryptionOpt = findPrevOpt('--no-encryption');
|
|
var prevPassphraseOpt = findPrevOpt('passphrase');
|
|
var previousEncryptionModuleOpt = findPrevOpt('encryption-module');
|
|
|
|
var prevPassphrase = prevPassphraseOpt == null ? null : prevPassphraseOpt.Value;
|
|
var previousEncryptionEnabled = previousEncryptionOpt == null ? true : !AppUtils.parseBoolString(previousEncryptionOpt.Value, true);
|
|
var previousEncryptionModule = (!previousEncryptionEnabled || previousEncryptionModuleOpt == null) ? '' : (previousEncryptionModuleOpt.Value || '');
|
|
|
|
var encryptionModule = opts['encryption-module'] || '';
|
|
|
|
if (encryptionEnabled && previousEncryptionEnabled && prevPassphrase != opts['passphrase'])
|
|
{
|
|
DialogService.dialog(gettextCatalog.getString('Passphrase changed'), gettextCatalog.getString('You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes, please break my backup!')], function(ix) {
|
|
if (ix == 0)
|
|
$scope.CurrentStep = 0;
|
|
else
|
|
continuation();
|
|
});
|
|
}
|
|
else if (encryptionEnabled != previousEncryptionEnabled || encryptionModule != previousEncryptionModule)
|
|
{
|
|
DialogService.dialog(gettextCatalog.getString('Encryption changed'), gettextCatalog.getString('You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes, I\'m brave!')], function(ix) {
|
|
if (ix == 1)
|
|
continuation();
|
|
});
|
|
}
|
|
else
|
|
continuation();
|
|
|
|
};
|
|
|
|
function checkForValidBackupDestination(continuation) {
|
|
var success = false;
|
|
$scope.builduri(function(res) {
|
|
result.Backup.TargetURL = res;
|
|
$scope.Backup.TargetURL = res;
|
|
success = true;
|
|
continuation();
|
|
});
|
|
|
|
if (!success)
|
|
$scope.CurrentStep = 1;
|
|
}
|
|
|
|
function checkForDisabledEncryption(continuation) {
|
|
if (encryptionEnabled || $scope.Backup.TargetURL.indexOf('file://') == 0 || $scope.SystemInfo.EncryptionModules.length == 0)
|
|
continuation();
|
|
else
|
|
DialogService.dialog(gettextCatalog.getString('No encryption'), gettextCatalog.getString('You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Continue without encryption')], function(ix) {
|
|
if (ix == 0)
|
|
$scope.CurrentStep = 0;
|
|
else
|
|
continuation();
|
|
});
|
|
};
|
|
|
|
|
|
if ($routeParams.backupid == null) {
|
|
|
|
function postDb() {
|
|
AppService.postJson('/backups', result).then(function() {
|
|
$location.path('/');
|
|
}, AppUtils.connectionError);
|
|
};
|
|
|
|
function checkForExistingDb(continuation) {
|
|
AppService.postJson('/remoteoperation/dbpath', { path: $scope.Backup.TargetURL }).then(
|
|
function(resp) {
|
|
if (resp.data.Exists) {
|
|
DialogService.dialog(gettextCatalog.getString('Use existing database?'), gettextCatalog.getString('An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?'), [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Yes'), gettextCatalog.getString('No')], function(ix) {
|
|
if (ix == 2)
|
|
result.Backup.DBPath = resp.data.Path;
|
|
|
|
if (ix == 1 || ix == 2)
|
|
continuation();
|
|
});
|
|
}
|
|
else
|
|
continuation();
|
|
|
|
}, AppUtils.connectionError
|
|
);
|
|
};
|
|
|
|
// Chain calls
|
|
checkForGeneratedPassphrase(function() {
|
|
checkForValidBackupDestination(function() {
|
|
checkForDisabledEncryption(function() {
|
|
warnWeakPassphrase(function() {
|
|
checkForExistingDb(function () {
|
|
EditBackupService.postValidate($scope, postDb);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
function putDb() {
|
|
AppService.put('/backup/' + $routeParams.backupid, result).then(function() {
|
|
$location.path('/');
|
|
}, AppUtils.connectionError);
|
|
}
|
|
|
|
checkForChangedPassphrase(function() {
|
|
checkForValidBackupDestination(putDb);
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
function setupScope(data) {
|
|
$scope.Backup = angular.copy(data.Backup);
|
|
$scope.Schedule = angular.copy(data.Schedule);
|
|
|
|
$scope.Options = {};
|
|
var extopts = {};
|
|
|
|
for(var n in $scope.Backup.Settings) {
|
|
var e = $scope.Backup.Settings[n];
|
|
if (e.Name.indexOf('--') == 0)
|
|
extopts[e.Name] = e.Value;
|
|
else
|
|
$scope.Options[e.Name] = e.Value;
|
|
}
|
|
|
|
var filters = $scope.Backup.Filters;
|
|
$scope.Backup.Filters = [];
|
|
|
|
// If Description is anything other than a string, we are either creating a new
|
|
// backup or something went wrong when retrieving an existing one
|
|
// Either way we should set it to an empty string
|
|
if (typeof $scope.Backup.Description !== 'string') {
|
|
$scope.Backup.Description = '';
|
|
}
|
|
|
|
$scope.Backup.Sources = $scope.Backup.Sources || [];
|
|
|
|
for(var ix in filters)
|
|
$scope.Backup.Filters.push((filters[ix].Include ? '+' : '-') + filters[ix].Expression);
|
|
|
|
$scope.ExcludeLargeFiles = (extopts['--skip-files-larger-than'] || '').trim().length > 0;
|
|
if ($scope.ExcludeLargeFiles)
|
|
$scope.Options['--skip-files-larger-than'] = extopts['--skip-files-larger-than'];
|
|
|
|
var exclattr = (extopts['--exclude-files-attributes'] || '').split(',');
|
|
var dispattr = [];
|
|
var dispmap = {};
|
|
|
|
for (var i = exclattr.length - 1; i >= 0; i--) {
|
|
var cmp = (exclattr[i] || '').trim().toLowerCase();
|
|
|
|
// Remove empty entries
|
|
if (cmp.length == 0) {
|
|
exclattr.splice(i, 1);
|
|
continue;
|
|
}
|
|
|
|
for (var j = scope.fileAttributes.length - 1; j >= 0; j--) {
|
|
if (scope.fileAttributes[j].value == cmp) {
|
|
// Remote duplicates
|
|
if (dispmap[cmp] == null) {
|
|
dispattr.push(scope.fileAttributes[j].value);
|
|
dispmap[cmp] = true;
|
|
}
|
|
exclattr.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$scope.ExcludeAttributes = dispattr;
|
|
if (exclattr.length == 0)
|
|
delete extopts['--exclude-files-attributes'];
|
|
else
|
|
extopts['--exclude-files-attributes'] = exclattr.join(',');
|
|
|
|
$scope.RepeatPasshrase = $scope.Options['passphrase'];
|
|
|
|
$scope.KeepType = '';
|
|
if (($scope.Options['keep-time'] || '').trim().length != 0)
|
|
{
|
|
$scope.KeepType = 'time';
|
|
}
|
|
else if (($scope.Options['keep-versions'] || '').trim().length != 0)
|
|
{
|
|
$scope.Options['keep-versions'] = parseInt($scope.Options['keep-versions']);
|
|
$scope.KeepType = 'versions';
|
|
}
|
|
else if (($scope.Options['retention-policy'] || '').trim().length != 0)
|
|
{
|
|
if (($scope.Options['retention-policy'] || '').trim() == SMART_RETENTION)
|
|
$scope.KeepType = 'smart';
|
|
else
|
|
$scope.KeepType = 'custom';
|
|
}
|
|
|
|
var delopts = ['--skip-files-larger-than', '--no-encryption'];
|
|
for(var n in delopts)
|
|
delete extopts[delopts[n]];
|
|
|
|
$scope.ExtendedOptions = AppUtils.serializeAdvancedOptionsToArray(extopts);
|
|
|
|
$scope.servermodulesettings = {};
|
|
AppUtils.extractServerModuleOptions($scope.ExtendedOptions, $scope.ServerModules, $scope.servermodulesettings, 'SupportedLocalCommands');
|
|
|
|
$scope.showAdvanced = $scope.ExtendedOptions.length > 0;
|
|
|
|
var now = new Date();
|
|
if ($scope.Schedule != null) {
|
|
var time = AppUtils.parseDate($scope.Schedule.Time);
|
|
if (isNaN(time)) {
|
|
time = AppUtils.parseDate("1970-01-01T" + $scope.Schedule.Time);
|
|
if (!isNaN(time))
|
|
time = new Date(now.getFullYear(), now.getMonth(), now.getDate(), time.getHours(), time.getMinutes(), time.getSeconds());
|
|
|
|
if (time < now)
|
|
time = new Date(time.setDate(time.getDate() + 1));
|
|
}
|
|
|
|
if (isNaN(time)) {
|
|
time = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 13, 0, 0);
|
|
if (time < now)
|
|
time = new Date(time.setDate(time.getDate() + 1));
|
|
}
|
|
|
|
$scope.Schedule.Time = time;
|
|
} else {
|
|
time = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 13, 0, 0);
|
|
if (time < now)
|
|
time = new Date(time.setDate(time.getDate() + 1));
|
|
|
|
oldSchedule = {
|
|
Repeat: '1D',
|
|
Time: time
|
|
};
|
|
}
|
|
}
|
|
|
|
function setupServerModules()
|
|
{
|
|
var mods = [];
|
|
if ($scope.SystemInfo.ServerModules != null)
|
|
for(var ix in $scope.SystemInfo.ServerModules)
|
|
{
|
|
var m = $scope.SystemInfo.ServerModules[ix];
|
|
if (m.SupportedLocalCommands != null && m.SupportedLocalCommands.length > 0)
|
|
mods.push(m);
|
|
}
|
|
|
|
$scope.ServerModules = mods;
|
|
};
|
|
|
|
function reloadOptionsList()
|
|
{
|
|
if ($scope.Options == null)
|
|
return;
|
|
|
|
var encmodule = $scope.Options['encryption-module'] || '';
|
|
var compmodule = $scope.Options['compression-module'] || $scope.Options['--compression-module'] || 'zip';
|
|
var backmodule = $scope.Backup.TargetURL || '';
|
|
var ix = backmodule.indexOf(':');
|
|
if (ix > 0)
|
|
backmodule = backmodule.substr(0, ix);
|
|
|
|
$scope.ExtendedOptionList = AppUtils.buildOptionList($scope.SystemInfo, encmodule, compmodule, backmodule);
|
|
setupServerModules();
|
|
|
|
AppUtils.extractServerModuleOptions($scope.ExtendedOptions, $scope.ServerModules, $scope.servermodulesettings, 'SupportedLocalCommands');
|
|
};
|
|
|
|
function checkAllowedDaysConfig()
|
|
{
|
|
if ($scope.Schedule == null || $scope.Schedule.AllowedDays == null)
|
|
return;
|
|
|
|
// Remove invalid values
|
|
var alldays = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
|
|
for (var i = $scope.Schedule.AllowedDays.length - 1; i >= 0; i--)
|
|
if (alldays.indexOf($scope.Schedule.AllowedDays[i]) < 0)
|
|
$scope.Schedule.AllowedDays.splice(i, 1);
|
|
|
|
// Empty and all are the same, but the UI confuses if no days are selected
|
|
if ($scope.Schedule.AllowedDays.length == 0)
|
|
$timeout(function() {
|
|
$scope.Schedule.AllowedDays = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
|
|
});
|
|
};
|
|
|
|
setupServerModules();
|
|
|
|
$scope.$watch("Options['encryption-module']", reloadOptionsList);
|
|
$scope.$watch("Options['compression-module']", reloadOptionsList);
|
|
$scope.$watch("Options['--compression-module']", reloadOptionsList);
|
|
$scope.$watch("Backup.TargetURL", reloadOptionsList);
|
|
$scope.$on('systeminfochanged', reloadOptionsList);
|
|
$scope.$watch('ExcludeLargeFiles', function(enabled) {
|
|
if ($scope.Options != null) {
|
|
if ($scope.Options['--skip-files-larger-than'] == null) {
|
|
$scope.Options['--skip-files-larger-than'] = '100MB';
|
|
}
|
|
$scope.ExcludeFileSize = enabled ? AppUtils.parseSizeString($scope.Options['--skip-files-larger-than']) : null;
|
|
}
|
|
}, true);
|
|
$scope.$watch("Options['--skip-files-larger-than']", function (value) {
|
|
if ($scope.ExcludeLargeFiles) {
|
|
$scope.ExcludeFileSize = AppUtils.parseSizeString(value);
|
|
}
|
|
}, true);
|
|
$scope.$watch("Schedule.AllowedDays", checkAllowedDaysConfig, true);
|
|
|
|
if ($routeParams.backupid == null) {
|
|
|
|
AppService.get('/backupdefaults').then(function(data) {
|
|
|
|
$scope.rawddata = data.data;
|
|
|
|
if ($location.$$path.indexOf('/add-import') == 0 && $rootScope.importConfig != null)
|
|
angular.merge($scope.rawddata, $rootScope.importConfig);
|
|
|
|
setupScope($scope.rawddata);
|
|
|
|
}, function(data) {
|
|
AppUtils.connectionError(gettextCatalog.getString('Failed to read backup defaults:') + ' ', data);
|
|
$location.path('/');
|
|
});
|
|
} else {
|
|
|
|
AppService.get('/backup/' + $routeParams.backupid).then(function(data) {
|
|
|
|
$scope.rawddata = data.data;
|
|
setupScope($scope.rawddata);
|
|
|
|
}, function() {
|
|
AppUtils.connectionError.apply(AppUtils, arguments);
|
|
$location.path('/');
|
|
});
|
|
}
|
|
});
|