Files
cloudron-box/src/js/restore.js
Johannes Zellner b6e00a3107 Do not redirect to error.html if the angular main application fails to init
We now only show the offline banner and retry the application init until
box comes back up
2019-09-05 22:23:28 +02:00

287 lines
12 KiB
JavaScript

'use strict';
/* global angular */
/* global tld */
/* global $ */
// create main application module
var app = angular.module('Application', ['angular-md5', 'ui-notification']);
app.filter('zoneName', function () {
return function (domain) {
return tld.getDomain(domain);
};
});
app.controller('RestoreController', ['$scope', 'Client', function ($scope, Client) {
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
$scope.client = Client;
$scope.busy = false;
$scope.error = {};
$scope.message = ''; // progress
$scope.provider = '';
$scope.bucket = '';
$scope.prefix = '';
$scope.accessKeyId = '';
$scope.secretAccessKey = '';
$scope.gcsKey = { keyFileName: '', content: '' };
$scope.region = '';
$scope.endpoint = '';
$scope.backupFolder = '';
$scope.backupId = '';
$scope.instanceId = '';
$scope.acceptSelfSignedCerts = false;
$scope.format = 'tgz';
// List is from http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
$scope.s3Regions = [
{ name: 'Asia Pacific (Mumbai)', value: 'ap-south-1' },
{ name: 'Asia Pacific (Osaka-Local)', value: 'ap-northeast-3' },
{ name: 'Asia Pacific (Seoul)', value: 'ap-northeast-2' },
{ name: 'Asia Pacific (Singapore)', value: 'ap-southeast-1' },
{ name: 'Asia Pacific (Sydney)', value: 'ap-southeast-2' },
{ name: 'Asia Pacific (Tokyo)', value: 'ap-northeast-1' },
{ name: 'Canada (Central)', value: 'ca-central-1' },
{ name: 'EU (Frankfurt)', value: 'eu-central-1' },
{ name: 'EU (Ireland)', value: 'eu-west-1' },
{ name: 'EU (London)', value: 'eu-west-2' },
{ name: 'EU (Paris)', value: 'eu-west-3' },
{ name: 'EU (Stockholm)', value: 'eu-north-1' },
{ name: 'South America (São Paulo)', value: 'sa-east-1' },
{ name: 'US East (N. Virginia)', value: 'us-east-1' },
{ name: 'US East (Ohio)', value: 'us-east-2' },
{ name: 'US West (N. California)', value: 'us-west-1' },
{ name: 'US West (Oregon)', value: 'us-west-2' },
];
$scope.doSpacesRegions = [
{ name: 'AMS3', value: 'https://ams3.digitaloceanspaces.com' },
{ name: 'FRA1', value: 'https://fra1.digitaloceanspaces.com' },
{ name: 'NYC3', value: 'https://nyc3.digitaloceanspaces.com' },
{ name: 'SFO2', value: 'https://sfo2.digitaloceanspaces.com' },
{ name: 'SGP1', value: 'https://sgp1.digitaloceanspaces.com' }
];
$scope.exoscaleSosRegions = [
{ name: 'AT-VIE-1', value: 'https://sos-at-vie-1.exo.io' },
{ name: 'CH-DK-2', value: 'https://sos-ch-dk-2.exo.io' },
{ name: 'CH-GVA-2', value: 'https://sos-ch-gva-2.exo.io' },
{ name: 'DE-FRA-1', value: 'https://sos-de-fra-1.exo.io' },
];
// https://www.scaleway.com/docs/object-storage-feature/
$scope.scalewayRegions = [
{ name: 'FR-PAR', value: 'https://s3.fr-par.scw.cloud', region: 'fr-par' }, // default
{ name: 'NL-AMS', value: 'https://s3.nl-ams.scw.cloud', region: 'nl-ams' }
];
$scope.wasabiRegions = [
{ name: 'EU Central 1', value: 'https://s3.eu-central-1.wasabisys.com' },
{ name: 'US East 1', value: 'https://s3.wasabisys.com' },
{ name: 'US West 1', value: 'https://s3.us-west-1.wasabisys.com' }
];
$scope.storageProvider = [
{ name: 'Amazon S3', value: 's3' },
{ name: 'DigitalOcean Spaces', value: 'digitalocean-spaces' },
{ name: 'Exoscale SOS', value: 'exoscale-sos' },
{ name: 'Filesystem', value: 'filesystem' },
{ name: 'Google Cloud Storage', value: 'gcs' },
{ name: 'Minio', value: 'minio' },
{ name: 'Scaleway Object Storage', value: 'scaleway-objectstorage' },
{ name: 'S3 API Compatible (v4)', value: 's3-v4-compat' },
{ name: 'Wasabi', value: 'wasabi' }
];
$scope.formats = [
{ name: 'Tarball (zipped)', value: 'tgz' },
{ name: 'rsync', value: 'rsync' }
];
$scope.s3like = function (provider) {
return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat' || provider === 'exoscale-sos'
|| provider === 'digitalocean-spaces' || provider === 'wasabi' || provider === 'scaleway-objectstorage';
};
$scope.restore = function () {
$scope.error = {};
$scope.busy = true;
var backupConfig = {
provider: $scope.provider,
key: $scope.key,
format: $scope.format
};
// only set provider specific fields, this will clear them in the db
if ($scope.s3like(backupConfig.provider)) {
backupConfig.bucket = $scope.bucket;
backupConfig.prefix = $scope.prefix;
backupConfig.accessKeyId = $scope.accessKeyId;
backupConfig.secretAccessKey = $scope.secretAccessKey;
if ($scope.endpoint) backupConfig.endpoint = $scope.endpoint;
if (backupConfig.provider === 's3') {
if ($scope.region) backupConfig.region = $scope.region;
delete backupConfig.endpoint;
} else if (backupConfig.provider === 'minio' || backupConfig.provider === 's3-v4-compat') {
backupConfig.region = 'us-east-1';
backupConfig.acceptSelfSignedCerts = $scope.acceptSelfSignedCerts;
} else if (backupConfig.provider === 'exoscale-sos') {
backupConfig.region = 'us-east-1';
backupConfig.signatureVersion = 'v4';
} else if (backupConfig.provider === 'wasabi') {
backupConfig.region = 'us-east-1';
backupConfig.signatureVersion = 'v4';
} else if (backupConfig.provider === 'scaleway-objectstorage') {
backupConfig.region = $scope.scalewayRegions.find(function (x) { return x.value === $scope.endpoint; }).region;
backupConfig.signatureVersion = 'v4';
} else if (backupConfig.provider === 'digitalocean-spaces') {
backupConfig.region = 'us-east-1';
}
} else if (backupConfig.provider === 'gcs') {
backupConfig.bucket = $scope.bucket;
backupConfig.prefix = $scope.prefix;
try {
var serviceAccountKey = JSON.parse($scope.gcsKey.content);
backupConfig.projectId = serviceAccountKey.project_id;
backupConfig.credentials = {
client_email: serviceAccountKey.client_email,
private_key: serviceAccountKey.private_key
};
if (!backupConfig.projectId || !backupConfig.credentials || !backupConfig.credentials.client_email || !backupConfig.credentials.private_key) {
throw 'fields_missing';
}
} catch (e) {
$scope.error.generic = 'Cannot parse Google Service Account Key: ' + e.message;
$scope.error.gcsKeyInput = true;
$scope.busy = false;
return;
}
} else if (backupConfig.provider === 'filesystem') {
backupConfig.backupFolder = $scope.backupFolder;
}
if ($scope.backupId.indexOf('/') === -1) {
$scope.error.generic = 'Backup id must include the directory path';
$scope.error.backupId = true;
$scope.busy = false;
return;
}
var version = $scope.backupId.match(/_v(\d+.\d+.\d+)/);
if (!version) {
$scope.error.generic = 'Backup id is missing version information';
$scope.error.backupId = true;
$scope.busy = false;
return;
}
Client.restore(backupConfig, $scope.backupId.replace(/\.tar\.gz(\.enc)?$/, ''), version ? version[1] : '', function (error) {
$scope.busy = false;
if (error) {
if (error.statusCode === 424) {
$scope.error.generic = error.message;
if (error.message.indexOf('AWS Access Key Id') !== -1) {
$scope.error.accessKeyId = true;
$scope.accessKeyId = '';
$scope.configureBackupForm.accessKeyId.$setPristine();
$('#inputConfigureBackupAccessKeyId').focus();
} else if (error.message.indexOf('not match the signature') !== -1 ) {
$scope.error.secretAccessKey = true;
$scope.secretAccessKey = '';
$scope.configureBackupForm.secretAccessKey.$setPristine();
$('#inputConfigureBackupSecretAccessKey').focus();
} else if (error.message.toLowerCase() === 'access denied') {
$scope.error.bucket = true;
$scope.bucket = '';
$scope.configureBackupForm.bucket.$setPristine();
$('#inputConfigureBackupBucket').focus();
} else if (error.message.indexOf('ECONNREFUSED') !== -1) {
$scope.error.generic = 'Unknown region';
$scope.error.region = true;
$scope.configureBackupForm.region.$setPristine();
$('#inputConfigureBackupDORegion').focus();
} else if (error.message.toLowerCase() === 'wrong region') {
$scope.error.generic = 'Wrong S3 Region';
$scope.error.region = true;
$scope.configureBackupForm.region.$setPristine();
$('#inputConfigureBackupS3Region').focus();
} else {
$('#inputConfigureBackupBucket').focus();
}
} else {
$scope.error.generic = error.message;
}
return;
}
waitForRestore();
});
}
function waitForRestore() {
$scope.busy = true;
Client.getStatus(function (error, status) {
if (!error && !status.restore.active) { // restore finished
if (status.restore.errorMessage) {
$scope.busy = false;
$scope.error.generic = status.restore.errorMessage;
} else { // restore worked, redirect to admin page
window.location.href = '/';
}
return;
}
$scope.message = status.restore.message;
setTimeout(waitForRestore, 5000);
});
}
function readFileLocally(obj, file, fileName) {
return function (event) {
$scope.$apply(function () {
obj[file] = null;
obj[fileName] = event.target.files[0].name;
var reader = new FileReader();
reader.onload = function (result) {
if (!result.target || !result.target.result) return console.error('Unable to read local file');
obj[file] = result.target.result;
};
reader.readAsText(event.target.files[0]);
});
};
}
document.getElementById('gcsKeyFileInput').onchange = readFileLocally($scope.gcsKey, 'content', 'keyFileName');
function init() {
Client.getStatus(function (error, status) {
if (error) return Client.initError(error, init);
if (status.restore.active) return waitForRestore();
if (status.restore.errorMessage) $scope.error.generic = status.restore.errorMessage;
if (status.activated) {
window.location.href = '/';
return;
}
$scope.instanceId = search.instanceId;
$scope.initialized = true;
});
}
init();
}]);