duplicati/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js
Kenneth Skovhede 45f902594e Mask passwords to avoid leaking them in the UI
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
2025-09-04 22:03:10 +02:00

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('/');
});
}
});