made a separate route instead of reusing install route. this was because we want to copy over all the old app config as much as possible.
1765 lines
118 KiB
HTML
1765 lines
118 KiB
HTML
<!-- Modal postinstall -->
|
|
<div class="modal fade" id="postInstallModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4>{{ 'app.appInfo.firstTimeTitle' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p ng-show="app.manifest.addons.email && postInstallMessage.openApp">{{ 'app.appInfo.ssoEmail' | tr }}</p>
|
|
<p ng-show="app.sso && !app.manifest.addons.email && postInstallMessage.openApp">{{ 'app.appInfo.sso' | tr }}</p>
|
|
|
|
<div ng-bind-html="app.manifest.postInstallMessage | markdown2html"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
<a class="btn btn-success" ng-href="{{ postInstallMessage.confirmed ? ('https://' + app.fqdn) : '' }}" target="_blank" ng-disabled="!postInstallMessage.confirmed" ng-click="postInstallMessage.submit()" ng-show="postInstallMessage.openApp">{{ 'app.appInfo.openAction' | tr:{ app: app.manifest.title } }}</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal postinstall confirm -->
|
|
<div class="modal fade" id="appPostInstallConfirmModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<img ng-src="{{appPostInstallConfirm.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-info-icon"/>
|
|
<h5 class="app-info-title">
|
|
{{ appPostInstallConfirm.app.manifest.title }}
|
|
<span class="app-info-meta text-small">{{ 'app.appInfo.package' | tr }} <a ng-href="/#/appstore/{{appPostInstallConfirm.app.manifest.id}}?version={{appPostInstallConfirm.app.manifest.version}}">v{{ appPostInstallConfirm.app.manifest.version }}</a> </span>
|
|
<br/>
|
|
<span ng-show="appPostInstallConfirm.app.manifest.documentationUrl"><a target="_blank" ng-href="{{appPostInstallConfirm.app.manifest.documentationUrl}}">{{ 'app.docsAction' | tr }}</a> </span>
|
|
<br/>
|
|
</h5>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div ng-repeat="item in appPostInstallConfirm.app.checklist">
|
|
<div class="checklist-item" ng-hide="item.acknowledged">
|
|
<span ng-bind-html="item.message | markdown2html"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<p ng-show="appPostInstallConfirm.app.manifest.addons.email">{{ 'app.appInfo.ssoEmail' | tr }}</p>
|
|
<p ng-show="appPostInstallConfirm.app.sso && !appPostInstallConfirm.app.manifest.addons.email">{{ 'app.appInfo.sso' | tr }}</p>
|
|
|
|
<div ng-bind-html="appPostInstallConfirm.app.manifest.postInstallMessage | markdown2html"></div>
|
|
<div ng-show="appPostInstallConfirm.app.manifest.documentationUrl" ng-bind-html="'app.appInfo.appDocsUrl' | tr:{ docsUrl: appPostInstallConfirm.app.manifest.documentationUrl, title: appPostInstallConfirm.app.manifest.title, forumUrl: (appPostInstallConfirm.app.manifest.forumUrl || 'https://forum.cloudron.io') }"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<div class="form-group pull-left">
|
|
<input type="checkbox" id="appPostInstallConfirmCheckbox" ng-model="appPostInstallConfirm.confirmed">
|
|
<label class="control-label" for="appPostInstallConfirmCheckbox">{{ 'app.appInfo.postInstallConfirmCheckbox' | tr }}</label>
|
|
</div>
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
<a class="btn btn-success" ng-href="{{ appPostInstallConfirm.confirmed ? ('https://' + appPostInstallConfirm.app.fqdn) : '' }}" target="_blank" ng-disabled="!appPostInstallConfirm.confirmed" ng-click="appPostInstallConfirm.submit()">{{ 'app.appInfo.openAction' | tr:{ app: appPostInstallConfirm.app.manifest.title } }}</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal sftpInfo -->
|
|
<div class="modal fade" id="sftpInfoModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4>{{ 'app.accessControl.sftp.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#sftp-access" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-small text-warning text-bold" ng-show="location.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<span class="text-muted">{{ 'app.accessControl.sftp.server' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-6 text-right">
|
|
<span>{{ config.adminFqdn }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<span class="text-muted">{{ 'app.accessControl.sftp.port' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-6 text-right">
|
|
<span>222</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-6">
|
|
<span class="text-muted">{{ 'app.accessControl.sftp.username' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-6 text-right">
|
|
<span>{{ user.username }}@{{ app.fqdn }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal backup 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-1 text-muted">{{ 'backups.backupDetails.id' | tr }}:</div>
|
|
<div class="col-xs-11 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>
|
|
</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>
|
|
</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 archive app -->
|
|
<div class="modal fade" id="archiveModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.archiveDialog.title' | tr:{ app: (app.label || app.fqdn) } }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p ng-bind-html="'app.archiveDialog.description' | tr:{ date: (uninstall.latestBackup.creationTime | prettyLongDate) }"></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="uninstall.submit('archive')" ng-disabled="uninstall.busy"><i class="fa fa-circle-notch fa-spin" ng-show="uninstall.busy"></i> {{ 'app.archive.action' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal uninstall app -->
|
|
<div class="modal fade" id="uninstallModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.uninstallDialog.title' | tr:{ app: (app.label || app.fqdn) } }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p ng-bind-html="'app.uninstallDialog.description' | tr:{ app: (app.label || app.fqdn) }"></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="uninstall.submit('uninstall')" ng-disabled="uninstall.busy"><i class="fa fa-circle-notch fa-spin" ng-show="uninstall.busy"></i> {{ 'app.uninstallDialog.uninstallAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal stop app -->
|
|
<div class="modal fade" id="stopModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.stopDialog.title' | tr:{ app: (app.label || app.fqdn) } }}</h4>
|
|
</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="uninstall.toggleRunState()" ng-disabled="uninstall.busyRunState"><i class="fa fa-circle-notch fa-spin" ng-show="uninstall.busyRunState"></i> {{ 'app.uninstall.startStop.stopAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal domain collision -->
|
|
<div class="modal fade" id="domainCollisionsModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.domainCollisionDialog.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>{{ 'app.domainCollisionDialog.collisionListTitle' | tr }}</p>
|
|
<ul>
|
|
<li ng-repeat="domain in location.domainCollisions">{{ domain.subdomain + '.' + domain.domain }}</li>
|
|
</ul>
|
|
<p>{{ 'app.domainCollisionDialog.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="location.submit(true)">{{ 'app.domainCollisionDialog.overwriteAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal repair -->
|
|
<div class="modal fade" id="repairModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.repairDialog.title' | tr:{ app: app.fqdn } }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div ng-if="!app.error">
|
|
<p>{{ 'app.repairDialog.description' | tr }}</p>
|
|
</div>
|
|
<div ng-if="app.error">
|
|
<p ng-bind-html="'app.repairDialog.taskError' | tr:{ task: (app.error.installationState | taskName) }"></p>
|
|
<p class="text-danger">{{ app.error.reason + ': ' + app.error.message }}</p>
|
|
</div>
|
|
<div class="form-group" ng-show="repair.subdomain && repair.domain">
|
|
<p>{{ 'app.repairDialog.domainDescription' | tr }}</p>
|
|
<label class="control-label">{{ 'app.repairDialog.location' | tr }}</label>
|
|
<div class="input-group form-inline">
|
|
<input type="text" class="form-control" ng-model="repair.subdomain" name="location" placeholder="{{ 'Leave empty to use bare domain' }}" autofocus>
|
|
|
|
<div class="input-group-btn">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
<span>{{ '.' + repair.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="repair.domain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div ng-show="repair.aliasDomains.length">
|
|
<p ng-repeat="aliasDomain in repair.aliasDomains">
|
|
<label class="control-label"><input type="checkbox" ng-model="aliasDomain.enabled">
|
|
{{ aliasDomain.subdomain + (!aliasDomain.subdomain ? '' : '.') + aliasDomain.domain.domain }}
|
|
</label>
|
|
</p>
|
|
</div>
|
|
|
|
<div ng-show="repair.redirectDomains.length">
|
|
<p ng-repeat="redirectDomain in repair.redirectDomains">
|
|
<label class="control-label"><input type="checkbox" ng-model="redirectDomain.enabled">
|
|
{{ redirectDomain.subdomain + (!redirectDomain.subdomain ? '' : '.') + redirectDomain.domain.domain }}
|
|
</label>
|
|
</p>
|
|
</div>
|
|
<div ng-show="repair.backups.length">
|
|
<label class="control-label">{{ 'app.repairDialog.fromBackup' | tr }}</label>
|
|
<select class="form-control" ng-model="repair.backupId">
|
|
<option ng-repeat="backup in repair.backups" value="{{ backup.id }}">{{ backup.creationTime | prettyDate }} - v{{ backup.packageVersion }}</option>
|
|
</select>
|
|
</div>
|
|
</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="repair.submit()" ng-disabled="repair.retryBusy">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="repair.retryBusy"></i> {{ 'app.repairDialog.retryAction' | tr:{ task: (app.error.installationState | taskName) } }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- modal import backup -->
|
|
<div class="modal fade" id="importBackupModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.importBackupDialog.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-info">{{ 'app.importBackupDialog.description' | tr }}</p>
|
|
|
|
<form name="importBackupForm" role="form" novalidate ng-submit="importBackup.submit()" autocomplete="off">
|
|
<p class="has-error text-center" ng-show="importBackup.error">{{ importBackup.error.generic }}</p>
|
|
|
|
<div class="form-group">
|
|
<label class="control-label" for="storageProvider">{{ '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>
|
|
<select class="form-control" id="storageProvider" ng-model="importBackup.provider" ng-options="a.value as a.name for a in storageProviders" ng-change="importBackup.clearForm()" ng-disabled="importBackup.busy"></select>
|
|
</div>
|
|
|
|
<!-- S3/Minio/SOS/GCS -->
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.endpoint }" ng-show="importBackup.provider === 'minio' || importBackup.provider === 'upcloud-objectstorage' || importBackup.provider === 'backblaze-b2' || importBackup.provider === 'cloudflare-r2' || importBackup.provider === 's3-v4-compat' || importBackup.provider === 'idrive-e2'">
|
|
<label class="control-label" for="inputimportBackupEndpoint">{{ 'backups.configureBackupStorage.s3Endpoint' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.endpoint" id="inputimportBackupEndpoint" name="endpoint" ng-disabled="importBackup.busy" placeholder="URL" ng-required="importBackup.provider === 'minio' || importBackup.provider === 'upcloud-objectstorage'|| importBackup.provider === 'backblaze-b2' || importBackup.provider === 'cloudflare-r2' || importBackup.provider === 's3-v4-compat' || importBackup.provider === 'idrive-e2'">
|
|
</div>
|
|
|
|
<div class="checkbox" ng-show="importBackup.provider === 'minio' || importBackup.provider === 's3-v4-compat'" >
|
|
<label>
|
|
<input type="checkbox" ng-model="importBackup.acceptSelfSignedCerts" id="inputimportBackupSelfSigned">Accept Self-signed certificate</input>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.bucket }" ng-show="s3like(importBackup.provider) || importBackup.provider === 'gcs'">
|
|
<label class="control-label" for="inputImportBackupBucket">{{ 'backups.configureBackupStorage.bucketName' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.bucket" id="inputImportBackupBucket" name="bucket" ng-disabled="importBackup.busy" ng-required="s3like(importBackup.provider)">
|
|
</div>
|
|
|
|
<!-- mountpoint -->
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.mountPoint }" ng-show="importBackup.provider === 'mountpoint'">
|
|
<label class="control-label" for="inputImportMountPoint">{{ 'backups.configureBackupStorage.mountPoint' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.mountPoint" id="inputImportMountPoint" name="mountPoint" ng-disabled="importBackup.busy" placeholder="Folder where filesystem is mounted" ng-required="importBackup.provider === 'mountpoint'">
|
|
</div>
|
|
|
|
<!-- S3/Minio/SOS/GCS/SSHFS/CIFS/NFS/B2/Mountpoint -->
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.prefix }" ng-show="importBackup.provider !== 'filesystem'">
|
|
<label class="control-label" for="inputImportBackupPrefix">{{ 'backups.configureBackupStorage.prefix' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.prefix" id="inputImportBackupPrefix" name="prefix" ng-disabled="importBackup.busy" placeholder="Prefix for backup file names">
|
|
</div>
|
|
|
|
<!-- CIFS/NFS/SSHFS -->
|
|
<div class="form-group" ng-show="importBackup.provider === 'cifs' || importBackup.provider === 'nfs' || importBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="importBackupHost">{{ 'backups.configureBackupStorage.server' | tr }} ({{ importBackup.provider }})</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.mountOptions.host" id="importBackupHost" name="host" ng-disabled="importBackup.busy" placeholder="Server IP or hostname" ng-required="importBackup.provider === 'cifs' || importBackup.provider === 'nfs' || importBackup.provider === 'sshfs'">
|
|
</div>
|
|
|
|
<!-- SSHFS -->
|
|
<div class="form-group" ng-show="importBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="importBackupPort">{{ 'backups.configureBackupStorage.port' | tr }}</label>
|
|
<input type="number" class="form-control" ng-model="importBackup.mountOptions.port" id="importBackupPort" name="port" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'sshfs'">
|
|
</div>
|
|
|
|
<!-- CIFS -->
|
|
<div class="checkbox" ng-show="importBackup.provider === 'cifs'">
|
|
<label>
|
|
<input type="checkbox" ng-model="importBackup.mountOptions.seal">{{ 'backups.configureBackupStorage.cifsSealSupport' | tr }}</input>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- CIFS/NFS/SSHFS -->
|
|
<div class="form-group" ng-show="importBackup.provider === 'cifs' || importBackup.provider === 'nfs' || importBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="importBackupRemoteDir">{{ 'backups.configureBackupStorage.remoteDirectory' | tr }} ({{ importBackup.provider }})</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.mountOptions.remoteDir" id="importBackupRemoteDir" name="remoteDir" ng-disabled="importBackup.busy" placeholder="/share" ng-required="importBackup.provider === 'cifs' || importBackup.provider === 'nfs' || importBackup.provider === 'sshfs'">
|
|
</div>
|
|
|
|
<!-- EXT4/XFS -->
|
|
<div class="form-group" ng-show="importBackup.provider === 'ext4' || importBackup.provider === 'xfs'" ng-class="{ 'has-error': importBackup.error.diskPath }">
|
|
<label class="control-label" for="importBackupDiskPath">{{ 'backups.configureBackupStorage.diskPath' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.mountOptions.diskPath" id="importBackupDiskPath" name="diskPath" ng-disabled="importBackup.busy" placeholder="/dev/disk/by-uuid/uuid" ng-required="importBackup.provider === 'ext4' || importBackup.provider === 'xfs'">
|
|
</div>
|
|
|
|
<!-- remotePath contains the prefix as well -->
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.remotePath }">
|
|
<label class="control-label" for="inputImportBackupId">{{ 'app.importBackupDialog.remotePath' | tr }} <sup><a ng-href="https://docs.cloudron.io/backups/#import-app-backup" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<input type="text" class="form-control" ng-model="importBackup.remotePath" id="inputImportRemotePath" ng-disabled="importBackup.busy" required>
|
|
</div>
|
|
|
|
<!-- CIFS -->
|
|
<div class="form-group" ng-show="importBackup.provider === 'cifs'">
|
|
<label class="control-label" for="importBackupUsername">{{ 'backups.configureBackupStorage.username' | tr }} ({{ importBackup.provider }})</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.mountOptions.username" id="importBackupUsername" name="username" ng-disabled="importBackup.busy">
|
|
</div>
|
|
|
|
<!-- CIFS -->
|
|
<div class="form-group" ng-show="importBackup.provider === 'cifs'">
|
|
<label class="control-label" for="importBackupPassword">{{ 'backups.configureBackupStorage.password' | tr }} ({{ importBackup.provider }})</label>
|
|
<input type="password" class="form-control" ng-model="importBackup.mountOptions.password" id="importBackupPassword" name="password" ng-disabled="importBackup.busy" password-reveal>
|
|
</div>
|
|
|
|
<!-- SSHFS -->
|
|
<div class="form-group" ng-show="importBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="importBackupUser">{{ 'backups.configureBackupStorage.user' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.mountOptions.user" id="importBackupUser" name="user" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'sshfs'">
|
|
</div>
|
|
|
|
<!-- SSHFS -->
|
|
<div class="form-group" ng-show="importBackup.provider === 'sshfs'">
|
|
<label class="control-label" for="importBackupPrivateKey">{{ 'backups.configureBackupStorage.privateKey' | tr }}</label>
|
|
<textarea class="form-control" ng-model="importBackup.mountOptions.privateKey" id="importBackupPrivateKey" name="privateKey" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'sshfs'"></textarea>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 's3'">
|
|
<label class="control-label" for="inputImportBackupS3Region">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputImportBackupS3Region" ng-model="importBackup.region" ng-options="a.value as a.name for a in s3Regions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 's3'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 's3-v4-compat'">
|
|
<label class="control-label" for="inputImportBackupS3V4CompatRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<input class="form-control" type="text" name="region" id="inputImportBackupS3V4CompatRegion" ng-model="importBackup.region" ng-disabled="importBackup.busy" placeholder="Leave empty to use us-east-1 as default"></input>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'digitalocean-spaces'">
|
|
<label class="control-label" for="inputImportBackupDORegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputImportBackupDORegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in doSpacesRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'digitalocean-spaces'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'hetzner-objectstorage'">
|
|
<label class="control-label" for="inputImportBackupHetznerRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputImportBackupHetznerRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in hetznerRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'hetzner-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'exoscale-sos'">
|
|
<label class="control-label" for="inputimportBackupExoscaleRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputimportBackupExoscaleRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in exoscaleSosRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'exoscale-sos'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'wasabi'">
|
|
<label class="control-label" for="inputimportBackupWasabiRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputimportBackupWasabiRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in wasabiRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'wasabi'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'scaleway-objectstorage'">
|
|
<label class="control-label" for="inputimportBackupScalewayRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputimportBackupScalewayRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in scalewayRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'scaleway-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'linode-objectstorage'">
|
|
<label class="control-label" for="inputimportBackupLinodeRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputimportBackupLinodeRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in linodeRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'linode-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'ovh-objectstorage'">
|
|
<label class="control-label" for="inputimportBackupOvhRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputimportBackupOvhRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in ovhRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'ovh-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'ionos-objectstorage'">
|
|
<label class="control-label" for="inputimportBackupIonosRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputimportBackupIonosRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in ionosRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'ionos-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'vultr-objectstorage'">
|
|
<label class="control-label" for="inputimportBackupVultrRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputimportBackupVultrRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in vultrRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'vultr-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.region }" ng-show="importBackup.provider === 'contabo-objectstorage'">
|
|
<label class="control-label" for="inputimportBackupContaboRegion">{{ 'backups.configureBackupStorage.region' | tr }}</label>
|
|
<select class="form-control" name="region" id="inputimportBackupContaboRegion" ng-model="importBackup.endpoint" ng-options="a.value as a.name for a in contaboRegions" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'contabo-objectstorage'"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.accessKeyId }" ng-show="s3like(importBackup.provider)">
|
|
<label class="control-label" for="inputImportBackupAccessKeyId">{{ 'backups.configureBackupStorage.s3AccessKeyId' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.accessKeyId" id="inputImportBackupAccessKeyId" name="accessKeyId" ng-disabled="importBackup.busy" ng-required="s3like(importBackup.provider)">
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.secretAccessKey }" ng-show="s3like(importBackup.provider)">
|
|
<label class="control-label" for="inputImportBackupSecretAccessKey">{{ 'backups.configureBackupStorage.s3SecretAccessKey' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="importBackup.secretAccessKey" id="inputImportBackupSecretAccessKey" name="secretAccessKey" ng-disabled="importBackup.busy" ng-required="s3like(importBackup.provider)">
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.gcsKeyInput }" ng-show="importBackup.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="importBackup.gcsKey.keyFileName" id="gcsKeyInput" name="cert" onclick="getElementById('gcsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="importBackup.busy" ng-required="importBackup.provider === 'gcs'">
|
|
<span class="input-group-addon">
|
|
<i class="fa fa-upload" onclick="getElementById('gcsKeyFileInput').click();"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<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>
|
|
<select class="form-control" id="storageFormat" ng-change="importBackup.password = ''" ng-model="importBackup.format" ng-options="a.value as a.name for a in formats" ng-disabled="importBackup.busy"></select>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': importBackup.error.password }">
|
|
<label class="control-label" for="inputImportBackupPassword">{{ '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>
|
|
<input type="text" class="form-control" ng-model="importBackup.password" id="inputImportBackupPassword" ng-disabled="importBackup.busy" ng-required="importBackup.encrypted" placeholder="Passphrase used to encrypt the backups">
|
|
</div>
|
|
|
|
<div class="checkbox" ng-show="importBackup.format === 'rsync' && importBackup.password !== ''">
|
|
<label>
|
|
<input type="checkbox" ng-model="importBackup.encryptedFilenames">{{ 'backups.configureBackupStorage.encryptedFilenames' | tr }}</input>
|
|
</label>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="importBackupForm.$invalid"/>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer ">
|
|
<input type="file" id="backupConfigFileInput" style="display:none"/>
|
|
<button type="button" class="btn btn-default pull-left" onclick="getElementById('backupConfigFileInput').click();">{{ 'app.importBackupDialog.uploadAction' | tr }}</button>
|
|
|
|
<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="importBackup.submit()" ng-disabled="importBackupForm.$invalid || importBackup.busy"><i class="fa fa-circle-notch fa-spin" ng-show="importBackup.busy"></i> {{ 'app.importBackupDialog.importAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal update app -->
|
|
<div class="modal fade" id="updateModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.updateDialog.title' | tr:{ app: app.fqdn } }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-danger" ng-show="config.update[app.id].unstable">{{ 'app.updateDialog.unstableWarning' | tr }}</p>
|
|
<p>{{ 'app.updateDialog.changelogHeader' | tr:{ version: config.update[app.id].manifest.version } }}</p>
|
|
<div ng-bind-html="config.update[app.id].manifest.changelog | markdown2html"></div>
|
|
<p class="text-danger text-bold" ng-show="!config.update[app.id].manifest.dockerImage">
|
|
<br/>
|
|
{{ 'app.updateDialog.subscriptionExpired' | tr }}
|
|
</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<label class="checkbox-inline pull-left" ng-show="config.update[app.id].manifest.dockerImage">
|
|
<input type="checkbox" ng-model="updates.skipBackup"><b>{{ 'app.updateDialog.skipBackupCheckbox' | tr }}</b>
|
|
</label>
|
|
|
|
<button type="button" class="btn btn-primary pull-left" ng-show="!config.update[app.id].manifest.dockerImage && user.isAtLeastOwner" ng-click="openSubscriptionSetup()">{{ 'app.updateDialog.setupSubscriptionAction' | tr }}</button>
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
|
|
<button type="button" ng-class="config.update[app.id].unstable ? 'btn btn-danger' : 'btn btn-success'" ng-click="updates.confirmUpdate()" ng-disabled="!config.update[app.id].manifest.dockerImage || updates.busyUpdate"><i class="fa fa-circle-notch fa-spin" ng-show="updates.busyUpdate"></i> {{ 'app.updateDialog.updateAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal restore app -->
|
|
<div class="modal fade" id="restoreModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.restoreDialog.title' | tr:{ app: app.fqdn } }}</h4>
|
|
</div>
|
|
<div class="modal-body" style="padding: 0 15px">
|
|
<p>{{ 'app.restoreDialog.description' | tr:{ creationTime: (restore.backup.creationTime | prettyDate) } }}</p>
|
|
<p class="text-danger">{{ 'app.restoreDialog.warning' | tr }}</p>
|
|
<br/>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
<button type="button" class="btn btn-danger" ng-click="restore.submit()"><i class="fas fa-history" ng-hide="restore.busy"></i><i class="fa fa-circle-notch fa-spin" ng-show="restore.busy"></i> {{ 'app.restoreDialog.restoreAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal clone app -->
|
|
<div class="modal fade" id="appCloneModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.cloneDialog.title' | tr:{ app: app.fqdn } }}</h4>
|
|
</div>
|
|
<div class="modal-body" style="padding: 0 15px">
|
|
<p ng-bind-html="'app.cloneDialog.description' | tr:{ creationTime: (clone.backup.creationTime | prettyDate), packageVersion: clone.backup.packageVersion }"></p>
|
|
<form role="form" ng-submit="clone.submit()" autocomplete="off">
|
|
<fieldset>
|
|
<div class="form-group" ng-class="{ 'has-error': clone.error.location.fqdn === clone.subdomain + '.' + clone.domain.domain }">
|
|
<label class="control-label" for="cloneLocationInput">{{ 'app.cloneDialog.location' | tr }}</label>
|
|
<div ng-show="clone.error.location.fqdn === clone.subdomain + '.' + clone.domain.domain"><small>{{ clone.error.location.message }}</small></div>
|
|
<div class="input-group form-inline">
|
|
<input type="text" class="form-control" ng-model="clone.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>{{ '.' + clone.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="clone.domain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="has-error text-center" ng-show="clone.error.secondaryDomain">{{ clone.error.secondaryDomain }}</div>
|
|
<div ng-repeat="(env, info) in clone.backup.manifest.httpPorts">
|
|
<ng-form name="secondaryDomainInfo_form">
|
|
<div class="form-group" ng-class="{ 'has-error': (!secondaryDomainInfo_form.itemName{{$index}}.$dirty && clone.error.secondaryDomain) || (secondaryDomainInfo_form.itemName{{$index}}.$dirty && secondaryDomainInfo_form.itemName{{$index}}.$invalid) || (clone.error.location.fqdn === clone.secondaryDomains[env].subdomain + '.' + clone.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="clone.error.location.fqdn === clone.secondaryDomains[env].subdomain + '.' + clone.secondaryDomains[env].domain.domain"><small>{{ clone.error.location.message }}</small></div>
|
|
<div class="input-group form-inline">
|
|
<input type="text" class="form-control" ng-model="clone.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>.{{ clone.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="clone.secondaryDomains[env].domain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ng-form>
|
|
</div>
|
|
|
|
<p class="text-small text-warning" ng-show="clone.domain.provider === 'noop' || clone.domain.provider === 'manual'" ng-bind-html="'appstore.installDialog.manualWarning' | tr:{ location: ((clone.subdomain ? clone.subdomain + '.' : '') + clone.domain.domain) }"></p>
|
|
|
|
<div class="has-error text-center" ng-show="clone.error.port">{{ clone.error.port }}</div>
|
|
<div ng-repeat="(env, info) in clone.portInfo">
|
|
<ng-form name="portInfo_form">
|
|
<div class="form-group" ng-class="{ 'has-error': (!clone.itemName{{$index}}.$dirty && clone.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
|
|
<label class="control-label" for="inputPortInfo{{env}}"><input type="checkbox" ng-model="clone.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="clone.ports[env]" ng-disabled="!clone.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="clone.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="clone.submit()"><i class="far fa-clone" ng-hide="clone.busy"></i><i class="fa fa-circle-notch fa-spin" ng-show="clone.busy"></i> {{ 'app.cloneDialog.cloneAction' | tr:{ dnsOverwrite: clone.needsOverwrite } }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content content-large app-configure">
|
|
|
|
<a href="/#/apps" class="back-to-view-link"><i class="fas fa-arrow-left"></i> {{ 'app.backAction' | tr }}</a>
|
|
|
|
<br/>
|
|
|
|
<div class="row" ng-show="view">
|
|
<div class="col-sm-2 text-center">
|
|
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-icon"/>
|
|
</div>
|
|
<div class="col-sm-8">
|
|
<div class="app-header-container">
|
|
<h1>
|
|
<a ng-href="{{ app | applicationLink }}" target="_blank" ng-class="{ 'hand': (app | appIsInstalledAndHealthy) }" ng-click="onAppClick(app, $event)">{{ app.label || app.fqdn }} <sup ng-show="app | appIsInstalledAndHealthy"><i class="fas fa-external-link-alt" style="font-size: 12px;"></i></sup></a>
|
|
</h1>
|
|
<div>
|
|
<button class="btn btn-sm" ng-class="{ 'btn-primary': uninstall.startButton, 'btn-default': !uninstall.startButton }" ng-click="uninstall.toggleRunState(true)" ng-disabled="app.taskId || app.error || app.installationState === 'pending_start' || app.installationState === 'pending_stop'" uib-tooltip="{{ uninstall.startButton ? ('app.uninstall.startStop.startAction' | tr) : ('app.uninstall.startStop.stopAction' | tr) }}" tooltip-append-to-body="true" tooltip-placement="bottom">
|
|
<i ng-show="app.installationState === 'pending_start' || app.installationState === 'pending_stop'" class="fa fa-circle-notch fa-spin"></i>
|
|
<i ng-hide="app.installationState === 'pending_start' || app.installationState === 'pending_stop'" class="fas" ng-class="{ 'fa-power-off': !uninstall.startButton, 'fa-play': uninstall.startButton }"></i>
|
|
</button>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<a class="btn btn-sm btn-default" ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank" uib-tooltip="{{ 'app.logsActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fas fa-align-left"></i></a>
|
|
<a class="btn btn-sm btn-default" ng-if="app.type !== APP_TYPES.PROXIED" ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank" uib-tooltip="{{ 'app.terminalActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fa fa-terminal"></i></a>
|
|
<a class="btn btn-sm btn-default" ng-if="app.manifest.addons.localstorage" ng-href="{{ '/filemanager.html#/home/app/' + app.id }}" target="_blank" uib-tooltip="{{ 'app.filemanagerActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fas fa-folder"></i></a>
|
|
</div>
|
|
<div class="dropdown" style="display: inline-block">
|
|
<button class="btn btn-sm btn-default dropdown-toggle" type="button" data-toggle="dropdown" uib-tooltip="{{ 'app.docsActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom">
|
|
<i class="fas fa-book"></i>
|
|
<span class="caret"></span>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-right">
|
|
<li ng-class="{ 'disabled': !app.manifest.documentationUrl }"><a ng-href="{{ app.manifest.documentationUrl }}" target="_blank">{{ 'app.docsAction' | tr }}</a></li>
|
|
<li ng-show="app.manifest.postInstallMessage"><a href="" ng-click="postInstallMessage.show(false)">{{ 'app.firstTimeSetupAction' | tr }}</a></li>
|
|
<li ng-show="app.manifest.configurePath"><a ng-href="{{ (app | applicationLink) + app.manifest.configurePath }}" target="_blank">{{ 'app.adminPageAction' | tr }}</a></li>
|
|
<li ng-show="app.manifest.addons.localstorage.ftp"><a href="" ng-click="sftpInfo.show()">{{ 'app.sftpInfoAction' | tr }}</a></li>
|
|
<li role="separator" class="divider"></li>
|
|
<li ng-class="{ 'disabled': !app.manifest.forumUrl }"><a ng-href="{{ app.manifest.forumUrl }}" target="_blank">{{ 'app.forumUrlAction' | tr }}</a></li>
|
|
<li role="separator" class="divider"></li>
|
|
<li ng-class="{ 'disabled': !app.manifest.website }"><a ng-href="{{ app.manifest.website }}" target="_blank">{{ 'app.projectWebsiteAction' | tr }}</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="app-status-container">
|
|
<span class="text-small">{{ app | installationStateLabel }} {{ app.message ? ' - ' + app.message : '' }}</span>
|
|
<span ng-click="setView('repair')" class="text-small hand text-hover" ng-show="app.error"> : {{ app.error.reason + ' - ' + app.error.message }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" ng-show="app.taskId">
|
|
<div class="col-sm-8 col-sm-offset-2" style="height: 10px; display: flex; align-items: center;">
|
|
<div class="progress progress-striped active animateMeOpacity" style="height: 10px; flex-grow: 1;">
|
|
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress || 5 }}%"></div>
|
|
</div>
|
|
<div ng-show="app.taskMinutesActive >= 2" class="text-danger hand" style="margin: 0 4px;" ng-click="stopAppTask(app.taskId)" uib-tooltip="Cancel Task"><i class="fas fa-times"></i></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" ng-hide="view">
|
|
<div class="col-md-12 text-center">
|
|
<br/><br/><h2><i class="fa fa-circle-notch fa-spin"></i></h2>
|
|
</div>
|
|
</div>
|
|
<div class="row app-configure-links-container" ng-show="view">
|
|
<div class="col-sm-2">
|
|
<div class="app-configure-links">
|
|
<div ng-click="setView('info')" ng-class="{ 'active': view === 'info' }">{{ 'app.infoTabTitle' | tr }}</div>
|
|
<div ng-click="setView('display')" ng-class="{ 'active': view === 'display' }">{{ 'app.displayTabTitle' | tr }}</div>
|
|
<div ng-click="setView('location')" ng-class="{ 'active': view === 'location' }" ng-show="app.accessLevel === 'admin'">{{ 'app.locationTabTitle' | tr }}</div>
|
|
<div ng-click="setView('proxy')" ng-class="{ 'active': view === 'proxy' }" ng-show="app.type === APP_TYPES.PROXIED">Proxy</div>
|
|
<div ng-click="setView('access')" ng-class="{ 'active': view === 'access' }" ng-show="app.accessLevel === 'admin'">{{ 'app.accessControlTabTitle' | tr }}</div>
|
|
<div ng-click="setView('resources')" ng-class="{ 'active': view === 'resources' }" ng-show="app.type !== APP_TYPES.PROXIED">{{ 'app.resourcesTabTitle' | tr }}</div>
|
|
<div ng-click="setView('services')" ng-class="{ 'active': view === 'services' }" ng-show="app.type !== APP_TYPES.PROXIED && (app.manifest.addons.turn.optional || app.manifest.addons.redis.optional)">{{ 'app.servicesTabTitle' | tr }}</div>
|
|
<div ng-click="setView('storage')" ng-class="{ 'active': view === 'storage' }" ng-show="app.accessLevel === 'admin' && app.type !== APP_TYPES.PROXIED">{{ 'app.storageTabTitle' | tr }}</div>
|
|
<div ng-click="setView('graphs')" ng-class="{ 'active': view === 'graphs' }" ng-show="app.type !== APP_TYPES.PROXIED">{{ 'app.graphsTabTitle' | tr }}</div>
|
|
<div ng-click="setView('security')" ng-class="{ 'active': view === 'security' }">{{ 'app.securityTabTitle' | tr }}</div>
|
|
<div ng-click="setView('email')" ng-class="{ 'active': view === 'email' }" ng-show="app.accessLevel === 'admin' && (app.manifest.addons.sendmail || app.manifest.addons.recvmail) && app.type !== APP_TYPES.PROXIED">{{ 'app.emailTabTitle' | tr }}</div>
|
|
<div ng-click="setView('cron')" ng-class="{ 'active': view === 'cron' }" ng-show="app.type !== APP_TYPES.PROXIED">{{ 'app.cronTabTitle' | tr }}</div>
|
|
<div ng-click="setView('updates')" ng-class="{ 'active': view === 'updates' }">{{ 'app.updatesTabTitle' | tr }}</div>
|
|
<div ng-click="setView('backups')" ng-class="{ 'active': view === 'backups' }" ng-show="app.type !== APP_TYPES.PROXIED">{{ 'app.backupsTabTitle' | tr }}</div>
|
|
<div ng-click="setView('repair')" ng-class="{ 'active': view === 'repair' }">{{ 'app.repairTabTitle' | tr }}</div>
|
|
<div ng-click="setView('eventlog')" ng-class="{ 'active': view === 'eventlog' }">{{ 'app.eventlogTabTitle' | tr }}</div>
|
|
<div ng-click="setView('uninstall')" ng-class="{ 'active': view === 'uninstall' }" ng-show="app.accessLevel === 'admin'">{{ 'app.uninstallTabTitle' | tr }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-8 card-container">
|
|
<div class="card" ng-show="view === 'info'">
|
|
<p>
|
|
<label class="control-label">{{ 'app.updates.info.title' | tr }}</label>
|
|
<a href="" class="pull-right" ng-click="info.showDoneChecklist = true" ng-show="info.hasOldChecklist && !info.showDoneChecklist">Show Checklist</a>
|
|
<a href="" class="pull-right" ng-click="info.showDoneChecklist = false" ng-show="info.showDoneChecklist">Hide Checklist</a>
|
|
</p>
|
|
|
|
<div ng-repeat="(key, item) in app.checklist">
|
|
<div class="checklist-item" ng-hide="item.acknowledged">
|
|
<span ng-bind-html="item.message | markdown2html"></span>
|
|
<button class="btn btn-xs btn-default" style="margin-left: 10px;" ng-click="info.checklistAck(item, key)">Done</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div ng-repeat="(key, item) in app.checklist" ng-show="info.showDoneChecklist">
|
|
<div class="checklist-item checklist-item-acknowledged" ng-show="item.acknowledged">
|
|
<span ng-bind-html="item.message | markdown2html"></span>
|
|
<span class="text-muted text-small">{{ item.changedBy }} {{ item.changedAt | prettyDate }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top: 10px"></div>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-4">
|
|
<span class="text-muted">{{ 'app.updates.info.description' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-8 text-right no-wrap-scroll">
|
|
<span ng-show="app.appStoreId">{{ app.manifest.title }} {{ app.upstreamVersion }}</span>
|
|
<span ng-show="!app.appStoreId">{{ app.manifest.dockerImage }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-4">
|
|
<span class="text-muted">{{ 'app.updates.info.appId' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-8 text-right">
|
|
<span>{{ app.id }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-4">
|
|
<span class="text-muted">{{ 'app.updates.info.packageVersion' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-8 text-right">
|
|
<span ng-show="app.appStoreId"><a ng-href="/#/appstore/{{app.manifest.id}}?version={{app.manifest.version}}">{{ app.manifest.id }}@{{ app.manifest.version }}</a></span>
|
|
<span ng-show="!app.appStoreId">{{ app.manifest.version }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-4">
|
|
<span class="text-muted">{{ 'app.updates.info.installedAt' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-8 text-right">
|
|
<span>{{ app.creationTime | prettyDate }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-xs-4">
|
|
<span class="text-muted">{{ 'app.updates.info.lastUpdated' | tr }}</span>
|
|
</div>
|
|
<div class="col-xs-8 text-right">
|
|
<span>{{ app.updateTime | prettyDate }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<p><label class="control-label">{{ 'app.info.notes.title' | tr }}</label><i ng-show="!info.notes.editing" class="info-edit-indicator fa fa-pencil-alt" ng-click="info.notes.edit()"></i></p>
|
|
<div class="row">
|
|
<div class="col-md-12" ng-show="!info.notes.busy">
|
|
<div ng-show="!info.notes.editing">
|
|
<div ng-show="info.notes.content" ng-bind-html="info.notes.content | markdown2html"></div>
|
|
<div ng-show="!info.notes.content" class="text-muted hand" ng-click="info.notes.edit()">{{ info.notes.placeholder }}</div>
|
|
</div>
|
|
<div ng-show="info.notes.editing" class="text-right">
|
|
<textarea id="adminNotesTextarea" ng-trim="false" style="white-space: pre-wrap; margin-bottom: 5px" ng-model="info.notes.content" class="form-control" rows="10"></textarea>
|
|
<button class="btn btn-default" ng-click="info.notes.dismiss()" ng-disabled="info.notes.busySave">{{ 'main.dialog.cancel' | tr }}</button>
|
|
<button class="btn btn-success" ng-click="info.notes.submit()" ng-disabled="info.notes.busySave"><i class="fa fa-circle-notch fa-spin" ng-show="info.notes.busySave"></i> {{ 'app.display.saveAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'display'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="displayForm" ng-submit="display.submit()" autocomplete="off">
|
|
<fieldset>
|
|
<div class="form-group" ng-class="{ 'has-error': !displayForm.label.$dirty && display.error.label }">
|
|
<label class="control-label">{{ 'app.display.label' | tr }}</label>
|
|
<div class="control-label" ng-show="display.error.label">{{display.error.label}}</div>
|
|
<input type="text" class="form-control" id="displayLabelInput" name="label" ng-model="display.label">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="control-label">{{ 'app.display.tags' | tr }}</label>
|
|
<tag-input class="form-control" placeholder="{{ 'app.display.tagsPlaceholder' | tr }}" taglist="display.tags" name="tags" uib-tooltip="{{ 'app.display.tagsTooltip' | tr }}"></tag-input>
|
|
</div>
|
|
<div class="form-group">
|
|
<div>
|
|
<label class="control-label">{{ 'app.display.icon' | tr }}</label>
|
|
</div>
|
|
<div id="previewIcon" class="app-custom-icon" ng-click="display.showCustomIconSelector()">
|
|
<img ng-src="{{ display.iconUrl() || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)"/>
|
|
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
|
</div>
|
|
<a href="" style="font-weight: normal;" ng-click="display.resetCustomIcon()">{{ 'app.display.iconResetAction' | tr }}</a>
|
|
<input type="file" id="iconFileInput" style="display: none" accept="image/png"/>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="(!display.icon.data && !displayForm.$dirty) || displayForm.$invalid || display.busy"/>
|
|
</fieldset>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="display.submit()" ng-disabled="(!display.icon.data && !displayForm.$dirty) || display.$invalid || display.busy"><i class="fa fa-circle-notch fa-spin" ng-show="display.busy"></i> {{ 'app.display.saveAction' | tr }}</button> </div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'location'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="locationForm" ng-submit="location.submit()" autocomplete="off">
|
|
<div class="form-group" ng-class="{ 'has-error': (locationForm.location.$dirty && locationForm.location.$invalid) || (!locationForm.location.$dirty && location.error.location) }">
|
|
<label class="control-label">{{ 'app.location.location' | tr }}</label>
|
|
<div class="has-error" ng-show="location.error.location">{{ location.error.location }}</div>
|
|
|
|
<div class="input-group form-inline">
|
|
<input type="text" class="form-control" ng-model="location.subdomain" name="location" placeholder="{{ 'app.location.locationPlaceholder' | tr }}" autofocus>
|
|
|
|
<div class="input-group-btn">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
<span>{{ '.' + location.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="location.domain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-small text-bold text-warning" ng-show="location.domain.provider === 'noop' || location.domain.provider === 'manual'" ng-bind-html="'appstore.installDialog.manualWarning' | tr:{ location: ((location.subdomain ? location.subdomain + '.' : '') + location.domain.domain) }"></p>
|
|
|
|
<!-- hidden submit has to be prior to other button elements, otherwise firefox will treat them as the "enter" key action, in this case the alternate domain delete button! -->
|
|
<input class="ng-hide" type="submit" ng-disabled="locationForm.$invalid || location.busy"/>
|
|
|
|
<div class="has-error text-center" ng-show="location.error.secondaryDomain">{{ location.error.secondaryDomain }}</div>
|
|
<div ng-repeat="(env, info) in app.manifest.httpPorts">
|
|
<ng-form name="secondaryDomainInfo_form">
|
|
<div class="form-group" ng-class="{ 'has-error': (!secondaryDomainInfo_form.itemName{{$index}}.$dirty && location.error.secondaryDomain) || (secondaryDomainInfo_form.itemName{{$index}}.$dirty && secondaryDomainInfo_form.itemName{{$index}}.$invalid) }">
|
|
<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 class="input-group form-inline">
|
|
<input type="text" class="form-control" ng-model="location.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>.{{ location.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="location.secondaryDomains[env].domain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ng-form>
|
|
</div>
|
|
|
|
<div class="has-error text-center" ng-show="location.error.port">{{ location.error.port }}</div>
|
|
<div ng-repeat="(env, info) in location.portInfo">
|
|
<ng-form name="portInfo_form">
|
|
<div class="form-group" ng-class="{ 'has-error': (!portInfo_form.itemName{{$index}}.$dirty && location.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
|
|
<label class="control-label" style="width: 100%" for="locationPortInput{{env}}"><input type="checkbox" ng-model="location.portsEnabled[env]">
|
|
{{ info.title }}
|
|
<sup>
|
|
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}}. {{info.portCount >=1 ? (info.portCount + ' ports. ') : ''}}"><i class="fa fa-question-circle"></i></a>
|
|
</sup>
|
|
<small style="padding-left: 5px;" ng-show="info.readOnly">{{ 'appstore.installDialog.portReadOnly' | tr }}</small>
|
|
<span ng-show="info.portCount" style="display: block; float: right">{{ location.ports[env] }} to {{ location.ports[env] + info.portCount - 1 }} ({{ info.portCount }} ports)</span>
|
|
</label>
|
|
<input type="number" class="form-control" ng-model="location.ports[env]" ng-disabled="!location.portsEnabled[env]" ng-readonly="info.readOnly" id="locationPortInput{{env}}" later-name="itemName{{$index}}" min="{{HOST_PORT_MIN}}" max="{{HOST_PORT_MAX}}" required>
|
|
<p class="text-small text-warning text-bold" ng-show="location.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p>
|
|
</div>
|
|
</ng-form>
|
|
</div>
|
|
|
|
<div class="form-group alias-domains" ng-show="app.manifest.multiDomain">
|
|
<label class="control-label">{{ 'app.location.aliases' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#aliases" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<div class="has-error" ng-show="location.error.aliasDomains">{{ location.error.aliasDomains }}</div>
|
|
|
|
<div class="row" ng-repeat="aliasDomain in location.aliasDomains">
|
|
<div class="col col-lg-11">
|
|
<div class="input-group input-group-sm">
|
|
<input type="text" class="form-control" id="aliasDomainsInput-{{ $index }}" ng-model="aliasDomain.subdomain" placeholder="{{ 'app.location.aliasesPlaceholder' | tr }}">
|
|
|
|
<div class="input-group-btn">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
<span>.{{ aliasDomain.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="aliasDomain.domain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col col-lg-1">
|
|
<button class="btn btn-danger btn-sm" ng-click="location.delAliasDomain($event, $index)"><i class="far fa-trash-alt"></i></button>
|
|
</div>
|
|
</div>
|
|
<div ng-show="location.aliasDomains.length === 0">{{ 'app.location.noAliases' | tr }}</div>
|
|
<div style="margin-top: 5px;"><a href="" ng-click="location.addAliasDomain($event)">{{ 'app.location.addAliasAction' | tr }}</a></div>
|
|
</div>
|
|
|
|
<div class="form-group redirect-domains">
|
|
<label class="control-label">{{ 'app.location.redirections' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#redirections" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<div class="has-error" ng-show="location.error.redirectDomains">{{ location.error.redirectDomains }}</div>
|
|
|
|
<div class="row" ng-repeat="redirectDomain in location.redirectDomains">
|
|
<div class="col col-lg-11">
|
|
<div class="input-group input-group-sm">
|
|
<input type="text" class="form-control" id="redirectDomainsInput-{{ $index }}" ng-model="redirectDomain.subdomain" placeholder="{{ 'app.location.redirectionsPlaceholder' | tr }}">
|
|
|
|
<div class="input-group-btn">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
<span>.{{ redirectDomain.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="redirectDomain.domain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col col-lg-1">
|
|
<button class="btn btn-danger btn-sm" ng-click="location.delRedirectDomain($event, $index)"><i class="far fa-trash-alt"></i></button>
|
|
</div>
|
|
</div>
|
|
<div ng-show="location.redirectDomains.length === 0">{{ 'app.location.noRedirections' | tr }}</div>
|
|
<div style="margin-top: 5px;"><a href="" ng-click="location.addRedirectDomain($event)">{{ 'app.location.addRedirectionAction' | tr }}</a></div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="location.submit()" ng-disabled="location.$invalid || location.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="location.busy"></i> {{ 'app.location.saveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'access'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="accessForm" ng-submit="access.submit()" autocomplete="off">
|
|
<div class="form-group" ng-show="app.manifest.addons.email">
|
|
<label class="control-label">{{ 'app.accessControl.userManagement.title' | tr }}</label>
|
|
<p>{{ 'appstore.installDialog.userManagementMailbox' | tr }}
|
|
<span ng-bind-html="'appstore.installDialog.configuredForCloudronEmail' | tr:{ emailDocsLink: 'https://docs.cloudron.io/email/' }">
|
|
</p>
|
|
</div>
|
|
|
|
<div ng-show="access.ssoAuth && !app.manifest.addons.email">
|
|
<label class="control-label">{{ 'app.accessControl.userManagement.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#access-restriction" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<p>{{ 'app.accessControl.userManagement.description' | tr }}</p>
|
|
</div>
|
|
<div ng-show="!access.ssoAuth || app.manifest.addons.email">
|
|
<label class="control-label">{{ 'app.accessControl.userManagement.dashboardVisibility' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<p ng-show="!app.manifest.addons.email">{{ 'appstore.installDialog.userManagementNone' | tr }}</p>
|
|
</div>
|
|
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="access.accessRestrictionOption" value="any">
|
|
<span ng-show="access.ssoAuth">{{ 'appstore.installDialog.userManagementAllUsers' | tr }}</span>
|
|
<span ng-show="!access.ssoAuth">{{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}</span>
|
|
</label>
|
|
</div>
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="access.accessRestrictionOption" value="groups">
|
|
|
|
<span ng-show="access.ssoAuth">{{ 'appstore.installDialog.userManagementSelectUsers' | tr }}</span>
|
|
<span ng-show="!access.ssoAuth">{{ 'app.accessControl.userManagement.visibleForSelected' | tr }}</span>
|
|
|
|
<span class="label label-danger" ng-show="access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()">{{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<div style="margin-left: 20px;">
|
|
<div class="col-md-5">
|
|
{{ 'appstore.installDialog.users' | tr }}: <multiselect name="accessUsersSelect" class="input-sm stretch" ng-model="access.accessRestriction.users" ng-disabled="access.accessRestrictionOption !== 'groups'" options="(user.username || user.email) for user in users" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
</div>
|
|
|
|
<div class="col-md-5">
|
|
{{ 'appstore.installDialog.groups' | tr }}: <multiselect name="accessGroupsSelect" class="input-sm stretch" ng-model="access.accessRestriction.groups" ng-disabled="access.accessRestrictionOption !== 'groups'" options="group.name for group in groups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
<br/>
|
|
<br/>
|
|
|
|
<div>
|
|
<label class="control-label">{{ 'app.accessControl.operators.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#operators" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<p>{{ 'app.accessControl.operators.description' | tr }} <span ng-show="access.ftp">{{ 'app.accessControl.userManagement.descriptionSftp' | tr }}</span></p>
|
|
</div>
|
|
|
|
<div>
|
|
<div style="margin-left: 20px;">
|
|
<div class="col-md-5">
|
|
{{ 'appstore.installDialog.users' | tr }}: <multiselect name="operatorsUsersSelect" class="input-sm stretch" ng-model="access.operators.users" options="(user.username || user.email) for user in users" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
</div>
|
|
|
|
<div class="col-md-5">
|
|
{{ 'appstore.installDialog.groups' | tr }}: <multiselect name="operatorsGroupsSelect" class="input-sm stretch" ng-model="access.operators.groups" options="group.name for group in groups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="(access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()) || accessForm.$invalid || access.busy"/>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="access.submit()" ng-disabled="(access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()) || access.$invalid || access.busy"><i class="fa fa-circle-notch fa-spin" ng-show="access.busy"></i> {{ 'main.dialog.save' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'resources'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="resourcesForm" ng-submit="resources.submitMemoryLimit()" autocomplete="off">
|
|
<div class="form-group">
|
|
<label class="control-label" for="memoryLimit">{{ 'app.resources.memory.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#memory-limit" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ resources.memoryLimit | prettyBinarySize:'Default (256 MiB)' }}</b></label>
|
|
<p>{{ 'app.resources.memory.description' | tr }}</p>
|
|
<input type="range" id="memoryLimit" ng-model="resources.memoryLimit" step="134217728" min="{{ resources.memoryTicks[0] }}" max="{{ resources.memoryTicks[resources.memoryTicks.length-1] }}" list="memoryLimitTicks" />
|
|
<datalist id="memoryLimitTicks">
|
|
<option ng-repeat="limit in resources.memoryTicks" value="{{ limit }}"></option>
|
|
</datalist>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<span ng-show="resources.error.memoryLimit" class="text-danger">{{ 'app.resources.memory.error' | tr }}</span>
|
|
</div>
|
|
<div class="col-md-4 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="resources.submitMemoryLimit()" ng-disabled="resources.memoryLimit === resources.currentMemoryLimit || resourcesForm.$invalid || resources.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">{{ 'app.resources.memory.resizeAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="resourcesForm" ng-submit="resources.submitCpuQuota()" autocomplete="off">
|
|
<fieldset>
|
|
<div class="form-group">
|
|
<label class="control-label" for="cpuQuota">{{ 'app.resources.cpu.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#cpu-limit" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ resources.cpuQuota + ' %' }}</b></label>
|
|
<p>{{ 'app.resources.cpu.description' | tr }}</p>
|
|
<input type="range" id="cpuQuota" ng-model="resources.cpuQuota" step="1" min="1" max="100"/>
|
|
<datalist id="cpuQuotaTicks">
|
|
<option value="25"></option>
|
|
<option value="50"></option>
|
|
<option value="75"></option>
|
|
</datalist>
|
|
</div>
|
|
</fieldset>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="resources.submitCpuQuota()" ng-disabled="resources.cpuQuota === resources.currentCpuQuota || resourcesForm.$invalid || resources.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">{{ 'app.resources.cpu.setAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="devicesForm" ng-submit="resources.submitDevices()" autocomplete="off">
|
|
<fieldset>
|
|
<div class="form-group">
|
|
<label class="control-label" for="devicesInput">Devices <sup><a ng-href="https://docs.cloudron.io/apps/#devices" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<p>Comma serparated list of devices mounted into the app</p>
|
|
<input type="text" class="form-control" ng-class="{ 'has-error': resources.error.devices }" id="devicesInput" ng-model="resources.devices" placeholder="/dev/ttyUSB, /dev/hidraw0, ..." ng-disabled="resources.busy"/>
|
|
<span class="text-danger" ng-show="resources.error.devices">{{ resources.error.devices }}</span>
|
|
</div>
|
|
</fieldset>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="resources.submitDevices()" ng-disabled="devicesForm.$invalid || resources.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">Set Devices</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'services'">
|
|
<div class="row" ng-show="app.manifest.addons.turn.optional">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.turn.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#turn" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="services.enableTurn" value="1"> {{ 'app.turn.enable' | tr }}
|
|
</label>
|
|
</div>
|
|
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="services.enableTurn" value="0"> {{ 'app.turn.disable' | tr }}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-12 text-right">
|
|
<br/>
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="services.submitTurn()" ng-disabled="app.enableTurn === services.enableTurn || services.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="services.busy"></i> {{ 'main.saveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<hr ng-show="app.manifest.addons.turn.optional && app.manifest.addons.redis.optional">
|
|
|
|
<div class="row" ng-show="app.manifest.addons.redis.optional">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.redis.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#redis" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="services.enableRedis" value="1"> {{ 'app.redis.enable' | tr }}
|
|
</label>
|
|
</div>
|
|
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="services.enableRedis" value="0"> {{ 'app.redis.disable' | tr }}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-12 text-right">
|
|
<br/>
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="services.submitRedis()" ng-disabled="app.enablRedis === services.enableRedis || services.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="services.busy"></i> {{ 'main.saveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'storage'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label" style="display: block;">{{ 'app.storage.appdata.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#data-directory" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<p ng-show="diskUsage !== -1" ng-bind-html="'app.storage.appdata.diskUsage' | tr:{ size: '<b>' + (diskUsage | prettyDiskSize) + '</b>', date: (diskUsageDate | prettyDate) }"></p>
|
|
<p ng-bind-html="'app.storage.appdata.description' | tr:{ storagePath: ('/home/yellowtent/appsdata/' + app.id) }"></p>
|
|
<form role="form" name="storageDataDirForm" ng-submit="storage.submitDataDir()" autocomplete="off">
|
|
<select class="form-control" ng-model="storage.location" ng-options="location.displayName for location in storage.locationOptions track by location.id"></select>
|
|
<p class="text-warning" ng-show="storage.location.type === 'volume' && storage.location.mountType === 'mountpoint'" ng-bind-html="'app.storage.appdata.mountTypeWarning' | tr"></p>
|
|
|
|
<br/>
|
|
|
|
<div class="form-group" ng-show="storage.location.type === 'volume'" ng-class="{ 'has-error': storageDataDirForm.$dirty && storage.error.storageVolumePrefix }">
|
|
<label class="control-label">Subdirectory</label>
|
|
<input type="text" class="form-control" name="storageVolumePrefix" placeholder="Prefix within the Volume" ng-model="storage.storageVolumePrefix">
|
|
<div ng-show="storage.error.storageVolumePrefix">{{ storage.error.storageVolumePrefix }}</div>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="!storageDataDirForm.$dirty || storageDataDirForm.$invalid || storage.busyDataDir || app.error || app.taskId"/>
|
|
</form>
|
|
</div>
|
|
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="storage.submitDataDir()" ng-disabled="!storageDataDirForm.$dirty || storageDataDirForm.$invalid || storage.busyDataDir || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="storage.busyDataDir"></i> {{ 'app.storage.appdata.moveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<div class="form-group mounts">
|
|
<label class="control-label">{{ 'app.storage.mounts.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#mounts" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
<div class="has-error" ng-show="storage.error.mounts">{{ storage.error.mounts }}</div>
|
|
<p ng-bind-html="'storage.mounts.description' | tr"></p>
|
|
|
|
<table class="table table-hover" style="margin-top: 10px;">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 40%">{{ 'app.storage.mounts.volume' | tr }}</th>
|
|
<th class="text-left hidden-xs hidden-sm">{{ 'app.storage.mounts.permissions.label' | tr }}</th>
|
|
<th style="width: 100px" class="text-right">{{ 'main.actions' | tr }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="mount in storage.mounts">
|
|
<td>
|
|
<multiselect ng-model="mount.volume" data-compare-by="hostPath" options="volume.name for volume in volumes" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
</td>
|
|
<td class="text-left" style="vertical-align: middle;">
|
|
<select class="form-control" ng-model="mount.readOnly">
|
|
<option value="true">{{ 'app.storage.mounts.permissions.readOnly' | tr }}</option>
|
|
<option value="false">{{ 'app.storage.mounts.permissions.readWrite' | tr }}</option>
|
|
</select>
|
|
</td>
|
|
<td class="text-right no-wrap" style="vertical-align: middle">
|
|
<a class="btn btn-xs btn-default" ng-href="{{ '/filemanager.html#/home/volume/' + mount.volume.id }}" target="_blank" uib-tooltip="{{ 'volumes.openFileManagerActionTooltip' | tr }}"><i class="fas fa-folder"></i></a>
|
|
<button class="btn btn-danger btn-xs" ng-click="storage.delMount($event, $index)"><i class="far fa-trash-alt"></i></button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="3">
|
|
<div style="margin-top: 5px;"><span ng-show="storage.mounts.length === 0">{{ 'app.storage.mounts.noMounts' | tr }}</span>
|
|
<a href="" ng-click="storage.addMount($event)">{{ 'app.storage.mounts.addMountAction' | tr }}</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="storage.submitMounts()" ng-disabled="storage.busyMounts || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="storage.busyMounts"></i> {{ 'app.storage.mounts.saveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'graphs'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="dropdown pull-right">
|
|
<button class="btn btn-sm btn-primary dropdown-toggle" type="button" data-toggle="dropdown">
|
|
{{ graphs.period | trKeyFromPeriod | tr }}
|
|
<span class="caret"></span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a href="" ng-click="graphs.setPeriod(6)">{{ 6 | trKeyFromPeriod | tr }}</a></li>
|
|
<li><a href="" ng-click="graphs.setPeriod(12)">{{ 12 | trKeyFromPeriod | tr }}</a></li>
|
|
<li><a href="" ng-click="graphs.setPeriod(24)">{{ 24 | trKeyFromPeriod | tr }}</a></li>
|
|
<li><a href="" ng-click="graphs.setPeriod(24*7)">{{ 24*7 | trKeyFromPeriod | tr }}</a></li>
|
|
<li><a href="" ng-click="graphs.setPeriod(24*30)">{{ 24*30 | trKeyFromPeriod | tr }}</a></li>
|
|
</ul>
|
|
</div>
|
|
<button class="btn btn-sm btn-outline pull-right" style="margin-right: 5px" ng-click="graphs.show()" ng-disabled="graphs.busy"><i class="fas fa-sync-alt" ng-class="{ 'fa-spin': graphs.busy }"></i></button>
|
|
<label style="margin-top: 10px;">Memory</label>
|
|
<canvas id="graphsMemoryChart" style="width: 100%; margin-bottom: 10px;"></canvas>
|
|
<label style="margin-top: 10px;">CPU</label>
|
|
<canvas id="graphsCpuChart" style="width: 100%; margin-bottom: 10px;"></canvas>
|
|
<label style="margin-top: 10px; display: block;">Disk I/O <span class="pull-right text-small">{{ 'app.graphs.diskIOTotal' | tr:{ read: graphs.blockReadTotal, write: graphs.blockWriteTotal } }}</span></label>
|
|
<canvas id="graphsDiskChart" style="width: 100%; margin-bottom: 10px;"></canvas>
|
|
<label style="margin-top: 10px; display: block;">Network I/O <span class="pull-right text-small">{{ 'app.graphs.networkIOTotal' | tr:{ inbound: graphs.networkReadTotal, outbound: graphs.networkWriteTotal } }}</span></label>
|
|
<canvas id="graphsNetworkChart" style="width: 100%; margin-bottom: 10px;"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'email'">
|
|
<div class="row" ng-show="app.manifest.addons.sendmail">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.email.from.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#mail-from-address" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
|
|
<div class="radio" ng-show="app.manifest.addons.sendmail.optional">
|
|
<label>
|
|
<input type="radio" ng-model="email.enableMailbox" value="1"> {{ 'app.email.from.enable' | tr }}
|
|
</label>
|
|
</div>
|
|
|
|
<div ng-style="{ 'padding-left': app.manifest.addons.sendmail.optional ? '20px' : '0' }">
|
|
<p ng-bind-html="'app.email.from.enableDescription' | tr:{ domain: app.domain, domainConfigLink: ('/#/email/' + app.domain) }"></p>
|
|
|
|
<form role="form" name="emailForm" ng-submit="email.submitMailbox()" autocomplete="off">
|
|
<fieldset ng-disabled="email.enableMailbox === '0'">
|
|
<div class="form-group" ng-class="{ 'has-error': emailForm.$dirty && email.error.mailboxName }">
|
|
<div class="has-error" ng-show="email.error.mailboxName">{{ email.error.mailboxName }}</div>
|
|
|
|
<div style="display: flex;">
|
|
<input type="text" class="form-control" ng-show="app.manifest.addons.sendmail.supportsDisplayName" style="width: 200px; margin-right: 5px;" name="mailboxDisplayName" placeholder="{{ 'app.email.from.displayName' | tr }}" ng-model="email.mailboxDisplayName">
|
|
<div class="input-group form-inline" style="flex-grow: 1;" ng-class="{ 'has-error': !emailForm.mailboxName.$dirty && email.error.mailboxName }">
|
|
<input type="text" class="form-control" name="mailboxName" placeholder="{{ 'app.email.from.mailboxPlaceholder' | tr }}" ng-model="email.mailboxName">
|
|
|
|
<div class="input-group-btn">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
<span>{{ '@' + email.mailboxDomain.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="email.mailboxDomain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
</div>
|
|
</fieldset>
|
|
<input class="ng-hide" type="submit" ng-disabled="(email.currentMailboxDomainName === email.mailboxDomain.domain && email.currentMailboxName === email.mailboxName) || email.mailboxBusy || app.error || app.taskId"/>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="radio" ng-show="app.manifest.addons.sendmail.optional">
|
|
<label>
|
|
<input type="radio" ng-model="email.enableMailbox" value="0"> {{ 'app.email.from.disable' | tr }}
|
|
</label>
|
|
</div>
|
|
|
|
<div style="padding-left: 20px;">
|
|
<p ng-show="app.manifest.addons.sendmail.optional">{{ 'app.email.from.disableDescription' | tr }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row" ng-show="app.manifest.addons.sendmail">
|
|
<div class="col-md-12 text-right">
|
|
<br/>
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="email.submitMailbox()" ng-disabled="(app.enableMailbox === email.enableMailbox && email.currentMailboxDomainName === email.mailboxDomain.domain && email.currentMailboxName === email.mailboxName) || email.mailboxBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="email.mailboxBusy"></i> {{ 'app.email.from.saveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<hr ng-show="app.manifest.addons.sendmail && app.manifest.addons.recvmail"/>
|
|
|
|
<div class="row" ng-show="app.manifest.addons.recvmail">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.email.inbox.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#inbox" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="email.enableInbox" ng-value="true"> {{ 'app.email.inbox.enable' | tr }}
|
|
</label>
|
|
</div>
|
|
|
|
<div ng-style="{ 'padding-left': '20px' }">
|
|
<p ng-bind-html="'app.email.inbox.enableDescription' | tr:{ domain: app.domain, domainConfigLink: ('/#/email/' + app.domain) }"></p>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': email.error.inboxName }">
|
|
<div class="has-error" ng-show="email.error.inboxName">{{ email.error.inboxName }}</div>
|
|
<multiselect name="inboxSelect" ng-model="email.inbox" ng-disabled="!email.enableInbox" options="inbox.display for inbox in email.inboxes" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="email.enableInbox" ng-value="false"> {{ 'app.email.inbox.disable' | tr }}
|
|
</label>
|
|
</div>
|
|
|
|
<div style="padding-left: 20px;">
|
|
<p>{{ 'app.email.inbox.disableDescription' | tr }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row" ng-show="app.manifest.addons.recvmail">
|
|
<div class="col-md-12 text-right">
|
|
<br/>
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="email.submitInbox()" ng-disabled="(email.enableInbox && !email.inbox) || (app.enableInbox === email.enableInbox && email.currentInbox.name === email.inbox.name && email.currentInbox.domain === email.inbox.domain) || email.inboxBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="email.inboxBusy"></i> {{ 'app.email.from.saveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'cron'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="cronForm" ng-submit="cron.submit()" autocomplete="off">
|
|
<div class="form-group" ng-class="{ 'has-error': cron.error.crontab }">
|
|
<label class="control-label" style="width: 100%">{{ 'app.cron.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#cron" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup>
|
|
<div class="dropdown pull-right">
|
|
<a class="dropdown-toggle hand" style="font-weight: normal;" id="commonCronPatternDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
|
{{ 'app.cron.addCommonPattern' | tr }}
|
|
<span class="caret"></span>
|
|
</a>
|
|
<ul class="dropdown-menu" aria-labelledby="commonCronPatternDropdown">
|
|
<li ng-repeat="pattern in cron.commonPatterns"><a class="hand" ng-click="cron.addCommonPattern(pattern.value)">{{ pattern.label }}</a></li>
|
|
</ul>
|
|
</div>
|
|
</label>
|
|
<p>{{ 'app.cron.description' | tr }}</p>
|
|
<div ng-show="cron.error.crontab"><small>{{ cron.error.crontab }}</small></div>
|
|
<textarea ng-trim="false" style="white-space: pre-wrap" ng-model="cron.crontab" class="form-control text-monospace" rows="10"></textarea>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="cronForm.$invalid || cron.busy"/>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="cron.submit()" ng-disabled="cron.$invalid || cron.busy || app.error" tooltip-enable="app.error" uib-tooltip="App is in error state">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="cron.busy"></i> {{ 'app.cron.saveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'eventlog'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<center ng-show="eventlog.busy"><h2><i class="fa fa-circle-notch fa-spin"></i></h2></center>
|
|
<table ng-hide="eventlog.busy" class="table table-condensed table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th class="col-md-2">{{ 'eventlog.time' | tr }}</th> <!-- "minutes ago" takes space -->
|
|
<th class="col-md-2">{{ 'eventlog.source' | tr }}</th>
|
|
<th class="col-md-6">{{ 'eventlog.details' | tr }}</th>
|
|
<th class="col-md-2" style="text-align: right;">
|
|
<button class="btn btn-xs btn-default btn-outline" ng-click="eventlog.showPrevPage()" ng-disabled="eventlog.busy || eventlog.currentPage <= 1"><i class="fa fa-angle-double-left"></i></button>
|
|
<button class="btn btn-xs btn-default btn-outline" ng-click="eventlog.showNextPage()" ng-disabled="eventlog.busy || eventlog.perPage > eventlog.eventLogs.length"><i class="fa fa-angle-double-right"></i></button>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody ng-repeat="eventLog in eventlog.eventLogs">
|
|
<tr ng-click="eventlog.showDetails(eventLog)" class="hand">
|
|
<td><span uib-tooltip="{{ eventLog.raw.creationTime | prettyLongDate }}" class="arrow">{{ eventLog.raw.creationTime | prettyDate }}</span></td>
|
|
<td>{{ eventLog.source }}</td>
|
|
<td style="word-wrap: anywhere;" colspan="2" ng-bind-html="eventLog.details"></td>
|
|
</tr>
|
|
<tr ng-show="eventlog.activeEventLog === eventLog">
|
|
<td colspan="4">
|
|
<p ng-show="eventLog.raw.source.ip">Source IP: <code>{{ eventLog.raw.source.ip }}</code></p>
|
|
<pre class="eventlog-details">{{ eventLog.raw.data | json }}</pre>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'proxy'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="proxyForm" ng-submit="proxy.submit()" autocomplete="off">
|
|
<div class="form-group" ng-class="{ 'has-error': !proxyForm.upstreamUri.$dirty && proxy.error }">
|
|
<label class="control-label" style="width: 100%">Upstream URI</label>
|
|
<input type="text" ng-model="proxy.upstreamUri" name="upstreamUri" placeholder="" class="form-control"/>
|
|
<p class="text-danger" ng-show="!proxyForm.upstreamUri.$dirty && proxy.error">{{ proxy.error }}</p>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="proxyForm.$invalid || proxy.busy"/>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="proxy.submit()" ng-disabled="proxyForm.$invalid || proxy.busy || app.error" tooltip-enable="app.error" uib-tooltip="App is in error state">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="proxy.busy"></i> Save
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'security'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<form role="form" name="securityForm" ng-submit="security.submit()" autocomplete="off">
|
|
<div class="form-group">
|
|
<label class="control-label" style="width: 100%">{{ 'app.security.robots.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#robotstxt" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> <a href="" class="pull-right" style="font-weight: normal;" ng-click="security.robotsTxt = ROBOTS_DISABLE_INDEXING_TEMPLATE">{{ 'app.security.robots.disableIndexingAction' | tr }}</a></label>
|
|
<textarea ng-trim="false" style="white-space: pre-wrap" ng-model="security.robotsTxt" placeholder="{{ 'app.security.robots.txtPlaceholder' | tr }}" class="form-control text-monospace" rows="4"></textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="control-label" style="width: 100%">{{ 'app.security.csp.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#custom-csp" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> </label>
|
|
<p>{{ 'app.security.csp.description' | tr }}</p>
|
|
<textarea ng-model="security.csp" placeholder="default-src 'self'; frame-ancestors 'none';" class="form-control text-monospace" rows="2"></textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="checkbox">
|
|
<label>
|
|
<input type="checkbox" ng-model="security.hstsPreload">{{ 'app.security.hstsPreload' | tr }}</input>
|
|
<sup><a ng-href="https://docs.cloudron.io/apps/#hsts-preload" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="securityForm.$invalid || security.busy"/>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="security.submit()" ng-disabled="securityForm.$invalid || security.busy || app.error" tooltip-enable="app.error" uib-tooltip="App is in error state">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="security.busy"></i> {{ 'app.security.csp.saveAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'updates'">
|
|
<p><label class="control-label">{{ 'app.updatesTabTitle' | tr }}</label></p>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<p>
|
|
<span ng-bind-html="'app.updates.auto.description' | tr:{ appStoreLink: 'https://www.cloudron.io/store/index.html' }"></span>
|
|
<span ng-show="app.appStoreId && updates.enableAutomaticUpdate" class="text-success">{{ 'app.updates.auto.enabled' | tr }}</span>
|
|
<span ng-show="app.appStoreId && !updates.enableAutomaticUpdate" class="text-danger">{{ 'app.updates.auto.disabled' | tr }}</span>
|
|
<span ng-show="!app.appStoreId" class="text-danger">{{ 'app.updates.info.customAppUpdateInfo' | tr }}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<button class="btn pull-right" uib-tooltip="{{ app.appStoreId ? '' : 'Not available for custom apps' }}" ng-class="updates.enableAutomaticUpdate ? 'btn-danger' : 'btn-success'" ng-click="updates.toggleAutomaticUpdates()" ng-disabled="updates.busyAutomaticUpdates || !app.appStoreId"><i class="fa fa-circle-notch fa-spin" ng-show="updates.busyAutomaticUpdates"></i> {{ updates.enableAutomaticUpdate ? ('app.updates.auto.disableAction' | tr) : ('app.updates.auto.enableAction' | tr) }} </button>
|
|
<!-- check for updates button is always visible -->
|
|
<button class="btn btn-default btn-outline pull-right" uib-tooltip="{{ app.appStoreId ? '' : 'Not available for custom apps' }}" ng-click="updates.check()" ng-disabled="updates.busyCheck || !app.appStoreId"><i class="fas fa-sync-alt fa-spin" ng-show="updates.busyCheck"></i><i class="fas fa-sync-alt" ng-hide="updates.busyCheck"></i> {{ 'settings.updates.checkForUpdatesAction' | tr }}</button>
|
|
<!-- show update button only if update available -->
|
|
<button class="btn pull-right" ng-show="config.update[app.id].manifest.version && config.update[app.id].manifest.version !== app.manifest.version && app.installationState !== 'pending_update' && !app.taskId" ng-class="config.update[app.id].unstable ? 'btn-danger' : 'btn-success'" ng-click="updates.askUpdate()" ng-disabled="app.error || app.runState === 'stopped'" tooltip-enable="app.error || app.taskId || app.runState === 'stopped'" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is not running' }}">{{ 'app.updateDialog.updateAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'backups'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.backups.backups.title' | tr }}</label>
|
|
<div>{{ 'app.backups.backups.description' | tr }}</div>
|
|
|
|
<br/>
|
|
|
|
<table ng-hide="!backups.backups.length" class="table table-hover" style="margin: 0;">
|
|
<thead>
|
|
<tr>
|
|
<th width="25px"> </th>
|
|
<th>{{ 'app.backups.backups.packageVersion' | tr }}</th>
|
|
<th>{{ 'app.backups.backups.time' | tr }}</th>
|
|
<th class="text-center" width="25px">{{ 'main.actions' | tr }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-repeat="backup in backups.backups">
|
|
<td><i class="fas fa-archive" ng-show="backup.preserveSecs === -1" uib-tooltip="{{ 'backups.listing.tooltipPreservedBackup' | tr }}"></i></td>
|
|
<!-- <td><div class="hand clipboard" data-clipboard-text="{{ backup.id }}" uib-tooltip="{{ copyBackupIdDone ? ('main.clipboard.copied' | tr) : ('main.clipboard.clickToCopyBackupId' | tr) }}" tooltip-placement="right"><i class="fa fa-copy"></i></div></td> -->
|
|
<td ng-click="backupDetails.show(backup)" class="hand"><div>v{{ backup.packageVersion }}</div></td>
|
|
<td ng-click="backupDetails.show(backup)" class="hand">{{ backup.creationTime | prettyLongDate }} <b ng-show="backup.label">({{ backup.label }})</b></td>
|
|
<td class="text-center" style="vertical-align: bottom">
|
|
<div class="dropdown">
|
|
<button class="btn btn-xs btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
|
<i class="fas fa-ellipsis-h"></i>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenu1">
|
|
<li><a class="hand" ng-show="app.accessLevel === 'admin'" ng-click="editBackup.show(backup)"><i class="fa fa-pencil-alt fa-fw"></i> {{ 'backups.listing.tooltipEditBackup' | tr }}</a></li>
|
|
<li role="separator" class="divider"></li>
|
|
<li><a class="hand" ng-show="backup.format === 'tgz' && app.accessLevel === 'admin'" ng-href="{{ getAppBackupDownloadLink(backup) }}" target="_blank"><i class="fas fa-download fa-fw"></i> {{ 'app.backups.backups.downloadBackupTooltip' | tr }}</a></li>
|
|
<li><a class="hand" ng-show="app.accessLevel === 'admin'" ng-click="downloadConfig(backup)"><i class="fas fa-file-alt fa-fw"></i> {{ 'app.backups.backups.downloadConfigTooltip' | tr }}</a></li>
|
|
<li role="separator" class="divider"></li>
|
|
<li><a class="hand" ng-show="app.accessLevel === 'admin'" ng-click="clone.show(backup)"><i class="far fa-clone fa-fw"></i> {{ 'app.backups.backups.cloneTooltip' | tr }}</a></li>
|
|
<li><a class="hand" ng-click="restore.show(backup)" ng-disabled="app.taskId || app.runState === 'stopped'"><i class="fas fa-history fa-fw"></i> {{ 'app.backups.backups.restoreTooltip' | tr }}</a></li>
|
|
</ul>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<br/>
|
|
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<p class="has-error" ng-show="backups.error.message">{{ backups.error.message }}</p>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<button type="button" class="btn btn-primary pull-right" ng-click="backups.createBackup()" ng-disabled="app.taskId || backups.busyCreate || app.error || app.runState === 'stopped'" tooltip-enable="app.error || app.taskId || app.runState === 'stopped'" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is not running' }}">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="app.installationState === 'pending_backup' || backups.busyCreate"></i> {{ 'app.backups.backups.createBackupAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.backups.import.title' | tr }}</label>
|
|
<p>{{ 'app.backups.import.description' | tr }}</p>
|
|
|
|
<button class="btn btn-primary pull-right" class="btn-primary" ng-click="importBackup.show()" ng-disabled="importBackup.busy || app.taskId || app.runState === 'stopped'" tooltip-enable="app.taskId" uib-tooltip="App is not running">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="backups.busy"></i> {{ 'app.backups.backups.importAction' | tr }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.backups.auto.title' | tr }}</label>
|
|
|
|
<p ng-bind-html="'app.backups.auto.description' | tr:{ backupLink: '/#/backups' }"></p>
|
|
<p class="text-success" ng-show="backups.enableBackup">{{ 'app.backups.auto.enabled' | tr }}</p>
|
|
<p class="text-danger" ng-hide="backups.enableBackup">{{ 'app.backups.auto.disabled' | tr }}</p>
|
|
|
|
<button class="btn btn-primary pull-right" ng-class="{ 'btn-danger': backups.enableBackup }" ng-click="backups.toggleAutomaticBackups()" ng-disabled="backups.busyAutomaticBackups"><i class="fa fa-circle-notch fa-spin" ng-show="backups.busyAutomaticBackups"></i> {{ backups.enableBackup ? ('app.backups.auto.disableAction' | tr) : ('app.backups.auto.enableAction' | tr) }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'repair'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.repair.recovery.title' | tr }}</label>
|
|
<p ng-bind-html="'app.repair.recovery.description' | tr:{ docsLink: 'https://docs.cloudron.io/troubleshooting/#unresponsive-app' }"></p>
|
|
<button class="btn btn-primary pull-right" ng-click="repair.pauseAppBegin()" ng-show="!app.debugMode" ng-disabled="repair.pauseBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">{{ 'app.repair.recovery.enableRecoveryModeAction' | tr }}</button>
|
|
<button class="btn btn-primary pull-right" ng-click="repair.pauseAppDone()" ng-show="app.debugMode" ng-disabled="repair.pauseBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">{{ 'app.repair.recovery.disableRecoveryModeAction' | tr }}</button>
|
|
<button class="btn btn-primary pull-right" ng-click="repair.restartApp()" ng-disabled="repair.restartBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}"><i ng-show="repair.restartBusy" class="fa fa-circle-notch fa-spin"></i> {{ 'app.repair.recovery.restartAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.repair.taskError.title' | tr }}</label>
|
|
<p>{{ 'app.repair.taskError.description' | tr }}</p>
|
|
<p ng-show="app.error">An error occurred during the <b>{{ app.error.installationState | taskName }}</b> operation: <span class="text-danger"><b>{{ app.error.reason + ': ' + app.error.message }}</b></span></p>
|
|
<button class="btn btn-primary pull-right" ng-click="repair.confirm()" ng-disabled="app.taskId || !app.error" tooltip-enable="app.taskId" uib-tooltip="{{ 'app.repair.appIsBusyTooltip' | tr }}">{{ 'app.repair.taskError.retryAction' | tr:{ task: (app.error.installationState | taskName) } }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'uninstall'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.uninstall.startStop.title' | tr }}</label>
|
|
<p>{{ 'app.uninstall.startStop.description' | tr }}</p>
|
|
<button class="btn btn-default pull-right" ng-class="{ 'btn-primary': uninstall.startButton }" ng-click="uninstall.toggleRunState()" ng-disabled="app.taskId || app.error || app.installationState === 'pending_start' || app.installationState === 'pending_stop'">
|
|
<i ng-show="app.installationState === 'pending_start' || app.installationState === 'pending_stop'" class="fa fa-circle-notch fa-spin"></i>
|
|
{{ uninstall.startButton ? ('app.uninstall.startStop.startAction' | tr) : ('app.uninstall.startStop.stopAction' | tr) }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.archive.title' | tr }}</label>
|
|
<p>{{ 'app.archive.description' | tr }}</p>
|
|
<p class="text-bold text-success" ng-show="uninstall.latestBackup" ng-bind-html="'app.archive.latestBackupInfo' | tr:{ date: (uninstall.latestBackup.creationTime | prettyLongDate) }"></p>
|
|
<p class="text-bold text-warning" ng-show="!uninstall.latestBackup" ng-bind-html="'app.archive.noBackup' | tr"></p>
|
|
<button ng-disabled="!uninstall.latestBackup" class="btn btn-default pull-right" ng-click="uninstall.ask('archive')">{{ 'app.archive.action' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.uninstall.uninstall.title' | tr }}</label>
|
|
<p>{{ 'app.uninstall.uninstall.description' | tr }}</p>
|
|
<button class="btn btn-danger pull-right" ng-click="uninstall.ask('uninstall')">{{ 'app.uninstall.uninstall.uninstallAction' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|