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 >
< div class = "content" >
2019-09-11 21:24:25 +02:00
< div class = "task-indicator animateMe" ng-show = "app.taskId" >
< i class = "fa fa-circle-notch fa-spin" > < / i >
< span > {{ app | installationStateLabel:user }}< / span >
< / div >
2019-09-12 15:59:40 +02:00
< div class = "app-configure-links" >
< div ng-click = "scrollTo('overview')" > Overview< / div >
< div ng-click = "scrollTo('display')" > Display< / div >
< div ng-click = "scrollTo('location')" > Location< / div >
< div ng-click = "scrollTo('access')" > Access Control< / div >
< div ng-click = "scrollTo('resources')" > Resources< / div >
< div ng-click = "scrollTo('security')" > Security< / div >
< div ng-click = "scrollTo('updates')" > Updates< / div >
< div ng-click = "scrollTo('backups')" > Backups< / div >
< / div >
< div id = "overview" >
2019-09-10 19:21:30 +02:00
< h1 >
< img ng-src = "{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon = "img/appicon_fallback.png" onerror = "imageErrorHandler(this)" class = "app-icon" / >
{{ app.label || app.location || app.fqdn }}
< / h1 >
< / div >
< div class = "card" >
< div class = "row" >
< div class = "col-md-12" >
< table width = "100%" >
2019-09-11 21:24:25 +02:00
< tr >
< td class = "text-muted" style = "vertical-align: top;" > Status< / td >
< td class = "text-right" style = "vertical-align: top;" > {{ app | installationStateLabel:user }}< / td >
< / tr >
2019-09-10 19:21:30 +02:00
< tr >
< td class = "text-muted" style = "vertical-align: top;" > App ID< / td >
< td class = "text-right" style = "vertical-align: top;" > {{ app.id }}< / td >
< / tr >
< tr >
< td class = "text-muted" style = "vertical-align: top;" > Upstream Version< / td >
< td class = "text-right" style = "vertical-align: top;" > {{ app.upstreamVersion }}< / td >
< / tr >
< tr >
< td class = "text-muted" style = "vertical-align: top;" > App Package< / td >
< td class = "text-right" style = "vertical-align: top;" > {{ app.manifest.title }}< / td >
< / tr >
< tr >
< td class = "text-muted" style = "vertical-align: top;" > App Package Version< / td >
< td class = "text-right" style = "vertical-align: top;" > {{ app.manifest.version }}< / td >
< / tr >
< / table >
< br / >
2019-09-12 15:59:40 +02:00
< a class = "btn btn-outline btn-primary" ng-href = "{{ app.manifest.documentationUrl }}" target = "_blank" > Documentation< / a >
< a class = "btn btn-outline btn-primary" ng-href = "{{ '/logs.html?appId=' + app.id }}" target = "_blank" > Logs< / a >
< button class = "btn btn-danger pull-right" ng-show = "app.taskId" ng-click = "stopAppTask(app.taskId)" > Cancel Current Task< / button >
2019-09-10 19:21:30 +02:00
< / div >
< / div >
< / div >
2019-09-12 15:59:40 +02:00
< div id = "display" > < h3 > Display< / h3 > < / div >
2019-09-10 19:21:30 +02:00
< div class = "card" >
< 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 >
< div id = "previewIcon" class = "app-custom-icon" ng-click = "display.showCustomIconSelector()" style = "background-image: url('{{ display.iconUrl() }}');" >
< 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" / >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "displayForm.$invalid || display.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6" >
< span class = "text-success text-bold" ng-show = "display.success" > Saved< / span >
< / div >
< div class = "col-md-6 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "display.submit()" ng-disabled = "display.$invalid || display.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "display.busy" > < / i > Save< / button >
< / div >
< / div >
< / div >
2019-09-12 15:59:40 +02:00
< div id = "location" > < h3 > Location< / h3 > < / div >
2019-09-10 19:21:30 +02:00
< div class = "card" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "locationForm" ng-submit = "location.submit()" autocomplete = "off" >
< div class = "has-error text-center" ng-show = "location.error.other" > {{ location.error.other }}< / div >
< div class = "form-group" ng-class = "{ 'has-error': (locationForm.location.$dirty && locationForm.location.$invalid) || (!locationForm.location.$dirty && location.error.location) }" >
< label class = "control-label" for = "locationLocationInput" > Location {{ location.error.location }} < / label >
< 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 >
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
< span > {{ (!location.location ? '' : (location.domain.config.hyphenatedSubdomains ? '-' : '.')) + location.domain.domain }}< / span >
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-right" role = "menu" >
< li ng-repeat = "domain in domains" >
< a href = "" ng-click = "location.domain = domain" > {{ domain.domain }}< / a >
< / li >
< / ul >
< / div >
< / div >
< / div >
< p class = "text-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 >
< 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 >
< / div >
< 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 >
< input class = "ng-hide" type = "submit" ng-disabled = "locationForm.$invalid || location.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6" >
< span class = "text-success text-bold" ng-show = "location.success" > Saved< / span >
< / div >
< div class = "col-md-6 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "location.submit()" ng-disabled = "location.$invalid || location.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "location.busy" > < / i > Save< / button >
< / div >
< / div >
< / div >
2019-09-12 15:59:40 +02:00
< div id = "access" > < h3 > Access Control< / h3 > < / div >
2019-09-10 19:21:30 +02:00
< div class = "card" >
< 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" >
This app is pre-configured for use with < a href = "https://cloudron.io/documentation/email/" target = "_blank" > Cloudron Email< / a > .
< / p >
< / div >
< 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" >
< 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 >
< span class = "label label-danger" ng-show = "access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()" > Select at least one user or group< / span >
< / label >
< / div >
< div >
< div style = "margin-left: 20px;" >
< div class = "col-md-5" >
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" > < / multiselect >
< / div >
< div class = "col-md-5" >
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" > < / multiselect >
< / div >
< / div >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "accessForm.$invalid || access.busy" / >
< / div >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6" >
< span class = "text-success text-bold" ng-show = "access.success" > Saved< / span >
< / div >
< div class = "col-md-6 text-right" >
< 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 >
< / div >
2019-09-12 15:59:40 +02:00
< div id = "resources" > < h3 > Resources< / h3 > < / div >
2019-09-10 19:21:30 +02:00
< div class = "card" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "resourcesForm" ng-submit = "resources.submit()" autocomplete = "off" >
< div class = "form-group" >
< label class = "control-label" for = "memoryLimit" > Memory Limit < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#increasing-the-memory-limit-of-an-app" 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 >
< br / >
< 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 >
<!-- We do not show this currently -->
< div ng-if = "false" class = "form-group" ng-class = "{ 'has-error': !resourcesForm.dataDir.$dirty && resources.error.dataDir }" >
< input type = "checkbox" id = "resourcesEnableDataDir" ng-model = "resources.dataDirEnabled" >
< label class = "control-label" for = "resourcesEnableDataDir" > Custom Data Directory< / label >
< div class = "control-label" ng-show = "resources.error.dataDir" > {{resources.error.dataDir}}< / div >
< input type = "text" class = "form-control" id = "resourcesDataDirInput" name = "dataDir" ng-disabled = "!resources.dataDirEnabled" placeholder = "/mnt/appdata" ng-model = "resources.dataDir" >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "resourcesForm.$invalid || resources.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6" >
< span class = "text-success text-bold" ng-show = "resources.success" > Saved< / span >
< / div >
< div class = "col-md-6 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "resources.submit()" ng-disabled = "resources.$invalid || resources.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "resources.busy" > < / i > Save< / button >
< / div >
< / div >
< / div >
2019-09-12 15:59:40 +02:00
< div id = "email" ng-if = "app.manifest.addons.sendmail || app.manifest.addons.recvmail" > < h3 > Email< / h3 > < / div >
2019-09-10 19:21:30 +02:00
< div class = "card" ng-if = "app.manifest.addons.sendmail || app.manifest.addons.recvmail" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "emailForm" ng-submit = "email.submit()" autocomplete = "off" >
<!-- recvmail currently only works with cloudron email -->
< div class = "form-group" ng-class = "{ 'has-error': !emailForm.mailboxName.$dirty && email.error.mailboxName }" >
< input type = "checkbox" id = "emailMailboxNameEnabled" ng-model = "email.mailboxNameEnabled" >
< label class = "control-label" for = "emailMailboxNameEnabled" > Custom Mail FROM< / label >
< div class = "has-error" ng-show = "email.error.mailboxName" > {{ email.error.mailboxName }}< / div >
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-required = "email.mailboxNameEnabled" name = "mailboxName" ng-model = "email.mailboxName" uib-tooltip = "App FROM email address. Addresses ending with '.app' are reserved." tooltip-class = "long" ng-disabled = "!email.mailboxNameEnabled" >
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" ng-disabled = "!email.mailboxNameEnabled" >
@{{ email.domain.domain }}
< / button >
< / div >
< / div >
< p class = "text-small" >
< br / >
This app is configured to send mail using < a ng-href = "/#/email/{{ app.domain }}" > {{app.domain}}'s Outbound Email< / a > settings.
< / p >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "emailForm.$invalid || email.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6" >
< span class = "text-success text-bold" ng-show = "email.success" > Saved< / span >
< / div >
< div class = "col-md-6 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "email.submit()" ng-disabled = "email.$invalid || email.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "email.busy" > < / i > Save< / button >
< / div >
< / div >
< / div >
2019-09-12 15:59:40 +02:00
< div id = "security" > < h3 > Security< / h3 > < / div >
2019-09-10 19:21:30 +02:00
< div class = "card" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "securityForm" ng-submit = "security.submit()" autocomplete = "off" >
< div class = "form-group" >
< label class = "control-label" style = "width: 100%" > Specify robots.txt file content < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#indexing-by-search-engines-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 = disableIndexingTemplate" > Disable indexing< / a > < / label >
< textarea ng-model = "security.robotsTxt" placeholder = "Leave empty to allow all bots to index this app." class = "form-control" rows = "4" > < / textarea >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "securityForm.$invalid || security.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6" >
< span class = "text-success text-bold" ng-show = "security.success" > Saved< / span >
< / div >
< div class = "col-md-6 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "security.submit()" ng-disabled = "security.$invalid || security.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "security.busy" > < / i > Save< / button >
< / div >
< / div >
< / div >
2019-09-12 15:59:40 +02:00
< div id = "updates" > < h3 > Updates< / h3 > < / div >
2019-09-10 19:21:30 +02:00
< div class = "card" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "updatesForm" ng-submit = "updates.submit()" autocomplete = "off" >
< div class = "form-group" >
< input type = "checkbox" id = "updatesEnableAutomaticUpdate" ng-model = "updates.enableAutomaticUpdate" >
< label class = "control-label" for = "updatesEnableAutomaticUpdate" > Enable automatic updates< / label >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "updatesForm.$invalid || updates.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6" >
< span class = "text-success text-bold" ng-show = "updates.success" > Saved< / span >
< / div >
< div class = "col-md-6 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "updates.submit()" ng-disabled = "updates.$invalid || updates.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busy" > < / i > Save< / button >
< / div >
< / div >
< / div >
2019-09-12 15:59:40 +02:00
< div id = "backups" > < h3 > Backups< / h3 > < / div >
2019-09-10 19:21:30 +02:00
< div class = "card" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "backupsForm" ng-submit = "backups.submit()" autocomplete = "off" >
< div class = "form-group" >
< input type = "checkbox" id = "backupsEnableBackup" ng-model = "backups.enableBackup" >
< label class = "control-label" for = "backupsEnableBackup" > Enable automatic daily backups< / label >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "backupsForm.$invalid || backups.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-6" >
< span class = "text-success text-bold" ng-show = "backups.success" > Saved< / span >
< / div >
< div class = "col-md-6 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "backups.submit()" ng-disabled = "backupsForm.$invalid || backups.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "backups.busy" > < / i > Save< / button >
< / div >
< / div >
< / div >
< / div >