2018-06-07 14:22:48 +02:00
'use strict' ;
2022-12-24 14:48:46 +01:00
/* global $, angular, TASK_TYPES, SECRET_PLACEHOLDER, STORAGE_PROVIDERS, BACKUP_FORMATS, APP_TYPES */
2024-09-25 12:21:42 +02:00
/* global REGIONS_S3, REGIONS_WASABI, REGIONS_DIGITALOCEAN, REGIONS_EXOSCALE, REGIONS_SCALEWAY, REGIONS_LINODE, REGIONS_OVH, REGIONS_IONOS, REGIONS_UPCLOUD, REGIONS_VULTR , REGIONS_CONTABO, REGIONS_HETZNER */
2024-12-10 11:53:29 +01:00
/* global async, ERROR */
2019-01-22 10:54:03 +01:00
angular . module ( 'Application' ) . controller ( 'BackupsController' , [ '$scope' , '$location' , '$rootScope' , '$timeout' , 'Client' , function ( $scope , $location , $rootScope , $timeout , Client ) {
2020-02-24 12:56:13 +01:00
Client . onReady ( function ( ) { if ( ! Client . getUserInfo ( ) . isAtLeastAdmin ) $location . path ( '/' ) ; } ) ;
2018-06-07 14:22:48 +02:00
2020-11-14 11:01:46 +01:00
$scope . SECRET _PLACEHOLDER = SECRET _PLACEHOLDER ;
2024-02-20 22:12:13 +01:00
$scope . MIN _MEMORY _LIMIT = 1024 * 1024 * 1024 ; // 1 GB
2024-03-12 16:44:08 +01:00
$scope . MAX _MEMORY _LIMIT = $scope . MIN _MEMORY _LIMIT ; // set later
2020-11-14 11:01:46 +01:00
2018-06-07 14:22:48 +02:00
$scope . config = Client . getConfig ( ) ;
2018-09-05 14:37:54 -07:00
$scope . user = Client . getUserInfo ( ) ;
2020-09-10 00:07:12 -07:00
$scope . memory = null ; // { memory, swap }
2023-04-30 17:21:18 +02:00
$scope . mountStatus = null ; // { state, message }
2020-04-03 10:36:51 -07:00
$scope . manualBackupApps = [ ] ;
2024-09-08 16:35:08 +02:00
$scope . currentTimeZone = '' ;
2020-04-03 10:36:51 -07:00
2018-06-07 14:22:48 +02:00
$scope . backupConfig = { } ;
$scope . backups = [ ] ;
2023-05-03 16:50:07 +02:00
$scope . backupTasks = [ ] ;
$scope . cleanupTasks = [ ] ;
2018-06-07 14:22:48 +02:00
2024-12-10 11:53:29 +01:00
$scope . domains = [ ] ;
2022-09-27 13:31:13 +02:00
$scope . s3Regions = REGIONS _S3 ;
$scope . wasabiRegions = REGIONS _WASABI ;
$scope . doSpacesRegions = REGIONS _DIGITALOCEAN ;
$scope . exoscaleSosRegions = REGIONS _EXOSCALE ;
$scope . scalewayRegions = REGIONS _SCALEWAY ;
$scope . linodeRegions = REGIONS _LINODE ;
$scope . ovhRegions = REGIONS _OVH ;
$scope . ionosRegions = REGIONS _IONOS ;
$scope . upcloudRegions = REGIONS _UPCLOUD ;
$scope . vultrRegions = REGIONS _VULTR ;
2023-08-25 07:59:40 +05:30
$scope . contaboRegions = REGIONS _CONTABO ;
2024-09-25 12:21:42 +02:00
$scope . hetznerRegions = REGIONS _HETZNER ;
2022-09-27 13:31:13 +02:00
2022-12-24 11:10:04 +01:00
$scope . storageProviders = STORAGE _PROVIDERS . concat ( [
2020-06-05 12:48:27 +02:00
{ name : 'No-op (Only for testing)' , value : 'noop' }
2022-09-27 13:31:13 +02:00
] ) ;
2018-06-07 14:22:48 +02:00
2023-07-12 10:01:53 +05:30
$scope . backupRetentions = [
2020-05-14 16:45:52 -07:00
{ name : '2 days' , value : { keepWithinSecs : 2 * 24 * 60 * 60 } } ,
{ name : '1 week' , value : { keepWithinSecs : 7 * 24 * 60 * 60 } } , // default
{ name : '1 month' , value : { keepWithinSecs : 30 * 24 * 60 * 60 } } ,
2022-05-26 08:38:18 -07:00
{ name : '3 months' , value : { keepWithinSecs : 3 * 30 * 24 * 60 * 60 } } ,
2020-05-21 14:09:06 -07:00
{ name : '2 daily, 4 weekly' , value : { keepDaily : 2 , keepWeekly : 4 } } ,
2020-05-14 21:36:22 -07:00
{ name : '3 daily, 4 weekly, 6 monthly' , value : { keepDaily : 3 , keepWeekly : 4 , keepMonthly : 6 } } ,
{ name : '7 daily, 4 weekly, 12 monthly' , value : { keepDaily : 7 , keepWeekly : 4 , keepMonthly : 12 } } ,
2020-05-14 16:45:52 -07:00
{ name : 'Forever' , value : { keepWithinSecs : - 1 } }
2018-06-07 14:22:48 +02:00
] ;
2020-07-28 21:48:24 -07:00
// values correspond to cron days
2020-07-29 15:09:06 -07:00
$scope . cronDays = [
2020-07-28 21:48:24 -07:00
{ name : 'Sunday' , value : 0 } ,
{ name : 'Monday' , value : 1 } ,
{ name : 'Tuesday' , value : 2 } ,
{ name : 'Wednesday' , value : 3 } ,
{ name : 'Thursday' , value : 4 } ,
{ name : 'Friday' , value : 5 } ,
{ name : 'Saturday' , value : 6 } ,
] ;
2021-03-31 14:42:10 +02:00
// generates 24h time sets (instead of american 12h) to avoid having to translate everything to locales eg. 12:00
$scope . cronHours = Array . from ( { length : 24 } ) . map ( function ( v , i ) { return { name : ( i < 10 ? '0' : '' ) + i + ':00' , value : i } ; } ) ;
2018-08-13 22:40:02 -07:00
2022-09-27 13:31:13 +02:00
$scope . formats = BACKUP _FORMATS ;
2018-06-07 14:22:48 +02:00
$scope . prettyProviderName = function ( provider ) {
switch ( provider ) {
case 'caas' : return 'Managed Cloudron' ;
default : return provider ;
}
} ;
2020-07-28 21:48:24 -07:00
$scope . prettyBackupSchedule = function ( pattern ) {
if ( ! pattern ) return '' ;
var tmp = pattern . split ( ' ' ) ;
var hours = tmp [ 2 ] . split ( ',' ) , days = tmp [ 5 ] . split ( ',' ) ;
var prettyDay ;
if ( days . length === 7 || days [ 0 ] === '*' ) {
prettyDay = 'Everyday' ;
} else {
2020-07-29 15:09:06 -07:00
prettyDay = days . map ( function ( day ) { return $scope . cronDays [ parseInt ( day , 10 ) ] . name . substr ( 0 , 3 ) ; } ) . join ( ',' ) ;
2020-07-28 21:48:24 -07:00
}
2020-07-29 15:09:06 -07:00
var prettyHour = hours . map ( function ( hour ) { return $scope . cronHours [ parseInt ( hour , 10 ) ] . name ; } ) . join ( ',' ) ;
2020-07-28 21:48:24 -07:00
return prettyDay + ' at ' + prettyHour ;
2020-05-19 16:13:20 +02:00
} ;
2023-07-12 10:01:53 +05:30
$scope . prettyBackupRetention = function ( retention ) {
var tmp = $scope . backupRetentions . find ( function ( p ) { return angular . equals ( p . value , retention ) ; } ) ;
2020-05-19 16:13:20 +02:00
return tmp ? tmp . name : '' ;
} ;
2021-10-11 17:45:55 +02:00
$scope . remount = {
busy : false ,
error : null ,
submit : function ( ) {
if ( ! $scope . mountlike ( $scope . backupConfig . provider ) ) return ;
$scope . remount . busy = true ;
$scope . remount . error = null ;
Client . remountBackupStorage ( function ( error ) {
if ( error ) {
console . error ( 'Failed to remount backup storage.' , error ) ;
$scope . remount . error = error . message ;
}
// give the backend some time
$timeout ( function ( ) {
$scope . remount . busy = false ;
getBackupConfig ( ) ;
} , 2000 ) ;
} ) ;
}
} ;
2018-06-07 14:22:48 +02:00
$scope . createBackup = {
busy : false ,
percent : 0 ,
message : '' ,
errorMessage : '' ,
2018-12-08 21:45:49 -08:00
taskId : '' ,
2018-06-07 14:22:48 +02:00
2023-05-02 15:02:41 +02:00
init : function ( ) {
Client . getLatestTaskByType ( TASK _TYPES . TASK _BACKUP , function ( error , task ) {
2018-12-08 20:21:11 -08:00
if ( error ) return console . error ( error ) ;
if ( ! task ) return ;
2018-12-08 21:45:49 -08:00
$scope . createBackup . taskId = task . id ;
$scope . createBackup . updateStatus ( ) ;
2018-12-08 20:21:11 -08:00
} ) ;
} ,
2018-12-08 21:45:49 -08:00
updateStatus : function ( ) {
Client . getTask ( $scope . createBackup . taskId , function ( error , data ) {
if ( error ) return window . setTimeout ( $scope . createBackup . updateStatus , 5000 ) ;
2018-06-07 14:22:48 +02:00
2018-11-29 23:13:58 -08:00
if ( ! data . active ) {
2018-06-07 14:22:48 +02:00
$scope . createBackup . busy = false ;
$scope . createBackup . message = '' ;
$scope . createBackup . percent = 100 ; // indicates that 'result' is valid
2019-08-30 14:24:59 -07:00
$scope . createBackup . errorMessage = data . success ? '' : data . error . message ;
2018-06-07 14:22:48 +02:00
2023-05-03 16:50:07 +02:00
getBackupTasks ( ) ;
2018-06-07 14:22:48 +02:00
return fetchBackups ( ) ;
}
$scope . createBackup . busy = true ;
2018-11-19 17:34:14 -08:00
$scope . createBackup . percent = data . percent ;
$scope . createBackup . message = data . message ;
2019-04-03 11:45:56 -07:00
window . setTimeout ( $scope . createBackup . updateStatus , 3000 ) ;
2018-06-07 14:22:48 +02:00
} ) ;
} ,
2018-11-17 20:04:41 -08:00
startBackup : function ( ) {
2018-06-07 14:22:48 +02:00
$scope . createBackup . busy = true ;
$scope . createBackup . percent = 0 ;
$scope . createBackup . message = '' ;
$scope . createBackup . errorMessage = '' ;
2018-12-08 20:21:11 -08:00
Client . startBackup ( function ( error , taskId ) {
2018-06-07 14:22:48 +02:00
if ( error ) {
if ( error . statusCode === 409 && error . message . indexOf ( 'full_backup' ) !== - 1 ) {
$scope . createBackup . errorMessage = 'Backup already in progress. Please retry later.' ;
} else if ( error . statusCode === 409 ) {
$scope . createBackup . errorMessage = 'App task is currently in progress. Please retry later.' ;
} else {
console . error ( error ) ;
$scope . createBackup . errorMessage = error . message ;
}
$scope . createBackup . busy = false ;
$ ( '#createBackupFailedModal' ) . modal ( 'show' ) ;
return ;
}
2023-05-03 16:50:07 +02:00
getBackupTasks ( ) ;
2018-12-08 21:45:49 -08:00
$scope . createBackup . taskId = taskId ;
$scope . createBackup . updateStatus ( ) ;
2018-06-07 14:22:48 +02:00
} ) ;
2018-11-17 20:04:41 -08:00
} ,
2020-05-14 22:42:41 -07:00
stopTask : function ( ) {
2019-01-24 15:55:21 -08:00
Client . stopTask ( $scope . createBackup . taskId , function ( error ) {
2018-11-17 20:04:41 -08:00
if ( error ) {
if ( error . statusCode === 409 ) {
$scope . createBackup . errorMessage = 'No backup is currently in progress' ;
} else {
console . error ( error ) ;
$scope . createBackup . errorMessage = error . message ;
}
$scope . createBackup . busy = false ;
2023-05-03 16:50:07 +02:00
getBackupTasks ( ) ;
2018-11-17 20:04:41 -08:00
return ;
}
} ) ;
2018-06-07 14:22:48 +02:00
}
} ;
2023-05-02 15:02:41 +02:00
$scope . cleanupBackups = {
busy : false ,
taskId : 0 ,
init : function ( ) {
Client . getLatestTaskByType ( TASK _TYPES . TASK _CLEAN _BACKUPS , function ( error , task ) {
if ( error ) return console . error ( error ) ;
if ( ! task ) return ;
$scope . cleanupBackups . taskId = task . id ;
$scope . cleanupBackups . updateStatus ( ) ;
2023-05-03 16:50:07 +02:00
getCleanupTasks ( ) ;
2023-05-02 15:02:41 +02:00
} ) ;
} ,
updateStatus : function ( ) {
Client . getTask ( $scope . cleanupBackups . taskId , function ( error , data ) {
if ( error ) return window . setTimeout ( $scope . cleanupBackups . updateStatus , 5000 ) ;
if ( ! data . active ) {
$scope . cleanupBackups . busy = false ;
2023-05-03 16:50:07 +02:00
getCleanupTasks ( ) ;
fetchBackups ( ) ;
return ;
2023-05-02 15:02:41 +02:00
}
$scope . cleanupBackups . busy = true ;
$scope . cleanupBackups . message = data . message ;
window . setTimeout ( $scope . cleanupBackups . updateStatus , 3000 ) ;
} ) ;
} ,
ask : function ( ) {
$ ( '#cleanupBackupsModal' ) . modal ( 'show' ) ;
} ,
start : function ( ) {
$scope . cleanupBackups . busy = true ;
$ ( '#cleanupBackupsModal' ) . modal ( 'hide' ) ;
Client . cleanupBackups ( function ( error , taskId ) {
if ( error ) console . error ( error ) ;
$scope . cleanupBackups . taskId = taskId ;
$scope . cleanupBackups . updateStatus ( ) ;
2023-05-03 16:50:07 +02:00
getCleanupTasks ( ) ;
2023-05-02 15:02:41 +02:00
} ) ;
}
} ;
2024-12-10 14:46:30 +01:00
$scope . archiveList = {
2024-12-10 11:53:29 +01:00
ready : false ,
archives : [ ] ,
fetch : function ( ) {
Client . listArchives ( function ( error , archives ) {
if ( error ) Client . error ( error ) ;
2024-12-10 14:46:30 +01:00
$scope . archiveList . archives = archives ;
$scope . archiveList . ready = true ;
2024-12-10 13:25:44 +01:00
// ensure we use the full api oprigin
2024-12-10 14:46:30 +01:00
$scope . archiveList . archives . forEach ( a => {
2024-12-10 13:25:44 +01:00
a . iconUrl = window . cloudronApiOrigin + a . iconUrl ;
} ) ;
2024-12-10 11:53:29 +01:00
} ) ;
} ,
2024-12-10 14:26:07 +01:00
} ;
2024-12-10 11:53:29 +01:00
2024-12-10 14:26:07 +01:00
$scope . archiveDelete = {
busy : false ,
error : { } ,
archive : null ,
2024-12-19 14:21:39 +01:00
title : '' ,
fqdn : '' ,
2024-12-10 14:26:07 +01:00
ask : function ( archive ) {
$scope . archiveDelete . busy = false ;
$scope . archiveDelete . error = { } ;
$scope . archiveDelete . archive = archive ;
2024-12-19 14:21:39 +01:00
$scope . archiveDelete . title = archive . manifest . title ;
$scope . archiveDelete . fqdn = archive . appConfig ? . fqdn || '-' ;
2024-12-10 14:26:07 +01:00
$ ( '#archiveDeleteModal' ) . modal ( 'show' ) ;
} ,
submit : function ( ) {
$scope . archiveDelete . busy = true ;
$scope . archiveDelete . error = { } ;
Client . deleteArchive ( $scope . archiveDelete . archive . id , function ( error ) {
$scope . archiveDelete . busy = false ;
if ( error ) return console . error ( 'Unable to delete archive.' , error . statusCode , error . message ) ;
2024-12-10 14:46:30 +01:00
$scope . archiveList . fetch ( ) ;
2024-12-10 14:26:07 +01:00
$ ( '#archiveDeleteModal' ) . modal ( 'hide' ) ;
2024-12-10 11:53:29 +01:00
} ) ;
2024-12-10 14:26:07 +01:00
}
2024-12-10 11:53:29 +01:00
} ;
// keep in sync with app.js
2024-12-10 14:46:30 +01:00
$scope . archiveRestore = {
2024-12-10 11:53:29 +01:00
busy : false ,
error : { } ,
archive : null ,
2024-12-19 14:21:39 +01:00
manifest : null ,
appStoreId : '' ,
fqdn : '' ,
2024-12-10 14:46:30 +01:00
2024-12-10 11:53:29 +01:00
subdomain : '' ,
domain : null ,
secondaryDomains : { } ,
needsOverwrite : false ,
overwriteDns : false ,
ports : { } ,
portsEnabled : { } ,
portInfo : { } ,
2024-12-10 14:46:30 +01:00
accessRestriction : { users : [ ] , groups : [ ] } ,
2024-12-10 11:53:29 +01:00
init : function ( ) {
Client . getDomains ( function ( error , domains ) {
if ( error ) return console . error ( 'Unable to get domain listing.' , error ) ;
$scope . domains = domains ;
} ) ;
} ,
show : function ( archive ) {
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . error = { } ;
$scope . archiveRestore . archive = archive ;
2024-12-19 14:21:39 +01:00
$scope . archiveRestore . manifest = archive . manifest ;
2024-12-10 11:53:29 +01:00
2024-12-19 14:21:39 +01:00
const app = archive . appConfig || {
subdomain : '' ,
domain : $scope . domains [ 0 ] . domain ,
secondaryDomains : [ ] ,
portBindings : { }
} ; // pre-8.2 backups do not have appConfig
$scope . archiveRestore . fqdn = archive . appConfig ? . fqdn || '-' ;
$scope . archiveRestore . subdomain = app . subdomain ;
$scope . archiveRestore . domain = $scope . domains . find ( function ( d ) { return app . domain === d . domain ; } ) ; // try to pre-select the app's domain
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . needsOverwrite = false ;
$scope . archiveRestore . overwriteDns = false ;
2024-12-10 11:53:29 +01:00
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . secondaryDomains = { } ;
2024-12-10 11:53:29 +01:00
2024-12-19 14:21:39 +01:00
var httpPorts = archive . manifest . httpPorts || { } ;
2024-12-10 11:53:29 +01:00
for ( var env2 in httpPorts ) {
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . secondaryDomains [ env2 ] = {
2024-12-10 11:53:29 +01:00
subdomain : httpPorts [ env2 ] . defaultValue || '' ,
2024-12-10 14:46:30 +01:00
domain : $scope . archiveRestore . domain
2024-12-10 11:53:29 +01:00
} ;
}
2024-12-10 19:27:07 +01:00
// now fill secondaryDomains with real values, if it exists
2024-12-19 14:21:39 +01:00
app . secondaryDomains . forEach ( function ( sd ) {
2024-12-10 19:27:07 +01:00
$scope . archiveRestore . secondaryDomains [ sd . environmentVariable ] = {
subdomain : sd . subdomain ,
domain : $scope . domains . find ( function ( d ) { return sd . domain === d . domain ; } )
} ;
} ) ;
2024-12-10 11:53:29 +01:00
2024-12-19 14:21:39 +01:00
$scope . archiveRestore . portInfo = angular . extend ( { } , archive . manifest . tcpPorts , archive . manifest . udpPorts ) ; // Portbinding map only for information
2024-12-10 11:53:29 +01:00
// set default ports
2024-12-10 14:46:30 +01:00
for ( var env in $scope . archiveRestore . portInfo ) {
2024-12-19 14:21:39 +01:00
if ( app . portBindings [ env ] ) { // was enabled in the app
$scope . archiveRestore . ports [ env ] = app . portBindings [ env ] . hostPort ;
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . portsEnabled [ env ] = true ;
} else {
$scope . archiveRestore . ports [ env ] = $scope . archiveRestore . portInfo [ env ] . defaultValue || 0 ;
$scope . archiveRestore . portsEnabled [ env ] = false ;
}
2024-12-10 11:53:29 +01:00
}
2024-12-10 14:46:30 +01:00
$ ( '#restoreArchiveModal' ) . modal ( 'show' ) ;
2024-12-10 11:53:29 +01:00
} ,
submit : function ( ) {
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . busy = true ;
2024-12-10 11:53:29 +01:00
var secondaryDomains = { } ;
2024-12-10 14:46:30 +01:00
for ( var env2 in $scope . archiveRestore . secondaryDomains ) {
2024-12-10 11:53:29 +01:00
secondaryDomains [ env2 ] = {
2024-12-10 14:46:30 +01:00
subdomain : $scope . archiveRestore . secondaryDomains [ env2 ] . subdomain ,
domain : $scope . archiveRestore . secondaryDomains [ env2 ] . domain . domain
2024-12-10 11:53:29 +01:00
} ;
}
// only use enabled ports
var finalPorts = { } ;
2024-12-10 14:46:30 +01:00
for ( var env in $scope . archiveRestore . ports ) {
if ( $scope . archiveRestore . portsEnabled [ env ] ) {
finalPorts [ env ] = $scope . archiveRestore . ports [ env ] ;
2024-12-10 11:53:29 +01:00
}
}
var data = {
2024-12-10 14:46:30 +01:00
subdomain : $scope . archiveRestore . subdomain ,
domain : $scope . archiveRestore . domain . domain ,
2024-12-10 11:53:29 +01:00
secondaryDomains : secondaryDomains ,
ports : finalPorts ,
2024-12-10 14:46:30 +01:00
overwriteDns : $scope . archiveRestore . overwriteDns ,
2024-12-10 11:53:29 +01:00
} ;
var allDomains = [ { domain : data . domain , subdomain : data . subdomain } ] . concat ( Object . keys ( secondaryDomains ) . map ( function ( k ) {
return {
domain : secondaryDomains [ k ] . domain ,
subdomain : secondaryDomains [ k ] . subdomain
} ;
} ) ) ;
async . eachSeries ( allDomains , function ( domain , callback ) {
2024-12-10 14:46:30 +01:00
if ( $scope . archiveRestore . overwriteDns ) return callback ( ) ;
2024-12-10 11:53:29 +01:00
Client . checkDNSRecords ( domain . domain , domain . subdomain , function ( error , result ) {
if ( error ) return callback ( error ) ;
var fqdn = domain . subdomain + '.' + domain . domain ;
if ( result . error ) {
if ( result . error . reason === ERROR . ACCESS _DENIED ) return callback ( { type : 'provider' , fqdn : fqdn , message : 'DNS credentials for ' + domain . domain + ' are invalid. Update it in Domains & Certs view' } ) ;
return callback ( { type : 'provider' , fqdn : fqdn , message : result . error . message } ) ;
}
if ( result . needsOverwrite ) {
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . needsOverwrite = true ;
$scope . archiveRestore . overwriteDns = true ;
2024-12-10 11:53:29 +01:00
return callback ( { type : 'externally_exists' , fqdn : fqdn , message : 'DNS Record already exists. Confirm that the domain is not in use for services external to Cloudron' } ) ;
}
callback ( ) ;
} ) ;
} , function ( error ) {
if ( error ) {
if ( error . type ) {
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . error . location = error ;
$scope . archiveRestore . busy = false ;
2024-12-10 11:53:29 +01:00
} else {
Client . error ( error ) ;
}
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . error . location = error ;
$scope . archiveRestore . busy = false ;
2024-12-10 11:53:29 +01:00
return ;
}
2024-12-10 14:46:30 +01:00
Client . unarchiveApp ( $scope . archiveRestore . archive . id , data , function ( error /*, newApp */ ) {
$scope . archiveRestore . busy = false ;
2024-12-10 11:53:29 +01:00
if ( error ) {
var errorMessage = error . message . toLowerCase ( ) ;
if ( errorMessage . indexOf ( 'port' ) !== - 1 ) {
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . error . port = error . message ;
2024-12-10 11:53:29 +01:00
} else if ( error . message . indexOf ( 'location' ) !== - 1 || error . message . indexOf ( 'subdomain' ) !== - 1 ) {
// TODO extract fqdn from error message, currently we just set it always to the main location
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . error . location = { type : 'internally_exists' , fqdn : data . subdomain + '.' + data . domain , message : error . message } ;
2024-12-10 11:53:29 +01:00
$ ( '#cloneLocationInput' ) . focus ( ) ;
} else {
Client . error ( error ) ;
}
return ;
}
2024-12-10 14:46:30 +01:00
$ ( '#restoreArchiveModal' ) . modal ( 'hide' ) ;
2024-12-10 11:53:29 +01:00
$location . path ( '/apps' ) ;
} ) ;
} ) ;
}
2020-05-14 22:42:41 -07:00
} ;
2018-06-07 14:22:48 +02:00
$scope . s3like = function ( provider ) {
2019-04-12 10:04:26 -07:00
return provider === 's3' || provider === 'minio' || provider === 's3-v4-compat'
2024-09-25 12:21:42 +02:00
|| provider === 'exoscale-sos' || provider === 'digitalocean-spaces' || provider === 'hetzner-objectstorage'
2022-09-27 19:40:58 +02:00
|| provider === 'scaleway-objectstorage' || provider === 'wasabi' || provider === 'backblaze-b2' || provider === 'cloudflare-r2'
2021-09-27 10:01:09 -07:00
|| provider === 'linode-objectstorage' || provider === 'ovh-objectstorage' || provider === 'ionos-objectstorage'
2023-08-25 07:59:40 +05:30
|| provider === 'vultr-objectstorage' || provider === 'upcloud-objectstorage' || provider === 'idrive-e2'
|| provider === 'contabo-objectstorage' ;
2018-06-07 14:22:48 +02:00
} ;
2020-06-22 15:51:18 +02:00
$scope . mountlike = function ( provider ) {
2023-08-08 13:21:56 +02:00
return provider === 'sshfs' || provider === 'cifs' || provider === 'nfs' || provider === 'mountpoint' || provider === 'ext4' || provider === 'xfs' || provider === 'disk' ;
2020-06-22 15:51:18 +02:00
} ;
2020-04-20 18:21:35 +02:00
// https://stackoverflow.com/questions/3665115/how-to-create-a-file-in-memory-for-user-to-download-but-not-through-server#18197341
function download ( filename , text ) {
var element = document . createElement ( 'a' ) ;
element . setAttribute ( 'href' , 'data:text/plain;charset=utf-8,' + encodeURIComponent ( text ) ) ;
element . setAttribute ( 'download' , filename ) ;
element . style . display = 'none' ;
document . body . appendChild ( element ) ;
element . click ( ) ;
document . body . removeChild ( element ) ;
}
2024-12-11 10:54:51 +01:00
$scope . downloadConfig = function ( backup , isArchive ) { // can also be a archive object
2020-05-14 23:19:17 +02:00
// secrets and tokens already come with placeholder characters we remove them
var tmp = {
2022-04-05 09:41:09 -07:00
remotePath : backup . remotePath ,
2020-05-14 23:19:17 +02:00
encrypted : ! ! $scope . backupConfig . password // we add this just to help the import UI
} ;
Object . keys ( $scope . backupConfig ) . forEach ( function ( k ) {
if ( $scope . backupConfig [ k ] !== SECRET _PLACEHOLDER ) tmp [ k ] = $scope . backupConfig [ k ] ;
} ) ;
2024-12-11 10:54:51 +01:00
let filename ;
if ( isArchive ) {
filename = ` ${ backup . appConfig . fqdn } -archive-config- ${ ( new Date ( backup . creationTime ) ) . toISOString ( ) . split ( 'T' ) [ 0 ] } .json ` ;
} else {
filename = ` ${ $scope . config . adminFqdn } -backup-config- ${ ( new Date ( backup . creationTime ) ) . toISOString ( ) . split ( 'T' ) [ 0 ] } .json ` ;
}
2022-02-22 16:28:14 +01:00
download ( filename , JSON . stringify ( tmp , null , 4 ) ) ;
2020-04-20 18:21:35 +02:00
} ;
2022-04-05 14:41:41 +02:00
$scope . editBackup = {
busy : false ,
error : null ,
backup : null ,
label : '' ,
2022-04-07 15:31:41 +02:00
persist : false ,
2022-04-05 14:41:41 +02:00
show : function ( backup ) {
$scope . editBackup . backup = backup ;
$scope . editBackup . label = backup . label ;
2022-04-07 15:31:41 +02:00
$scope . editBackup . persist = backup . preserveSecs === - 1 ;
2022-04-05 14:41:41 +02:00
$scope . editBackup . error = null ;
$scope . editBackup . busy = false ;
$ ( '#editBackupModal' ) . modal ( 'show' ) ;
} ,
submit : function ( ) {
$scope . editBackup . error = null ;
$scope . editBackup . busy = true ;
2022-04-07 15:31:41 +02:00
Client . editBackup ( $scope . editBackup . backup . id , $scope . editBackup . label , $scope . editBackup . persist ? - 1 : 0 , function ( error ) {
2022-04-05 14:41:41 +02:00
$scope . editBackup . busy = false ;
if ( error ) return $scope . editBackup . error = error . message ;
fetchBackups ( ) ;
$ ( '#editBackupModal' ) . modal ( 'hide' ) ;
} ) ;
}
} ;
2020-05-22 13:48:26 +02:00
$scope . backupDetails = {
backup : null ,
show : function ( backup ) {
$scope . backupDetails . backup = backup ;
$ ( '#backupDetailsModal' ) . modal ( 'show' ) ;
}
} ;
2023-07-12 10:01:53 +05:30
$scope . backupPolicy = {
2020-05-19 16:13:20 +02:00
busy : false ,
error : { } ,
2023-07-12 10:01:53 +05:30
currentPolicy : null ,
retention : null ,
2020-07-28 21:48:24 -07:00
days : [ ] ,
hours : [ ] ,
2020-05-19 16:13:20 +02:00
2023-07-12 10:01:53 +05:30
init : function ( ) {
Client . getBackupPolicy ( function ( error , policy ) {
if ( error ) Client . error ( error ) ;
$scope . backupPolicy . currentPolicy = policy ;
} ) ;
} ,
2020-05-19 16:13:20 +02:00
show : function ( ) {
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . error = { } ;
$scope . backupPolicy . busy = false ;
2020-06-05 14:35:34 +02:00
2023-07-12 10:01:53 +05:30
var selectedRetention = $scope . backupRetentions . find ( function ( x ) { return angular . equals ( x . value , $scope . backupPolicy . currentPolicy . retention ) ; } ) ;
if ( ! selectedRetention ) selectedRetention = $scope . backupRetentions [ 0 ] ;
2020-06-05 14:35:34 +02:00
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . retention = selectedRetention . value ;
2020-07-28 21:48:24 -07:00
2023-07-12 10:01:53 +05:30
var tmp = $scope . backupPolicy . currentPolicy . schedule . split ( ' ' ) ;
2020-07-28 21:48:24 -07:00
var hours = tmp [ 2 ] . split ( ',' ) , days = tmp [ 5 ] . split ( ',' ) ;
2020-07-29 09:27:55 -07:00
if ( days [ 0 ] === '*' ) {
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . days = angular . copy ( $scope . cronDays , [ ] ) ;
2020-07-29 09:27:55 -07:00
} else {
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . days = days . map ( function ( day ) { return $scope . cronDays [ parseInt ( day , 10 ) ] ; } ) ;
2020-07-29 09:27:55 -07:00
}
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . hours = hours . map ( function ( hour ) { return $scope . cronHours [ parseInt ( hour , 10 ) ] ; } ) ;
2020-05-19 16:13:20 +02:00
2023-07-12 10:01:53 +05:30
$ ( '#backupPolicyModal' ) . modal ( 'show' ) ;
2020-05-19 16:13:20 +02:00
} ,
2023-01-19 12:43:11 +01:00
valid : function ( ) {
2023-07-12 10:01:53 +05:30
return $scope . backupPolicy . days . length && $scope . backupPolicy . hours . length ;
2023-01-19 12:43:11 +01:00
} ,
2020-05-19 16:13:20 +02:00
submit : function ( ) {
2023-07-12 10:01:53 +05:30
if ( ! $scope . backupPolicy . days . length ) return ;
if ( ! $scope . backupPolicy . hours . length ) return ;
2023-01-19 12:43:11 +01:00
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . error = { } ;
$scope . backupPolicy . busy = true ;
2020-07-28 21:48:24 -07:00
var daysPattern ;
2023-07-12 10:01:53 +05:30
if ( $scope . backupPolicy . days . length === 7 ) daysPattern = '*' ;
else daysPattern = $scope . backupPolicy . days . map ( function ( d ) { return d . value ; } ) ;
2020-07-28 21:48:24 -07:00
var hoursPattern ;
2023-07-12 10:01:53 +05:30
if ( $scope . backupPolicy . hours . length === 24 ) hoursPattern = '*' ;
else hoursPattern = $scope . backupPolicy . hours . map ( function ( d ) { return d . value ; } ) ;
2020-07-28 21:48:24 -07:00
2023-07-12 10:01:53 +05:30
var policy = {
retention : $scope . backupPolicy . retention ,
schedule : '00 00 ' + hoursPattern + ' * * ' + daysPattern
} ;
2020-05-19 16:13:20 +02:00
2023-07-12 10:01:53 +05:30
Client . setBackupPolicy ( policy , function ( error ) {
$scope . backupPolicy . busy = false ;
2020-05-19 16:13:20 +02:00
if ( error ) {
if ( error . statusCode === 424 ) {
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . error . generic = error . message ;
2020-05-19 16:13:20 +02:00
} else if ( error . statusCode === 400 ) {
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . error . generic = error . message ;
2020-05-19 16:13:20 +02:00
} else {
console . error ( 'Unable to change schedule or retention.' , error ) ;
}
return ;
}
2023-07-12 10:01:53 +05:30
$ ( '#backupPolicyModal' ) . modal ( 'hide' ) ;
2020-05-19 16:13:20 +02:00
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . init ( ) ;
2020-05-19 16:13:20 +02:00
} ) ;
}
} ;
2023-08-08 13:21:56 +02:00
$scope . $watch ( 'configureBackup.disk' , function ( newValue ) {
if ( ! newValue ) return ;
$scope . configureBackup . mountOptions . diskPath = '/dev/disk/by-uuid/' + newValue . uuid ;
} ) ;
2018-06-07 14:22:48 +02:00
$scope . configureBackup = {
busy : false ,
error : { } ,
provider : '' ,
bucket : '' ,
prefix : '' ,
accessKeyId : '' ,
secretAccessKey : '' ,
gcsKey : { keyFileName : '' , content : '' } ,
region : '' ,
endpoint : '' ,
backupFolder : '' ,
2020-06-05 12:47:33 +02:00
mountPoint : '' ,
2018-06-07 14:22:48 +02:00
acceptSelfSignedCerts : false ,
useHardlinks : true ,
2022-02-16 11:48:57 -08:00
chown : true ,
2018-06-07 14:22:48 +02:00
format : 'tgz' ,
2020-05-12 10:54:15 -07:00
password : '' ,
2020-11-14 11:01:46 +01:00
passwordRepeat : '' ,
2022-06-27 09:02:44 -07:00
encryptedFilenames : true ,
2020-08-10 23:01:09 -07:00
advancedVisible : false ,
memoryTicks : [ ] ,
2021-10-26 09:24:15 -07:00
memoryLimit : $scope . MIN _MEMORY _LIMIT ,
2020-08-19 14:39:41 -07:00
uploadPartSize : 50 * 1024 * 1024 ,
2020-08-10 23:01:09 -07:00
copyConcurrency : '' ,
downloadConcurrency : '' ,
syncConcurrency : '' , // sort of similar to upload
2018-06-07 14:22:48 +02:00
2025-01-24 13:41:54 +01:00
noHardlinks : false ,
preserveAttributes : true ,
2023-08-08 13:21:56 +02:00
blockDevices : [ ] ,
disk : null ,
2021-05-17 15:24:19 -07:00
mountOptions : {
host : '' ,
remoteDir : '' ,
username : '' ,
password : '' ,
2021-05-27 15:31:37 -07:00
diskPath : '' ,
2024-04-15 21:54:40 +02:00
seal : true ,
2021-05-27 15:31:37 -07:00
user : '' ,
port : 22 ,
privateKey : ''
2021-05-17 15:24:19 -07:00
} ,
2020-05-12 21:41:35 -07:00
clearProviderFields : function ( ) {
2018-06-07 14:22:48 +02:00
$scope . configureBackup . bucket = '' ;
$scope . configureBackup . prefix = '' ;
$scope . configureBackup . accessKeyId = '' ;
$scope . configureBackup . secretAccessKey = '' ;
$scope . configureBackup . gcsKey . keyFileName = '' ;
$scope . configureBackup . gcsKey . content = '' ;
$scope . configureBackup . endpoint = '' ;
$scope . configureBackup . region = '' ;
$scope . configureBackup . backupFolder = '' ;
2020-06-05 12:47:33 +02:00
$scope . configureBackup . mountPoint = '' ;
2018-06-07 14:22:48 +02:00
$scope . configureBackup . acceptSelfSignedCerts = false ;
$scope . configureBackup . useHardlinks = true ;
2022-02-16 11:48:57 -08:00
$scope . configureBackup . chown = true ;
2021-10-26 09:24:15 -07:00
$scope . configureBackup . memoryLimit = $scope . MIN _MEMORY _LIMIT ;
2020-08-10 23:01:09 -07:00
2020-08-19 14:39:41 -07:00
// scaleway only supports 1000 parts per object (https://www.scaleway.com/en/docs/s3-multipart-upload/)
$scope . configureBackup . uploadPartSize = $scope . configureBackup . provider === 'scaleway-objectstorage' ? 100 * 1024 * 1024 : 10 * 1024 * 1024 ;
2020-08-11 16:51:02 -07:00
$scope . configureBackup . downloadConcurrency = $scope . configureBackup . provider === 's3' ? 30 : 10 ;
$scope . configureBackup . syncConcurrency = $scope . configureBackup . provider === 's3' ? 20 : 10 ;
$scope . configureBackup . copyConcurrency = $scope . configureBackup . provider === 's3' ? 500 : 10 ;
2021-05-17 15:24:19 -07:00
2023-08-08 13:21:56 +02:00
$scope . configureBackup . disk = null ;
2024-04-15 21:54:40 +02:00
$scope . configureBackup . mountOptions = { host : '' , remoteDir : '' , username : '' , password : '' , diskPath : '' , seal : true , user : '' , port : 22 , privateKey : '' } ;
2018-06-07 14:22:48 +02:00
} ,
show : function ( ) {
$scope . configureBackup . error = { } ;
$scope . configureBackup . busy = false ;
2020-08-11 17:03:49 -07:00
$scope . configureBackup . advancedVisible = false ;
2018-06-07 14:22:48 +02:00
$scope . configureBackup . provider = $scope . backupConfig . provider ;
$scope . configureBackup . bucket = $scope . backupConfig . bucket ;
$scope . configureBackup . prefix = $scope . backupConfig . prefix ;
$scope . configureBackup . region = $scope . backupConfig . region ;
$scope . configureBackup . accessKeyId = $scope . backupConfig . accessKeyId ;
$scope . configureBackup . secretAccessKey = $scope . backupConfig . secretAccessKey ;
if ( $scope . backupConfig . provider === 'gcs' ) {
$scope . configureBackup . gcsKey . keyFileName = $scope . backupConfig . credentials . client _email ;
$scope . configureBackup . gcsKey . content = JSON . stringify ( {
project _id : $scope . backupConfig . projectId ,
client _email : $scope . backupConfig . credentials . client _email ,
private _key : $scope . backupConfig . credentials . private _key ,
} ) ;
}
$scope . configureBackup . endpoint = $scope . backupConfig . endpoint ;
2020-11-17 21:12:27 -08:00
$scope . configureBackup . password = $scope . backupConfig . password || '' ;
2020-11-14 11:01:46 +01:00
$scope . configureBackup . passwordRepeat = '' ;
2022-06-27 09:02:44 -07:00
$scope . configureBackup . encryptedFilenames = 'encryptedFilenames' in $scope . backupConfig ? $scope . backupConfig . encryptedFilenames : true ;
2018-06-07 14:22:48 +02:00
$scope . configureBackup . backupFolder = $scope . backupConfig . backupFolder ;
2020-06-05 12:47:33 +02:00
$scope . configureBackup . mountPoint = $scope . backupConfig . mountPoint ;
2018-06-07 14:22:48 +02:00
$scope . configureBackup . format = $scope . backupConfig . format ;
$scope . configureBackup . acceptSelfSignedCerts = ! ! $scope . backupConfig . acceptSelfSignedCerts ;
$scope . configureBackup . useHardlinks = ! $scope . backupConfig . noHardlinks ;
2025-01-24 13:41:54 +01:00
$scope . configureBackup . preserveAttributes = ! ! $scope . backupConfig . preserveAttributes ;
2022-02-16 11:48:57 -08:00
$scope . configureBackup . chown = $scope . backupConfig . chown ;
2018-06-07 14:22:48 +02:00
2024-03-12 16:44:08 +01:00
const limits = $scope . backupConfig . limits || { } ;
2020-08-10 23:01:09 -07:00
2024-07-14 18:21:12 +02:00
$scope . configureBackup . memoryLimit = limits . memoryLimit ? Math . max ( limits . memoryLimit , $scope . MIN _MEMORY _LIMIT ) : $scope . MIN _MEMORY _LIMIT ;
2023-07-13 11:50:57 +05:30
$scope . configureBackup . uploadPartSize = limits . uploadPartSize || ( $scope . configureBackup . provider === 'scaleway-objectstorage' ? 100 * 1024 * 1024 : 10 * 1024 * 1024 ) ;
$scope . configureBackup . downloadConcurrency = limits . downloadConcurrency || ( $scope . backupConfig . provider === 's3' ? 30 : 10 ) ;
$scope . configureBackup . syncConcurrency = limits . syncConcurrency || ( $scope . backupConfig . provider === 's3' ? 20 : 10 ) ;
$scope . configureBackup . copyConcurrency = limits . copyConcurrency || ( $scope . backupConfig . provider === 's3' ? 500 : 10 ) ;
2020-08-10 23:01:09 -07:00
2021-05-17 15:24:19 -07:00
var mountOptions = $scope . backupConfig . mountOptions || { } ;
2021-05-27 13:19:13 -07:00
$scope . configureBackup . mountOptions = {
2021-05-17 15:24:19 -07:00
host : mountOptions . host || '' ,
remoteDir : mountOptions . remoteDir || '' ,
username : mountOptions . username || '' ,
password : mountOptions . password || '' ,
2021-05-27 15:31:37 -07:00
diskPath : mountOptions . diskPath || '' ,
2022-01-10 14:28:25 +01:00
seal : mountOptions . seal ,
2021-05-27 15:31:37 -07:00
user : mountOptions . user || '' ,
port : mountOptions . port || 22 ,
privateKey : mountOptions . privateKey || ''
2021-05-17 15:24:19 -07:00
} ;
2020-08-19 14:39:41 -07:00
2023-08-08 13:21:56 +02:00
Client . getBlockDevices ( function ( error , result ) {
if ( error ) return console . error ( 'Failed to list blockdevices:' , error ) ;
// only offer non /, /boot or /home disks
result = result . filter ( function ( d ) { return d . mountpoint !== '/' && d . mountpoint !== '/home' && d . mountpoint !== '/boot' ; } ) ;
// only offer xfs and ext4 disks
result = result . filter ( function ( d ) { return d . type === 'xfs' || d . type === 'ext4' ; } ) ;
// amend label for UI
result . forEach ( function ( d ) {
d . label = d . path ;
// pre-select current if set
if ( d . path === $scope . configureBackup . mountOptions . diskPath || ( '/dev/disk/by-uuid/' + d . uuid ) === $scope . configureBackup . mountOptions . diskPath ) {
$scope . configureBackup . disk = d ;
}
} ) ;
$scope . configureBackup . blockDevices = result ;
$ ( '#configureBackupModal' ) . modal ( 'show' ) ;
} ) ;
2018-06-07 14:22:48 +02:00
} ,
submit : function ( ) {
$scope . configureBackup . error = { } ;
$scope . configureBackup . busy = true ;
var backupConfig = {
provider : $scope . configureBackup . provider ,
2020-05-19 16:13:20 +02:00
format : $scope . configureBackup . format ,
// required for api call to provide all fields
2020-07-28 21:48:24 -07:00
schedulePattern : $scope . backupConfig . schedulePattern ,
2023-07-13 11:50:57 +05:30
retentionPolicy : $scope . backupConfig . retentionPolicy ,
limits : {
2024-03-12 16:44:08 +01:00
memoryLimit : parseInt ( $scope . configureBackup . memoryLimit ) ,
2023-07-13 11:50:57 +05:30
} ,
2018-06-07 14:22:48 +02:00
} ;
2022-06-27 09:02:44 -07:00
if ( $scope . configureBackup . password ) {
backupConfig . password = $scope . configureBackup . password ;
backupConfig . encryptedFilenames = $scope . configureBackup . encryptedFilenames ; // ignored with tgz format
}
2018-06-07 14:22:48 +02:00
// only set provider specific fields, this will clear them in the db
if ( $scope . s3like ( backupConfig . provider ) ) {
backupConfig . bucket = $scope . configureBackup . bucket ;
backupConfig . prefix = $scope . configureBackup . prefix ;
backupConfig . accessKeyId = $scope . configureBackup . accessKeyId ;
backupConfig . secretAccessKey = $scope . configureBackup . secretAccessKey ;
if ( $scope . configureBackup . endpoint ) backupConfig . endpoint = $scope . configureBackup . endpoint ;
if ( backupConfig . provider === 's3' ) {
if ( $scope . configureBackup . region ) backupConfig . region = $scope . configureBackup . region ;
2018-07-30 07:29:20 -07:00
delete backupConfig . endpoint ;
2018-06-07 14:22:48 +02:00
} else if ( backupConfig . provider === 'minio' || backupConfig . provider === 's3-v4-compat' ) {
2020-06-15 16:51:56 +02:00
backupConfig . region = $scope . configureBackup . region || 'us-east-1' ;
2018-06-07 14:22:48 +02:00
backupConfig . acceptSelfSignedCerts = $scope . configureBackup . acceptSelfSignedCerts ;
2020-05-27 17:48:51 -07:00
backupConfig . s3ForcePathStyle = true ; // might want to expose this in the UI
2018-06-07 14:22:48 +02:00
} else if ( backupConfig . provider === 'exoscale-sos' ) {
backupConfig . region = 'us-east-1' ;
backupConfig . signatureVersion = 'v4' ;
2019-07-22 16:34:16 -07:00
} else if ( backupConfig . provider === 'wasabi' ) {
2020-05-19 14:52:40 +02:00
backupConfig . region = $scope . wasabiRegions . find ( function ( x ) { return x . value === $scope . configureBackup . endpoint ; } ) . region ;
2019-07-22 16:34:16 -07:00
backupConfig . signatureVersion = 'v4' ;
2019-04-12 10:04:26 -07:00
} else if ( backupConfig . provider === 'scaleway-objectstorage' ) {
backupConfig . region = $scope . scalewayRegions . find ( function ( x ) { return x . value === $scope . configureBackup . endpoint ; } ) . region ;
backupConfig . signatureVersion = 'v4' ;
2020-02-26 09:08:34 -08:00
} else if ( backupConfig . provider === 'linode-objectstorage' ) {
backupConfig . region = $scope . linodeRegions . find ( function ( x ) { return x . value === $scope . configureBackup . endpoint ; } ) . region ;
backupConfig . signatureVersion = 'v4' ;
2020-04-29 12:54:19 -07:00
} else if ( backupConfig . provider === 'ovh-objectstorage' ) {
backupConfig . region = $scope . ovhRegions . find ( function ( x ) { return x . value === $scope . configureBackup . endpoint ; } ) . region ;
backupConfig . signatureVersion = 'v4' ;
2021-02-04 10:14:42 -08:00
} else if ( backupConfig . provider === 'ionos-objectstorage' ) {
backupConfig . region = $scope . ionosRegions . find ( function ( x ) { return x . value === $scope . configureBackup . endpoint ; } ) . region ;
backupConfig . signatureVersion = 'v4' ;
2021-06-16 22:35:46 -07:00
} else if ( backupConfig . provider === 'vultr-objectstorage' ) {
backupConfig . region = $scope . vultrRegions . find ( function ( x ) { return x . value === $scope . configureBackup . endpoint ; } ) . region ;
backupConfig . signatureVersion = 'v4' ;
2023-08-25 07:59:40 +05:30
} else if ( backupConfig . provider === 'contabo-objectstorage' ) {
backupConfig . region = $scope . contaboRegions . find ( function ( x ) { return x . value === $scope . configureBackup . endpoint ; } ) . region ;
backupConfig . signatureVersion = 'v4' ;
backupConfig . s3ForcePathStyle = true ; // https://docs.contabo.com/docs/products/Object-Storage/technical-description (no virtual buckets)
2021-09-27 10:01:09 -07:00
} else if ( backupConfig . provider === 'upcloud-objectstorage' ) { // the UI sets region and endpoint
var m = /^.*\.(.*)\.upcloudobjects.com$/ . exec ( backupConfig . endpoint ) ;
backupConfig . region = m ? m [ 1 ] : 'us-east-1' ; // let it fail in validation phase if m is not valid
backupConfig . signatureVersion = 'v4' ;
2018-06-07 14:22:48 +02:00
} else if ( backupConfig . provider === 'digitalocean-spaces' ) {
backupConfig . region = 'us-east-1' ;
2024-09-25 12:21:42 +02:00
} else if ( backupConfig . provider === 'hetzner-objectstorage' ) {
backupConfig . region = 'us-east-1' ;
backupConfig . signatureVersion = 'v4' ;
2018-06-07 14:22:48 +02:00
}
2024-05-01 12:39:32 +02:00
backupConfig . limits . uploadPartSize = parseInt ( $scope . configureBackup . uploadPartSize ) ;
2018-06-07 14:22:48 +02:00
} else if ( backupConfig . provider === 'gcs' ) {
backupConfig . bucket = $scope . configureBackup . bucket ;
backupConfig . prefix = $scope . configureBackup . prefix ;
try {
var serviceAccountKey = JSON . parse ( $scope . configureBackup . 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 . configureBackup . error . generic = 'Cannot parse Google Service Account Key: ' + e . message ;
$scope . configureBackup . error . gcsKeyInput = true ;
$scope . configureBackup . busy = false ;
return ;
}
2021-05-17 15:24:19 -07:00
} else if ( $scope . mountlike ( backupConfig . provider ) ) {
2020-06-05 12:47:33 +02:00
backupConfig . prefix = $scope . configureBackup . prefix ;
backupConfig . noHardlinks = ! $scope . configureBackup . useHardlinks ;
2021-05-17 15:24:19 -07:00
backupConfig . mountOptions = { } ;
if ( backupConfig . provider === 'cifs' || backupConfig . provider === 'sshfs' || backupConfig . provider === 'nfs' ) {
backupConfig . mountOptions . host = $scope . configureBackup . mountOptions . host ;
backupConfig . mountOptions . remoteDir = $scope . configureBackup . mountOptions . remoteDir ;
if ( backupConfig . provider === 'cifs' ) {
backupConfig . mountOptions . username = $scope . configureBackup . mountOptions . username ;
backupConfig . mountOptions . password = $scope . configureBackup . mountOptions . password ;
2022-01-10 14:28:25 +01:00
backupConfig . mountOptions . seal = $scope . configureBackup . mountOptions . seal ;
2025-01-24 13:41:54 +01:00
backupConfig . preserveAttributes = $scope . configureBackup . preserveAttributes ;
2021-05-27 15:31:37 -07:00
} else if ( backupConfig . provider === 'sshfs' ) {
backupConfig . mountOptions . user = $scope . configureBackup . mountOptions . user ;
backupConfig . mountOptions . port = $scope . configureBackup . mountOptions . port ;
backupConfig . mountOptions . privateKey = $scope . configureBackup . mountOptions . privateKey ;
2025-01-24 13:41:54 +01:00
backupConfig . preserveAttributes = true ;
2021-05-17 15:24:19 -07:00
}
2023-08-08 13:21:56 +02:00
} else if ( backupConfig . provider === 'ext4' || backupConfig . provider === 'xfs' || backupConfig . provider === 'disk' ) {
2021-06-18 23:03:18 -07:00
backupConfig . mountOptions . diskPath = $scope . configureBackup . mountOptions . diskPath ;
2025-01-24 13:41:54 +01:00
backupConfig . preserveAttributes = true ;
2021-06-21 22:08:19 -07:00
} else if ( backupConfig . provider === 'mountpoint' ) {
backupConfig . mountPoint = $scope . configureBackup . mountPoint ;
2022-02-16 11:48:57 -08:00
backupConfig . chown = $scope . configureBackup . chown ;
2025-01-24 13:41:54 +01:00
backupConfig . preserveAttributes = $scope . configureBackup . preserveAttributes ;
2021-05-17 15:24:19 -07:00
}
2021-06-21 22:08:19 -07:00
} else if ( backupConfig . provider === 'filesystem' ) {
2018-06-07 14:22:48 +02:00
backupConfig . backupFolder = $scope . configureBackup . backupFolder ;
backupConfig . noHardlinks = ! $scope . configureBackup . useHardlinks ;
2025-01-24 13:41:54 +01:00
backupConfig . preserveAttributes = true ;
2018-06-07 14:22:48 +02:00
}
2020-08-10 23:01:09 -07:00
if ( backupConfig . format === 'rsync' ) {
2024-03-12 17:31:07 +01:00
backupConfig . limits . downloadConcurrency = parseInt ( $scope . configureBackup . downloadConcurrency ) ;
backupConfig . limits . syncConcurrency = parseInt ( $scope . configureBackup . syncConcurrency ) ;
backupConfig . limits . copyConcurrency = parseInt ( $scope . configureBackup . copyConcurrency ) ;
2020-08-10 23:01:09 -07:00
}
2018-06-07 14:22:48 +02:00
Client . setBackupConfig ( backupConfig , function ( error ) {
$scope . configureBackup . busy = false ;
if ( error ) {
2018-09-10 10:36:32 -07:00
if ( error . statusCode === 424 ) {
2018-06-07 14:22:48 +02:00
$scope . configureBackup . error . generic = error . message ;
if ( error . message . indexOf ( 'AWS Access Key Id' ) !== - 1 ) {
$scope . configureBackup . error . accessKeyId = true ;
$scope . configureBackup . accessKeyId = '' ;
$scope . configureBackupForm . accessKeyId . $setPristine ( ) ;
$ ( '#inputConfigureBackupAccessKeyId' ) . focus ( ) ;
} else if ( error . message . indexOf ( 'not match the signature' ) !== - 1 ) {
$scope . configureBackup . error . secretAccessKey = true ;
$scope . configureBackup . secretAccessKey = '' ;
$scope . configureBackupForm . secretAccessKey . $setPristine ( ) ;
$ ( '#inputConfigureBackupSecretAccessKey' ) . focus ( ) ;
} else if ( error . message . toLowerCase ( ) === 'access denied' ) {
$scope . configureBackup . error . bucket = true ;
$scope . configureBackup . bucket = '' ;
$scope . configureBackupForm . bucket . $setPristine ( ) ;
$ ( '#inputConfigureBackupBucket' ) . focus ( ) ;
} else if ( error . message . indexOf ( 'ECONNREFUSED' ) !== - 1 ) {
$scope . configureBackup . error . generic = 'Unknown region' ;
$scope . configureBackup . error . region = true ;
$scope . configureBackupForm . region . $setPristine ( ) ;
$ ( '#inputConfigureBackupDORegion' ) . focus ( ) ;
} else if ( error . message . toLowerCase ( ) === 'wrong region' ) {
$scope . configureBackup . error . generic = 'Wrong S3 Region' ;
$scope . configureBackup . error . region = true ;
$scope . configureBackupForm . region . $setPristine ( ) ;
$ ( '#inputConfigureBackupS3Region' ) . focus ( ) ;
} else {
$ ( '#inputConfigureBackupBucket' ) . focus ( ) ;
}
} else if ( error . statusCode === 400 ) {
$scope . configureBackup . error . generic = error . message ;
2020-05-12 10:54:15 -07:00
if ( error . message . indexOf ( 'password' ) !== - 1 ) {
$scope . configureBackup . error . password = true ;
$scope . configureBackupForm . password . $setPristine ( ) ;
} else if ( $scope . configureBackup . provider === 'filesystem' ) {
2018-06-07 14:22:48 +02:00
$scope . configureBackup . error . backupFolder = true ;
}
} else {
2024-06-06 11:41:33 +02:00
$scope . configureBackup . error . generic = error . message ;
2018-06-07 14:22:48 +02:00
}
return ;
}
// $scope.configureBackup.reset();
$ ( '#configureBackupModal' ) . modal ( 'hide' ) ;
getBackupConfig ( ) ;
} ) ;
}
} ;
function fetchBackups ( ) {
Client . getBackups ( function ( error , backups ) {
if ( error ) return console . error ( error ) ;
$scope . backups = backups ;
2020-05-16 09:46:57 -07:00
// add contents property
2021-05-08 17:18:26 -07:00
var appsById = { } , appsByFqdn = { } ;
Client . getInstalledApps ( ) . forEach ( function ( app ) {
appsById [ app . id ] = app ;
appsByFqdn [ app . fqdn ] = app ;
} ) ;
2020-05-16 09:46:57 -07:00
$scope . backups . forEach ( function ( backup ) {
2024-03-14 16:06:38 +01:00
backup . contents = [ ] ; // { id, label, fqdn }
2020-05-16 09:46:57 -07:00
backup . dependsOn . forEach ( function ( appBackupId ) {
2024-09-08 16:35:08 +02:00
const match = appBackupId . match ( /app_(.*?)_.*/ ) ; // *? means non-greedy
2024-03-14 16:06:38 +01:00
if ( ! match ) return ; // for example, 'mail'
const app = appsById [ match [ 1 ] ] ;
if ( app ) {
backup . contents . push ( {
id : app . id ,
label : app . label ,
fqdn : app . fqdn
} ) ;
2021-05-08 17:18:26 -07:00
} else {
2024-03-14 16:06:38 +01:00
backup . contents . push ( {
id : match [ 1 ] ,
label : null ,
fqdn : null
} ) ;
2021-05-08 17:18:26 -07:00
}
2020-05-16 09:46:57 -07:00
} ) ;
} ) ;
2018-06-07 14:22:48 +02:00
} ) ;
}
function getBackupConfig ( ) {
Client . getBackupConfig ( function ( error , backupConfig ) {
if ( error ) return console . error ( error ) ;
$scope . backupConfig = backupConfig ;
2023-05-15 10:32:39 +02:00
$scope . mountStatus = null ;
if ( ! $scope . mountlike ( $scope . backupConfig . provider ) ) return ;
2023-04-30 17:21:18 +02:00
Client . getBackupMountStatus ( function ( error , mountStatus ) {
if ( error ) return console . error ( error ) ;
$scope . mountStatus = mountStatus ;
} ) ;
2018-06-07 14:22:48 +02:00
} ) ;
}
2023-05-03 16:50:07 +02:00
function getBackupTasks ( ) {
Client . getTasksByType ( TASK _TYPES . TASK _BACKUP , function ( error , tasks ) {
if ( error ) return console . error ( error ) ;
if ( ! tasks . length ) return ;
$scope . backupTasks = tasks . slice ( 0 , 10 ) ;
} ) ;
}
function getCleanupTasks ( ) {
Client . getTasksByType ( TASK _TYPES . TASK _CLEAN _BACKUPS , function ( error , tasks ) {
if ( error ) return console . error ( error ) ;
if ( ! tasks . length ) return ;
$scope . cleanupTasks = tasks . slice ( 0 , 10 ) ;
} ) ;
}
2018-06-07 14:22:48 +02:00
Client . onReady ( function ( ) {
2020-09-10 00:07:12 -07:00
Client . memory ( function ( error , memory ) {
if ( error ) console . error ( error ) ;
2018-06-07 14:22:48 +02:00
2020-09-10 00:07:12 -07:00
$scope . memory = memory ;
2024-04-10 12:48:07 +02:00
var nearestGb = Math . ceil ( $scope . memory . memory / ( 1024 * 1024 * 1024 ) ) * 1024 * 1024 * 1024 ;
$scope . MAX _MEMORY _LIMIT = nearestGb ;
2020-04-03 10:36:51 -07:00
2020-09-10 00:07:12 -07:00
fetchBackups ( ) ;
getBackupConfig ( ) ;
2024-12-10 14:46:30 +01:00
$scope . archiveList . fetch ( ) ;
2024-12-10 11:53:29 +01:00
2022-09-28 12:13:32 +02:00
$scope . manualBackupApps = Client . getInstalledApps ( ) . filter ( function ( app ) { return app . type !== APP _TYPES . LINK && ! app . enableBackup ; } ) ;
2020-09-10 00:07:12 -07:00
// show backup status
2023-05-02 15:02:41 +02:00
$scope . createBackup . init ( ) ;
$scope . cleanupBackups . init ( ) ;
2023-07-12 10:01:53 +05:30
$scope . backupPolicy . init ( ) ;
2024-12-10 14:46:30 +01:00
$scope . archiveRestore . init ( ) ;
2023-05-03 16:50:07 +02:00
getBackupTasks ( ) ;
getCleanupTasks ( ) ;
2020-09-10 00:07:12 -07:00
} ) ;
2018-06-07 14:22:48 +02:00
} ) ;
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 . configureBackup . gcsKey , 'content' , 'keyFileName' ) ;
// setup all the dialog focus handling
2022-04-05 14:41:41 +02:00
[ 'configureBackupModal' , 'editBackupModal' ] . forEach ( function ( id ) {
2018-06-07 14:22:48 +02:00
$ ( '#' + id ) . on ( 'shown.bs.modal' , function ( ) {
2024-03-12 16:44:08 +01:00
$ ( this ) . find ( '[autofocus]:first' ) . focus ( ) ;
2018-06-07 14:22:48 +02:00
} ) ;
} ) ;
$ ( '.modal-backdrop' ) . remove ( ) ;
} ] ) ;