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" >
2020-08-28 17:08:18 -07:00
< p ng-show = "app.manifest.addons.email" > This app is set up to allow all users with a mailbox on this Cloudron. Login with the email and Cloudron password to access the mailbox.< / p >
2020-10-29 21:37:49 -07:00
< p ng-show = "app.sso && !app.manifest.addons.email" > This app is set up to authenticate with the Cloudron User Directory. Cloudron users can login and use {{ appPostInstallConfirm.app.manifest.title }}.< / p >
2020-08-28 17:08:18 -07:00
2020-08-08 18:07:06 -07:00
< div ng-bind-html = "app.manifest.postInstallMessage | markdown2html" > < / div >
2019-09-19 22:49:21 +02:00
< 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" >
2020-08-15 18:35:51 -07:00
< span > {{ (!repair.location ? '' : '.') + repair.domain.domain }}< / span >
2019-09-26 22:23:08 -07:00
< 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" >
2020-08-15 18:35:51 -07:00
{{ alternateDomain.subdomain + (!alternateDomain.subdomain ? '' : '.') + alternateDomain.domain.domain }}
2019-09-27 15:34:54 -07:00
< / 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" >
2020-05-13 22:37:11 -07:00
< option ng-repeat = "backup in repair.backups" value = "{{ backup.id }}" > {{ backup.creationTime | prettyDate }} - v{{ backup.packageVersion }}< / option >
2019-09-21 22:45:26 +02:00
< / 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" >
2020-09-09 10:08:13 -07:00
< label class = "control-label" for = "storageProvider" > Storage provider < 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 >
2020-04-09 13:15:26 +02:00
< 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()" ng-disabled = "importBackup.busy" > < / select >
2020-02-06 16:08:22 -08:00
< / 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 -->
2020-07-13 15:36:44 -07:00
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.endpoint }" ng-show = "importBackup.provider === 'minio' || importBackup.provider === 'backblaze-b2' || importBackup.provider === 's3-v4-compat'" >
2020-02-06 16:08:22 -08:00
< label class = "control-label" for = "inputimportBackupEndpoint" > Endpoint< / label >
2020-07-13 15:36:44 -07:00
< 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 === 'backblaze-b2' || importBackup.provider === 's3-v4-compat'" >
2020-02-06 16:08:22 -08:00
< / 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 >
2020-07-05 10:58:20 -07:00
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.region }" ng-show = "importBackup.provider === 's3-v4-compat'" >
< label class = "control-label" for = "inputImportBackupS3V4CompatRegion" > Region< / 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 >
2020-02-06 16:08:22 -08:00
< 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 >
2020-03-05 11:24:42 -08:00
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.region }" ng-show = "importBackup.provider === 'linode-objectstorage'" >
< label class = "control-label" for = "inputimportBackupLinodeRegion" > Region< / 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 >
2020-04-29 12:54:19 -07:00
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.region }" ng-show = "importBackup.provider === 'ovh-objectstorage'" >
< label class = "control-label" for = "inputimportBackupOvhRegion" > Region< / 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 >
2020-02-06 16:08:22 -08:00
< 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" >
2020-09-09 10:08:13 -07:00
< label class = "control-label" for = "storageFormat" > Storage Format < 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 >
2020-05-12 10:54:15 -07:00
< 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 >
2020-02-06 16:08:22 -08:00
< / div >
2020-05-12 10:54:15 -07:00
< div class = "form-group" ng-class = "{ 'has-error': importBackup.error.password }" >
2020-09-09 10:08:13 -07:00
< label class = "control-label" for = "inputImportBackupPassword" > Encryption password (optional) < sup > < a ng-href = "https://docs.cloudron.io/backups/#encryption" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
2020-05-12 10:54:15 -07:00
< input type = "text" class = "form-control" ng-model = "importBackup.password" id = "inputImportBackupPassword" ng-disabled = "importBackup.busy" placeholder = "Passphrase used to encrypt the backups" >
2020-02-06 16:08:22 -08:00
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "importBackupForm.$invalid" / >
< / fieldset >
< / form >
< / div >
< div class = "modal-footer " >
2020-05-16 11:19:47 -07:00
< input type = "file" id = "backupConfigFileInput" style = "display:none" / >
< button type = "button" class = "btn btn-default pull-left" onclick = "getElementById('backupConfigFileInput').click();" > Upload Backup Config< / button >
2020-02-06 16:08:22 -08:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
2020-04-09 13:13:14 +02:00
< 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 > Import< / button >
2020-02-06 16:08:22 -08:00
< / 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" >
2020-08-28 17:08:18 -07:00
< p ng-show = "app.manifest.addons.email" > This app is set up to allow all users with a mailbox on this Cloudron. Login with the email and Cloudron password to access the mailbox.< / p >
2020-10-29 21:37:49 -07:00
< p ng-show = "app.sso && !app.manifest.addons.email" > This app is set up to authenticate with the Cloudron User Directory. Cloudron users can login and use {{ appPostInstallConfirm.app.manifest.title }}.< / p >
2020-08-28 17:08:18 -07:00
2020-08-08 18:07:06 -07:00
< div ng-bind-html = "app.manifest.postInstallMessage | markdown2html" > < / div >
2019-09-27 19:43:03 +02:00
< / 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" >
2020-06-05 16:25:52 -07:00
< p class = "text-danger" ng-show = "config.update.apps[app.id].unstable" >
This update is a pre-release and not considered stable yet. Please update at your own risk.
< / p >
2020-05-11 23:14:19 +02:00
< p > Changes for new version < b > {{ config.update.apps[app.id].manifest.version}}< / b > :< / p >
2019-09-17 17:14:40 +02:00
< div ng-bind-html = "config.update.apps[app.id].manifest.changelog | markdown2html" > < / div >
2020-05-11 23:14:19 +02:00
< p class = "text-danger text-bold" ng-show = "!config.update.apps[app.id].manifest.dockerImage" >
< br / >
Your Cloudron subscription has expired. Please setup a subscription to update the app.
< / p >
2019-09-17 17:14:40 +02:00
< / div >
< div class = "modal-footer" >
2020-05-11 23:14:19 +02:00
< label class = "checkbox-inline pull-left" ng-show = "config.update.apps[app.id].manifest.dockerImage" >
< input type = "checkbox" ng-model = "updates.skipBackup" > < b > Skip backup< / b >
< / label >
2019-09-26 20:10:25 -07:00
2020-05-11 23:14:19 +02:00
< button type = "button" class = "btn btn-primary pull-left" ng-show = "!config.update.apps[app.id].manifest.dockerImage && user.isAtLeastOwner" ng-click = "openSubscriptionSetup()" > Setup Subscription< / button >
2019-09-17 17:14:40 +02:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
2020-06-05 16:25:52 -07:00
< button type = "button" ng-class = "config.update.apps[app.id].unstable ? 'btn btn-danger' : 'btn btn-success'" ng-click = "updates.confirmUpdate()" ng-disabled = "!config.update.apps[app.id].manifest.dockerImage || updates.busyUpdate" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busyUpdate" > < / i > Update< / button >
2019-09-17 17:14:40 +02:00
< / 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" >
2020-05-13 22:37:11 -07:00
< p > Using backup from < b > {{ clone.backup.creationTime | prettyDate }}< / b > and version < b > v{{ clone.backup.packageVersion }}< / b > < / p >
2020-03-19 19:26:19 -07:00
< form role = "form" ng-submit = "clone.submit()" autocomplete = "off" >
< fieldset >
2019-09-13 17:18:37 +02:00
< 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" >
2020-08-15 18:35:51 -07:00
< span > {{ (!clone.location ? '' : '.') + clone.domain.domain }}< / span >
2019-09-13 17:18:37 +02:00
< 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 >
2020-03-13 11:47:55 -07:00
< p class = "small text-center text-warning" ng-show = "clone.domain.provider === 'linode'" >
2020-09-09 10:08:13 -07:00
< b > Linode DNS average < a target = "_blank" ng-href = "https://docs.cloudron.io/domains/#linode-dns" > propagation time< / a > is 30 minutes.
2020-03-13 11:47:55 -07:00
Cloning the app will take a while.< / b >
< br >
< / p >
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 >
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2019-09-13 17:18:37 +02:00
< / 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
2020-04-12 13:20:01 +02:00
< div class = "row" ng-show = "view" >
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 / >
2020-06-08 17:53:29 -07:00
< span class = "text-small" > {{ app | installationStateLabel }} {{ app.message ? ' - ' + app.message : '' }}< / span >
2020-05-12 21:34:44 -07:00
< span class = "text-small" ng-show = "app.error" > : {{ app.error.reason + ' - ' + app.error.message }}< / span >
2019-09-21 11:02:00 +02:00
< / h1 >
< div >
2020-11-08 09:54:21 +01:00
< a class = "btn btn-sm btn-default" ng-href = "{{ '/logs.html?appId=' + app.id }}" target = "_blank" uib-tooltip = "Logs" tooltip-append-to-body = "true" tooltip-placement = "bottom" > < i class = "fas fa-align-left" > < / i > < / a >
< a class = "btn btn-sm btn-default" ng-href = "{{ '/terminal.html?id=' + app.id }}" target = "_blank" uib-tooltip = "Terminal" tooltip-append-to-body = "true" tooltip-placement = "bottom" > < i class = "fa fa-terminal" > < / i > < / a >
< a class = "btn btn-sm btn-default" ng-href = "{{ '/filemanager.html?appId=' + app.id }}" target = "_blank" uib-tooltip = "File Manager" tooltip-append-to-body = "true" tooltip-placement = "bottom" > < i class = "fas fa-folder" > < / i > < / a >
< div class = "dropdown" style = "display: inline-block" >
< button class = "btn btn-sm btn-default dropdown-toggle" type = "button" data-toggle = "dropdown" uib-tooltip = "Documentation" tooltip-append-to-body = "true" tooltip-placement = "bottom" >
< i class = "fas fa-book" > < / i >
2019-09-26 20:48:04 +02:00
< 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 >
2020-07-20 23:41:00 +02:00
< li ng-class = "{ 'disabled': (!app.manifest.configurePath || !(app | applicationLink)) }" > < a ng-href = "{{ (app.manifest.configurePath && (app | applicationLink)) ? ((app | applicationLink) + app.manifest.configurePath) : ''}}" target = "_blank" > Admin Page< / 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 >
2020-03-26 00:22:46 +01:00
< div class = "row" ng-show = "app.taskId" >
2020-03-26 00:16:23 +01:00
< div class = "col-sm-8 col-sm-offset-2" style = "height: 10px; display: flex; align-items: center;" >
2020-03-26 00:22:46 +01:00
< div class = "progress progress-striped active animateMeOpacity" style = "height: 10px; flex-grow: 1;" >
2020-06-08 17:53:29 -07:00
< div class = "progress-bar progress-bar-success" role = "progressbar" style = "width: {{ app.progress || 5 }}%" > < / div >
2019-09-24 21:08:42 +02:00
< / div >
2020-03-26 00:19:06 +01:00
< div ng-show = "app.taskMinutesActive >= 5" class = "text-danger hand" style = "margin: 0 4px;" ng-click = "stopAppTask(app.taskId)" uib-tooltip = "Cancel Task" > < i class = "fas fa-times" > < / i > < / div >
2019-09-24 21:08:42 +02:00
< / div >
< / div >
2020-04-08 12:56:57 +02:00
< 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" >
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 >
2020-10-28 22:11:05 -07:00
< div ng-click = "setView('storage')" ng-class = "{ 'active': view === 'storage' }" > Storage< / div >
2020-05-13 00:42:27 +02:00
< div ng-click = "setView('graphs')" ng-class = "{ 'active': view === 'graphs' }" > Graphs< / div >
2019-12-20 17:05:45 -08:00
< 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" >
2020-03-19 19:26:19 -07:00
< form role = "form" name = "displayForm" ng-submit = "display.submit()" autocomplete = "off" >
< fieldset >
2019-09-16 14:03:13 +02:00
< 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 >
2020-03-28 16:46:06 -07:00
< tag-input class = "form-control" placeholder = "Use space to separate tags" taglist = "display.tags" name = "tags" uib-tooltip = "For grouping in the dashboard" > < / tag-input >
2019-09-16 14:03:13 +02:00
< / 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" / >
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2019-09-16 14:03:13 +02:00
< / 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" >
2020-03-19 19:26:19 -07:00
< form role = "form" name = "locationForm" ng-submit = "location.submit()" autocomplete = "off" >
< fieldset >
2019-09-16 14:03:13 +02:00
< 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" >
2020-08-15 18:35:51 -07:00
< span > {{ (!location.location ? '' : '.') + 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
2020-03-13 11:47:55 -07:00
< p class = "small text-center text-warning" ng-show = "location.domain.provider === 'linode'" >
2020-09-09 10:08:13 -07:00
< b > Linode DNS average < a target = "_blank" ng-href = "https://docs.cloudron.io/domains/#linode-dns" > propagation time< / a > is 30 minutes.
2020-03-13 11:47:55 -07:00
Changing the location will take a while.< / b >
< br >
< / p >
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" >
2020-09-09 10:08:13 -07:00
< label class = "control-label" > Redirections < sup > < a ng-href = "https://docs.cloudron.io/apps/#redirections" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
2019-09-16 14:03:13 +02:00
< 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" >
2020-08-15 18:35:51 -07:00
< span > {{ (!alternateDomain.subdomain ? '' : '.') + alternateDomain.domain.domain }}< / span >
2019-09-16 14:03:13 +02:00
< 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" >
2020-10-08 11:15:26 -07:00
No redirect domains are configured. < a href = "" ng-click = "location.addAlternateDomain($event)" > Add a redirection< / a >
2019-09-16 14:03:13 +02:00
< / div >
< div ng-show = "location.alternateDomains.length > 0" style = "margin-top: 5px;" >
2020-10-08 11:16:40 -07:00
< a href = "" ng-click = "location.addAlternateDomain($event)" > Add another redirection< / a >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2020-04-19 19:44:45 -07:00
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2019-09-16 14:03:13 +02:00
< / 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" >
2020-03-19 19:26:19 -07:00
< form role = "form" name = "accessForm" ng-submit = "access.submit()" autocomplete = "off" >
< fieldset >
2019-09-16 14:03:13 +02:00
< div class = "form-group" >
< div ng-show = "access.ssoAuth" >
< label class = "control-label" > User management< / label >
2020-06-24 23:06:12 -07:00
< p >
This app is configured to authenticate with the Cloudron User Directory.
2020-06-25 10:36:25 +02:00
< span class = "text-small text-warning" ng-show = "access.ftp" > Also controls SFTP access.< / span >
2020-06-24 23:06:12 -07:00
< / p >
2019-09-16 14:03:13 +02:00
< / 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" >
2020-09-09 10:08:13 -07:00
This app is pre-configured for use with < a ng-href = "https://docs.cloudron.io/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 >
2020-03-05 20:12:50 -08:00
< input class = "ng-hide" type = "submit" ng-disabled = "(access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()) || accessForm.$invalid || access.busy" / >
2019-09-16 14:03:13 +02:00
< / div >
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
< div class = "row" >
2019-09-17 14:49:26 +02:00
< div class = "col-md-12 text-right" >
2020-03-05 20:12:50 -08:00
< 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 > Save< / button >
2019-09-16 14:03:13 +02:00
< / 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" >
2020-09-09 10:08:13 -07:00
< label > SFTP< / label > < sup > < a ng-href = "https://docs.cloudron.io/apps/#ftp-access" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < br / >
2020-06-25 10:36:25 +02:00
< br / >
< div class = "row" >
< div class = "col-xs-6" >
< span class = "text-muted" > Server< / 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" > Port< / 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" > Username< / span >
< / div >
< div class = "col-xs-6 text-right" >
2020-08-08 19:15:21 -07:00
< span > {{ user.username }}@{{ app.fqdn }}< / span >
2020-06-25 10:36:25 +02:00
< / div >
< / div >
2019-09-17 15:32:43 +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 === 'resources'" >
< div class = "row" >
< div class = "col-md-12" >
2020-03-19 19:26:19 -07:00
< form role = "form" name = "resourcesForm" ng-submit = "resources.submitMemoryLimit()" autocomplete = "off" >
< fieldset >
2019-09-16 14:03:13 +02:00
< div class = "form-group" >
2020-09-09 10:08:13 -07:00
< label class = "control-label" for = "memoryLimit" > Memory Limit < 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 | prettyByteSize:'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" / >
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2019-09-18 17:12:10 +02:00
< / div >
< / div >
< div class = "row" >
2020-10-21 13:27:31 +02:00
< div class = "col-md-8" >
< span ng-show = "resources.error.memoryLimit" class = "text-danger" > Unable to set memory limit, try less.< / span >
< / div >
< div class = "col-md-4 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" >
2020-03-19 19:26:19 -07:00
< form role = "form" name = "resourcesForm" ng-submit = "resources.submitCpuShares()" autocomplete = "off" >
< fieldset >
2020-01-28 22:05:06 -08:00
< div class = "form-group" >
2020-09-09 10:08:13 -07:00
< label class = "control-label" for = "cpuShares" > CPU Shares < sup > < a ng-href = "https://docs.cloudron.io/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 >
2020-01-28 22:05:06 -08:00
< p > Percent of CPU time when system is under heavy load.< / p >
< div style = "padding: 0 10px;" >
2020-03-06 10:40:21 -08:00
< slider id = "cpuShares" ng-model = "resources.cpuShares" ticks = "[32, 256, 512, 768, 1024]" step = "32" 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" / >
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2020-01-28 22:05:06 -08:00
< / 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 >
2020-10-28 22:11:05 -07:00
< / div >
< div class = "card" ng-show = "view === 'storage'" >
2019-09-18 17:12:10 +02:00
< div class = "row" >
< div class = "col-md-12" >
2020-10-28 22:11:05 -07:00
< label class = "control-label" for = "storageEnableDataDir" > App Data< sup > < a ng-href = "https://docs.cloudron.io/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,
2020-04-29 22:18:44 -07:00
you can bind an external disk and move this app's data there. Only Ext4 and NFS binds are supported.
2019-09-20 01:22:10 +02:00
< / p >
2020-10-28 22:11:05 -07:00
< form role = "form" name = "storageDataDirForm" ng-submit = "storage.submitDataDir()" autocomplete = "off" >
2020-03-19 19:26:19 -07:00
< fieldset >
2020-10-28 22:11:05 -07:00
< div class = "form-group" ng-class = "{ 'has-error': storageDataDirForm.$dirty && storage.error.dataDir }" >
< div ng-show = "storage.error.dataDir" > {{ storage.error.dataDir }}< / div >
< input type = "text" class = "form-control" name = "dataDir" placeholder = "Leave empty to use platform default" ng-model = "storage.dataDir" >
2019-09-16 14:03:13 +02:00
< / div >
2019-09-10 19:21:30 +02:00
2020-10-28 22:11:05 -07:00
< input class = "ng-hide" type = "submit" ng-disabled = "!storageDataDirForm.$dirty || storageDataDirForm.$invalid || storage.busyDataDir" / >
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
2020-10-28 22:11:05 -07:00
< 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 > Move Data
2019-12-20 17:05:45 -08:00
< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2020-05-22 12:05:23 -07:00
2020-09-24 14:32:00 -07:00
< hr >
2020-04-29 22:18:44 -07:00
2020-10-28 22:11:05 -07:00
< div class = "form-group mounts" >
< label class = "control-label" > Mounts < sup > < a ng-href = "https://docs.cloudron.io/apps/#volumes" 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 >
< table class = "table table-hover" style = "margin-top: 10px;" ng-show = "storage.mounts.length > 0" >
< thead >
< tr >
< th style = "width: 40%" > Volume< / th >
< th class = "text-left hidden-xs hidden-sm" > Read Only< / th >
2020-11-11 22:50:57 +01:00
< th style = "width: 100px" class = "text-right" > {{ 'main.actions' | tr }}< / th >
2020-10-28 22:11:05 -07:00
< / tr >
< / thead >
< tbody >
< tr ng-repeat = "mount in storage.mounts" >
< td >
2020-10-30 15:14:59 +01:00
< multiselect ng-model = "mount.volume" data-compare-by = "hostPath" options = "volume.name + ' - ' + volume.hostPath for volume in volumes" data-multiple = "false" filter-after-rows = "5" scroll-after-rows = "10" > < / multiselect >
2020-10-28 22:11:05 -07:00
< / td >
2020-10-30 12:37:32 +01:00
< td class = "text-left" style = "vertical-align: middle;" >
< input type = "checkbox" ng-model = "mount.readOnly" style = "margin-top: initial;" / >
< / td >
< td class = "text-right no-wrap" style = "vertical-align: middle" >
2020-10-28 22:11:05 -07:00
< button class = "btn btn-danger btn-xs" ng-click = "storage.delMount($event, $index)" > < i class = "far fa-trash-alt" > < / i > < / button >
< / td >
< / tr >
< / tbody >
< / table >
< div ng-show = "storage.mounts.length === 0" >
No volumes are mounted. < a href = "" ng-click = "storage.addMount($event)" > Add a mount< / a >
2020-04-29 22:18:44 -07:00
< / div >
2020-10-28 22:11:05 -07:00
< div ng-show = "storage.mounts.length > 0" style = "margin-top: 5px;" >
< a href = "" ng-click = "storage.addMount($event)" > Add another mount< / a >
2020-04-29 22:18:44 -07:00
< / div >
< / div >
2020-09-24 14:32:00 -07:00
< div class = "row" >
2020-04-29 22:18:44 -07:00
< div class = "col-md-12 text-right" >
2020-10-28 22:11:05 -07:00
< 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 > Save
2020-04-29 22:18:44 -07:00
< / button >
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2020-05-13 00:42:27 +02:00
< div class = "card" ng-show = "view === 'graphs'" >
< div class = "row" >
< div class = "col-md-12" >
< div class = "dropdown pull-right" >
2020-05-13 01:12:13 +02:00
< button class = "btn btn-sm btn-primary dropdown-toggle" type = "button" data-toggle = "dropdown" >
2020-05-13 00:42:27 +02:00
Select Period {{ graphs.periodLabel }}
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu" >
2020-05-23 12:40:39 -07:00
< li > < a href = "" ng-click = "graphs.setPeriod(12, '12 hours')" > 12 hours< / a > < / li >
2020-05-13 00:42:27 +02:00
< li > < a href = "" ng-click = "graphs.setPeriod(24, '24 hours')" > 24 hours< / a > < / li >
< li > < a href = "" ng-click = "graphs.setPeriod(24*7, '7 days')" > 7 days< / a > < / li >
< li > < a href = "" ng-click = "graphs.setPeriod(24*30, '30 days')" > 30 days< / a > < / li >
< / ul >
< / div >
2020-05-14 21:56:22 -07:00
< label style = "margin-top: 10px;" > Memory (RAM + Swap) in MB< / label >
2020-05-13 01:12:13 +02:00
< canvas id = "graphsMemoryChart" style = "width: 100%;" > < / canvas >
2020-05-13 00:42:27 +02:00
< / div >
< / div >
< / 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 >
2020-03-19 19:26:19 -07:00
< form role = "form" name = "emailForm" ng-submit = "email.submit()" autocomplete = "off" >
< fieldset >
2019-09-16 14:03:13 +02:00
<!-- 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" / >
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2019-09-16 14:03:13 +02:00
< / 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" >
2020-03-19 19:26:19 -07:00
< form role = "form" name = "securityForm" ng-submit = "security.submit()" autocomplete = "off" >
< fieldset >
2019-09-16 14:03:13 +02:00
< div class = "form-group" >
2020-09-09 10:08:13 -07:00
< label class = "control-label" style = "width: 100%" > Robots.txt < 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" > Disable indexing< / a > < / label >
2020-10-05 21:31:10 -07:00
< textarea ng-trim = "false" style = "white-space: pre-wrap" 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" >
2020-09-09 10:08:13 -07:00
< label class = "control-label" style = "width: 100%" > Content Security Policy < 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 >
2019-10-14 16:50:15 -07:00
< 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" / >
2020-03-19 19:26:19 -07:00
< / fieldset >
< / form >
2019-09-16 14:03:13 +02:00
< / 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'" >
2020-05-18 14:57:21 -07:00
< p > < label class = "control-label" > App Info< / label > < / p >
< div class = "row" >
2020-05-27 22:31:10 -07:00
< div class = "col-xs-4" >
2020-05-18 14:57:21 -07:00
< span class = "text-muted" > App Title & Version< / span >
< / div >
2020-05-27 22:31:10 -07:00
< div class = "col-xs-8 text-right" >
2020-05-18 14:57:21 -07:00
< 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-6" >
< span class = "text-muted" > App ID< / span >
< / div >
< div class = "col-xs-6 text-right" >
< span > {{ app.id }}< / span >
< / div >
< / div >
< div class = "row" >
< div class = "col-xs-6" >
< span class = "text-muted" > Package Version< / span >
< / div >
< div class = "col-xs-6 text-right" >
< span ng-show = "app.appStoreId" > < a ng-href = "/#/appstore/{{app.manifest.id}}?version={{app.manifest.version}}" > v{{ app.manifest.version }}< / a > < / span >
2020-05-27 22:31:10 -07:00
< span ng-show = "!app.appStoreId" > v{{ app.manifest.version }}< / span >
2020-05-18 14:57:21 -07:00
< / div >
< / div >
< div class = "row" >
< div class = "col-xs-6" >
< span class = "text-muted" > Last Updated< / span >
< / div >
< div class = "col-xs-6 text-right" >
< span > {{ app.updateTime | prettyDate }}< / span >
< / div >
< / div >
< br / >
2019-09-17 15:32:43 +02:00
< div class = "row" >
2020-09-29 17:32:25 +02:00
< div class = "col-md-12" ng-show = "!config.update.apps[app.id].manifest.version || config.update.apps[app.id].manifest.version === app.manifest.version" >
< button class = "btn btn-primary pull-right" ng-show = "app.appStoreId" ng-click = "updates.check()" ng-disabled = "updates.busyCheck" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busyCheck" > < / i > Check for Updates< / button >
< span ng-show = "!app.appStoreId" class = "text-danger pull-right" > Updates are not available for custom apps< / span >
< / div >
< div class = "col-md-12" ng-show = "config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && app.installationState !== 'pending_update'" >
2020-10-17 08:50:48 -07:00
< button type = "button" class = "btn btn-success pull-right" ng-click = "updates.askUpdate()" ng-disabled = "app.taskId || 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' }}" > 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 >
2020-05-20 12:16:35 +02: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.busyAutomaticUpdates || !app.appStoreId" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busyAutomaticUpdates" > < / 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
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 >
2020-11-11 22:50:57 +01:00
< th class = "text-right" width = "180px" > {{ 'main.actions' | tr }}< / th >
2019-09-16 14:03:13 +02:00
< / tr >
< / thead >
< tbody >
< tr ng-repeat = "backup in backups.backups" >
2020-05-25 21:47:58 +02:00
< td > < div class = "hand clipboard" data-clipboard-text = "{{ backup.id }}" uib-tooltip = "{{ copyBackupIdDone ? 'Copied to clipboard' : 'Click to copy backup id' }}" tooltip-placement = "right" > < i class = "fa fa-copy" > < / i > < / div > < / td >
2020-05-13 22:37:11 -07:00
< td > < div uib-tooltip = "{{ backup.creationTime | prettyLongDate }}" > v{{ backup.packageVersion }} - {{ backup.creationTime | prettyDate }}< / div > < / td >
2019-09-16 14:03:13 +02:00
< td class = "text-right no-wrap" style = "vertical-align: bottom" >
2020-05-16 11:19:47 -07:00
< button class = "btn btn-xs btn-default" ng-click = "downloadConfig(backup)" uib-tooltip = "Download Backup Configuration" > < i class = "fas fa-file-alt" > < / i > < / button >
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 >
2020-05-28 12:08:39 -07:00
< button class = "btn btn-xs btn-danger" ng-click = "restore.show(backup)" ng-disabled = "app.taskId || app.runState === 'stopped'" 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-05-28 12:08:39 -07:00
< 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' }}" >
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
2020-03-06 02:09:44 -08:00
control setting as this one.
2020-02-06 16:08:22 -08:00
< / p >
2020-02-07 10:42:58 -08:00
2020-05-28 12:08:39 -07:00
< 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" >
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 >
2020-05-17 09:11:19 -07:00
< p > Cloudron periodically creates a backup based on the < a href = "/#/backups" > backup< / a > settings.
Automatic Backups is currently < b ng-class = "backups.enableBackup ? 'text-success' : 'text-danger'" > {{ backups.enableBackup ? 'enabled' : 'disabled' }}< / b > .< / p >
2019-09-19 12:24:22 -07:00
2020-05-20 12:16:35 +02:00
< 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 ? '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 >
2020-07-10 16:19:01 +02:00
< a class = "btn btn-primary pull-right" ng-href = "{{ '/filemanager.html?appId=' + app.id }}" target = "_blank" > File Manager< / 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 >
2020-05-28 13:19:33 -07:00
< p > Apps can be stopped to conserve server resources. Future app backups will not include any app changes between now and the most recent app backup.
For this reason, it is recommended to trigger a backup before stopping the app.
< / 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.
2020-09-09 10:08:13 -07:00
Use the following < a target = "_blank" ng-href = "https://docs.cloudron.io/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-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
2020-09-09 10:08:13 -07:00
app backup using the following < a target = "_blank" ng-href = "https://docs.cloudron.io/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 >