1761 lines
117 KiB
HTML
1761 lines
117 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="{{ 'https://' + app.fqdn }}" target="_blank" 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">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
<a class="btn btn-success" ng-href="{{ 'https://' + appPostInstallConfirm.app.fqdn }}" target="_blank" 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/ttyUSB0, /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 ng-if="app.type !== APP_TYPES.PROXIED"/>
|
|
<div class="row" ng-if="app.type !== APP_TYPES.PROXIED">
|
|
<div class="col-md-12">
|
|
<label class="control-label">{{ 'app.archive.title' | tr }}</label>
|
|
<p ng-bind-html="'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>
|