610 lines
43 KiB
HTML
610 lines
43 KiB
HTML
<!-- Modal details -->
|
|
<div class="modal fade" id="backupDetailsModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog" style="width: 750px">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'backups.backupDetails.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="row">
|
|
<div class="col-xs-2 text-muted">{{ 'backups.backupDetails.id' | tr }}:</div>
|
|
<div class="col-xs-10 text-right">{{ backupDetails.backup.id }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-xs-2 text-muted">{{ 'backups.backupEdit.label' | tr }}:</div>
|
|
<div class="col-xs-10 text-right">{{ backupDetails.backup.label }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-xs-2 text-muted">{{ 'backups.backupDetails.date' | tr }}:</div>
|
|
<div class="col-xs-10 text-right">{{ backupDetails.backup.creationTime | prettyLongDate }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-xs-2 text-muted">{{ 'backups.backupDetails.version' | tr }}:</div>
|
|
<div class="col-xs-10 text-right">v{{ backupDetails.backup.packageVersion }}</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-xs-2 text-muted">{{ 'backups.backupDetails.format' | tr }}:</div>
|
|
<div class="col-xs-10 text-right">{{ backupDetails.backup.format }}</div>
|
|
</div>
|
|
<br/>
|
|
<p class="text-muted">{{ 'backups.backupDetails.list' | tr:{ appCount: backupDetails.backup.contents.length } }}:</p>
|
|
<span ng-repeat="app in backupDetails.backup.contents | orderBy:['label','fqdn']">
|
|
<a ng-href="/#/app/{{app.id}}/backups">{{ app.label || app.fqdn }}</a><span ng-hide="$last">,</span>
|
|
</span>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal edit individual backup (label and retention sec) -->
|
|
<div class="modal fade" id="editBackupModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'backups.backupEdit.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form name="editBackupForm" role="form" novalidate ng-submit="editBackup.submit()" autocomplete="off">
|
|
<p class="has-error text-center" ng-show="editBackup.error">{{ editBackup.error }}</p>
|
|
|
|
<div class="form-group">
|
|
<label class="control-label" for="inputBackupLabel">{{ 'backups.backupEdit.label' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="editBackup.label" id="inputBackupLabel" name="label" ng-disabled="editBackup.busy" placeholder="" autofocus>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="checkbox">
|
|
<label>
|
|
<input type="checkbox" ng-model="editBackup.persist">{{ 'backups.backupEdit.preserved.description' | tr }}</input>
|
|
|
|
<sup><a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{ 'backups.backupEdit.preserved.tooltip' | tr: { appsLength: editBackup.backup.contents.length} }}"><i class="fa fa-question-circle"></i></a></sup>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
|
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="editBackup.submit()" ng-disabled="editBackupForm.$invalid || editBackup.busy"><i class="fa fa-circle-notch fa-spin" ng-show="editBackup.busy"></i><span> {{ 'main.dialog.save' | tr }}</span></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal backup failed -->
|
|
<div class="modal fade" id="createBackupFailedModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'backups.backupFailed.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
{{ createBackup.errorMessage }}
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-primary" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cleanup backups info -->
|
|
<div class="modal fade" id="cleanupBackupsModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'backups.cleanupBackups.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">{{ 'backups.cleanupBackups.description' | tr }}</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
|
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="cleanupBackups.start()">{{ 'backups.cleanupBackups.cleanupNow' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- modal backup config -->
|
|
<div class="modal fade" id="configureScheduleAndRetentionModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'backups.configureBackupSchedule.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form name="configureScheduleAndRetentionForm" role="form" novalidate ng-submit="configureScheduleAndRetention.submit()" autocomplete="off">
|
|
<p class="has-error text-center" ng-show="configureScheduleAndRetention.error">{{ configureScheduleAndRetention.error.generic }}</p>
|
|
|
|
<div class="form-group">
|
|
<label class="control-label" for="backupSchedule">{{ 'backups.configureBackupSchedule.schedule' | tr }}</label>
|
|
<p ng-bind-html="'backups.configureBackupSchedule.scheduleDescription' | tr"></p>
|
|
|
|
<div class="row" style="margin-left: 20px;">
|
|
<div class="col-md-5" ng-class="{ 'has-error': !configureScheduleAndRetention.days.length }">
|
|
{{ 'backups.configureBackupSchedule.days' | tr }}: <multiselect id="backupSchedule" class="input-sm stretch" ng-model="configureScheduleAndRetention.days" options="a.name for a in cronDays" data-multiple="true" ng-required></multiselect>
|
|
</div>
|
|
|
|
<div class="col-md-5" ng-class="{ 'has-error': !configureScheduleAndRetention.hours.length }">
|
|
{{ 'backups.configureBackupSchedule.hours' | tr }}: <multiselect class="input-sm stretch" ng-model="configureScheduleAndRetention.hours" options="a.name for a in cronHours" data-multiple="true"></multiselect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="control-label" for="backupRetention">{{ 'backups.configureBackupSchedule.retentionPolicy' | tr }}</label>
|
|
<select class="form-control" id="backupRetention" ng-model="configureScheduleAndRetention.retentionPolicy" ng-options="a.value as a.name for a in retentionPolicies"></select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer ">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
|
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="configureScheduleAndRetention.submit()" ng-disabled="!configureScheduleAndRetention.valid() || configureScheduleAndRetention.busy"><i class="fa fa-circle-notch fa-spin" ng-show="configureScheduleAndRetention.busy"></i><span> {{ 'main.dialog.save' | tr }}</span></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- modal backup config -->
|
|
<div class="modal fade" id="configureBackupModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'backups.configureBackupStorage.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form name="configureBackupForm" role="form" novalidate ng-submit="configureBackup.submit()" autocomplete="off">
|
|
<p class="has-error text-center" ng-show="configureBackup.error">{{ configureBackup.error.generic }}</p>
|
|
|
|
<div class="form-group">
|
|
<label class="control-label" for="storageProviderProvider">{{ 'backups.configureBackupStorage.provider' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#storage-providers" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<p class="small text-info" ng-show="backupConfig.provider !== configureBackup.provider">Backups in the old storage location have to be removed manually.</p>
|
|
<select class="form-control" id="storageProviderProvider" ng-model="configureBackup.provider" ng-options="a.value as a.name for a in storageProviders" ng-change=configureBackup.clearProviderFields()></select>
|
|
</div>
|
|
|
|
<!-- Noop -->
|
|
<div class="form-group" ng-show="configureBackup.provider === 'noop'">
|
|
<p class="has-error">{{ 'backups.configureBackupStorage.noopNote' | tr }}</p>
|
|
</div>
|
|
|
|
<!-- mountpoint -->
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.mountPoint || (configureBackupForm.mountPoint.$dirty && !configureBackup.mountPoint) }" ng-show="configureBackup.provider === 'mountpoint'">
|
|
<label class="control-label" for="inputConfigureMountPoint">{{ 'backups.configureBackupStorage.mountPoint' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.mountPoint" id="inputConfigureMountPoint" name="mountPoint" ng-disabled="configureBackup.busy" placeholder="/mnt/backups" ng-required="configureBackup.provider === 'mountpoint'">
|
|
<p ng-show="configureBackup.provider === 'mointpoint'" ng-bind-html="'backups.configureBackupStorage.mountPointDescription' | tr:{ providerDocsLink: 'https://docs.cloudron.io/backups/#'+configureBackup.provider }"></p>
|
|
</div>
|
|
|
|
<!-- CIFS/NFS/SSHFS -->
|
|
<div class="form-group" ng-show="configureBackup.provider === 'cifs' || configureBackup.provider === 'nfs' || configureBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="configureBackupHost">{{ 'backups.configureBackupStorage.server' | tr }} ({{ configureBackup.provider }})</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.host" id="configureBackupHost" name="host" ng-disabled="configureBackup.busy" placeholder="Server IP or hostname" ng-required="configureBackup.provider === 'cifs' || configureBackup.provider === 'nfs' || configureBackup.provider === 'sshfs'">
|
|
</div>
|
|
|
|
<!-- CIFS -->
|
|
<div class="checkbox" ng-show="configureBackup.provider === 'cifs'">
|
|
<label>
|
|
<input type="checkbox" ng-model="configureBackup.mountOptions.seal">{{ 'backups.configureBackupStorage.cifsSealSupport' | tr }}</input>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- CIFS/NFS/SSHFS -->
|
|
<div class="form-group" ng-show="configureBackup.provider === 'cifs' || configureBackup.provider === 'nfs' || configureBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="configureBackupRemoteDir">{{ 'backups.configureBackupStorage.remoteDirectory' | tr }} ({{ configureBackup.provider }})</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.remoteDir" id="configureBackupRemoteDir" name="remoteDir" ng-disabled="configureBackup.busy" placeholder="/share" ng-required="configureBackup.provider === 'cifs' || configureBackup.provider === 'nfs' || configureBackup.provider === 'sshfs'">
|
|
</div>
|
|
|
|
<!-- CIFS -->
|
|
<div class="form-group" ng-show="configureBackup.provider === 'cifs'">
|
|
<label class="control-label" for="configureBackupUsername">{{ 'backups.configureBackupStorage.username' | tr }} ({{ configureBackup.provider }})</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.username" id="configureBackupUsername" name="cifsUsername" ng-disabled="configureBackup.busy">
|
|
</div>
|
|
|
|
<!-- CIFS -->
|
|
<div class="form-group" ng-show="configureBackup.provider === 'cifs'">
|
|
<label class="control-label" for="configureBackupPassword">{{ 'backups.configureBackupStorage.password' | tr }} ({{ configureBackup.provider }})</label>
|
|
<input type="password" class="form-control" ng-model="configureBackup.mountOptions.password" id="configureBackupPassword" name="cifsPassword" ng-disabled="configureBackup.busy" password-reveal>
|
|
</div>
|
|
|
|
<!-- EXT4 -->
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.diskPath || !configureBackup.mountOptions.diskPath }" ng-show="configureBackup.provider === 'ext4' || configureBackup.provider === 'xfs'">
|
|
<label class="control-label" for="inputConfigureDiskPath">{{ 'backups.configureBackupStorage.diskPath' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.diskPath" id="inputConfigureDiskPath" name="diskPath" ng-disabled="configureBackup.busy" placeholder="/dev/disk/by-uuid/uuid" ng-required="configureBackup.provider === 'ext4' || configureBackup.provider === 'xfs'">
|
|
</div>
|
|
|
|
<!-- SSHFS -->
|
|
<div class="form-group" ng-show="configureBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="configureBackupPort">{{ 'backups.configureBackupStorage.port' | tr }}</label>
|
|
<input type="number" class="form-control" ng-model="configureBackup.mountOptions.port" id="configureBackupPort" name="port" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'sshfs'">
|
|
</div>
|
|
|
|
<!-- SSHFS -->
|
|
<div class="form-group" ng-show="configureBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="configureBackupUser">{{ 'backups.configureBackupStorage.user' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.mountOptions.user" id="configureBackupUser" name="user" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'sshfs'">
|
|
</div>
|
|
|
|
<!-- SSHFS -->
|
|
<div class="form-group" ng-show="configureBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="configureBackupPrivateKey">{{ 'backups.configureBackupStorage.privateKey' | tr }}</label>
|
|
<textarea class="form-control" ng-model="configureBackup.mountOptions.privateKey" id="configureBackupPrivateKey" name="privateKey" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'sshfs'"></textarea>
|
|
</div>
|
|
|
|
<!-- Filesystem -->
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.backupFolder }" ng-show="configureBackup.provider === 'filesystem'">
|
|
<label class="control-label" for="inputConfigureBackupFolder">{{ 'backups.configureBackupStorage.localDirectory' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.backupFolder" id="inputConfigureBackupFolder" name="backupFolder" ng-disabled="configureBackup.busy" placeholder="Directory for backups" ng-required="configureBackup.provider === 'filesystem'">
|
|
</div>
|
|
|
|
<!-- Filesystem/SSHFS/CIFS/NFS/EXT4/mountpoint -->
|
|
<div class="checkbox" ng-show="configureBackup.provider === 'filesystem' || mountlike(configureBackup.provider)">
|
|
<label>
|
|
<input type="checkbox" ng-model="configureBackup.useHardlinks">{{ 'backups.configureBackupStorage.hardlinksLabel' | tr }}</input>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- mountpoint -->
|
|
<div class="checkbox" ng-show="configureBackup.provider === 'mountpoint'">
|
|
<label>
|
|
<input type="checkbox" ng-model="configureBackup.chown">{{ 'backups.configureBackupStorage.chown' | tr }}</input>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- S3/Minio/SOS/GCS/UpCloud/B2/R2 -->
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.endpoint }" ng-show="configureBackup.provider === 'minio' || configureBackup.provider === 'upcloud-objectstorage' || configureBackup.provider === 'backblaze-b2' || configureBackup.provider === 'cloudflare-r2' || configureBackup.provider === 's3-v4-compat' || configureBackup.provider === 'idrive-e2'">
|
|
<label class="control-label" for="inputConfigureBackupEndpoint">{{ 'backups.configureBackupStorage.s3Endpoint' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.endpoint" id="inputConfigureBackupEndpoint" name="endpoint" ng-disabled="configureBackup.busy" placeholder="URL" ng-required="configureBackup.provider === 'minio' || configureBackup.provider === 'upcloud-objectstorage' || configureBackup.provider === 'backblaze-b2' || configureBackup.provider === 'cloudflare-r2' || configureBackup.provider === 's3-v4-compat' || configureBackup.provider === 'idrive-e2'">
|
|
</div>
|
|
|
|
<div class="checkbox" ng-show="configureBackup.provider === 'minio' || configureBackup.provider === 's3-v4-compat'" >
|
|
<label>
|
|
<input type="checkbox" ng-model="configureBackup.acceptSelfSignedCerts">{{ 'backups.configureBackupStorage.acceptSelfSignedCerts' | tr }}</input>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.bucket }" ng-show="s3like(configureBackup.provider) || configureBackup.provider === 'gcs'">
|
|
<label class="control-label" for="inputConfigureBackupBucket">{{ 'backups.configureBackupStorage.bucketName' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.bucket" id="inputConfigureBackupBucket" name="bucket" ng-disabled="configureBackup.busy" ng-required="s3like(configureBackup.provider)">
|
|
</div>
|
|
|
|
<!-- S3/Minio/SOS/GCS/SSHFS/CIFS/NFS/B2 -->
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.prefix }" ng-show="configureBackup.provider !== 'filesystem' && configureBackup.provider !== 'noop'">
|
|
<label class="control-label" for="inputConfigureBackupPrefix">{{ 'backups.configureBackupStorage.prefix' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.prefix" id="inputConfigureBackupPrefix" name="prefix" ng-disabled="configureBackup.busy" placeholder="Prefix for backup file names">
|
|
</div>
|
|
|
|
<!-- S3/Minio/SOS/GCS -->
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 's3'">
|
|
<label class="control-label" for="inputConfigureBackupS3Region">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupS3Region" ng-model="configureBackup.region" ng-options="a.value as a.name for a in s3Regions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 's3'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 's3-v4-compat'">
|
|
<label class="control-label" for="inputConfigureBackupS3V4CompatRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<input class="form-control" type="text" name="region" id="inputConfigureBackupS3V4CompatRegion" ng-model="configureBackup.region" ng-disabled="configureBackup.busy" placeholder="Leave empty to use us-east-1 as default"></input>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'digitalocean-spaces'">
|
|
<label class="control-label" for="inputConfigureBackupDORegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupDORegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in doSpacesRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'digitalocean-spaces'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'exoscale-sos'">
|
|
<label class="control-label" for="inputConfigureBackupExoscaleRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupExoscaleRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in exoscaleSosRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'exoscale-sos'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'wasabi'">
|
|
<label class="control-label" for="inputConfigureBackupWasabiRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupWasabiRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in wasabiRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'wasabi'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'scaleway-objectstorage'">
|
|
<label class="control-label" for="inputConfigureBackupScalewayRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupScalewayRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in scalewayRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'scaleway-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'linode-objectstorage'">
|
|
<label class="control-label" for="inputConfigureBackupLinodeRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupLinodeRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in linodeRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'linode-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'ovh-objectstorage'">
|
|
<label class="control-label" for="inputConfigureBackupOvhRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupOvhRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in ovhRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'ovh-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'ionos-objectstorage'">
|
|
<label class="control-label" for="inputConfigureBackupIonosRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupIonosRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in ionosRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'ionos-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.region }" ng-show="configureBackup.provider === 'vultr-objectstorage'">
|
|
<label class="control-label" for="inputConfigureBackupVultrRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputConfigureBackupVultrRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in vultrRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'vultr-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.accessKeyId }" ng-show="s3like(configureBackup.provider)">
|
|
<label class="control-label" for="inputConfigureBackupAccessKeyId">{{ 'backups.configureBackupStorage.s3AccessKeyId' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.accessKeyId" id="inputConfigureBackupAccessKeyId" name="accessKeyId" ng-disabled="configureBackup.busy" ng-required="s3like(configureBackup.provider)">
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.secretAccessKey }" ng-show="s3like(configureBackup.provider)">
|
|
<label class="control-label" for="inputConfigureBackupSecretAccessKey">{{ 'backups.configureBackupStorage.s3SecretAccessKey' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="configureBackup.secretAccessKey" id="inputConfigureBackupSecretAccessKey" name="secretAccessKey" ng-disabled="configureBackup.busy" ng-required="s3like(configureBackup.provider)">
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.gcsKeyInput }" ng-show="configureBackup.provider === 'gcs'">
|
|
<label class="control-label" for="gcsKeyInput">{{ 'backups.configureBackupStorage.gcsServiceKey' | tr }}</label>
|
|
|
|
<div class="input-group">
|
|
<input type="file" id="gcsKeyFileInput" style="display:none"/>
|
|
<input type="text" class="form-control" placeholder="Service Account Key" ng-model="configureBackup.gcsKey.keyFileName" id="gcsKeyInput" name="cert" onclick="getElementById('gcsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'gcs'">
|
|
<span class="input-group-addon">
|
|
<i class="fa fa-upload" onclick="getElementById('gcsKeyFileInput').click();"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-show="configureBackup.provider !== 'noop'">
|
|
<label class="control-label" for="storageFormat">{{ 'backups.configureBackupStorage.format' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#backup-formats" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<p class="small text-info" ng-show="backupConfig.format !== configureBackup.format">{{ 'backups.configureBackupStorage.formatChangeNote' | tr }}</p>
|
|
<p class="small text-info" ng-show="configureBackup.format === 'rsync' && (s3like(configureBackup.provider) || configureBackup.provider === 'gcs')">{{ 'backups.configureBackupStorage.s3LikeNote' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#amazon-s3" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></p>
|
|
<select class="form-control" id="storageFormat" ng-model="configureBackup.format" ng-options="a.value as a.name for a in formats"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.password }" ng-show="configureBackup.provider !== 'noop'">
|
|
<label class="control-label" for="inputConfigureBackupPassword">{{ 'backups.configureBackupStorage.encryptionPassword' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#encryption" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<p class="small">{{ 'backups.configureBackupStorage.encryptionDescription' | tr }}</p>
|
|
<input type="text" class="form-control" name="encryptionPassword" ng-model="configureBackup.password" id="inputConfigureBackupPassword" ng-disabled="configureBackup.busy" placeholder="{{ 'backups.configureBackupStorage.encryptionPasswordPlaceholder' | tr }}">
|
|
</div>
|
|
|
|
<div class="form-group" ng-show="configureBackup.password && configureBackup.password !== SECRET_PLACEHOLDER" ng-class="{ 'has-error': (configureBackupForm.encryptionPassword.$dirty && configureBackup.password !== configureBackup.passwordRepeat) }">
|
|
<label class="control-label" for="inputConfigureBackupPasswordRepeat">{{ 'backups.configureBackupStorage.encryptionPasswordRepeat' | tr }}</label>
|
|
<input id="inputConfigureBackupPasswordRepeat" type="text" class="form-control" name="passwordRepeat" ng-model="configureBackup.passwordRepeat" ng-disabled="configureBackup.busy">
|
|
</div>
|
|
|
|
<div class="checkbox" ng-show="configureBackup.password !== '' && configureBackup.format === 'rsync'">
|
|
<label>
|
|
<input type="checkbox" ng-model="configureBackup.encryptedFilenames">{{ 'backups.configureBackupStorage.encryptFilenames' | tr }}</input>
|
|
<sup><a ng-href="https://docs.cloudron.io/backups/#filenames" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup>
|
|
</label>
|
|
</div>
|
|
|
|
<a href="" ng-click="configureBackup.advancedVisible = true" ng-hide="configureBackup.advancedVisible">{{ 'backups.configureBackupStorage.advancedSettings' | tr }}</a>
|
|
<div uib-collapse="!configureBackup.advancedVisible">
|
|
|
|
<div class="form-group">
|
|
<label class="control-label">{{ 'backups.configureBackupStorage.memoryLimit' | tr }}: <b>{{ configureBackup.memoryLimit | prettyBinarySize:'800 MB' }}</b></label>
|
|
<p class="small">{{ 'backups.configureBackupStorage.memoryLimitDescription' | tr }}</p>
|
|
<div style="padding: 0 10px;">
|
|
<slider id="sliderConfigureBackupMemoryLimit" ng-model="configureBackup.memoryLimit" step="134217728" tooltip="hide" ticks="configureBackup.memoryTicks" ticks-snap-bounds="67108864"></slider>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-show="s3like(configureBackup.provider)">
|
|
<label class="control-label">{{ 'backups.configureBackupStorage.uploadPartSize' | tr }}: <b>{{ configureBackup.uploadPartSize | prettyBinarySize:'Default (50 MiB)' }}</b></label>
|
|
<p class="small">{{ 'backups.configureBackupStorage.uploadPartSizeDescription' | tr }}</p>
|
|
<div style="padding: 0 10px;">
|
|
<slider id="sliderConfigureBackupUploadPartSize" ng-model="configureBackup.uploadPartSize" step="1048576" tooltip="hide" ticks="configureBackup.uploadPartSizeTicks" ticks-snap-bounds="2097152"></slider>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-show="configureBackup.format === 'rsync' && configureBackup.provider !== 'noop'">
|
|
<label class="control-label">{{ 'backups.configureBackupStorage.uploadConcurrency' | tr }}: <b>{{ configureBackup.syncConcurrency }}</b></label>
|
|
<p class="small">{{ 'backups.configureBackupStorage.uploadConcurrencyDescription' | tr }}</p>
|
|
<div style="padding: 0 10px;">
|
|
<slider id="sliderConfigureBackupSyncConcurrency" ng-model="configureBackup.syncConcurrency" tooltip="hide" min="10" max="200" step="10"></slider>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-show="configureBackup.format === 'rsync' && (s3like(configureBackup.provider) || configureBackup.provider === 'gcs')">
|
|
<label class="control-label">{{ 'backups.configureBackupStorage.downloadConcurrency' | tr }}: <b>{{ configureBackup.downloadConcurrency }}</b></label>
|
|
<p class="small">{{ 'backups.configureBackupStorage.downloadConcurrencyDescription' | tr }}</p>
|
|
<div style="padding: 0 10px;">
|
|
<slider id="sliderConfigureBackupCopyConcurrency" ng-model="configureBackup.downloadConcurrency" tooltip="hide" min="10" max="200" step="10"></slider>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-show="configureBackup.format === 'rsync' && (s3like(configureBackup.provider) || configureBackup.provider === 'gcs')">
|
|
<label class="control-label">{{ 'backups.configureBackupStorage.copyConcurrency' | tr }}: <b>{{ configureBackup.copyConcurrency }}</b></label>
|
|
<p class="small">{{ 'backups.configureBackupStorage.copyConcurrencyDescription' | tr }}
|
|
<span ng-show="configureBackup.provider === 'digitalocean-spaces'">{{ 'backups.configureBackupStorage.copyConcurrencyDigitalOceanNote' | tr }}</span>
|
|
</p>
|
|
<div style="padding: 0 10px;">
|
|
<slider id="sliderConfigureBackupCopyConcurrency" ng-model="configureBackup.copyConcurrency" tooltip="hide" min="10" max="500" step="10"></slider>
|
|
</div>
|
|
</div>
|
|
|
|
</div> <!-- advanced -->
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="configureBackupForm.$invalid || (configureBackup.password !== SECRET_PLACEHOLDER && configureBackup.password !== configureBackup.passwordRepeat)"/>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer ">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
|
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="configureBackup.submit()" ng-disabled="configureBackupForm.$invalid || configureBackup.busy || (configureBackup.password !== SECRET_PLACEHOLDER && configureBackup.password !== configureBackup.passwordRepeat)"><i class="fa fa-circle-notch fa-spin" ng-show="configureBackup.busy"></i><span> {{ 'main.dialog.save' | tr }}</span></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
|
|
<div class="text-left">
|
|
<h1>{{ 'backups.title' | tr }}</h1>
|
|
</div>
|
|
|
|
<div class="text-left">
|
|
<h3>{{ 'backups.location.title' | tr }}</h3>
|
|
</div>
|
|
|
|
<div class="card" style="margin-bottom: 15px;">
|
|
<p>{{ 'backups.location.description' | tr }}
|
|
<span ng-show="manualBackupApps.length">
|
|
{{ 'backups.location.disabledList' | tr }}
|
|
<span ng-repeat="app in manualBackupApps">
|
|
<a ng-href="/#/app/{{app.id}}/backups">{{app.label || app.fqdn}}</a><span ng-hide="$last">,</span>
|
|
</span>
|
|
</span>
|
|
</p>
|
|
|
|
<p ng-show="backupConfig.provider === 'noop'" class="text-danger" ng-bind-html="'backups.check.noop' | tr | markdown2html"></p>
|
|
<p ng-show="backupConfig.provider === 'filesystem'" class="text-danger" ng-bind-html="'backups.check.sameDisk' | tr | markdown2html"></p>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<span class="text-muted">{{ 'backups.location.provider' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-6 text-right">
|
|
<span>{{ prettyProviderName(backupConfig.provider) }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<span class="text-muted">{{ 'backups.location.location' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-6 text-right no-wrap">
|
|
<span ng-show="backupConfig.provider === 'filesystem'">{{ backupConfig.backupFolder }}</span>
|
|
<span ng-show="mountlike(backupConfig.provider)">
|
|
<i class="fa fa-circle" ng-style="{ color: mountStatus.state === 'active' ? '#27CE65' : '#d9534f' }" ng-show="mountStatus" uib-tooltip="{{ mountStatus.message }}"></i>
|
|
<span ng-show="backupConfig.provider === 'filesystem' || backupConfig.provider === 'ext4' || backupConfig.provider === 'xfs' || backupConfig.provider === 'mountpoint'">{{ backupConfig.mountOptions.diskPath || backupConfig.mountPoint }}{{ (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
|
<span ng-show="backupConfig.provider === 'cifs' || backupConfig.provider === 'nfs' || backupConfig.provider === 'sshfs'">{{ backupConfig.mountOptions.host }}:{{ backupConfig.mountOptions.remoteDir }}{{ (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
|
</span>
|
|
|
|
<span ng-show="backupConfig.provider !== 's3' && backupConfig.provider !== 'minio' && (s3like(backupConfig.provider) || backupConfig.provider === 'gcs')">{{ backupConfig.bucket + (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
|
<span ng-show="backupConfig.provider === 's3'">{{ backupConfig.region + ' ' + backupConfig.bucket + (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
|
<span ng-show="backupConfig.provider === 'minio'">{{ backupConfig.endpoint + ' ' + backupConfig.bucket + (backupConfig.prefix ? '/' : '') + backupConfig.prefix }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" ng-show="backupConfig.endpoint && backupConfig.provider !== 'minio'">
|
|
<div class="col-xs-3">
|
|
<span class="text-muted">{{ 'backups.location.endpoint' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-9 text-right">
|
|
<span>{{ backupConfig.endpoint || backupConfig.region }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<span class="text-muted">{{ 'backups.location.format' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-6 text-right">
|
|
<span>{{ backupConfig.format }} <i class="fas fa-lock" ng-show="backupConfig.password" ></i></span>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-show="user.isAtLeastOwner" ng-click="configureBackup.show()">{{ 'backups.location.configure' | tr }}</button>
|
|
<button class="btn btn-outline btn-default pull-right" ng-show="user.isAtLeastOwner && mountlike(backupConfig.provider)" ng-disabled="remount.busy" ng-click="remount.submit()"><i class="fa fa-circle-notch fa-spin" ng-show="remount.busy"></i> {{ 'backups.location.remount' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-left section-header">
|
|
<h3>
|
|
{{ 'backups.schedule.title' | tr }}
|
|
<a class="btn btn-sm btn-default pull-right" ng-href="/logs.html?taskId={{cleanupBackups.taskId}}" target="_blank" uib-tooltip="{{ 'backups.logs.showLogs' | tr }}"><i class="fas fa-align-left"></i></a>
|
|
</h3>
|
|
</div>
|
|
|
|
<div class="card" style="margin-bottom: 15px;">
|
|
<p>{{ 'backups.schedule.description' | tr }}</p>
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<span class="text-muted">{{ 'backups.schedule.schedule' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-6 text-right">
|
|
<span>{{ prettyBackupSchedule(backupConfig.schedulePattern) }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<span class="text-muted">{{ 'backups.schedule.retentionPolicy' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-6 text-right">
|
|
<span>{{ prettyBackupRetentionPolicy(backupConfig.retentionPolicy) }}</span>
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-default" ng-click="cleanupBackups.ask()" ng-disabled="cleanupBackups.busy" style="margin-right: 5px"><i class="fa fa-circle-notch fa-spin" ng-show="cleanupBackups.busy"></i> {{ 'backups.listing.cleanupBackups' | tr }}</button>
|
|
<button class="btn btn-outline btn-primary pull-right" ng-show="user.isAtLeastOwner" ng-click="configureScheduleAndRetention.show()">{{ 'backups.schedule.configure' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-left section-header">
|
|
<h3>
|
|
{{ 'backups.listing.title' | tr }}
|
|
<a class="btn btn-sm btn-default pull-right" ng-href="/logs.html?taskId={{createBackup.taskId}}" target="_blank" uib-tooltip="{{ 'backups.logs.showLogs' | tr }}"><i class="fas fa-align-left"></i></a>
|
|
</h3>
|
|
</div>
|
|
|
|
<div class="card card-large">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<p ng-show="!backups.length">{{ 'backups.listing.noBackups' | tr }}</p>
|
|
|
|
<table class="table table-hover" style="margin: 0;" ng-hide="!backups.length">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 20px"></th>
|
|
<th>{{ 'backups.listing.version' | tr }}</th>
|
|
<th>{{ 'main.table.date' | tr }}</th>
|
|
<th>{{ 'backups.listing.contents' | tr }}</th>
|
|
<th class="text-right">{{ 'main.actions' | tr }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="backup in backups">
|
|
<td><i class="fas fa-archive" ng-show="backup.preserveSecs === -1" uib-tooltip="{{ 'backups.listing.tooltipPreservedBackup' | tr }}"></i></td>
|
|
<td ng-click="backupDetails.show(backup)" class="hand">v{{ backup.packageVersion }}</td>
|
|
<td ng-click="backupDetails.show(backup)" class="hand"><span uib-tooltip="{{ backup.creationTime | prettyLongDate }}">{{ backup.creationTime | prettyDate }} <b ng-show="backup.label">({{ backup.label }})</b></span></td>
|
|
<td ng-click="backupDetails.show(backup)" class="hand">
|
|
<span ng-show="!backup.contents.length">{{ 'backups.listing.noApps' | tr }}</span>
|
|
<span ng-show="backup.contents.length">{{ 'backups.listing.appCount' | tr:{ appCount: backup.contents.length } }}</span>
|
|
</td>
|
|
<td class="text-right no-wrap" style="vertical-align: bottom">
|
|
<button class="btn btn-xs btn-default" ng-click="editBackup.show(backup)" uib-tooltip="{{ 'backups.listing.tooltipEditBackup' | tr }}"><i class="fa fa-pencil-alt"></i></button>
|
|
<button class="btn btn-xs btn-default" ng-click="downloadConfig(backup)" uib-tooltip="{{ 'backups.listing.tooltipDownloadBackupConfig' | tr }}"><i class="fas fa-file-alt"></i></button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<div class="row" ng-show="createBackup.busy">
|
|
<div class="col-md-12" style="margin-bottom: 10px;">
|
|
<div class="progress progress-striped active animateMe">
|
|
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ createBackup.percent }}%"></div>
|
|
</div>
|
|
<p>{{ createBackup.message }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" ng-show="!createBackup.busy && !createBackup.active && createBackup.errorMessage">
|
|
<div class="col-md-12">
|
|
<p class="has-error">{{ createBackup.errorMessage }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary" ng-click="createBackup.startBackup()" ng-show="!createBackup.busy">{{ 'backups.listing.backupNow' | tr }}</button>
|
|
<button class="btn btn-outline btn-danger" ng-click="createBackup.stopTask()" ng-show="createBackup.busy">{{ 'backups.listing.stopTask' | tr:{ taskType: createBackup.taskType } }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|