Files
cloudron-box/dashboard/public/views/backups.html
Girish Ramakrishnan 51ed5b78f2 backups: add preserve attributes checkbox
(cherry picked from commit 837d5803c8)
2025-02-01 09:14:34 +01:00

817 lines
57 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.backupEdit.remotePath' | tr }}:</div>
<div class="col-xs-10 text-right">{{ backupDetails.backup.remotePath }}</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="content in backupDetails.backup.contents | orderBy:['label','fqdn']">
<a ng-if="content.fqdn" ng-href="/#/app/{{content.id}}/backups">{{ content.label || content.fqdn }}</a>
<a ng-if="!content.fqdn" ng-href="/#/eventlog?search={{content.id}}">{{ content.id }}</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 schedule config -->
<div class="modal fade" id="backupPolicyModal" 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="backupPolicyForm" role="form" novalidate ng-submit="backupPolicy.submit()" autocomplete="off">
<p class="has-error text-center" ng-show="backupPolicy.error">{{ backupPolicy.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': !backupPolicy.days.length }">
{{ 'backups.configureBackupSchedule.days' | tr }}: <multiselect id="backupSchedule" class="input-sm stretch" ng-model="backupPolicy.days" options="a.name for a in cronDays" data-multiple="true" ng-required></multiselect>
</div>
<div class="col-md-5" ng-class="{ 'has-error': !backupPolicy.hours.length }">
{{ 'backups.configureBackupSchedule.hours' | tr }}: <multiselect class="input-sm stretch" ng-model="backupPolicy.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="backupPolicy.retention" ng-options="a.value as a.name for a in backupRetentions"></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="backupPolicy.submit()" ng-disabled="!backupPolicy.valid() || backupPolicy.busy"><i class="fa fa-circle-notch fa-spin" ng-show="backupPolicy.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 === 'mountpoint'" 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/XFS -->
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.diskPath || !configureBackup.mountOptions.diskPath }" ng-show="configureBackup.provider === 'xfs' || configureBackup.provider === 'ext4'">
<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 === 'xfs' || configureBackup.provider === 'ext4'">
</div>
<!-- Disk -->
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.diskPath || !configureBackup.mountOptions.diskPath }" ng-show="configureBackup.provider === 'disk'">
<label class="control-label">{{ 'backups.configureBackupStorage.diskPath' | tr }}</label>
<select class="form-control" ng-model="configureBackup.disk" ng-options="item as item.label for item in configureBackup.blockDevices track by item.path" ng-required="configureBackup.provider === 'disk'"></select>
</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>
<!-- CIFS/mountpoint -->
<div class="checkbox" ng-show="configureBackup.provider === 'mountpoint' || configureBackup.provider === 'cifs'">
<label>
<input type="checkbox" ng-model="configureBackup.preserveAttributes">{{ 'backups.configureBackupStorage.preserveAttributesLabel' | 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 === 'hetzner-objectstorage'">
<label class="control-label" for="inputConfigureBackupHetznerRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
<select class="form-control" name="region" id="inputConfigureBackupHetznerRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in hetznerRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'hetzner-objectstorage'"></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.region }" ng-show="configureBackup.provider === 'contabo-objectstorage'">
<label class="control-label" for="inputConfigureBackupContaboRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
<select class="form-control" name="region" id="inputConfigureBackupContaboRegion" ng-model="configureBackup.endpoint" ng-options="a.value as a.name for a in contaboRegions" ng-disabled="configureBackup.busy" ng-required="configureBackup.provider === 'contabo-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" for="sliderConfigureBackupMemoryLimit">{{ 'backups.configureBackupStorage.memoryLimit' | tr }}: <b>{{ configureBackup.memoryLimit | prettyBinarySize:'1024 MB' }}</b></label>
<p class="small">{{ 'backups.configureBackupStorage.memoryLimitDescription' | tr }}</p>
<input type="range" id="sliderConfigureBackupMemoryLimit" ng-model="configureBackup.memoryLimit" step="{{ 256*1024*1024 }}" min="{{ MIN_MEMORY_LIMIT }}" max="{{ MAX_MEMORY_LIMIT }}" />
</div>
<div class="form-group" ng-show="s3like(configureBackup.provider)">
<label class="control-label" for="sliderConfigureBackupUploadPartSize">{{ 'backups.configureBackupStorage.uploadPartSize' | tr }}: <b>{{ configureBackup.uploadPartSize | prettyBinarySize:'Default (50 MiB)' }}</b></label>
<p class="small">{{ 'backups.configureBackupStorage.uploadPartSizeDescription' | tr }}</p>
<input type="range" id="sliderConfigureBackupUploadPartSize" ng-model="configureBackup.uploadPartSize" list="uploadPartSizeTicks" step="{{ 1024*1024 }}" min="{{ 1024*1024 }}" max="{{ 1024*1024*1024 }}" />
<datalist id="uploadPartSizeTicks">
<option value="{{ 1024*1024 }}"></option>
<option value="{{ 64*1024*1024 }}"></option>
<option value="{{ 128*1024*1024 }}"></option>
<option value="{{ 256*1024*1024 }}"></option>
<option value="{{ 512*1024*1024 }}"></option>
<option value="{{ 1024*1024*1024 }}"></option>
</datalist>
</div>
<div class="form-group" ng-show="configureBackup.format === 'rsync' && configureBackup.provider !== 'noop'">
<label class="control-label" for="sliderConfigureBackupSyncConcurrency">{{ 'backups.configureBackupStorage.uploadConcurrency' | tr }}: <b>{{ configureBackup.syncConcurrency }}</b></label>
<p class="small">{{ 'backups.configureBackupStorage.uploadConcurrencyDescription' | tr }}</p>
<input type="range" id="sliderConfigureBackupSyncConcurrency" ng-model="configureBackup.syncConcurrency" step="10" min="10" max="200" />
</div>
<div class="form-group" ng-show="configureBackup.format === 'rsync' && (s3like(configureBackup.provider) || configureBackup.provider === 'gcs')">
<label class="control-label" for="sliderConfigureBackupDownloadConcurrency">{{ 'backups.configureBackupStorage.downloadConcurrency' | tr }}: <b>{{ configureBackup.downloadConcurrency }}</b></label>
<p class="small">{{ 'backups.configureBackupStorage.downloadConcurrencyDescription' | tr }}</p>
<input type="range" id="sliderConfigureBackupDownloadConcurrency" ng-model="configureBackup.downloadConcurrency" step="10" min="10" max="200" />
</div>
<div class="form-group" ng-show="configureBackup.format === 'rsync' && (s3like(configureBackup.provider) || configureBackup.provider === 'gcs')">
<label class="control-label" for="sliderConfigureBackupCopyConcurrency">{{ '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>
<input type="range" id="sliderConfigureBackupCopyConcurrency" ng-model="configureBackup.copyConcurrency" step="10" min="10" max="500" />
</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>
<!-- Modal archive restore -->
<div class="modal fade" id="restoreArchiveModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'backups.restoreArchiveDialog.title' | tr }}</h4>
</div>
<div class="modal-body" style="padding: 0 15px">
<p ng-bind-html="'backups.restoreArchiveDialog.description' | tr:{ appId: archiveRestore.manifest.id, fqdn: archiveRestore.fqdn, creationTime: (archiveRestore.archive.creationTime | prettyLongDate) }"></p>
<form role="form" ng-submit="archiveRestore.submit()" autocomplete="off">
<fieldset>
<div class="form-group" ng-class="{ 'has-error': archiveRestore.error.location.fqdn === archiveRestore.subdomain + '.' + archiveRestore.domain.domain }">
<label class="control-label" for="cloneLocationInput">{{ 'app.cloneDialog.location' | tr }}</label>
<div ng-show="archiveRestore.error.location.fqdn === archiveRestore.subdomain + '.' + archiveRestore.domain.domain"><small>{{ archiveRestore.error.location.message }}</small></div>
<div class="input-group form-inline">
<input type="text" class="form-control" ng-model="archiveRestore.subdomain" id="cloneLocationInput" name="location" placeholder="{{ 'appstore.installDialog.locationPlaceholder' | tr }}" autofocus>
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span>{{ '.' + archiveRestore.domain.domain }}</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-repeat="domain in domains">
<a href="" ng-click="archiveRestore.domain = domain">{{ domain.domain }}</a>
</li>
</ul>
</div>
</div>
</div>
<div class="has-error text-center" ng-show="archiveRestore.error.secondaryDomain">{{ archiveRestore.error.secondaryDomain }}</div>
<div ng-repeat="(env, info) in archiveRestore.manifest.httpPorts">
<ng-form name="secondaryDomainInfo_form">
<div class="form-group" ng-class="{ 'has-error': (!secondaryDomainInfo_form.itemName{{$index}}.$dirty && archiveRestore.error.secondaryDomain) || (secondaryDomainInfo_form.itemName{{$index}}.$dirty && secondaryDomainInfo_form.itemName{{$index}}.$invalid) || (archiveRestore.error.location.fqdn === archiveRestore.secondaryDomains[env].subdomain + '.' + archiveRestore.secondaryDomains[env].domain.domain) }">
<label class="control-label" for="secondaryDomainInput{{env}}">
{{ info.title }}
<sup>
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}}"><i class="fa fa-question-circle"></i></a>
</sup>
</label>
<div ng-show="archiveRestore.error.location.fqdn === archiveRestore.secondaryDomains[env].subdomain + '.' + archiveRestore.secondaryDomains[env].domain.domain"><small>{{ archiveRestore.error.location.message }}</small></div>
<div class="input-group form-inline">
<input type="text" class="form-control" ng-model="archiveRestore.secondaryDomains[env].subdomain" name="location{{$index}}" placeholder="{{ 'app.location.locationPlaceholder' | tr }}" autofocus>
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span>.{{ archiveRestore.secondaryDomains[env].domain.domain }}</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-repeat="domain in domains">
<a href="" ng-click="archiveRestore.secondaryDomains[env].domain = domain">{{ domain.domain }}</a>
</li>
</ul>
</div>
</div>
</div>
</ng-form>
</div>
<p class="text-small text-warning" ng-show="archiveRestore.domain.provider === 'noop' || archiveRestore.domain.provider === 'manual'" ng-bind-html="'appstore.installDialog.manualWarning' | tr:{ location: ((archiveRestore.subdomain ? archiveRestore.subdomain + '.' : '') + archiveRestore.domain.domain) }"></p>
<div class="has-error text-center" ng-show="archiveRestore.error.port">{{ archiveRestore.error.port }}</div>
<div ng-repeat="(env, info) in archiveRestore.portInfo">
<ng-form name="portInfo_form">
<div class="form-group" ng-class="{ 'has-error': (!archiveRestore.itemName{{$index}}.$dirty && archiveRestore.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
<label class="control-label" for="inputPortInfo{{env}}"><input type="checkbox" ng-model="archiveRestore.portsEnabled[env]">
{{ info.title }}
<sup>
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}}"><i class="fa fa-question-circle"></i></a>
</sup>
<small style="padding-left: 5px;" ng-show="info.readOnly">{{ 'appstore.installDialog.portReadOnly' | tr }}</small>
</label>
<input type="number" class="form-control" ng-model="archiveRestore.ports[env]" ng-disabled="!archiveRestore.portsEnabled[env]" ng-readonly="info.readOnly" id="inputPortInfo{{env}}" later-name="itemName{{$index}}" min="{{hostPortMin}}" max="{{hostPortMax}}" required>
<p class="text-small text-warning text-bold" ng-show="archiveRestore.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p>
</div>
</ng-form>
</div>
</fieldset>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="archiveRestore.submit()" ng-disabled="archiveRestore.busy"><i class="fas fa-history" ng-hide="archiveRestore.busy"></i><i class="fa fa-circle-notch fa-spin" ng-show="archiveRestore.busy"></i> {{ 'backups.restoreArchiveDialog.restoreAction' | tr:{ dnsOverwrite: archiveRestore.needsOverwrite } }}</button>
</div>
</div>
</div>
</div>
<!-- Modal delete archive -->
<div class="modal fade" id="archiveDeleteModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'backups.deleteArchiveDialog.title' | tr:{ appTitle: archiveDelete.title, fqdn: archiveDelete.fqdn } }}</h4>
</div>
<div class="modal-body">
<p>{{ 'backups.deleteArchiveDialog.description' | tr }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-danger" ng-click="archiveDelete.submit()" ng-disabled="archiveDelete.busy"><i class="fa fa-circle-notch fa-spin" ng-show="archiveDelete.busy"></i> {{ 'backups.deleteArchive.deleteAction' | tr }}</button>
</div>
</div>
</div>
</div>
<div class="content">
<h1 class="section-header">{{ 'backups.title' | tr }}</h1>
<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" ng-show="backupConfig.provider !== 'noop'">
<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 === 'disk' || 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>
<h3 class="section-header">
{{ '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> -->
<div class="btn-group btn-group-sm pull-right">
<button type="button" class="btn btn-small btn-default dropdown-toggle" ng-show="cleanupTasks.length" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" uib-tooltip="{{ 'backups.logs.showLogs' | tr }}">
<i class="fas fa-align-left"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li ng-repeat="task in cleanupTasks">
<a ng-href="/logs.html?taskId={{task.id}}" target="_blank" class="text-right">
{{ task.ts | prettyLongDate }} <i class="fa" style="margin-left: 20px" ng-class="{ 'status-active fa-check-circle': !task.active && task.success, 'fa-circle-notch fa-spin': task.active, 'status-error fa-times-circle': !task.active && !task.success }"></i>
</a>
</li>
</ul>
</div>
</h3>
<div class="card" style="margin-bottom: 15px;">
<p ng-bind-html=" 'backups.schedule.description' | tr "></p>
<div class="row">
<div class="col-xs-4">
<span class="text-muted">{{ 'backups.schedule.schedule' | tr }}</span>
</div>
<div class="col-xs-8 text-right" style="text-overflow: ellipsis; overflow: hidden; white-space: nowrap;">
<span>{{ prettyBackupSchedule(backupPolicy.currentPolicy.schedule) }}</span>
</div>
</div>
<div class="row">
<div class="col-xs-4">
<span class="text-muted">{{ 'backups.schedule.retentionPolicy' | tr }}</span>
</div>
<div class="col-xs-8 text-right">
<span>{{ prettyBackupRetention(backupPolicy.currentPolicy.retention) }}</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="backupPolicy.show()">{{ 'backups.schedule.configure' | tr }}</button>
</div>
</div>
</div>
<h3 class="section-header">
{{ 'backups.listing.title' | tr }}
<div class="btn-group btn-group-sm pull-right">
<button type="button" class="btn btn-small btn-default dropdown-toggle" ng-show="backupTasks.length" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" uib-tooltip="{{ 'backups.logs.showLogs' | tr }}">
<i class="fas fa-align-left"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li ng-repeat="task in backupTasks">
<a ng-href="/logs.html?taskId={{task.id}}" target="_blank" class="text-right">
{{ task.ts | prettyLongDate }} <i class="fa" style="margin-left: 20px" ng-class="{ 'status-active fa-check-circle': !task.active && task.success, 'fa-circle-notch fa-spin': task.active, 'status-error fa-times-circle': !task.active && !task.success }"></i>
</a>
</li>
</ul>
</div>
</h3>
<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 class="hide-mobile">{{ '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">{{ backup.creationTime | prettyLongDate }} <b ng-show="backup.label">({{ backup.label }})</b></td>
<td ng-click="backupDetails.show(backup)" class="hand hide-mobile">
<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">
<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 }}</button>
</div>
</div>
</div>
<h3 class="section-header">
{{ 'backups.archives.title' | tr }}
</h3>
<div class="card card-large">
<p ng-bind-html=" 'backups.archive.description' | tr "></p>
<div class="grid-item-top">
<div class="row ng-hide" ng-show="!archiveList.ready">
<div class="col-lg-12 text-center">
<h2><i class="fa fa-circle-notch fa-spin"></i></h2>
</div>
</div>
<div class="row animateMeOpacity ng-hide" ng-show="archiveList.ready">
<div class="col-lg-12">
<table class="table table-hover" style="margin: 0;">
<thead>
<tr>
<th style="width: 5%"></th> <!-- icon -->
<th style="width: 35%">{{ 'backups.archives.location' | tr }}</th>
<th style="width: 35%" class="hide-mobile">{{ 'backups.archives.info' | tr }}</th>
<th style="width: 20%">{{ 'main.table.date' | tr }}</th>
<th style="width: 5%" class="text-right hide-mobile">{{ 'main.actions' | tr }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="archive in archiveList.archives">
<td>
<img ng-src="{{ archive.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" height="48" width="48"/>
</td>
<!-- for pre-8.2 backups, appConfig can be null -->
<td class="hand elide-table-cell" style="text-overflow: ellipsis; white-space: nowrap;" ng-click="archiveRestore.show(archive)">
{{ archive.appConfig ? archive.appConfig.fqdn : '-' }}
</td>
<td class="hand elide-table-cell hide-mobile" style="text-overflow: ellipsis; white-space: nowrap;" ng-click="archiveRestore.show(archive)">
<span uib-tooltip="{{ archive.manifest.id }}@{{ archive.manifest.version }}">{{ archive.manifest.title }}</span>
</td>
<td class="hand elide-table-cell" style="text-overflow: ellipsis; white-space: nowrap;" ng-click="archiveRestore.show(archive)">
{{ archive.creationTime | prettyDate }}
</td>
<td class="text-right no-wrap hide-mobile" style="vertical-align: middle;">
<button class="btn btn-xs btn-default" ng-click="archiveRestore.show(archive)" uib-tooltip="Restore from Archive"><i class="fas fa-history"></i></button>
<button class="btn btn-xs btn-default" ng-click="downloadConfig(archive, true)" uib-tooltip="{{ 'backups.listing.tooltipDownloadBackupConfig' | tr }}"><i class="fas fa-file-alt"></i></button>
<button class="btn btn-xs btn-danger" ng-click="archiveDelete.ask(archive)" uib-tooltip="Delete Archive"><i class="fa fa-trash-alt"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>