mirror of https://github.com/portainer/portainer
refactor(settings): move buckup settign to component
fix(settings/backup): sync with CErefactor/EE-2270/EE-5502/settings-components
parent
31047a5b3d
commit
f2e59f3b4e
|
@ -0,0 +1,131 @@
|
||||||
|
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||||
|
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||||
|
import { options } from '@/react/portainer/settings/SettingsView/backup-options';
|
||||||
|
|
||||||
|
/* @ngInject */
|
||||||
|
export default function BackupSettingsPanelController($scope, $async, BackupService, Notifications, FileSaver) {
|
||||||
|
this.$onInit = $onInit.bind(this);
|
||||||
|
this.onBackupOptionsChange = onBackupOptionsChange.bind(this);
|
||||||
|
this.onToggleAutoBackups = onToggleAutoBackups.bind(this);
|
||||||
|
this.getS3SettingsPayload = getS3SettingsPayload.bind(this);
|
||||||
|
|
||||||
|
this.downloadBackup = downloadBackup.bind(this);
|
||||||
|
this.saveS3BackupSettings = saveS3BackupSettings.bind(this);
|
||||||
|
this.exportBackup = exportBackup.bind(this);
|
||||||
|
|
||||||
|
this.backupOptions = options;
|
||||||
|
this.s3BackupFeatureId = FeatureId.S3_BACKUP_SETTING;
|
||||||
|
this.BACKUP_FORM_TYPES = { S3: 's3', FILE: 'file' };
|
||||||
|
|
||||||
|
this.formValues = {
|
||||||
|
passwordProtect: false,
|
||||||
|
password: '',
|
||||||
|
scheduleAutomaticBackups: true,
|
||||||
|
cronRule: '',
|
||||||
|
accessKeyId: '',
|
||||||
|
secretAccessKey: '',
|
||||||
|
region: '',
|
||||||
|
bucketName: '',
|
||||||
|
s3CompatibleHost: '',
|
||||||
|
backupFormType: this.BACKUP_FORM_TYPES.FILE,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
backupInProgress: false,
|
||||||
|
featureLimited: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
function onToggleAutoBackups(checked) {
|
||||||
|
$scope.$evalAsync(() => {
|
||||||
|
this.formValues.scheduleAutomaticBackups = checked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBackupOptionsChange(type, limited) {
|
||||||
|
this.formValues.backupFormType = type;
|
||||||
|
this.state.featureLimited = limited;
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadBackup() {
|
||||||
|
const payload = {};
|
||||||
|
if (this.formValues.passwordProtect) {
|
||||||
|
payload.password = this.formValues.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.backupInProgress = true;
|
||||||
|
|
||||||
|
BackupService.downloadBackup(payload)
|
||||||
|
.then((data) => {
|
||||||
|
const downloadData = new Blob([data.file], { type: 'application/gzip' });
|
||||||
|
FileSaver.saveAs(downloadData, data.name);
|
||||||
|
Notifications.success('Success', 'Backup successfully downloaded');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Notifications.error('Failure', err, 'Unable to download backup');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.state.backupInProgress = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveS3BackupSettings() {
|
||||||
|
const payload = this.getS3SettingsPayload();
|
||||||
|
BackupService.saveS3Settings(payload)
|
||||||
|
.then(() => {
|
||||||
|
Notifications.success('Success', 'S3 Backup settings successfully saved');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Notifications.error('Failure', err, 'Unable to save S3 backup settings');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.state.backupInProgress = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportBackup() {
|
||||||
|
const payload = this.getS3SettingsPayload();
|
||||||
|
BackupService.exportBackup(payload)
|
||||||
|
.then(() => {
|
||||||
|
Notifications.success('Success', 'Exported backup to S3 successfully');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Notifications.error('Failure', err, 'Unable to export backup to S3');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.state.backupInProgress = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getS3SettingsPayload() {
|
||||||
|
return {
|
||||||
|
Password: this.formValues.passwordProtectS3 ? this.formValues.passwordS3 : '',
|
||||||
|
CronRule: this.formValues.scheduleAutomaticBackups ? this.formValues.cronRule : '',
|
||||||
|
AccessKeyID: this.formValues.accessKeyId,
|
||||||
|
SecretAccessKey: this.formValues.secretAccessKey,
|
||||||
|
Region: this.formValues.region,
|
||||||
|
BucketName: this.formValues.bucketName,
|
||||||
|
S3CompatibleHost: this.formValues.s3CompatibleHost,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function $onInit() {
|
||||||
|
return $async(async () => {
|
||||||
|
try {
|
||||||
|
const data = isBE ? await BackupService.getS3Settings() : {};
|
||||||
|
|
||||||
|
this.formValues.passwordS3 = data.Password;
|
||||||
|
this.formValues.cronRule = data.CronRule;
|
||||||
|
this.formValues.accessKeyId = data.AccessKeyID;
|
||||||
|
this.formValues.secretAccessKey = data.SecretAccessKey;
|
||||||
|
this.formValues.region = data.Region;
|
||||||
|
this.formValues.bucketName = data.BucketName;
|
||||||
|
this.formValues.s3CompatibleHost = data.S3CompatibleHost;
|
||||||
|
|
||||||
|
this.formValues.scheduleAutomaticBackups = this.formValues.cronRule ? true : false;
|
||||||
|
this.formValues.passwordProtectS3 = this.formValues.passwordS3 ? true : false;
|
||||||
|
} catch (err) {
|
||||||
|
Notifications.error('Failure', err, 'Unable to retrieve S3 backup settings');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<rd-widget>
|
||||||
|
<rd-widget-header icon="download" title-text="Back up Portainer"></rd-widget-header>
|
||||||
|
<rd-widget-body>
|
||||||
|
<form class="form-horizontal" ng-submit="$ctrl.backupPortainer()" name="$ctrl.backupPortainerForm">
|
||||||
|
<div class="col-sm-12 form-section-title"> Backup configuration </div>
|
||||||
|
<div class="text-muted small mb-3">This will back up your Portainer server configuration and does not include containers.</div>
|
||||||
|
|
||||||
|
<box-selector
|
||||||
|
slim="true"
|
||||||
|
options="$ctrl.backupOptions"
|
||||||
|
value="$ctrl.formValues.backupFormType"
|
||||||
|
on-change="($ctrl.onBackupOptionsChange)"
|
||||||
|
radio-name="'backupOptions'"
|
||||||
|
></box-selector>
|
||||||
|
|
||||||
|
<div ng-if="$ctrl.formValues.backupFormType === $ctrl.BACKUP_FORM_TYPES.S3">
|
||||||
|
<!-- Schedule automatic backups -->
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<por-switch-field
|
||||||
|
label="'Schedule automatic backups'"
|
||||||
|
name="'s3-backup-setting'"
|
||||||
|
feature-id="s3BackupFeatureId"
|
||||||
|
checked="$ctrl.formValues.scheduleAutomaticBackups"
|
||||||
|
field-class="'col-sm-10'"
|
||||||
|
label-class="'col-sm-2'"
|
||||||
|
on-change="($ctrl.onToggleAutoBackups)"
|
||||||
|
></por-switch-field>
|
||||||
|
</div>
|
||||||
|
<!-- !Schedule automatic backups -->
|
||||||
|
<!-- Cron rule -->
|
||||||
|
<div class="form-group" ng-if="$ctrl.formValues.scheduleAutomaticBackups">
|
||||||
|
<label for="cron_rule" class="col-sm-2 control-label text-left">Cron rule</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="0 2 * * *"
|
||||||
|
id="cron_rule"
|
||||||
|
name="cron_rule"
|
||||||
|
ng-model="$ctrl.formValues.cronRule"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-disabled
|
||||||
|
limited-feature-class="limited-be"
|
||||||
|
required
|
||||||
|
cronRule
|
||||||
|
data-cy="settings-backupCronRuleInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Cron rule -->
|
||||||
|
<div class="form-group col-md-12" ng-show="$ctrl.backupPortainerForm.cron_rule.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="$ctrl.backupPortainerForm.cron_rule.$error">
|
||||||
|
<p ng-message="required"> <pr-icon icon="'alert-triangle'"></pr-icon> This field is required.</p>
|
||||||
|
<p ng-message="invalidCron">
|
||||||
|
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
|
||||||
|
Please enter a valid cron rule.</p
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Access key id -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="access_key_id" class="col-sm-2 control-label text-left">Access key ID</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="access_key_id"
|
||||||
|
name="access_key_id"
|
||||||
|
ng-model="$ctrl.formValues.accessKeyId"
|
||||||
|
ng-required="$ctrl.formValues.scheduleAutomaticBackups"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-disabled
|
||||||
|
limited-feature-class="limited-be"
|
||||||
|
data-cy="settings-accessKeyIdInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="$ctrl.backupPortainerForm.access_key_id.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="$ctrl.backupPortainerForm.access_key_id.$error">
|
||||||
|
<p ng-message="required"> <pr-icon icon="'alert-triangle'"></pr-icon> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Access key id -->
|
||||||
|
<!-- Secret access key -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="secret_access_key" class="col-sm-2 control-label text-left">Secret access key</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="secret_access_key"
|
||||||
|
name="secret_access_key"
|
||||||
|
ng-model="$ctrl.formValues.secretAccessKey"
|
||||||
|
ng-required="$ctrl.formValues.scheduleAutomaticBackups"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-disabled
|
||||||
|
limited-feature-class="limited-be"
|
||||||
|
data-cy="settings-secretAccessKeyInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="$ctrl.backupPortainerForm.secret_access_key.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="$ctrl.backupPortainerForm.secret_access_key.$error">
|
||||||
|
<p ng-message="required"> <pr-icon icon="'alert-triangle'"></pr-icon> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Secret access key -->
|
||||||
|
<!-- Region -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="region" class="col-sm-2 control-label text-left">Region</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="default region is us-east-1 if left empty"
|
||||||
|
id="region"
|
||||||
|
name="region"
|
||||||
|
ng-model="$ctrl.formValues.region"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-disabled
|
||||||
|
limited-feature-class="limited-be"
|
||||||
|
data-cy="settings-backupRegionInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Region -->
|
||||||
|
<!-- Bucket name -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="bucket_name" class="col-sm-2 control-label text-left">Bucket name</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="bucket_name"
|
||||||
|
name="bucket_name"
|
||||||
|
ng-model="$ctrl.formValues.bucketName"
|
||||||
|
ng-required="$ctrl.formValues.scheduleAutomaticBackups"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-disabled
|
||||||
|
limited-feature-class="limited-be"
|
||||||
|
data-cy="settings-backupBucketNameInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="$ctrl.backupPortainerForm.bucket_name.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="$ctrl.backupPortainerForm.bucket_name.$error">
|
||||||
|
<p ng-message="required"> <pr-icon icon="'alert-triangle'"></pr-icon> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Bucket name -->
|
||||||
|
<!-- S3 compatible host -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="s3_compatible_host" class="col-sm-2 control-label text-left">
|
||||||
|
S3 compatible host
|
||||||
|
<portainer-tooltip message="'Hostname of a S3 service'"></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="leave empty for AWS S3"
|
||||||
|
id="s3_compatible_host"
|
||||||
|
name="s3_compatible_host"
|
||||||
|
ng-model="$ctrl.formValues.s3CompatibleHost"
|
||||||
|
ng-pattern="/^https?://.+/"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-class="limited-be"
|
||||||
|
data-cy="settings-backupS3CompatibleHostInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="$ctrl.backupPortainerForm.s3_compatible_host.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<p class="vertical-center">
|
||||||
|
<pr-icon icon="'alert-triangle'"></pr-icon>
|
||||||
|
<span>S3 host must begin with http:// or https://</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !S3 compatible host -->
|
||||||
|
<div class="col-sm-12 form-section-title"> Security settings </div>
|
||||||
|
<!-- Password protect S3 -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_protect" class="col-sm-2 control-label text-left">Password protect</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="switch">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="password_protect_s3"
|
||||||
|
name="password_protect_s3"
|
||||||
|
ng-model="$ctrl.formValues.passwordProtectS3"
|
||||||
|
data-cy="settings-passwordProtectToggleS3"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-disabled
|
||||||
|
/><span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Password protect S3 -->
|
||||||
|
<!-- Password S3 -->
|
||||||
|
<div class="form-group" ng-if="$ctrl.formValues.passwordProtectS3">
|
||||||
|
<label for="password" class="col-sm-2 control-label text-left">Password</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input type="password" class="form-control" ng-model="$ctrl.formValues.passwordS3" id="password_S3" name="password_S3" required data-cy="settings-backups3pw" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="$ctrl.backupPortainerForm.password_S3.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="$ctrl.backupPortainerForm.password_S3.$error">
|
||||||
|
<p ng-message="required"> <pr-icon icon="'alert-triangle'"></pr-icon> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Password S3 -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
ng-disabled="$ctrl.backupPortainerForm.$invalid"
|
||||||
|
ng-click="$ctrl.exportBackup()"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-disabled
|
||||||
|
limited-feature-class="limited-be"
|
||||||
|
data-cy="settings-exportBackupS3Button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<pr-icon icon="'upload'" class-name="'mr-1'"></pr-icon>
|
||||||
|
Export backup
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<hr />
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
ng-disabled="$ctrl.backupPortainerForm.$invalid || $ctrl.state.backupInProgress"
|
||||||
|
ng-click="$ctrl.saveS3BackupSettings()"
|
||||||
|
limited-feature-dir="{{::$ctrl.s3BackupFeatureId}}"
|
||||||
|
limited-feature-disabled
|
||||||
|
limited-feature-class="limited-be"
|
||||||
|
data-cy="settings-saveBackupSettingsButton"
|
||||||
|
>
|
||||||
|
<span>Save backup settings</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-if="$ctrl.formValues.backupFormType === $ctrl.BACKUP_FORM_TYPES.FILE">
|
||||||
|
<div class="col-sm-12 form-section-title"> Security settings </div>
|
||||||
|
<!-- Password protect -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_protect" class="col-sm-2 control-label text-left">Password protect</label>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<label class="switch" data-cy="settings-passwordProtectLocal">
|
||||||
|
<input type="checkbox" id="password_protect" name="password_protect" ng-model="$ctrl.formValues.passwordProtect" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Password protect -->
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<div class="form-group" ng-if="$ctrl.formValues.passwordProtect">
|
||||||
|
<label for="password" class="col-sm-2 control-label text-left">Password</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input type="password" class="form-control" ng-model="$ctrl.formValues.password" id="password" name="password" required data-cy="settings-backupLocalPassword" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12" ng-show="$ctrl.backupPortainerForm.password.$invalid">
|
||||||
|
<div class="small text-warning">
|
||||||
|
<div ng-messages="$ctrl.backupPortainerForm.password.$error">
|
||||||
|
<p ng-message="required"> <pr-icon icon="'alert-triangle'"></pr-icon> This field is required.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !Password -->
|
||||||
|
|
||||||
|
<!-- actions -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary btn-sm !ml-0"
|
||||||
|
ng-click="$ctrl.downloadBackup()"
|
||||||
|
ng-disabled="$ctrl.backupPortainerForm.$invalid || $ctrl.state.backupInProgress || $ctrl.state.featureLimited"
|
||||||
|
button-spinner="$ctrl.state.backupInProgress"
|
||||||
|
data-cy="settings-downloadLocalBackup"
|
||||||
|
>
|
||||||
|
<span ng-hide="$ctrl.state.backupInProgress">
|
||||||
|
<pr-icon icon="'download'"></pr-icon>
|
||||||
|
Download backup
|
||||||
|
</span>
|
||||||
|
<span ng-show="$ctrl.state.backupInProgress">
|
||||||
|
<pr-icon icon="'download'"></pr-icon>
|
||||||
|
Downloading backup
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !actions -->
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</rd-widget-body>
|
||||||
|
</rd-widget>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,12 @@
|
||||||
|
import angular from 'angular';
|
||||||
|
|
||||||
|
import controller from './backup-settings-panel.controller';
|
||||||
|
|
||||||
|
angular.module('portainer.app').component('backupSettingsPanel', {
|
||||||
|
templateUrl: './backup-settings-panel.html',
|
||||||
|
controller,
|
||||||
|
bindings: {
|
||||||
|
settings: '<',
|
||||||
|
onSubmit: '<',
|
||||||
|
},
|
||||||
|
});
|
|
@ -10,241 +10,4 @@
|
||||||
<hidden-containers-panel ng-if="settings" settings="settings" on-submit="(updateSettings)"></hidden-containers-panel>
|
<hidden-containers-panel ng-if="settings" settings="settings" on-submit="(updateSettings)"></hidden-containers-panel>
|
||||||
|
|
||||||
<!-- backup -->
|
<!-- backup -->
|
||||||
<div class="row">
|
<backup-settings-panel ng-if="settings" settings="settings" on-submit="(updateSettings)"></backup-settings-panel>
|
||||||
<div class="col-sm-12">
|
|
||||||
<rd-widget>
|
|
||||||
<rd-widget-header icon="download" title-text="Back up Portainer"></rd-widget-header>
|
|
||||||
<rd-widget-body>
|
|
||||||
<form class="form-horizontal" ng-submit="backupPortainer()" name="backupPortainerForm">
|
|
||||||
<div class="col-sm-12 form-section-title"> Backup configuration </div>
|
|
||||||
<div class="text-muted small mb-3">This will back up your Portainer server configuration and does not include containers.</div>
|
|
||||||
|
|
||||||
<box-selector slim="true" options="backupOptions" value="formValues.backupFormType" on-change="(onBackupOptionsChange)" radio-name="'backupOptions'"></box-selector>
|
|
||||||
|
|
||||||
<div ng-if="formValues.backupFormType === BACKUP_FORM_TYPES.S3">
|
|
||||||
<!-- Schedule automatic backups -->
|
|
||||||
<div class="form-group mt-3">
|
|
||||||
<por-switch-field
|
|
||||||
label="'Schedule automatic backups'"
|
|
||||||
name="'s3-backup-setting'"
|
|
||||||
feature-id="s3BackupFeatureId"
|
|
||||||
checked="formValues.scheduleAutomaticBackups"
|
|
||||||
field-class="'col-sm-10'"
|
|
||||||
label-class="'col-sm-2'"
|
|
||||||
on-change="(onToggleAutoBackups)"
|
|
||||||
></por-switch-field>
|
|
||||||
</div>
|
|
||||||
<!-- !Schedule automatic backups -->
|
|
||||||
<!-- Cron rule -->
|
|
||||||
<div class="form-group" ng-if="formValues.scheduleAutomaticBackups">
|
|
||||||
<label for="cron_rule" class="col-sm-2 control-label text-left">Cron rule</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="0 2 * * *"
|
|
||||||
id="cron_rule"
|
|
||||||
name="cron_rule"
|
|
||||||
ng-model="formValues.cronRule"
|
|
||||||
limited-feature-dir="{{::s3BackupFeatureId}}"
|
|
||||||
limited-feature-disabled
|
|
||||||
limited-feature-class="limited-be"
|
|
||||||
required
|
|
||||||
cronRule
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Cron rule -->
|
|
||||||
<!-- Access key id -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="access_key_id" class="col-sm-2 control-label text-left">Access Key ID</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="access_key_id"
|
|
||||||
name="access_key_id"
|
|
||||||
ng-model="formValues.accessKeyId"
|
|
||||||
ng-required="formValues.scheduleAutomaticBackups"
|
|
||||||
limited-feature-dir="{{::s3BackupFeatureId}}"
|
|
||||||
limited-feature-disabled
|
|
||||||
limited-feature-class="limited-be"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Access key id -->
|
|
||||||
<!-- Secret access key -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="secret_access_key" class="col-sm-2 control-label text-left">Secret Access Key</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
id="secret_access_key"
|
|
||||||
name="secret_access_key"
|
|
||||||
ng-model="formValues.secretAccessKey"
|
|
||||||
ng-required="formValues.scheduleAutomaticBackups"
|
|
||||||
limited-feature-dir="{{::s3BackupFeatureId}}"
|
|
||||||
limited-feature-disabled
|
|
||||||
limited-feature-class="limited-be"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Secret access key -->
|
|
||||||
<!-- Region -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="region" class="col-sm-2 control-label text-left">Region</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="default region is us-east-1 if left empty"
|
|
||||||
id="region"
|
|
||||||
name="region"
|
|
||||||
ng-model="formValues.region"
|
|
||||||
ng-required="formValues.scheduleAutomaticBackups"
|
|
||||||
limited-feature-dir="{{::s3BackupFeatureId}}"
|
|
||||||
limited-feature-disabled
|
|
||||||
limited-feature-class="limited-be"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Region -->
|
|
||||||
<!-- Bucket name -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="bucket_name" class="col-sm-2 control-label text-left">Bucket name</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="bucket_name"
|
|
||||||
name="bucket_name"
|
|
||||||
ng-model="formValues.bucketName"
|
|
||||||
ng-required="formValues.scheduleAutomaticBackups"
|
|
||||||
limited-feature-dir="{{::s3BackupFeatureId}}"
|
|
||||||
limited-feature-disabled
|
|
||||||
limited-feature-class="limited-be"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Bucket name -->
|
|
||||||
<div class="col-sm-12 form-section-title"> Security settings </div>
|
|
||||||
<!-- Password protect S3 -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password_protect" class="col-sm-2 control-label text-left">Password protect</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<label class="switch">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="password_protect_s3"
|
|
||||||
name="password_protect_s3"
|
|
||||||
ng-model="formValues.passwordProtectS3"
|
|
||||||
data-cy="settings-passwordProtectToggleS3"
|
|
||||||
disabled
|
|
||||||
/><span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Password protect S3 -->
|
|
||||||
<!-- Password S3 -->
|
|
||||||
<div class="form-group" ng-if="formValues.passwordProtectS3">
|
|
||||||
<label for="password" class="col-sm-2 control-label text-left">Password</label>
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input type="password" class="form-control" ng-model="formValues.passwordS3" id="password_S3" name="password_S3" required data-cy="settings-backups3pw" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-12" ng-show="backupPortainerForm.password_S3.$invalid">
|
|
||||||
<div class="small text-warning">
|
|
||||||
<div ng-messages="backupPortainerForm.password_S3.$error">
|
|
||||||
<p ng-message="required"> <pr-icon icon="'alert-triangle'"></pr-icon> This field is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Password S3 -->
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
ng-disabled="backupPortainerForm.$invalid"
|
|
||||||
ng-click="exportBackup()"
|
|
||||||
limited-feature-dir="{{::s3BackupFeatureId}}"
|
|
||||||
limited-feature-disabled
|
|
||||||
limited-feature-class="limited-be"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<pr-icon icon="'upload'" class-name="'mr-1'"></pr-icon>
|
|
||||||
Export backup
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<hr />
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
ng-disabled="backupPortainerForm.$invalid ||state.backupInProgress"
|
|
||||||
ng-click="saveS3BackupSettings()"
|
|
||||||
limited-feature-dir="{{::s3BackupFeatureId}}"
|
|
||||||
limited-feature-disabled
|
|
||||||
limited-feature-class="limited-be"
|
|
||||||
>
|
|
||||||
<span>Save backup settings</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div ng-if="formValues.backupFormType === BACKUP_FORM_TYPES.FILE">
|
|
||||||
<div class="col-sm-12 form-section-title"> Security settings </div>
|
|
||||||
<!-- Password protect -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password_protect" class="col-sm-2 control-label text-left">Password protect</label>
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<label class="switch" data-cy="settings-passwordProtectLocal">
|
|
||||||
<input type="checkbox" id="password_protect" name="password_protect" ng-model="formValues.passwordProtect" /><span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Password protect -->
|
|
||||||
|
|
||||||
<!-- Password -->
|
|
||||||
<div class="form-group" ng-if="formValues.passwordProtect">
|
|
||||||
<label for="password" class="col-sm-2 control-label text-left">Password</label>
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input type="password" class="form-control" ng-model="formValues.password" id="password" name="password" required data-cy="settings-backupLocalPassword" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-md-12" ng-show="backupPortainerForm.password.$invalid">
|
|
||||||
<div class="small text-warning">
|
|
||||||
<div ng-messages="backupPortainerForm.password.$error">
|
|
||||||
<p ng-message="required"> <pr-icon icon="'alert-triangle'"></pr-icon> This field is required.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !Password -->
|
|
||||||
|
|
||||||
<!-- actions -->
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
ng-click="downloadBackup()"
|
|
||||||
ng-disabled="backupPortainerForm.$invalid || state.backupInProgress || state.featureLimited"
|
|
||||||
button-spinner="state.backupInProgress"
|
|
||||||
data-cy="settings-downloadLocalBackup"
|
|
||||||
>
|
|
||||||
<span ng-hide="state.backupInProgress">Download backup</span>
|
|
||||||
<span ng-show="state.backupInProgress">Downloading backup</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !actions -->
|
|
||||||
</form>
|
|
||||||
</rd-widget-body>
|
|
||||||
</rd-widget>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,113 +1,13 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
|
||||||
import { options } from '@/react/portainer/settings/SettingsView/backup-options';
|
|
||||||
|
|
||||||
angular.module('portainer.app').controller('SettingsController', [
|
angular.module('portainer.app').controller('SettingsController', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'Notifications',
|
'Notifications',
|
||||||
'SettingsService',
|
'SettingsService',
|
||||||
'StateManager',
|
'StateManager',
|
||||||
'BackupService',
|
function ($scope, Notifications, SettingsService, StateManager) {
|
||||||
'FileSaver',
|
|
||||||
function ($scope, Notifications, SettingsService, StateManager, BackupService, FileSaver) {
|
|
||||||
$scope.customBannerFeatureId = FeatureId.CUSTOM_LOGIN_BANNER;
|
|
||||||
$scope.s3BackupFeatureId = FeatureId.S3_BACKUP_SETTING;
|
|
||||||
$scope.enforceDeploymentOptions = FeatureId.ENFORCE_DEPLOYMENT_OPTIONS;
|
|
||||||
|
|
||||||
$scope.updateSettings = updateSettings;
|
$scope.updateSettings = updateSettings;
|
||||||
|
|
||||||
$scope.backupOptions = options;
|
|
||||||
|
|
||||||
$scope.state = {
|
|
||||||
actionInProgress: false,
|
|
||||||
|
|
||||||
backupInProgress: false,
|
|
||||||
featureLimited: false,
|
|
||||||
showHTTPS: !window.ddExtension,
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.BACKUP_FORM_TYPES = { S3: 's3', FILE: 'file' };
|
|
||||||
|
|
||||||
$scope.formValues = {
|
|
||||||
passwordProtect: false,
|
|
||||||
password: '',
|
|
||||||
backupFormType: $scope.BACKUP_FORM_TYPES.FILE,
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.onToggleAutoBackups = function onToggleAutoBackups(checked) {
|
|
||||||
$scope.$evalAsync(() => {
|
|
||||||
$scope.formValues.scheduleAutomaticBackups = checked;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.onBackupOptionsChange = function (type, limited) {
|
|
||||||
$scope.formValues.backupFormType = type;
|
|
||||||
$scope.state.featureLimited = limited;
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.downloadBackup = function () {
|
|
||||||
const payload = {};
|
|
||||||
if ($scope.formValues.passwordProtect) {
|
|
||||||
payload.password = $scope.formValues.password;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.state.backupInProgress = true;
|
|
||||||
|
|
||||||
BackupService.downloadBackup(payload)
|
|
||||||
.then(function success(data) {
|
|
||||||
const downloadData = new Blob([data.file], { type: 'application/gzip' });
|
|
||||||
FileSaver.saveAs(downloadData, data.name);
|
|
||||||
Notifications.success('Success', 'Backup successfully downloaded');
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to download backup');
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
$scope.state.backupInProgress = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.saveS3BackupSettings = function () {
|
|
||||||
const payload = getS3SettingsPayload();
|
|
||||||
BackupService.saveS3Settings(payload)
|
|
||||||
.then(function success() {
|
|
||||||
Notifications.success('Success', 'S3 Backup settings successfully saved');
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to save S3 backup settings');
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
$scope.state.backupInProgress = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.exportBackup = function () {
|
|
||||||
const payload = getS3SettingsPayload();
|
|
||||||
BackupService.exportBackup(payload)
|
|
||||||
.then(function success() {
|
|
||||||
Notifications.success('Success', 'Exported backup to S3 successfully');
|
|
||||||
})
|
|
||||||
.catch(function error(err) {
|
|
||||||
Notifications.error('Failure', err, 'Unable to export backup to S3');
|
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
$scope.state.backupInProgress = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function getS3SettingsPayload() {
|
|
||||||
return {
|
|
||||||
Password: $scope.formValues.passwordProtectS3 ? $scope.formValues.passwordS3 : '',
|
|
||||||
CronRule: $scope.formValues.scheduleAutomaticBackups ? $scope.formValues.cronRule : '',
|
|
||||||
AccessKeyID: $scope.formValues.accessKeyId,
|
|
||||||
SecretAccessKey: $scope.formValues.secretAccessKey,
|
|
||||||
Region: $scope.formValues.region,
|
|
||||||
BucketName: $scope.formValues.bucketName,
|
|
||||||
S3CompatibleHost: $scope.formValues.s3CompatibleHost,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSettings(settings, successMessage = 'Settings updated') {
|
function updateSettings(settings, successMessage = 'Settings updated') {
|
||||||
// ignore CloudApiKeys to avoid overriding them
|
// ignore CloudApiKeys to avoid overriding them
|
||||||
//
|
//
|
||||||
|
@ -123,9 +23,6 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to update settings');
|
Notifications.error('Failure', err, 'Unable to update settings');
|
||||||
})
|
|
||||||
.finally(function final() {
|
|
||||||
$scope.state.actionInProgress = false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue