2019-09-13 11:29:19 +02:00
<!-- TODO use this once we enable custom SSL certificate ui again -->
<!-- <div class="hide">
< label class = "control-label" for = "appConfigureCertificateInput" ng-show = "appConfigure.domain.provider !== 'caas'" > Certificate (optional)< / label >
< div class = "has-error text-center" ng-show = "appConfigure.error.cert && appConfigure.domain.provider !== 'caas'" > {{ appConfigure.error.cert }}< / div >
< div class = "form-group" ng-class = "{ 'has-error': !appConfigureForm.certificate.$dirty && appConfigure.error.cert }" ng-show = "appConfigure.domain.provider !== 'caas'" >
< div class = "input-group" >
< input type = "file" id = "appConfigureCertificateFileInput" onchange = "readCertificate()" style = "display:none" / >
< input type = "text" class = "form-control" placeholder = "Certificate" ng-model = "appConfigure.certificateFileName" id = "appConfigureCertificateInput" name = "certificate" onclick = "getElementById('appConfigureCertificateFileInput').click();" style = "cursor: pointer;" ng-required = "appConfigure.keyFileName" >
< span class = "input-group-addon" >
< i class = "fa fa-upload" onclick = "getElementById('appConfigureCertificateFileInput').click();" > < / i >
< / span >
< / div >
< / div >
< div class = "form-group" ng-class = "{ 'has-error': !appConfigureForm.key.$dirty && appConfigure.error.cert }" ng-show = "appConfigure.domain.provider !== 'caas'" >
< div class = "input-group" >
< input type = "file" id = "appConfigureKeyFileInput" onchange = "readKey()" style = "display:none" / >
< input type = "text" class = "form-control" placeholder = "Key" ng-model = "appConfigure.keyFileName" id = "appConfigureKeyInput" name = "key" onclick = "getElementById('appConfigureKeyFileInput').click();" style = "cursor: pointer;" ng-required = "appConfigure.certificateFileName" >
< span class = "input-group-addon" >
< i class = "fa fa-upload" onclick = "getElementById('appConfigureKeyFileInput').click();" > < / i >
< / span >
< / div >
< / div >
< / div > -->
2019-09-10 19:21:30 +02:00
< script >
function imageErrorHandler(elem) {
'use strict';
elem.src = elem.getAttribute('fallback-icon');
elem.onerror = null; // avoid retry after default icon cannot be loaded
}
< / script >
2019-09-19 22:49:21 +02:00
<!-- Modal postinstall confirm -->
< div class = "modal fade" id = "postInstallConfirmModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< img ng-src = "{{app.iconUrl}}" onerror = "this.onerror=null;this.src='img/appicon_fallback.png'" class = "app-info-icon" / >
< h5 class = "app-info-title" >
{{ app.manifest.title }}
< span class = "app-info-meta text-small" > Package < a ng-href = "/#/appstore/{{ app.manifest.id }}?version={{ app.manifest.version }}" > v{{ app.manifest.version }}< / a > < / span >
< br / >
< span ng-show = "app.manifest.documentationUrl" > < a target = "_blank" ng-href = "{{ app.manifest.documentationUrl }}" > Documentation< / a > < / span >
< br / >
< / h5 >
< / div >
< div class = "modal-body" >
< div ng-bind-html = "app.manifest.postInstallMessage | postInstallMessage:app | markdown2html" > < / div >
< div ng-show = "app.manifest.documentationUrl" >
Please see the < a target = "_blank" ng-href = "{{ app.manifest.documentationUrl }}" > documentation< / a > for more information.
< / div >
< / div >
< div class = "modal-footer" >
< div class = "form-group pull-left" >
< input type = "checkbox" id = "postInstallConfirmCheckbox" ng-model = "postInstallConfirm.confirmed" >
< label class = "control-label" for = "postInstallConfirmCheckbox" > Acknowledge instructions< / label >
< / div >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
< a class = "btn btn-success" ng-href = "{{ postInstallConfirm.confirmed ? ('https://' + app.fqdn) : '' }}" target = "_blank" ng-disabled = "!postInstallConfirm.confirmed" ng-click = "postInstallConfirm.submit()" > < i class = "fas fa-external-link-alt" > < / i > Open App< / a >
< / div >
< / div >
< / div >
< / div >
2019-09-13 11:18:43 +02:00
<!-- Modal uninstall app -->
< div class = "modal fade" id = "uninstallModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
2019-09-19 10:28:17 -07:00
< div class = "modal-header" >
< h4 class = "modal-title" > Uninstall {{ app.label || app.fqdn }}< / h4 >
< / div >
2019-09-13 11:18:43 +02:00
< div class = "modal-body" >
2019-09-19 10:28:17 -07:00
< p > This will immediately uninstall < b > {{ app.label || app.fqdn }}< / b > and remove all it's data.< / p >
2019-09-13 11:18:43 +02:00
< / div >
< div class = "modal-footer" >
2019-09-19 10:28:17 -07:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
< button type = "button" class = "btn btn-danger" ng-click = "uninstall.submit()" ng-disabled = "uninstall.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "uninstall.busy" > < / i > Uninstall< / button >
2019-09-13 11:18:43 +02:00
< / div >
< / div >
< / div >
< / div >
2019-09-17 17:14:40 +02:00
<!-- Modal update app -->
< div class = "modal fade" id = "updateModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" > Update {{ app.fqdn }}< / h4 >
< / div >
< div class = "modal-body" >
< p > Recent Changes for new version < b > {{ config.update.apps[app.id].manifest.version}}< / b > :< / p >
< div ng-bind-html = "config.update.apps[app.id].manifest.changelog | markdown2html" > < / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
< button type = "button" class = "btn btn-success" ng-click = "updates.confirmUpdate()" ng-disabled = "updates.busyUpdate" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busyUpdate" > < / i > Update< / button >
< / div >
< / div >
< / div >
< / div >
2019-09-13 17:18:37 +02:00
<!-- Modal clone app -->
< div class = "modal fade" id = "cloneModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" >
Clone - {{ app.fqdn }}
< / h4 >
< / div >
< div class = "modal-body" style = "padding: 0 15px" >
< p > Using backup from < b > {{ clone.backup.creationTime | prettyDate }}< / b > and version < b > v{{ clone.backup.version }}< / b > < / p >
< fieldset >
< form role = "form" ng-submit = "clone.submit()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': clone.error.location }" >
< label class = "control-label" for = "cloneLocationInput" > Location< / label >
< div ng-show = "clone.error.location" > < small > {{ clone.error.location }}< / small > < / div >
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "clone.location" id = "cloneLocationInput" name = "location" placeholder = "Leave empty to use bare domain" autofocus >
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
< span > {{ (!clone.location ? '' : (clone.domain.config.hyphenatedSubdomains ? '-' : '.')) + clone.domain.domain }}< / span >
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-right" role = "menu" >
< li ng-repeat = "domain in domains" >
< a href = "" ng-click = "clone.domain = domain" > {{ domain.domain }}< / a >
< / li >
< / ul >
< / div >
< / div >
< / div >
< p class = "text-center" ng-show = "appClone.location && appClone.domain.provider === 'manual'" >
< b > Add an A record manually for {{ appClone.location }} to this Cloudron's public IP< / b >
< br >
< / p >
< div class = "has-error text-center" ng-show = "appClone.error.port" > {{ appClone.error.port }}< / div >
< div ng-repeat = "(env, info) in appClone.portBindingsInfo" >
< ng-form name = "portInfo_form" >
< div class = "form-group" ng-class = "{ 'has-error': (!appClone.itemName{{$index}}.$dirty && appClone.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }" >
< label class = "control-label" for = "inputPortInfo{{env}}" > < input type = "checkbox" ng-model = "appClone.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 = "appClone.portBindings[env]" ng-disabled = "!appClone.portBindingsEnabled[env]" id = "inputPortInfo{{env}}" later-name = "itemName{{$index}}" min = "{{hostPortMin}}" max = "{{hostPortMax}}" required >
< / div >
< / ng-form >
< / div >
< / form >
< / fieldset >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
< button type = "button" class = "btn btn-success" ng-click = "appClone.submit()" > < i class = "far fa-clone" > < / i > Clone< / button >
< / 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
2019-09-16 14:24:17 +02:00
< a href = "/#/apps" class = "back-to-apps-link" > < i class = "fas fa-arrow-left" > < / i > Back to My Apps< / a >
2019-09-16 14:15:38 +02:00
2019-09-13 11:12:11 +02:00
< div class = "task-indicator animateMe" ng-show = "app.taskId" >
2019-09-11 21:24:25 +02:00
< i class = "fa fa-circle-notch fa-spin" > < / i >
< span > {{ app | installationStateLabel:user }}< / span >
< / div >
2019-09-12 16:28:21 +02:00
< br / >
2019-09-13 11:29:19 +02:00
2019-09-16 14:03:13 +02:00
< div class = "row" >
< div class = "col-md-2 text-center" >
< img ng-src = "{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon = "img/appicon_fallback.png" onerror = "imageErrorHandler(this)" class = "app-icon" / >
< / div >
< div class = "col-md-8" >
< h1 style = "line-height: 0.7;" >
{{ app.label || app.fqdn }}
2019-09-19 22:49:21 +02:00
< a class = "btn btn-sm btn-outline btn-primary pull-right" ng-href = "{{ app | applicationLink }}" target = "_blank" ng-disabled = "!(app | appIsInstalledAndHealthy)" ng-click = "user.admin && ((app | installError) === true && setView('debug')) || ((app | appIsInstalledAndHealthy) && app.pendingPostInstallConfirmation && postInstallConfirm.show())" > < i class = "fas fa-external-link-alt" > < / i > Open App< / a >
2019-09-17 15:40:04 +02:00
< a class = "btn btn-sm btn-outline btn-primary pull-right" ng-href = "{{ app.manifest.documentationUrl }}" target = "_blank" > Documentation< / a >
2019-09-10 19:21:30 +02:00
< br / >
2019-09-16 14:03:13 +02:00
< span class = "text-small" >
{{ app | installationStateLabel:user }}
< / span >
< / h1 >
2019-09-10 19:21:30 +02:00
< / div >
< / div >
2019-09-16 14:03:13 +02:00
< br / >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< div class = "row" >
< div class = "col-md-2" >
< div class = "app-configure-links" >
2019-09-17 14:52:22 +02:00
< div ng-click = "setView('display')" ng-class = "{ 'active': view === 'display' }" > Display< / div >
< div ng-click = "setView('location')" ng-class = "{ 'active': view === 'location' }" > Location< / div >
< div ng-click = "setView('access')" ng-class = "{ 'active': view === 'access' }" > Access Control< / div >
< div ng-click = "setView('resources')" ng-class = "{ 'active': view === 'resources' }" > Resources< / div >
< div ng-click = "setView('security')" ng-class = "{ 'active': view === 'security' }" > Security< / div >
2019-09-17 22:05:32 +02:00
< div ng-click = "setView('email')" ng-class = "{ 'active': view === 'email' }" ng-show = "app.manifest.addons.sendmail || app.manifest.addons.recvmail" > Email< / div >
2019-09-17 14:52:22 +02:00
< div ng-click = "setView('updates')" ng-class = "{ 'active': view === 'updates' }" > Updates< / div >
< div ng-click = "setView('backups')" ng-class = "{ 'active': view === 'backups' }" > Backups< / div >
< div ng-click = "setView('debug')" ng-class = "{ 'active': view === 'debug' }" > Debug< / 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-09-16 14:03:13 +02:00
< div class = "col-md-8" >
< div class = "card" ng-show = "view === 'display'" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "displayForm" ng-submit = "display.submit()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': !displayForm.label.$dirty && display.error.label }" >
< label class = "control-label" > Label< / label >
< div class = "control-label" ng-show = "display.error.label" > {{display.error.label}}< / div >
< input type = "text" class = "form-control" id = "displayLabelInput" name = "label" ng-model = "display.label" >
< / div >
< div class = "form-group" >
< label class = "control-label" > Tags< / label >
< tag-input class = "form-control" placeholder = "Use comma to separate tags" taglist = "display.tags" name = "tags" uib-tooltip = "For grouping in the dashboard" > < / tag-input >
< / div >
< div class = "form-group" >
< div >
< label class = "control-label" > Icon< / label >
< / div >
< 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" / >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "(!display.icon.data && !displayForm.$dirty) || displayForm.$invalid || display.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "display.submit()" ng-disabled = "(!display.icon.data && !displayForm.$dirty) || display.$invalid || display.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "display.busy" > < / i > Save< / button >
< / div >
< / div >
< / div >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'location'" >
2019-09-19 20:43:43 +02:00
< div class = "task-active-overlay" ng-show = "app.taskId" > < / div >
2019-09-16 14:03:13 +02:00
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "locationForm" ng-submit = "location.submit()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': (locationForm.location.$dirty && locationForm.location.$invalid) || (!locationForm.location.$dirty && location.error.location) }" >
2019-09-17 16:16:48 +02:00
< label class = "control-label" > Location< / label >
< div class = "has-error" ng-show = "location.error.location" > {{ location.error.location }}< / div >
2019-09-16 14:03:13 +02:00
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "location.location" id = "locationLocationInput" name = "location" placeholder = "{{ 'Leave empty to use bare domain' }}" autofocus >
2019-09-10 19:21:30 +02:00
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
2019-09-16 14:03:13 +02:00
< span > {{ (!location.location ? '' : (location.domain.config.hyphenatedSubdomains ? '-' : '.')) + location.domain.domain }}< / span >
2019-09-10 19:21:30 +02:00
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-right" role = "menu" >
< li ng-repeat = "domain in domains" >
2019-09-16 14:03:13 +02:00
< a href = "" ng-click = "location.domain = domain" > {{ domain.domain }}< / a >
2019-09-10 19:21:30 +02:00
< / li >
< / ul >
< / div >
< / div >
< / div >
2019-09-16 14:03:13 +02:00
< p class = "text-center" ng-show = "location.location && location.domain.provider === 'manual'" >
< b > Add an A record manually for {{ location.location }} to this Cloudron's public IP< / b >
< br >
< / p >
< div class = "has-error text-center" ng-show = "location.error.port" > {{ location.error.port }}< / div >
< div ng-repeat = "(env, info) in location.portBindingsInfo" >
< ng-form name = "portInfo_form" >
< div class = "form-group" ng-class = "{ 'has-error': (!locationForm.itemName{{$index}}.$dirty && location.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }" >
< label class = "control-label" for = "locationPortInput{{env}}" > < input type = "checkbox" ng-model = "location.portBindingsEnabled[env]" >
{{ info.title }}
< sup >
< a popover-placement = "top-right" popover-trigger = "outsideClick" uib-popover = "{{info.description}} ({{ HOST_PORT_MIN }} - {{ HOST_PORT_MAX }})" > < i class = "fa fa-question-circle" > < / i > < / a >
< / sup >
< / label >
< input type = "number" class = "form-control" ng-model = "location.portBindings[env]" ng-disabled = "!location.portBindingsEnabled[env]" id = "locationPortInput{{env}}" later-name = "itemName{{$index}}" min = "{{HOST_PORT_MIN}}" max = "{{HOST_PORT_MAX}}" required >
< / div >
< / ng-form >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "form-group alternate-domains" >
< label class = "control-label" > Redirections < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#redirections" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
< div class = "has-error" ng-show = "location.error.alternateDomains" > {{ location.error.alternateDomains }}< / div >
< div class = "row" ng-repeat = "alternateDomain in location.alternateDomains" >
< div class = "col col-lg-11" >
< div class = "input-group" >
< input type = "text" class = "form-control" ng-model = "alternateDomain.subdomain" placeholder = "Leave empty to use bare domain" >
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
< span > {{ (!alternateDomain.subdomain ? '' : (alternateDomain.domain.config.hyphenatedSubdomains ? '-' : '.')) + alternateDomain.domain.domain }}< / span >
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-right" role = "menu" >
< li ng-repeat = "domain in domains" >
< a href = "" ng-click = "alternateDomain.domain = domain" > {{ domain.domain }}< / a >
< / li >
< / ul >
< / div >
< / div >
< / div >
< div class = "col col-lg-1" >
< button class = "btn btn-danger btn-sm" ng-click = "location.delAlternateDomain($event, $index)" > < i class = "far fa-trash-alt" > < / i > < / button >
< / div >
< / div >
< div ng-show = "location.alternateDomains.length === 0" >
No alternate domains are configured. < a href = "" ng-click = "location.addAlternateDomain($event)" > Add a domain< / a >
< / div >
< div ng-show = "location.alternateDomains.length > 0" style = "margin-top: 5px;" >
< a href = "" ng-click = "location.addAlternateDomain($event)" > Add another domain< / a >
< / div >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "locationForm.$invalid || location.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "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 >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'access'" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "accessForm" ng-submit = "access.submit()" autocomplete = "off" >
< div class = "form-group" >
< div ng-show = "access.ssoAuth" >
< label class = "control-label" > User management< / label >
< p class = "text-small" ng-show = "access.ftp" > This setting also controls SFTP access.< / p >
< / div >
< div ng-show = "!access.ssoAuth" >
< label class = "control-label" > Dashboard visibility< / label >
< p ng-show = "!access.app.manifest.addons.email" class = "text-small" >
This app has it's own user management.
< span ng-show = "access.ftp" > This setting also controls SFTP access.< / span >
< / p >
< p ng-show = "access.app.manifest.addons.email" class = "text-small" >
This app is pre-configured for use with < a href = "https://cloudron.io/documentation/email/" target = "_blank" > Cloudron Email< / a > .
< / 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" >
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 >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "accessForm.$invalid || access.busy" / >
< / div >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
2019-09-17 14:49:26 +02:00
< div class = "col-md-12 text-right" >
2019-09-16 14:03:13 +02:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "access.submit()" ng-disabled = "access.$invalid || access.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "access.busy" > < / i > Save< / button >
< / div >
< / div >
2019-09-17 15:32:43 +02:00
< div class = "row" ng-show = "app.manifest.addons.localstorage.ftp" >
2019-09-19 09:35:25 -07:00
< hr / >
2019-09-17 15:32:43 +02:00
< div class = "col-md-12" >
< label > SFTP< / label > < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/apps/#ftp-access" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < br / >
Server: {{ config.adminFqdn }}< br / >
Port: 222< br / >
Username: {{ user.username }}@{{ app.fqdn }}< br / >
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'resources'" >
2019-09-19 20:43:43 +02:00
< div class = "task-active-overlay" ng-show = "app.taskId" > < / div >
2019-09-16 14:03:13 +02:00
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
2019-09-18 17:12:10 +02:00
< form role = "form" name = "resourcesForm" ng-submit = "resources.submitMemoryLimit()" autocomplete = "off" >
2019-09-16 14:03:13 +02:00
< div class = "form-group" >
< 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 >
2019-09-10 19:21:30 +02:00
2019-09-18 17:12:10 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "resources.memoryLimit === resources.currentMemoryLimit || resourcesForm.$invalid || resources.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
2019-09-19 11:22:49 -07:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "resources.submitMemoryLimit()" ng-disabled = "resources.memoryLimit === resources.currentMemoryLimit || resourcesForm.$invalid || resources.busy" > < 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 / >
< div class = "row" >
< div class = "col-md-12" >
2019-09-20 01:22:10 +02:00
< label class = "control-label" for = "resourcesEnableDataDir" > Custom Data Directory < sup > < a ng-href = "{{ config.webServerOrigin }}/documentation/storage/#moving-a-single-apps-data-directory-to-another-location" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
< p >
By default, this app's data is located at < code > /home/yellowtent/appsdata/{{ app.id }}< / code > . If the server is running out of disk space,
you can mount an external disk and move this app's data there.
< / p >
2019-09-18 17:12:10 +02:00
< fieldset >
< form role = "form" name = "resourcesDataDirForm" ng-submit = "resources.submitDataDir()" autocomplete = "off" >
2019-09-20 01:22:10 +02:00
< div class = "form-group" ng-class = "{ 'has-error': resourcesDataDirForm.$dirty && resources.error.dataDir }" >
< div ng-show = "resources.error.dataDir" > {{ resources.error.dataDir }}< / div >
< input type = "text" class = "form-control" name = "dataDir" placeholder = "Leave empty to use platform default" ng-model = "resources.dataDir" >
2019-09-16 14:03:13 +02:00
< / div >
2019-09-10 19:21:30 +02:00
2019-09-18 17:12:10 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "!resourcesDataDirForm.$dirty || resourcesDataDirForm.$invalid || resources.busyDataDir" / >
2019-09-16 14:03:13 +02:00
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
2019-09-19 11:22:49 -07:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "resources.submitDataDir()" ng-disabled = "!resourcesDataDirForm.$dirty || resourcesDataDirForm.$invalid || resources.busyDataDir" > < i class = "fa fa-circle-notch fa-spin" ng-show = "resources.busyDataDir" > < / i > Move Data< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'email'" >
2019-09-19 20:43:43 +02:00
< div class = "task-active-overlay" ng-show = "app.taskId" > < / div >
2019-09-16 14:03:13 +02:00
< div class = "row" >
< div class = "col-md-12" >
2019-09-19 18:00:18 -07:00
< label class = "control-label" for = "emailMailboxNameEnabled" > Custom Mail FROM< / label >
< p > This sets the address from which this app sends email. This app is already configured to send mail using {{app.domain}}'s < a ng-href = "/#/email/{{ app.domain }}" > Outbound Email< / a > settings.< / p >
2019-09-16 14:03:13 +02:00
< fieldset >
< form role = "form" name = "emailForm" ng-submit = "email.submit()" autocomplete = "off" >
<!-- recvmail currently only works with cloudron email -->
2019-09-19 18:00:18 -07:00
< div class = "form-group" ng-class = "{ 'has-error': emailForm.$dirty && email.error.mailboxName }" >
< div ng-show = "email.error.mailboxName" > {{ email.error.mailboxName }}< / div >
2019-09-16 14:03:13 +02:00
2019-09-17 15:09:39 +02:00
< div class = "input-group form-inline" ng-class = "{ 'has-error': !emailForm.mailboxName.$dirty && email.error.mailboxName }" >
2019-09-19 18:00:18 -07:00
< input type = "text" class = "form-control" name = "mailboxName" placeholder = "Leave empty to use platform default" ng-model = "email.mailboxName" >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" ng-disabled = "!email.mailboxNameEnabled" >
@{{ email.domain.domain }}
< / button >
< / div >
< / div >
2019-09-10 19:21:30 +02:00
2019-09-17 15:09:39 +02:00
< br / >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-19 18:00:18 -07:00
< input class = "ng-hide" type = "submit" ng-disabled = "!emailForm.$dirty || email.busy" / >
2019-09-16 14:03:13 +02:00
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
2019-09-17 15:09:39 +02:00
< div class = "col-md-12 text-right" >
2019-09-19 18:00:18 -07:00
< button class = "btn btn-outline btn-primary pull-right" ng-click = "email.submit()" ng-disabled = "!emailForm.$dirty || email.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "email.busy" > < / i > Save< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'security'" >
< div class = "row" >
< div class = "col-md-12" >
< fieldset >
< form role = "form" name = "securityForm" ng-submit = "security.submit()" autocomplete = "off" >
< div class = "form-group" >
2019-09-19 18:41:03 +02:00
< 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 = ROBOTS_DISABLE_INDEXING_TEMPLATE" > Disable indexing< / a > < / label >
2019-09-16 14:03:13 +02:00
< textarea ng-model = "security.robotsTxt" placeholder = "Leave empty to allow all bots to index this app." class = "form-control" rows = "4" > < / textarea >
< / div >
2019-09-10 19:21:30 +02:00
2019-09-16 14:03:13 +02:00
< input class = "ng-hide" type = "submit" ng-disabled = "security.robotsTxt === security.currentRobotsTxt || securityForm.$invalid || security.busy" / >
< / form >
< / fieldset >
< / div >
< / div >
< div class = "row" >
< div class = "col-md-12 text-right" >
< button class = "btn btn-outline btn-primary pull-right" ng-click = "security.submit()" ng-disabled = "security.robotsTxt === security.currentRobotsTxt || security.$invalid || security.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "security.busy" > < / i > Save< / button >
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'updates'" >
2019-09-17 15:32:43 +02:00
< div class = "row" >
< div class = "col-md-12" >
2019-09-19 12:00:35 -07:00
< label class = "control-label" > Check For Updates< / label >
2019-09-19 11:58:06 -07:00
< p > This app is running {{ app.manifest.title }} {{ app.upstreamVersion }} (Package < a ng-href = "/#/appstore/{{app.manifest.id}}?version={{app.manifest.version}}" > v{{ app.manifest.version }}< / a > ) and was last updated < code > {{ app.updateTime | prettyDate }}< / code > .< / p >
2019-09-17 16:16:48 +02:00
< br / >
2019-09-19 11:58:06 -07:00
< button class = "btn btn-primary pull-right" ng-click = "updates.check()" ng-hide = "config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version" ng-disabled = "updates.busyCheck" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busyCheck" > < / i > Check for Updates< / button >
< button class = "btn btn-success pull-right" ng-click = "updates.askUpdate()" ng-show = "app.installationState !== 'pending_update' && config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version" ng-disabled = "app.taskId || !(app | installSuccess)" > 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 >
< button class = "btn btn-primary pull-right" ng-class = "{ 'btn-danger': updates.enableAutomaticUpdate }" ng-click = "updates.toggleAutomaticUpdates()" ng-disabled = "updates.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "updates.busy" > < / i > {{ updates.enableAutomaticUpdate ? 'Disable' : 'Enable' }} Automatic Updates< / button >
2019-09-16 14:03:13 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >
2019-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'backups'" >
< div class = "row" >
< div class = "col-md-12" >
2019-09-19 12:24:22 -07:00
< label class = "control-label" > Create Backup< / label >
< p > This creates a snapshot of the app. You can use this snapshot to restore or clone this app.< / p >
2019-09-20 00:03:52 +02:00
2019-09-19 22:49:21 +02:00
< div ng-show = "app.installationState === 'pending_backup'" >
< div class = "progress progress-striped active animateMe" style = "margin-bottom: 10px;" >
2019-09-20 00:03:52 +02:00
< div class = "progress-bar progress-bar-success" role = "progressbar" style = "width: {{ backups.taskProgress }}%" > < / div >
2019-09-19 22:49:21 +02:00
< / div >
2019-09-20 00:03:52 +02:00
< div > {{ backups.taskMessage }}< / div >
2019-09-19 22:49:21 +02:00
< / div >
2019-09-20 00:03:52 +02:00
< button type = "button" class = "btn btn-primary pull-right" ng-click = "backups.createBackup()" ng-disabled = "app.taskId || backups.busyCreate" > < i class = "fa fa-circle-notch fa-spin" ng-show = "app.installationState === 'pending_backup' || backups.busyCreate" > < / i > Create Backup< / button >
2019-09-19 12:24:22 -07:00
< / div >
< / div >
< hr / >
< div class = "row" >
< div class = "col-md-12" >
< label class = "control-label" > Backups< / label >
2019-09-16 14:03:13 +02:00
<!-- backup id copy helper -->
< input type = "text" class = "offscreen" aria-hidden = "true" id = "backupIdHelper" value = "" >
< table class = "table table-hover" style = "margin: 0;" >
< thead >
< tr >
< th width = "25px" > < / th >
< th > Created< / th >
< th > Version< / th >
< th class = "text-right" width = "180px" > Actions< / th >
< / tr >
< / thead >
< tbody >
< tr ng-hide = "backups.backups.length" >
< td colspan = "4" class = "text-center" > This app has no backups yet.< / td >
< / tr >
< tr ng-repeat = "backup in backups.backups" >
< td > < div ng-click = "backups.copyBackupId(backup)" class = "hand" uib-tooltip = "{{ backups.copyBackupIdDone ? 'Copied to clipboard' : 'Click to copy backup id' }}" tooltip-placement = "right" > < i class = "fa fa-copy" > < / i > < / div > < / td >
< td > {{ backup.creationTime | prettyDate }}< / td >
< td > v{{ backup.version }}< / td >
< td class = "text-right no-wrap" style = "vertical-align: bottom" >
< button class = "btn btn-xs btn-default" ng-hide = "backup.ackRestore" ng-click = "clone.show(backup)" uib-tooltip = "Clone from this Backup" > < i class = "far fa-clone" > < / i > < / button >
< button class = "btn btn-xs btn-danger" ng-hide = "backup.ackRestore" ng-disabled = "app.taskId" ng-click = "backup.ackRestore = true" uib-tooltip = "Restore to this Backup" > < i class = "fas fa-history" > < / i > < / button >
< button class = "btn btn-xs btn-danger" ng-show = "backup.ackRestore" ng-click = "backups.restore(backup)" > Yes restore now< / button >
< button class = "btn btn-xs btn-default" ng-show = "backup.ackRestore" ng-click = "backup.ackRestore = false" > Back< / button >
< / td >
< / tr >
< / tbody >
< / table >
< / div >
< / div >
2019-09-17 16:16:48 +02: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 12:24:22 -07:00
< label class = "control-label" > Automatic Backups< / label >
< p > Cloudron periodically creates a backup based on the < a href = "/#/backups" > backup< / a > settings. If you disable automatic backups, be sure to manually create backups often.< / p >
< p > Automatic Backups is currently < b > {{ backups.enableBackup ? 'enabled' : 'disabled' }}< / b > .< / p >
< button class = "btn btn-primary pull-right" ng-class = "{ 'btn-danger': backups.enableBackup }" ng-click = "backups.toggleAutomaticBackups()" ng-disabled = "backups.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "backups.busy" > < / i > {{ backups.enableBackup ? 'Disable' : 'Enable' }} Automatic Daily 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-09-16 14:03:13 +02:00
< div class = "card" ng-show = "view === 'debug'" >
< div class = "row" >
< div class = "col-md-12" >
2019-09-19 12:50:06 -07:00
< label class = "control-label" > Status< / label >
2019-09-17 15:32:43 +02:00
< p >
2019-09-19 12:50:06 -07:00
Version
< span class = "app-info-meta text-small" > {{ app.upstreamVersion }} (Package < a ng-href = "/#/appstore/{{app.manifest.id}}?version={{app.manifest.version}}" > v{{ app.manifest.version }}< / a > ) < / span >
< br / >
App ID < span class = "app-info-meta text-small" > {{ app.id }}< / a > < / span >
2019-09-17 15:32:43 +02:00
< / p >
2019-09-19 12:50:06 -07:00
< / div >
< / div >
< hr / >
< div class = "row" >
< div class = "col-md-12" >
< label class = "control-label" > Console Access< / label >
< p > This will open a console connection to the app. The terminal is sandboxed and only provides access to the app container's filesystem.< / p >
< a class = "btn btn-primary pull-right" ng-href = "{{ '/terminal.html?id=' + app.id }}" target = "_blank" > Open Terminal< / a >
2019-09-16 14:03:13 +02:00
< a class = "btn btn-primary pull-right" ng-href = "{{ '/logs.html?appId=' + app.id }}" target = "_blank" > Logs< / a >
< / div >
< / div >
2019-09-19 12:50:06 -07:00
< hr / >
< div class = "row" >
< div class = "col-md-12" >
< label class = "control-label" > Repair< / label >
< p > If the app is not responding, try restarting the app.< / p >
< button class = "btn btn-danger pull-right" ng-click = "debug.stopAppTask(app.taskId)" ng-show = "app.taskId" > Cancel Current Task< / button >
< button class = "btn btn-danger pull-right" ng-click = "debug.restartApp()" ng-disabled = "app.taskId || debug.appIsRestarting" > < i class = "fa fa-circle-notch fa-spin" ng-show = "debug.appIsRestarting" > < / i > Restart App< / button >
< / 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'" >
< div class = "row" >
< div class = "col-md-12" >
2019-09-19 18:01:12 -07:00
< label class = "control-label" > Uninstall< / label >
2019-09-19 10:28:17 -07:00
< p > This will uninstall the app immediately and remove all it's data. The site will be inaccessible.
< / p >
< p > App backups are not removed and will be cleaned up based on the backup policy. You can resurrect this app from an existing
2019-09-19 11:22:49 -07:00
app backup using the following < a target = "_blank" ng-href = "{{ config.webServerOrigin }}/documentation/backups/#restoring-uninstalled-app" > instructions< / a > .< / p >
2019-09-17 15:40:04 +02:00
< button class = "btn btn-danger pull-right" ng-click = "uninstall.ask()" > Uninstall< / button >
< / div >
< / div >
< / div >
2019-09-13 10:34:12 +02:00
< / div >
< / div >
2019-09-10 19:21:30 +02:00
< / div >