2019-09-10 19:21:30 +02:00
< script >
function imageErrorHandler(elem) {
'use strict';
elem.src = elem.getAttribute('fallback-icon');
elem.onerror = null; // avoid retry after default icon cannot be loaded
}
< / script >
2019-09-19 22:49:21 +02:00
<!-- Modal postinstall confirm -->
< div class = "modal fade" id = "postInstallConfirmModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< img ng-src = "{{app.iconUrl}}" onerror = "this.onerror=null;this.src='img/appicon_fallback.png'" class = "app-info-icon" / >
< h5 class = "app-info-title" >
{{ app.manifest.title }}
< span class = "app-info-meta text-small" > Package < a ng-href = "/#/appstore/{{ app.manifest.id }}?version={{ app.manifest.version }}" > v{{ app.manifest.version }}< / a > < / span >
< br / >
< span ng-show = "app.manifest.documentationUrl" > < a target = "_blank" ng-href = "{{ app.manifest.documentationUrl }}" > Documentation< / a > < / span >
< br / >
< / h5 >
< / div >
< div class = "modal-body" >
< div ng-bind-html = "app.manifest.postInstallMessage | postInstallMessage:app | markdown2html" > < / div >
< div ng-show = "app.manifest.documentationUrl" >
Please see the < a target = "_blank" ng-href = "{{ app.manifest.documentationUrl }}" > documentation< / a > for more information.
< / div >
< / div >
< div class = "modal-footer" >
< div class = "form-group pull-left" >
< input type = "checkbox" id = "postInstallConfirmCheckbox" ng-model = "postInstallConfirm.confirmed" >
< label class = "control-label" for = "postInstallConfirmCheckbox" > Acknowledge instructions< / label >
< / div >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
< a class = "btn btn-success" ng-href = "{{ postInstallConfirm.confirmed ? ('https://' + app.fqdn) : '' }}" target = "_blank" ng-disabled = "!postInstallConfirm.confirmed" ng-click = "postInstallConfirm.submit()" > < i class = "fas fa-external-link-alt" > < / i > Open App< / a >
< / div >
< / div >
< / div >
< / div >
2019-09-13 11:18:43 +02:00
<!-- Modal uninstall app -->
< div class = "modal fade" id = "uninstallModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
2019-09-19 10:28:17 -07:00
< div class = "modal-header" >
< h4 class = "modal-title" > Uninstall {{ app.label || app.fqdn }}< / h4 >
< / div >
2019-09-13 11:18:43 +02:00
< div class = "modal-body" >
2019-09-19 10:28:17 -07:00
< p > This will immediately uninstall < b > {{ app.label || app.fqdn }}< / b > and remove all it's data.< / p >
2019-09-13 11:18:43 +02:00
< / div >
< div class = "modal-footer" >
2019-09-19 10:28:17 -07:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
< button type = "button" class = "btn btn-danger" ng-click = "uninstall.submit()" ng-disabled = "uninstall.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "uninstall.busy" > < / i > Uninstall< / button >
2019-09-13 11:18:43 +02:00
< / div >
< / div >
< / div >
< / div >
2019-09-20 01:55:45 +02:00
<!-- Modal domain collision -->
< div class = "modal fade" id = "domainCollisionsModal" tabindex = "-1" role = "dialog" >
2019-09-21 22:45:26 +02:00
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" > Domain Collision< / h4 >
2019-09-20 01:55:45 +02:00
< / div >
2019-09-21 22:45:26 +02:00
< div class = "modal-body" >
2019-09-23 09:54:40 -07:00
< p > The following domains already exist in your DNS:< / p >
2019-09-21 22:45:26 +02:00
< ul >
< li ng-repeat = "domain in location.domainCollisions" > {{ domain.subdomain + '.' + domain.domain }}< / li >
< / ul >
2019-09-23 15:13:50 -07:00
< p > As a precautionary measure, Cloudron does not overwrite existing DNS records. Please confirm that the above domains are not in use for services external to Cloudron.< / p >
2019-09-21 22:45:26 +02:00
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
< button type = "button" class = "btn btn-danger" ng-click = "location.submit(true)" > Overwrite existing DNS Records< / 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" > Repair {{ app.fqdn }}< / h4 >
< / div >
< div class = "modal-body" >
2019-10-03 11:25:27 -07:00
< div ng-if = "!app.error" >
< p > Cloudron will re-install the app in-place with existing configuration. Existing data will be retained.< / p >
< / div >
< div ng-if = "app.error" >
2019-11-23 17:49:13 -08:00
< p > The < b > {{ app.error.installationState | taskName }}< / b > operation failed with the following error:< / p >
2019-10-03 11:25:27 -07:00
< p class = "text-danger" > {{ app.error.reason + ': ' + app.error.message }}< / p >
< / div >
2019-09-23 22:45:45 +02:00
< div class = "form-group" ng-show = "repair.location && repair.domain" >
2019-09-27 15:39:11 -07:00
< p > Cloudron will repair the app to use the following domains:< / p >
2019-09-26 22:23:08 -07:00
< label class = "control-label" > Location< / label >
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "repair.location" name = "location" placeholder = "{{ 'Leave empty to use bare domain' }}" autofocus >
2019-09-23 22:45:45 +02:00
2019-09-26 22:23:08 -07:00
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
< span > {{ (!repair.location ? '' : (repair.domain.config.hyphenatedSubdomains ? '-' : '.')) + 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 >
2019-09-23 22:45:45 +02:00
< / div >
< / div >
2019-09-26 22:23:08 -07:00
< / div >
2019-09-23 22:45:45 +02:00
< div ng-show = "repair.alternateDomains.length" >
< p ng-repeat = "alternateDomain in repair.alternateDomains" >
2019-09-27 15:34:54 -07:00
< label class = "control-label" > < input type = "checkbox" ng-model = "alternateDomain.enabled" >
{{ alternateDomain.subdomain + (!alternateDomain.subdomain ? '' : (alternateDomain.domain.config.hyphenatedSubdomains ? '-' : '.')) + alternateDomain.domain.domain }}
< / label >
2019-09-21 22:45:26 +02:00
< / p >
2019-09-20 01:55:45 +02:00
< / div >
2019-09-23 15:01:44 +02:00
< div ng-show = "repair.backups.length" >
2019-09-21 22:45:26 +02:00
< label class = "control-label" > Restore from Backup:< / label >
< select class = "form-control" ng-model = "repair.backupId" >
< option ng-repeat = "backup in repair.backups" value = "{{ backup.id }}" > {{ backup.creationTime | prettyDate }} - v{{ backup.version }}< / option >
< / select >
< / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
2019-12-20 15:38:40 -08:00
< 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 > Retry {{ app.error.installationState | taskName }}
< / button >
2019-09-20 01:55:45 +02:00
< / div >
< / div >
< / div >
2019-09-21 22:45:26 +02:00
< / div >
2019-09-20 01:55:45 +02:00
2020-02-06 16:08:22 -08:00
<!-- 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" > Import Backup< / h4 >
< / div >
< div class = "modal-body" >
2020-02-07 10:22:52 -08:00
< p class = "text-info" > Any data generated between now and the last known backup will be irrevocably lost.
2020-02-06 16:08:22 -08:00
It is recommended to create a backup of the current data before attempting an import.
< / p >
< form name = "importBackupForm" role = "form" novalidate ng-submit = "importBackup.submit()" autocomplete = "off" >
< fieldset >
< p class = "has-error text-center" ng-show = "backups.error" > {{ importBackup.error.generic }}< / p >
< div class = "form-group" >
< label class = "control-label" for = "storageProvider" > Storage provider < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/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 storageProvider" ng-change = importBackup.clearForm() > < / select >
< / div >
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.key }" >
< label ng-show = "importBackup.provider !== 'filesystem'" class = "control-label" for = "inputImportBackupId" > Backup ID< / label >
< label ng-show = "importBackup.provider === 'filesystem'" class = "control-label" for = "inputImportBackupId" > Backup Path< / label >
< input type = "text" class = "form-control" ng-model = "importBackup.backupId" id = "inputImportBackupId" ng-disabled = "importBackup.busy" required >
< / div >
<!-- S3/Minio/SOS/GCS -->
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.endpoint }" ng-show = "importBackup.provider === 'minio' || importBackup.provider === 's3-v4-compat'" >
< label class = "control-label" for = "inputimportBackupEndpoint" > Endpoint< / label >
< input type = "text" class = "form-control" ng-model = "importBackup.endpoint" id = "inputimportBackupEndpoint" name = "endpoint" ng-disabled = "importBackup.busy" placeholder = "URL of Minio/S3 Compatible" ng-required = "importBackup.provider === 'minio' || importBackup.provider === 's3-v4-compat'" >
< / 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'" >
2020-02-07 10:22:52 -08:00
< label class = "control-label" for = "inputImportBackupBucket" > Bucket name< / label >
< input type = "text" class = "form-control" ng-model = "importBackup.bucket" id = "inputImportBackupBucket" name = "bucket" ng-disabled = "importBackup.busy" ng-required = "s3like(importBackup.provider)" >
2020-02-06 16:08:22 -08:00
< / div >
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.prefix }" ng-show = "importBackup.provider !== 'filesystem' && importBackup.provider !== ''" >
< label class = "control-label" for = "inputimportBackupPrefix" > Prefix< / 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 >
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.region }" ng-show = "importBackup.provider === 's3'" >
2020-02-07 10:22:52 -08:00
< label class = "control-label" for = "inputImportBackupS3Region" > Region< / 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 >
2020-02-06 16:08:22 -08:00
< / div >
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.region }" ng-show = "importBackup.provider === 'digitalocean-spaces'" >
2020-02-07 10:22:52 -08:00
< label class = "control-label" for = "inputImportBackupDORegion" > Region< / 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 >
2020-02-06 16:08:22 -08:00
< / div >
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.region }" ng-show = "importBackup.provider === 'exoscale-sos'" >
< label class = "control-label" for = "inputimportBackupExoscaleRegion" > Region< / 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" > Region< / 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" > Region< / 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.accessKeyId }" ng-show = "s3like(importBackup.provider)" >
2020-02-07 10:22:52 -08:00
< label class = "control-label" for = "inputImportBackupAccessKeyId" > Access key id< / label >
< input type = "text" class = "form-control" ng-model = "importBackup.accessKeyId" id = "inputImportBackupAccessKeyId" name = "accessKeyId" ng-disabled = "importBackup.busy" ng-required = "s3like(importBackup.provider)" >
2020-02-06 16:08:22 -08:00
< / div >
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.secretAccessKey }" ng-show = "s3like(importBackup.provider)" >
2020-02-07 10:22:52 -08:00
< label class = "control-label" for = "inputImportBackupSecretAccessKey" > Secret access key< / label >
< input type = "text" class = "form-control" ng-model = "importBackup.secretAccessKey" id = "inputImportBackupSecretAccessKey" name = "secretAccessKey" ng-disabled = "importBackup.busy" ng-required = "s3like(importBackup.provider)" >
2020-02-06 16:08:22 -08:00
< / div >
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.gcsKeyInput }" ng-show = "importBackup.provider === 'gcs'" >
< label class = "control-label" for = "gcsKeyInput" > Service Account Key< / 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" > Storage Format < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/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.key = ''" ng-model = "importBackup.format" ng-options = "a.value as a.name for a in formats" > < / select >
< / div >
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.key }" >
< label class = "control-label" for = "inputimportBackupKey" > Encryption key (optional) < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/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.key" id = "inputimportBackupKey" ng-disabled = "importBackup.busy" placeholder = "Passphrase used to encrypt the backups" >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "importBackupForm.$invalid" / >
< / fieldset >
< / form >
< / div >
< div class = "modal-footer " >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / 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 > < span > Import< / span > < / button >
< / div >
< / div >
< / div >
< / div >
2019-09-26 20:48:04 +02:00
<!-- Modal postinstall -->
< div class = "modal fade" id = "postInstallModal" tabindex = "-1" role = "dialog" >
2019-09-27 19:43:03 +02:00
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 > First Time Setup< / h4 >
< / div >
< div class = "modal-body" >
< div ng-bind-html = "app.manifest.postInstallMessage | postInstallMessage:app | markdown2html" > < / div >
< / div >
< div class = "modal-footer" >
< div class = "form-group pull-left" ng-show = "postInstallMessage.openApp" >
< input type = "checkbox" id = "appPostInstallConfirmCheckbox" ng-model = "postInstallMessage.confirmed" >
< label class = "control-label" for = "appPostInstallConfirmCheckbox" > Acknowledge instructions< / label >
2019-09-26 20:48:04 +02:00
< / div >
2019-09-27 19:43:03 +02:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
< a class = "btn btn-success" ng-href = "{{ postInstallMessage.confirmed ? ('https://' + app.fqdn) : '' }}" target = "_blank" ng-disabled = "!postInstallMessage.confirmed" ng-click = "postInstallMessage.submit()" ng-show = "postInstallMessage.openApp" > Open {{ app.manifest.title }}< / a >
2019-09-26 20:48:04 +02:00
< / div >
< / div >
< / div >
2019-09-27 19:43:03 +02:00
< / div >
2019-09-26 20:48:04 +02:00
2019-09-17 17:14:40 +02:00
<!-- 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" > Update {{ app.fqdn }}< / h4 >
< / div >
< div class = "modal-body" >
< p > Recent Changes for new version < b > {{ config.update.apps[app.id].manifest.version}}< / b > :< / p >
< div ng-bind-html = "config.update.apps[app.id].manifest.changelog | markdown2html" > < / div >
< / div >
< div class = "modal-footer" >
2019-09-26 20:10:25 -07:00
< label class = "checkbox-inline pull-left" >
< input type = "checkbox" ng-model = "updates.skipBackup" > < b > Skip backup< / b >
< / label >
2019-09-17 17:14:40 +02:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
< button type = "button" class = "btn btn-success" ng-click = "updates.confirmUpdate()" ng-disabled = "updates.busyUpdate" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busyUpdate" > < / i > Update< / button >
< / div >
< / div >
< / div >
< / div >
2019-10-24 10:01:23 -07:00
<!-- 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" >
Restore - {{ app.fqdn }}
< / h4 >
< / div >
< div class = "modal-body" style = "padding: 0 15px" >
2019-10-24 10:07:34 -07:00
< p > This will restore this app to the data from < b > {{ restore.backup.creationTime | prettyDate }}< / b > .< / p >
< p class = "text-danger" > Any data generated between now and the last known backup will be irrevocably lost.
It is recommended to create a backup of the current data before attempting a restore.
< / p >
< br / >
2019-10-24 10:01:23 -07:00
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
2019-10-24 10:07:34 -07:00
< 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 = "clone.busy" > < / i > Restore< / button >
2019-10-24 10:01:23 -07:00
< / div >
< / div >
< / div >
< / div >
2019-09-13 17:18:37 +02:00
<!-- Modal clone app -->
< div class = "modal fade" id = "cloneModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" >
Clone - {{ app.fqdn }}
< / h4 >
< / div >
< div class = "modal-body" style = "padding: 0 15px" >
< p > Using backup from < b > {{ clone.backup.creationTime | prettyDate }}< / b > and version < b > v{{ clone.backup.version }}< / b > < / p >
< fieldset >
< form role = "form" ng-submit = "clone.submit()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': clone.error.location }" >
< label class = "control-label" for = "cloneLocationInput" > Location< / label >
< div ng-show = "clone.error.location" > < small > {{ clone.error.location }}< / small > < / div >
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "clone.location" id = "cloneLocationInput" 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 > {{ (!clone.location ? '' : (clone.domain.config.hyphenatedSubdomains ? '-' : '.')) + 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 >
2019-09-24 18:50:52 +02:00
< p class = "text-center" ng-show = "clone.location && clone.domain.provider === 'manual'" >
< b > Add an A record manually for {{ clone.location }} to this Cloudron's public IP< / b >
2019-09-13 17:18:37 +02:00
< br >
< / p >
2019-09-24 18:50:52 +02:00
< div class = "has-error text-center" ng-show = "clone.error.port" > {{ clone.error.port }}< / div >
< div ng-repeat = "(env, info) in clone.portBindingsInfo" >
2019-09-13 17:18:37 +02:00
< ng-form name = "portInfo_form" >
2019-09-24 18:50:52 +02:00
< 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.portBindingsEnabled[env]" >
2019-09-13 17:18:37 +02:00
{{ info.title }}
< sup >
< a popover-placement = "top-right" popover-trigger = "outsideClick" uib-popover = "{{info.description}} ({{ HOST_PORT_MIN }} - {{ HOST_PORT_MAX }})" > < i class = "fa fa-question-circle" > < / i > < / a >
< / sup >
< / label >
2019-09-24 18:50:52 +02:00
< input type = "number" class = "form-control" ng-model = "clone.portBindings[env]" ng-disabled = "!clone.portBindingsEnabled[env]" id = "inputPortInfo{{env}}" later-name = "itemName{{$index}}" min = "{{hostPortMin}}" max = "{{hostPortMax}}" required >
2019-09-13 17:18:37 +02:00
< / div >
< / ng-form >
< / div >
< / form >
< / fieldset >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
2019-09-24 18:50:52 +02:00
< 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 > Clone< / button >
2019-09-13 17:18:37 +02:00
< / div >
< / div >
< / div >
< / div >
2019-09-16 14:03:13 +02:00
< div class = "content content-large app-configure" >
2019-09-10 19:21:30 +02:00
2020-02-11 21:06:34 +01:00
< a href = "/#/apps" class = "back-to-view-link" > < i class = "fas fa-arrow-left" > < / i > Back to My Apps< / a >
2019-09-16 14:15:38 +02:00
2019-09-12 16:28:21 +02:00
< br / >
2019-09-13 11:29:19 +02:00
2019-09-16 14:03:13 +02:00
< div class = "row" >
2019-09-30 15:18:44 +02:00
< div class = "col-sm-2 text-center" >
2019-09-16 14:03:13 +02:00
< img ng-src = "{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon = "img/appicon_fallback.png" onerror = "imageErrorHandler(this)" class = "app-icon" / >
< / div >
2019-09-30 15:18:44 +02:00
< div class = "col-sm-8" >
2019-09-21 11:02:00 +02:00
< div class = "app-header-container" >
< h1 >
2019-09-27 19:43:03 +02:00
< a ng-href = "{{ app | applicationLink }}" target = "_blank" ng-class = "{ 'hand': (app | appIsInstalledAndHealthy) }" ng-click = "(app | appIsInstalledAndHealthy) && app.pendingPostInstallConfirmation && postInstallMessage.show(true)" > {{ app.label || app.fqdn }} < sup ng-show = "app | appIsInstalledAndHealthy" > < i class = "fas fa-external-link-alt" style = "font-size: 12px;" > < / i > < / sup > < / a >
2019-09-21 11:02:00 +02:00
< br / >
2019-09-24 21:08:42 +02:00
< span class = "text-small" > {{ app | installationStateLabel:user }}< / span >
2019-09-21 11:02:00 +02:00
< / h1 >
< div >
2019-09-26 20:48:04 +02:00
< div class = "dropdown" >
2019-09-27 19:43:03 +02:00
< button class = "btn btn-sm btn-info dropdown-toggle" type = "button" data-toggle = "dropdown" >
2019-09-26 20:48:04 +02:00
Documentation
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-right" >
2019-09-27 19:43:03 +02:00
< li ng-class = "{ 'disabled': !app.manifest.postInstallMessage }" > < a href = "" ng-click = "postInstallMessage.show(false)" > First Time Setup< / a > < / li >
2019-10-05 19:54:51 +02:00
< li ng-class = "{ 'disabled': !app.manifest.documentationUrl }" > < a ng-href = "{{ app.manifest.documentationUrl }}" target = "_blank" > Documentation< / a > < / li >
2019-09-26 20:48:04 +02:00
< li role = "separator" class = "divider" > < / li >
2019-11-12 17:09:18 -08:00
< li ng-class = "{ 'disabled': !app.manifest.website }" > < a ng-href = "{{ app.manifest.website }}" target = "_blank" > Project Website< / a > < / li >
2019-09-26 20:48:04 +02:00
< / ul >
< / div >
2019-09-21 11:02:00 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
< / div >
2019-09-24 21:27:49 +02:00
< div class = "row" >
2019-09-30 15:18:44 +02:00
< div class = "col-sm-8 col-sm-offset-2" style = "height: 10px;" >
2019-09-24 21:27:49 +02:00
< div class = "progress progress-striped active animateMeOpacity" ng-show = "app.taskId" style = "height: 10px;" uib-tooltip = "{{ app.taskProgressMessage }}" >
2019-09-24 21:08:42 +02:00
< div class = "progress-bar progress-bar-success" role = "progressbar" style = "width: {{ app.taskProgress }}%" > < / div >
< / div >
< / div >
< / div >
2019-10-25 12:10:08 +02:00
< div class = "row app-configure-links-container" >
2019-09-30 15:18:44 +02:00
< div class = "col-sm-2" >
2019-09-16 14:03:13 +02:00
< div class = "app-configure-links" >
2019-12-20 17:05:45 -08:00
< div ng-click = "setView('display')" ng-class = "{ 'active': view === 'display' }" > Display< / div >
< div ng-click = "setView('location')" ng-class = "{ 'active': view === 'location' }" > Location< / div >
< div ng-click = "setView('access')" ng-class = "{ 'active': view === 'access' }" > Access Control< / div >
< div ng-click = "setView('resources')" ng-class = "{ 'active': view === 'resources' }" > Resources< / div >
< div ng-click = "setView('security')" ng-class = "{ 'active': view === 'security' }" > Security< / div >
< div ng-click = "setView('email')" ng-class = "{ 'active': view === 'email' }" ng-show = "app.manifest.addons.sendmail || app.manifest.addons.recvmail" > Email< / div >
< div ng-click = "setView('updates')" ng-class = "{ 'active': view === 'updates' }" > Updates< / div >
< div ng-click = "setView('backups')" ng-class = "{ 'active': view === 'backups' }" > Backups< / div >
2019-12-20 15:41:50 -08:00
< div ng-click = "setView('console')" ng-class = "{ 'active': view === 'console' }" > Console< / div >
2019-12-16 13:30:51 -08:00
< div ng-click = "setView('repair')" ng-class = "{ 'active': view === 'repair' }" > Repair< / div >
2019-09-17 15:40:04 +02:00
< div ng-click = "setView('uninstall')" ng-class = "{ 'active': view === 'uninstall' }" > Uninstall< / div >
2019-09-10 19:21:30 +02:00
< / div >
< / div >
2019-10-25 12:10:08 +02:00
< div class = "col-sm-8 card-container" >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'display'" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "displayForm" ng-submit = "display.submit()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': !displayForm.label.$dirty && display.error.label }" >
< label class = "control-label" > Label< / 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" > Tags< / label >
< tag-input class = "form-control" placeholder = "Use comma to separate tags" taglist = "display.tags" name = "tags" uib-tooltip = "For grouping in the dashboard" > < / tag-input >
< / div >
< div class = "form-group" >
< div >
< label class = "control-label" > Icon< / label >
< / div >
2019-09-26 21:12:11 +02:00
< 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)" / >
2019-09-16 14:03:13 +02:00
< div class = "overlay" > < / div >
< / div >
< a href = "" style = "font-weight: normal;" ng-click = "display.resetCustomIcon()" > Reset Icon< / a >
< input type = "file" id = "iconFileInput" style = "display: none" accept = "image/png" / >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "(!display.icon.data && !displayForm.$dirty) || displayForm.$invalid || display.busy" / >
< / form >
< / fieldset >
< / 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 > Save< / button >
< / div >
< / div >
< / div >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'location'" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< 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) }" >
2019-09-17 16:16:48 +02:00
< label class = "control-label" > Location< / label >
< div class = "has-error" ng-show = "location.error.location" > {{ location.error.location }}< / div >
2019-09-16 14:03:13 +02:00
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "location.location" id = "locationLocationInput" name = "location" placeholder = "{{ 'Leave empty to use bare domain' }}" autofocus >
2019-09-10 19:21:30 +02:00
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
2019-09-16 14:03:13 +02:00
< span > {{ (!location.location ? '' : (location.domain.config.hyphenatedSubdomains ? '-' : '.')) + location.domain.domain }}< / span >
2019-09-10 19:21:30 +02:00
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-right" role = "menu" >
< li ng-repeat = "domain in domains" >
2019-09-16 14:03:13 +02:00
< a href = "" ng-click = "location.domain = domain" > {{ domain.domain }}< / a >
2019-09-10 19:21:30 +02:00
< / li >
< / ul >
< / div >
< / div >
< / div >
2019-09-16 14:03:13 +02:00
< p class = "text-center" ng-show = "location.location && location.domain.provider === 'manual'" >
< b > Add an A record manually for {{ location.location }} to this Cloudron's public IP< / b >
< br >
< / p >
2019-09-21 11:07:20 +02:00
<!-- 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" / >
2019-09-16 14:03:13 +02:00
< div class = "has-error text-center" ng-show = "location.error.port" > {{ location.error.port }}< / div >
< div ng-repeat = "(env, info) in location.portBindingsInfo" >
< ng-form name = "portInfo_form" >
< div class = "form-group" ng-class = "{ 'has-error': (!locationForm.itemName{{$index}}.$dirty && location.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }" >
< label class = "control-label" for = "locationPortInput{{env}}" > < input type = "checkbox" ng-model = "location.portBindingsEnabled[env]" >
{{ info.title }}
< sup >
< a popover-placement = "top-right" popover-trigger = "outsideClick" uib-popover = "{{info.description}} ({{ HOST_PORT_MIN }} - {{ HOST_PORT_MAX }})" > < i class = "fa fa-question-circle" > < / i > < / a >
< / sup >
< / label >
< input type = "number" class = "form-control" ng-model = "location.portBindings[env]" ng-disabled = "!location.portBindingsEnabled[env]" id = "locationPortInput{{env}}" later-name = "itemName{{$index}}" min = "{{HOST_PORT_MIN}}" max = "{{HOST_PORT_MAX}}" required >
< / div >
< / ng-form >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "form-group alternate-domains" >
< label class = "control-label" > Redirections < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#redirections" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
< div class = "has-error" ng-show = "location.error.alternateDomains" > {{ location.error.alternateDomains }}< / div >
< div class = "row" ng-repeat = "alternateDomain in location.alternateDomains" >
< div class = "col col-lg-11" >
< div class = "input-group" >
< input type = "text" class = "form-control" ng-model = "alternateDomain.subdomain" placeholder = "Leave empty to use bare domain" >
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
< span > {{ (!alternateDomain.subdomain ? '' : (alternateDomain.domain.config.hyphenatedSubdomains ? '-' : '.')) + alternateDomain.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 = "alternateDomain.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.delAlternateDomain($event, $index)" > < i class = "far fa-trash-alt" > < / i > < / button >
< / div >
< / div >
< div ng-show = "location.alternateDomains.length === 0" >
No alternate domains are configured. < a href = "" ng-click = "location.addAlternateDomain($event)" > Add a domain< / a >
< / div >
< div ng-show = "location.alternateDomains.length > 0" style = "margin-top: 5px;" >
< a href = "" ng-click = "location.addAlternateDomain($event)" > Add another domain< / a >
< / div >
< / div >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
2020-02-07 10:42:58 -08:00
< 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' }}" >
2019-12-20 17:05:45 -08:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "location.busy" > < / i > Save
< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'access'" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "accessForm" ng-submit = "access.submit()" autocomplete = "off" >
< div class = "form-group" >
< div ng-show = "access.ssoAuth" >
< label class = "control-label" > User management< / label >
< p class = "text-small" ng-show = "access.ftp" > This setting also controls SFTP access.< / p >
< / div >
< div ng-show = "!access.ssoAuth" >
< label class = "control-label" > Dashboard visibility< / label >
< p ng-show = "!access.app.manifest.addons.email" class = "text-small" >
This app has it's own user management.
< span ng-show = "access.ftp" > This setting also controls SFTP access.< / span >
< / p >
< p ng-show = "access.app.manifest.addons.email" class = "text-small" >
2019-12-18 14:29:42 -08:00
This app is pre-configured for use with < a ng-href = "{{ config.webServerOrigin }}/documentation/email/" target = "_blank" > Cloudron Email< / a > .
2019-09-16 14:03:13 +02:00
< / p >
< / div >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< div class = "radio" >
< label >
< input type = "radio" ng-model = "access.accessRestrictionOption" value = "any" >
< span ng-show = "access.ssoAuth" > Allow all users on this Cloudron< / span >
< span ng-show = "!access.ssoAuth" > Visible to all users on this Cloudron< / span >
< / label >
< / div >
< div class = "radio" >
< label >
< input type = "radio" ng-model = "access.accessRestrictionOption" value = "groups" >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< span ng-show = "access.ssoAuth" > Only allow the following users and groups< / span >
< span ng-show = "!access.ssoAuth" > Only visible to the following users and groups< / span >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< span class = "label label-danger" ng-show = "access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()" > Select at least one user or group< / span >
< / label >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div >
< div style = "margin-left: 20px;" >
< div class = "col-md-5" >
2019-11-05 19:45:59 +01:00
Users: < multiselect class = "input-sm stretch" ng-model = "access.accessRestriction.users" ng-disabled = "access.accessRestrictionOption !== 'groups'" options = "user.display for user in users" data-multiple = "true" filter-after-rows = "5" scroll-after-rows = "10" > < / multiselect >
2019-09-16 14:03:13 +02:00
< / div >
< div class = "col-md-5" >
2019-11-05 19:45:59 +01:00
Groups: < multiselect 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 >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "accessForm.$invalid || access.busy" / >
< / div >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
2019-09-17 14:49:26 +02:00
< div class = "col-md-12 text-right" >
2019-09-16 14:03:13 +02:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "access.submit()" ng-disabled = "access.$invalid || access.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "access.busy" > < / i > Save< / button >
< / div >
< / div >
2019-09-17 15:32:43 +02:00
< div class = "row" ng-show = "app.manifest.addons.localstorage.ftp" >
2019-09-19 09:35:25 -07:00
< hr / >
2019-09-17 15:32:43 +02:00
< div class = "col-md-12" >
< label > SFTP< / label > < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#ftp-access" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < br / >
Server: {{ config.adminFqdn }}< br / >
Port: 222< br / >
Username: {{ user.username }}@{{ app.fqdn }}< br / >
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'resources'" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
2019-09-18 17:12:10 +02:00
< form role = "form" name = "resourcesForm" ng-submit = "resources.submitMemoryLimit()" autocomplete = "off" >
2019-09-16 14:03:13 +02:00
< div class = "form-group" >
2019-12-18 14:29:42 -08:00
< label class = "control-label" for = "memoryLimit" > Memory Limit < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#memory-limit" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > : < b > {{ resources.memoryLimit ? resources.memoryLimit / 1024 / 1024 + 'MB' : 'Default (256 MB)' }}< / b > < / label >
2020-01-23 08:10:33 +01:00
< p > Cloudron allocates 50% of this value as RAM and 50% as swap.< / p >
2019-09-16 14:03:13 +02:00
< div style = "padding: 0 10px;" >
< slider id = "memoryLimit" ng-model = "resources.memoryLimit" step = "134217728" tooltip = "hide" ticks = "resources.memoryTicks" ticks-snap-bounds = "67108864" > < / slider >
< / div >
< / div >
2019-09-10 19:21:30 +02:00
2019-09-18 17:12:10 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "resources.memoryLimit === resources.currentMemoryLimit || resourcesForm.$invalid || resources.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
2020-02-07 10:42:58 -08:00
< 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' }}" >
2019-12-20 17:05:45 -08:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "resources.busy" > < / i > Resize
< / button >
2019-09-18 17:12:10 +02:00
< / div >
< / div >
< hr / >
2020-01-28 22:05:06 -08:00
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "resourcesForm" ng-submit = "resources.submitCpuShares()" autocomplete = "off" >
< div class = "form-group" >
< label class = "control-label" for = "cpuShares" > CPU Shares < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#cpu-shares" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > : < b > {{ (resources.cpuShares * 100 / 1024 | number:0) + ' %' }}< / b > < / label >
< p > Percent of CPU time when system is under heavy load.< / p >
< div style = "padding: 0 10px;" >
2020-02-01 13:47:53 +01:00
< slider id = "cpuShares" ng-model = "resources.cpuShares" ticks = "[32, 512, 1024]" ticks-snap-bounds = "32" min = "32" max = "1024" tooltip = "hide" > < / slider >
2020-01-28 22:05:06 -08:00
< / div >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "resources.cpuShares === resources.currentCpuShares || resourcesForm.$invalid || resources.busyCpuShares" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
2020-02-07 10:42:58 -08:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "resources.submitCpuShares()" ng-disabled = "resources.cpuShares === resources.currentCpuShares || resourcesForm.$invalid || resources.busyCpuShares || app.error || app.taskId" tooltip-enable = "app.error || app.taskId" uib-tooltip = "{{ app.error ? 'App is in error state' : 'App is busy' }}" >
2020-01-28 22:05:06 -08:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "resources.busyCpuShares" > < / i > Set
< / button >
< / div >
< / div >
< hr / >
2019-09-18 17:12:10 +02:00
< div class = "row" >
< div class = "col-md-12" >
2020-01-28 22:33:35 -08:00
< label class = "control-label" for = "resourcesEnableDataDir" > Storage < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/storage/#app-data-directory" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
2019-09-20 01:22:10 +02:00
< p >
By default, this app's data is located at < code > /home/yellowtent/appsdata/{{ app.id }}< / code > . If the server is running out of disk space,
you can mount an external disk and move this app's data there.
< / p >
2019-09-18 17:12:10 +02:00
< fieldset >
< form role = "form" name = "resourcesDataDirForm" ng-submit = "resources.submitDataDir()" autocomplete = "off" >
2019-09-20 01:22:10 +02:00
< div class = "form-group" ng-class = "{ 'has-error': resourcesDataDirForm.$dirty && resources.error.dataDir }" >
< div ng-show = "resources.error.dataDir" > {{ resources.error.dataDir }}< / div >
< input type = "text" class = "form-control" name = "dataDir" placeholder = "Leave empty to use platform default" ng-model = "resources.dataDir" >
2019-09-16 14:03:13 +02:00
< / div >
2019-09-10 19:21:30 +02:00
2019-09-18 17:12:10 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "!resourcesDataDirForm.$dirty || resourcesDataDirForm.$invalid || resources.busyDataDir" / >
2019-09-16 14:03:13 +02:00
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
2020-02-07 10:42:58 -08:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "resources.submitDataDir()" ng-disabled = "!resourcesDataDirForm.$dirty || resourcesDataDirForm.$invalid || resources.busyDataDir || app.error || app.taskId" tooltip-enable = "app.error || app.taskId" uib-tooltip = "{{ app.error ? 'App is in error state' : 'App is busy' }}" >
2019-12-20 17:05:45 -08:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "resources.busyDataDir" > < / i > Move Data
< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'email'" >
< div class = "row" >
< div class = "col-md-12" >
2019-11-14 22:28:23 -08:00
< label class = "control-label" for = "emailMailboxNameEnabled" > Mail FROM Address< / label >
2019-09-19 18:00:18 -07:00
< p > This sets the address from which this app sends email. This app is already configured to send mail using {{app.domain}}'s < a ng-href = "/#/email/{{ app.domain }}" > Outbound Email< / a > settings.< / p >
2019-09-16 14:03:13 +02:00
< fieldset >
< form role = "form" name = "emailForm" ng-submit = "email.submit()" autocomplete = "off" >
<!-- recvmail currently only works with cloudron email -->
2019-09-19 18:00:18 -07:00
< div class = "form-group" ng-class = "{ 'has-error': emailForm.$dirty && email.error.mailboxName }" >
< div ng-show = "email.error.mailboxName" > {{ email.error.mailboxName }}< / div >
2019-09-16 14:03:13 +02:00
2019-09-17 15:09:39 +02:00
< div class = "input-group form-inline" ng-class = "{ 'has-error': !emailForm.mailboxName.$dirty && email.error.mailboxName }" >
2019-09-19 18:00:18 -07:00
< input type = "text" class = "form-control" name = "mailboxName" placeholder = "Leave empty to use platform default" ng-model = "email.mailboxName" >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< div class = "input-group-btn" >
2019-11-14 22:28:23 -08:00
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
2019-11-22 14:38:27 -08:00
< span > {{ '@' + email.mailboxDomain.domain }}< / span >
2019-11-14 22:28:23 -08:00
< span class = "caret" > < / span >
2019-09-16 14:03:13 +02:00
< / button >
2019-11-14 22:28:23 -08:00
< 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 >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-17 15:09:39 +02:00
< br / >
2019-09-10 19:21:30 +02:00
< / div >
2020-02-27 16:04:11 +01:00
< input class = "ng-hide" type = "submit" ng-disabled = "(email.currentMailboxDomainName === email.mailboxDomain.domain && email.currentMailboxName === email.mailboxName) || email.busy || app.error || app.taskId" / >
2019-09-16 14:03:13 +02:00
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
2019-09-17 15:09:39 +02:00
< div class = "col-md-12 text-right" >
2020-02-27 16:04:11 +01:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "email.submit()" ng-disabled = "(email.currentMailboxDomainName === email.mailboxDomain.domain && email.currentMailboxName === email.mailboxName) || email.busy || app.error || app.taskId" tooltip-enable = "app.error || app.taskId" uib-tooltip = "{{ app.error ? 'App is in error state' : 'App is busy' }}" >
2019-12-20 17:05:45 -08:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "email.busy" > < / i > Save
< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'security'" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "securityForm" ng-submit = "security.submit()" autocomplete = "off" >
< div class = "form-group" >
2019-12-18 14:29:42 -08:00
< label class = "control-label" style = "width: 100%" > Robots.txt < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/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" > Disable indexing< / a > < / label >
2019-10-14 15:20:48 -07:00
< textarea ng-model = "security.robotsTxt" placeholder = "Leave empty to allow all bots to index this app" class = "form-control" rows = "4" > < / textarea >
2019-09-16 14:03:13 +02:00
< / div >
2019-09-10 19:21:30 +02:00
2019-10-14 15:20:48 -07:00
< div class = "form-group" >
2019-10-14 16:50:15 -07:00
< label class = "control-label" style = "width: 100%" > Content Security Policy< sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#custom-csp" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
< p > Setting this option will override any CSP headers sent by the app itself< / p >
< textarea ng-model = "security.csp" placeholder = "default-src 'self'; frame-ancestors 'none';" class = "form-control" rows = "2" > < / textarea >
2019-10-14 15:20:48 -07:00
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "securityForm.$invalid || security.busy" / >
2019-09-16 14:03:13 +02:00
< / form >
< / fieldset >
< / div >
< / div >
2019-10-14 15:20:48 -07:00
< br / >
2019-09-16 14:03:13 +02:00
< div class = "row" >
< div class = "col-md-12 text-right" >
2019-12-20 17:05:45 -08:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "security.submit()" ng-disabled = "security.$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 > Save
< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'updates'" >
2019-09-17 15:32:43 +02:00
< div class = "row" >
< div class = "col-md-12" >
2019-09-19 12:00:35 -07:00
< label class = "control-label" > Check For Updates< / label >
2019-11-16 10:31:22 -08:00
< p ng-show = "app.appStoreId" > This app is running {{ app.manifest.title }} {{ app.upstreamVersion }} (Package < a ng-href = "/#/appstore/{{app.manifest.id}}?version={{app.manifest.version}}" > v{{ app.manifest.version }}< / a > ) and was last updated < code > {{ app.updateTime | prettyDate }}< / code > .< / p >
< p ng-show = "!app.appStoreId" > This app is running < code > {{ app.manifest.dockerImage }}< / code > (Package v{{ app.manifest.version }}) and was last updated < code > {{ app.updateTime | prettyDate }}< / code > .< / p >
2019-09-17 16:16:48 +02:00
< br / >
2019-11-16 10:31:22 -08:00
< button class = "btn btn-primary pull-right" uib-tooltip = "{{ app.appStoreId ? '' : 'Not available for custom apps' }}" ng-disabled = "!app.appStoreId" ng-click = "updates.check()" ng-hide = "config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version" ng-disabled = "updates.busyCheck" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busyCheck" > < / i > Check for Updates< / button >
2019-12-20 17:05:45 -08:00
< button class = "btn btn-success pull-right" ng-click = "updates.askUpdate()" ng-show = "app.installationState !== 'pending_update' && config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version" ng-disabled = "app.taskId || app.error" tooltip-enable = "app.error" uib-tooltip = "App is in error state" >
Update Available
< / button >
2019-09-17 15:32:43 +02:00
< / div >
< / div >
2019-09-19 11:58:06 -07:00
< hr / >
2019-09-16 14:03:13 +02:00
< div class = "row" >
2019-09-17 16:16:48 +02:00
< div class = "col-md-12" >
2019-09-19 11:58:06 -07:00
< label class = "control-label" > Automatic Updates< / label >
< p > Cloudron periodically polls the App Store to check for updates. If you disable automatic updates, be sure to manually check for updates.< / p >
< p > Automatic Updates is currently < b > {{ updates.enableAutomaticUpdate ? 'enabled' : 'disabled' }}< / b > .< / p >
2019-11-16 10:31:22 -08:00
< button class = "btn btn-primary pull-right" uib-tooltip = "{{ app.appStoreId ? '' : 'Not available for custom apps' }}" ng-class = "{ 'btn-danger': updates.enableAutomaticUpdate }" ng-click = "updates.toggleAutomaticUpdates()" ng-disabled = "updates.busy || !app.appStoreId" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busy" > < / i > {{ updates.enableAutomaticUpdate ? 'Disable' : 'Enable' }} Automatic Updates< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-12-17 10:24:42 -08:00
< div class = "card" ng-show = "view === 'backups'" >
2019-09-19 12:24:22 -07:00
< div class = "row" >
< div class = "col-md-12" >
< label class = "control-label" > Backups< / label >
2019-12-17 10:15:38 -08:00
< div >
< span > Backups are complete snapshots of the app. You can use app backups to restore or clone this app.< / span >
< / div >
< br / >
2019-09-19 12:24:22 -07:00
2019-09-16 14:03:13 +02:00
<!-- backup id copy helper -->
< input type = "text" class = "offscreen" aria-hidden = "true" id = "backupIdHelper" value = "" >
2020-02-06 16:11:21 -08:00
< table ng-hide = "!backups.backups.length" class = "table table-hover" style = "margin: 0;" >
2019-09-16 14:03:13 +02:00
< thead >
< tr >
< th width = "25px" > < / th >
2019-12-17 10:15:38 -08:00
< th > Backup< / th >
2019-09-16 14:03:13 +02:00
< th class = "text-right" width = "180px" > Actions< / th >
< / tr >
< / thead >
< tbody >
< tr ng-repeat = "backup in backups.backups" >
< td > < div ng-click = "backups.copyBackupId(backup)" class = "hand" uib-tooltip = "{{ backups.copyBackupIdDone ? 'Copied to clipboard' : 'Click to copy backup id' }}" tooltip-placement = "right" > < i class = "fa fa-copy" > < / i > < / div > < / td >
2019-12-17 10:15:38 -08:00
< td > < div uib-tooltip = "{{ backup.creationTime | prettyLongDate }}" > v{{ backup.version }} - {{ backup.creationTime | prettyDate }}< / div > < / td >
2019-09-16 14:03:13 +02:00
< td class = "text-right no-wrap" style = "vertical-align: bottom" >
2019-10-24 10:01:23 -07:00
< button class = "btn btn-xs btn-default" ng-click = "clone.show(backup)" uib-tooltip = "Clone from this Backup" > < i class = "far fa-clone" > < / i > < / button >
< button class = "btn btn-xs btn-danger" ng-click = "restore.show(backup)" ng-disabled = "app.taskId" uib-tooltip = "Restore to this Backup" > < i class = "fas fa-history" > < / i > < / button >
2019-09-16 14:03:13 +02:00
< / td >
< / tr >
< / tbody >
< / table >
2020-02-06 16:11:21 -08:00
< br / >
2020-02-07 10:42:58 -08:00
< button type = "button" class = "btn btn-primary pull-right" ng-click = "backups.createBackup()" ng-disabled = "app.taskId || backups.busyCreate || app.error || app.taskId" tooltip-enable = "app.error || app.taskId" uib-tooltip = "{{ app.error ? 'App is in error state' : 'App is busy' }}" >
2020-02-06 16:11:21 -08:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "app.installationState === 'pending_backup' || backups.busyCreate" > < / i > Create Backup
< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-17 16:16:48 +02:00
< hr / >
2020-02-06 16:08:22 -08:00
< div class = "row" >
< div class = "col-md-12" >
< label class = "control-label" > Import From External Backup< / label >
< p > Use this to migrate an app from another Cloudron. The other app must have the same package version and access
control stratey as this one.
< / p >
2020-02-07 10:42:58 -08:00
< button class = "btn btn-primary pull-right" class = "btn-primary" ng-click = "importBackup.show()" ng-disabled = "importBackup.busy || app.taskId" tooltip-enable = "app.taskId" uib-tooltip = "App is busy" >
2020-02-07 10:22:52 -08:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "backups.busy" > < / i > Import Backup
< / button >
2020-02-06 16:08:22 -08:00
< / div >
< / div >
< hr / >
2019-09-16 14:03:13 +02:00
< div class = "row" >
2019-09-17 16:16:48 +02:00
< div class = "col-md-12" >
2019-09-19 12:24:22 -07:00
< label class = "control-label" > Automatic Backups< / label >
2019-12-17 10:15:38 -08:00
< p > Cloudron periodically creates a backup based on the < a href = "/#/backups" > backup< / a > settings. Automatic Backups is currently < b > {{ backups.enableBackup ? 'enabled' : 'disabled' }}< / b > .< / p >
2019-09-19 12:24:22 -07:00
2019-09-26 10:07:15 -07:00
< button class = "btn btn-primary pull-right" ng-class = "{ 'btn-danger': backups.enableBackup }" ng-click = "backups.toggleAutomaticBackups()" ng-disabled = "backups.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "backups.busy" > < / i > {{ backups.enableBackup ? 'Disable' : 'Enable' }} Automatic Backups< / button >
2019-09-17 16:16:48 +02:00
< / div >
< / div >
2019-09-13 17:07:45 +02:00
< / div >
2019-09-10 19:21:30 +02:00
2019-12-16 13:30:51 -08:00
< div class = "card" ng-show = "view === 'console'" >
2019-09-19 12:50:06 -07:00
< div class = "row" >
< div class = "col-md-12" >
< label class = "control-label" > Console Access< / label >
2019-12-16 13:30:51 -08:00
< p > This will open a console connection to the app. The terminal is sandboxed and only provides access to this app container's filesystem.< / p >
2019-12-24 10:49:11 -08:00
< a class = "btn btn-primary pull-right" ng-href = "{{ (app.installationState === 'installed' && (app.health === 'healthy' || app.debugMode)) ? '/terminal.html?id=' + app.id : '' }}" ng-disabled = "app.installationState !== 'installed' || (app.health !== 'healthy' && !app.debugMode)" tooltip-class = "long" tooltip-enable = "app.installationState !== 'installed' || app.health !== 'healthy'" uib-tooltip = "App is not running. If app is constantly restarting, Repair it first." target = "_blank" > Terminal< / a >
2019-09-16 14:03:13 +02:00
< a class = "btn btn-primary pull-right" ng-href = "{{ '/logs.html?appId=' + app.id }}" target = "_blank" > Logs< / a >
< / div >
< / div >
2019-09-19 12:50:06 -07:00
< hr / >
< div class = "row" >
< div class = "col-md-12" >
2019-09-23 17:27:40 -07:00
< label class = "control-label" > Start / Stop< / label >
2019-12-16 13:30:51 -08:00
< p > Apps can be stopped to conserve server resources. They will continue to be backed up but won't be updated.< / p >
2020-02-07 10:42:58 -08:00
< button class = "btn btn-primary pull-right" ng-class = "{ 'btn-danger': !console.startButton }" ng-click = "console.toggleRunState()" ng-disabled = "app.taskId || app.error || console.busyRunState" tooltip-enable = "app.error || app.taskId" uib-tooltip = "{{ app.error ? 'App is in error state' : 'App is busy' }}" >
2019-12-16 16:27:24 -08:00
< i ng-show = "app.installationState === 'pending_start' || app.installationState === 'pending_stop'" class = "fa fa-circle-notch fa-spin" > < / i >
{{ console.startButton ? 'Start App' : 'Stop App' }}
2019-09-23 15:50:41 -07:00
< / button >
2019-09-23 17:27:40 -07:00
< / div >
< / div >
2019-12-16 13:30:51 -08:00
< / div >
< div class = "card" ng-show = "view === 'repair'" >
2019-09-23 17:27:40 -07:00
< div class = "row" >
< div class = "col-md-12" >
2019-12-16 18:18:22 -08:00
< label class = "control-label" > Crash Recovery< / label >
2019-12-20 11:18:48 -08:00
< p >
If the app is not responding, try restarting the app. If the app is constantly restarting because of a broken plugin or misconfiguration,
place the app in recovery mode in order to access the console.
2019-12-18 14:29:42 -08:00
Use the following < a target = "_blank" ng-href = "{{ config.webServerOrigin }}/documentation/troubleshooting/#unresponsive-app" > instructions< / a >
2019-12-16 18:18:22 -08:00
to get the app running again.
< / p >
2020-02-07 10:42:58 -08:00
< 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' }}" > Enable Recovery Mode< / 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' }}" > Disable Recovery Mode< / 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' }}" >
2019-12-20 11:18:48 -08:00
< i ng-show = "repair.restartBusy" class = "fa fa-circle-notch fa-spin" > < / i >
Restart App
< / button >
2019-12-16 18:18:22 -08:00
< / div >
< / div >
< hr / >
< div class = "row" >
< div class = "col-md-12" >
2019-12-17 10:03:04 -08:00
< label class = "control-label" > Task Error< / label >
2019-12-20 11:18:48 -08:00
< p > If a configuration, update, restore or backup action resulted in an error, you can retry the task.< / p >
2019-11-23 17:49:13 -08:00
< 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 >
2020-02-07 10:42:58 -08:00
< button class = "btn btn-primary pull-right" ng-click = "repair.confirm()" ng-disabled = "app.taskId || !app.error" tooltip-enable = "app.taskId" uib-tooltip = "App is busy" > Retry {{ app.error.installationState | taskName }}< / button >
2019-09-23 17:27:40 -07:00
2019-12-17 09:43:40 -08:00
<!-- this is hidden for now, use the CLI instead -->
< button class = "btn btn-danger pull-right" ng-click = "repair.stopAppTask(app.taskId)" ng-show = "false && app.taskId" > Cancel Current Task< / button >
2019-09-19 12:50:06 -07:00
< / div >
< / div >
2019-09-13 10:34:12 +02:00
< / div >
2019-09-16 14:03:13 +02:00
2019-09-17 15:40:04 +02:00
< div class = "card" ng-show = "view === 'uninstall'" >
2019-12-16 12:54:24 -08:00
< div class = "row" >
< div class = "col-md-12" >
< label class = "control-label" > Uninstall< / label >
< p > This will uninstall the app immediately and remove all it's data. The site will be inaccessible.
< / p >
< p > App backups are not removed and will be cleaned up based on the backup policy. You can resurrect this app from an existing
2019-12-18 14:29:42 -08:00
app backup using the following < a target = "_blank" ng-href = "{{ config.webServerOrigin }}/documentation/backups/##import-app-backup" > instructions< / a > .< / p >
2019-12-16 12:54:24 -08:00
< button class = "btn btn-danger pull-right" ng-click = "uninstall.ask()" > Uninstall< / button >
2019-09-17 15:40:04 +02:00
< / div >
< / div >
2019-12-16 12:54:24 -08:00
< / div >
2019-09-17 15:40:04 +02:00
2019-09-13 10:34:12 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >