2018-01-22 13:01:38 -08:00
<!-- Modal configure/repair app -->
< div class = "modal fade" id = "appConfigureModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" ng-show = "(appConfigure.app | installError)" > Repair {{ appConfigure.app.fqdn }}< / h4 >
< h4 class = "modal-title" ng-hide = "(appConfigure.app | installError)" > Configure {{ appConfigure.app.fqdn }}< / h4 >
< / div >
2018-05-24 13:53:30 -07:00
< div class = "modal-body" style = "padding: 0 15px" >
2018-01-22 13:01:38 -08:00
< fieldset >
< form role = "form" name = "appConfigureForm" ng-submit = "appConfigure.submit()" autocomplete = "off" >
2019-01-19 22:04:46 -08:00
< uib-tabset active = "appConfigure.action" >
< uib-tab index = "'general'" heading = "General" >
2018-05-24 13:53:30 -07:00
< br / >
< div class = "has-error text-center" ng-show = "appConfigure.error.other" > {{ appConfigure.error.other }}< / div >
< div class = "form-group" ng-class = "{ 'has-error': (appConfigureForm.location.$dirty && appConfigureForm.location.$invalid) || (!appConfigureForm.location.$dirty && appConfigure.error.location) }" >
< label class = "control-label" for = "appConfigureLocationInput" > Location {{ appConfigure.error.location }} < / label >
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "appConfigure.location" id = "appConfigureLocationInput" 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" >
2018-08-27 20:45:09 -07:00
<!-- the admin check is to check for spaces user -->
< span ng-if = "user.admin" > {{ (!appConfigure.location ? '' : (appConfigure.domain.config.hyphenatedSubdomains ? '-' : '.')) + appConfigure.domain.domain }}< / span >
2018-08-28 14:22:40 -07:00
< span ng-if = "!user.admin" > {{ (!appConfigure.location ? '' : '-') + spacesSuffix + (appConfigure.domain.config.hyphenatedSubdomains ? '-' : '.') + appConfigure.domain.domain }}< / span >
2018-05-24 13:53:30 -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 = "appConfigure.domain = domain" > {{ domain.domain }}< / a >
< / li >
< / ul >
< / div >
< / div >
< / div >
2018-09-06 19:49:20 -07:00
< p class = "text-center" ng-show = "appConfigure.location && appConfigure.domain.provider === 'manual'" >
2018-05-24 13:53:30 -07:00
< b > Add an A record manually for {{ appConfigure.location }} to this Cloudron's public IP< / b >
< br >
< / p >
< div class = "has-error text-center" ng-show = "appConfigure.error.port" > {{ appConfigure.error.port }}< / div >
< div ng-repeat = "(env, info) in appConfigure.portBindingsInfo" >
< ng-form name = "portInfo_form" >
< div class = "form-group" ng-class = "{ 'has-error': (!appConfigureForm.itemName{{$index}}.$dirty && appConfigure.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }" >
2018-09-12 11:22:02 -07:00
< label class = "control-label" for = "appConfigurePortInput{{env}}" > < input type = "checkbox" ng-model = "appConfigure.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 >
2018-05-24 13:53:30 -07:00
< input type = "number" class = "form-control" ng-model = "appConfigure.portBindings[env]" ng-disabled = "!appConfigure.portBindingsEnabled[env]" id = "appConfigurePortInput{{env}}" later-name = "itemName{{$index}}" min = "{{HOST_PORT_MIN}}" max = "{{HOST_PORT_MAX}}" required >
< / div >
< / ng-form >
2018-01-22 13:01:38 -08:00
< / div >
2018-05-24 13:53:30 -07:00
2018-05-25 13:59:53 -07:00
< div class = "form-group" >
< label ng-show = "appConfigure.ssoAuth" class = "control-label" > User management< / label >
< label ng-show = "!appConfigure.ssoAuth" class = "control-label" > Dashboard visibility< / label >
< p ng-show = "!appConfigure.ssoAuth && !appConfigure.app.manifest.addons.email" class = "text-small" >
2018-05-24 13:53:30 -07:00
This app has it's own user management.
< / p >
2018-05-25 13:59:53 -07:00
< p ng-show = "!appConfigure.ssoAuth && appConfigure.app.manifest.addons.email" >
This app is pre-configured for use with < a href = "https://cloudron.io/documentation/email/" target = "_blank" > Cloudron Email< / a > .
2018-05-24 13:53:30 -07:00
< / p >
< div class = "radio" >
< label >
< input type = "radio" ng-model = "appConfigure.accessRestrictionOption" value = "any" >
2018-05-25 13:59:53 -07:00
< span ng-show = "appConfigure.ssoAuth" > Allow all users on this Cloudron< / span >
< span ng-show = "!appConfigure.ssoAuth" > Visible to all users on this Cloudron< / span >
2018-05-24 13:53:30 -07:00
< / label >
< / div >
< div class = "radio" >
< label >
< input type = "radio" ng-model = "appConfigure.accessRestrictionOption" value = "groups" >
2018-05-25 13:59:53 -07:00
< span ng-show = "appConfigure.ssoAuth" > Only allow the following users and groups< / span >
< span ng-show = "!appConfigure.ssoAuth" > Only visible to the following users and groups< / span >
< span class = "label label-danger" ng-show = "appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid()" > Select at least one user or group< / span >
2018-05-24 13:53:30 -07:00
< / label >
2018-01-22 13:01:38 -08:00
< / div >
2018-05-24 13:53:30 -07:00
< div >
< div style = "margin-left: 20px;" >
< div class = "col-md-5" >
Users:
< multiselect class = "input-sm stretch" ng-model = "appConfigure.accessRestriction.users" ng-disabled = "appConfigure.accessRestrictionOption !== 'groups'" options = "user.display for user in users" data-multiple = "true" > < / multiselect >
< / div >
< div class = "col-md-5" >
Groups:
2018-07-26 15:45:52 -07:00
< multiselect class = "input-sm stretch" ng-model = "appConfigure.accessRestriction.groups" ng-disabled = "appConfigure.accessRestrictionOption !== 'groups'" options = "group.name for group in groups" data-multiple = "true" > < / multiselect >
2018-05-24 13:53:30 -07:00
< / div >
< / div >
< / div >
< br / >
< br / >
< / div >
2018-01-22 13:01:38 -08:00
2018-05-24 13:53:30 -07:00
< / uib-tab >
2019-01-19 22:04:46 -08:00
< uib-tab index = "'advanced'" heading = "Advanced" >
2018-05-24 13:53:30 -07:00
< br / >
< 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 > {{ appConfigure.memoryLimit ? appConfigure.memoryLimit / 1024 / 1024 + 'MB' : 'Default (256 MB)' }}< / b > < / label >
< br / >
< div style = "padding: 0 10px;" >
< slider id = "memoryLimit" ng-model = "appConfigure.memoryLimit" step = "134217728" tooltip = "hide" ticks = "appConfigure.memoryTicks" ticks-snap-bounds = "67108864" > < / slider >
2018-01-22 13:01:38 -08:00
< / div >
< / div >
2018-12-06 22:31:38 -08:00
<!-- recvmail currently only works with cloudron email -->
< div class = "form-group" ng-show = "appConfigure.app.manifest.addons.sendmail || appConfigure.app.manifest.addons.recvmail" ng-class = "{ 'has-error': !appConfigureForm.mailboxName.$dirty && appConfigure.error.mailboxName }" >
2018-09-25 11:26:10 -07:00
< input type = "checkbox" id = "appConfigureMailboxNameEnabled" ng-model = "appConfigure.mailboxNameEnabled" >
< label class = "control-label" for = "appConfigureMailboxNameEnabled" > Custom Mailbox Name< / label >
< div class = "has-error" ng-show = "appConfigure.error.mailboxName" > {{ appConfigure.error.mailboxName }}< / div >
2018-05-24 16:05:19 -07:00
< div class = "input-group form-inline" >
2018-09-25 11:26:10 -07:00
< input type = "text" class = "form-control" id = "appConfigureMailboxNameInput" ng-required = "appConfigure.mailboxNameEnabled" name = "mailboxName" ng-model = "appConfigure.mailboxName" uib-tooltip = "App FROM email address. Addresses ending with '.app' are reserved." ng-disabled = "!appConfigure.mailboxNameEnabled" >
2018-05-24 16:05:19 -07:00
< div class = "input-group-btn" >
2018-09-25 11:26:10 -07:00
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" ng-disabled = "!appConfigure.mailboxNameEnabled" >
2018-05-24 16:05:19 -07:00
@{{ appConfigure.domain.domain }}
< / button >
< / div >
< / div >
2018-06-29 16:38:40 +02:00
< / div >
2018-05-24 16:05:19 -07:00
2018-06-29 16:38:40 +02:00
< div class = "form-group" ng-class = "{ 'has-error': (appConfigureForm.alternateSubdomain.$dirty && appConfigureForm.alternateSubdomain.$invalid) || (!appConfigureForm.alternateSubdomain.$dirty && appConfigure.error.alternateDomains) }" >
2018-08-20 09:44:30 -07:00
< input type = "checkbox" id = "appConfigureAlternateDomainEnabled" ng-model = "appConfigure.alternateDomainEnabled" >
< label class = "control-label" for = "appConfigureAlternateDomainEnabled" > Redirect the following domain to this app< / label >
2018-09-25 11:26:10 -07:00
< div class = "has-error" ng-show = "appConfigure.error.alternateDomains" > {{ appConfigure.error.alternateDomains }}< / div >
2018-06-29 16:38:40 +02:00
< div class = "input-group form-inline" >
2018-07-03 18:04:03 +02:00
< input type = "text" class = "form-control" ng-model = "appConfigure.alternateSubdomain" id = "appConfigureAlternateSubdomainInput" name = "alternateSubdomain" placeholder = "Leave empty to use bare domain" ng-disabled = "!appConfigure.alternateDomainEnabled" >
2018-06-29 16:38:40 +02:00
< div class = "input-group-btn" >
2018-07-03 18:00:22 +02:00
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" ng-disabled = "!appConfigure.alternateDomainEnabled" >
2018-09-04 12:10:21 -07:00
<!-- the admin check is to check for spaces user -->
< span ng-if = "user.admin" > {{ (!appConfigure.alternateSubdomain ? '' : (appConfigure.alternateDomain.config.hyphenatedSubdomains ? '-' : '.')) + appConfigure.alternateDomain.domain }}< / span >
< span ng-if = "!user.admin" > {{ (!appConfigure.alternateSubdomain ? '' : '-') + spacesSuffix + (appConfigure.alternateDomain.config.hyphenatedSubdomains ? '-' : '.') + appConfigure.alternateDomain.domain }}< / span >
2018-06-29 16:38:40 +02:00
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu dropdown-menu-right" role = "menu" >
< li ng-repeat = "domain in domains" >
2018-09-04 12:10:21 -07:00
< a href = "" ng-click = "appConfigure.alternateDomain = domain" > {{ domain.domain }}< / a >
2018-06-29 16:38:40 +02:00
< / li >
< / ul >
< / div >
< / div >
2018-05-24 15:40:26 -07:00
< / div >
2018-05-24 13:53:30 -07:00
< div class = "form-group" ng-class = "{ 'has-error': !appConfigureForm.xFrameOptions.$dirty && appConfigure.error.xFrameOptions }" >
< label class = "control-label" > Allow embedding from the following site< / label >
2018-05-24 15:40:26 -07:00
< div class = "control-label" ng-show = "appConfigure.error.xFrameOptions" > {{appConfigure.error.xFrameOptions}}< / div >
2018-05-24 13:53:30 -07:00
< input type = "text" class = "form-control" id = "appConfigureXFrameOptionsInput" name = "xFrameOptions" placeholder = "https://example.com" ng-model = "appConfigure.xFrameOptions" uib-tooltip = "Leave blank to not allow embedding" >
< / div >
2018-01-22 13:01:38 -08:00
2019-03-04 18:16:40 -08:00
< div ng-hide = "true" class = "form-group" ng-class = "{ 'has-error': !appConfigureForm.dataDir.$dirty && appConfigure.error.dataDir }" >
2019-01-15 11:13:41 -08:00
< input type = "checkbox" id = "appConfigureEnableDataDir" ng-model = "appConfigure.dataDirEnabled" >
< label class = "control-label" for = "appConfigureEnableDataDir" > Custom Data Directory< / label >
< div class = "control-label" ng-show = "appConfigure.error.dataDir" > {{appConfigure.error.dataDir}}< / div >
< input type = "text" class = "form-control" id = "appConfigureDataDirInput" name = "dataDir" ng-disabled = "!appConfigure.dataDirEnabled" placeholder = "/mnt/appdata" ng-model = "appConfigure.dataDir" >
< / div >
2018-05-24 13:53:30 -07:00
< div class = "form-group" >
2019-03-20 09:53:10 -07: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 = "appConfigure.robotsTxt = disableIndexingTemplate" > Disable indexing< / a > < / label >
< textarea ng-model = "appConfigure.robotsTxt" placeholder = "Leave empty to allow all bots to index this app." class = "form-control" rows = "4" > < / textarea >
2018-05-24 13:53:30 -07:00
< / div >
2018-01-22 13:01:38 -08:00
2018-05-24 13:53:30 -07:00
< div class = "form-group" >
< input type = "checkbox" id = "appConfigureEnableBackup" ng-model = "appConfigure.enableBackup" >
< label class = "control-label" for = "appConfigureEnableBackup" > Enable automatic daily backups< / label >
2018-01-22 13:01:38 -08:00
< / div >
2018-05-24 13:53:30 -07:00
2018-12-07 09:04:06 -08:00
< div class = "form-group" >
< input type = "checkbox" id = "appConfigureEnableAutomaticUpdate" ng-model = "appConfigure.enableAutomaticUpdate" >
< label class = "control-label" for = "appConfigureEnableAutomaticUpdate" > Enable automatic updates< / label >
< / div >
2018-05-24 13:53:30 -07:00
< 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 >
2018-01-22 13:01:38 -08:00
< / div >
2018-05-24 13:53:30 -07:00
< 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 >
2018-01-22 13:01:38 -08:00
< / div >
< / div >
2018-05-24 13:53:30 -07:00
< / uib-tab >
< / uib-tabset >
2018-01-22 13:01:38 -08:00
2018-02-08 09:44:35 +01:00
< input class = "ng-hide" type = "submit" ng-disabled = "appConfigureForm.$invalid || appConfigure.busy || (appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid())" / >
2018-01-22 13:01:38 -08:00
< / form >
< / fieldset >
< / div >
< div class = "modal-footer " >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
2018-11-16 17:03:21 +01:00
< button type = "button" class = "btn btn-success" ng-click = "appConfigure.submit()" ng-disabled = "appConfigureForm.$invalid || appConfigure.busy || (appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid())" > < i class = "fa fa-circle-notch fa-spin" ng-show = "appConfigure.busy" > < / i > Configure< / button >
2018-01-22 13:01:38 -08:00
< / div >
< / div >
< / div >
< / div >
<!-- Modal restore app -->
< div class = "modal fade" id = "appRestoreModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
2018-05-28 00:47:53 -07:00
< h4 class = "modal-title" > Backups - {{ appRestore.app.fqdn }}< / h4 >
2018-01-22 13:01:38 -08:00
< / div >
2018-05-30 09:34:31 -07:00
< div class = "modal-body" style = "padding: 0 15px" >
2018-11-16 17:03:21 +01:00
< p class = "text-center" ng-show = "appRestore.busyFetching" > < i class = "fa fa-circle-notch fa-spin" > < / i > Fetching backups< / p >
2018-05-29 22:05:18 +02:00
2018-11-16 17:03:21 +01:00
< button type = "button" class = "btn btn-primary pull-right" ng-click = "appRestore.createBackup()" ng-hide = "appRestore.busyFetching" ng-disabled = "appRestore.app.installationState === 'pending_backup'" > < i class = "fa fa-circle-notch fa-spin" ng-show = "appRestore.app.installationState === 'pending_backup'" > < / i > Create Backup< / button >
2018-05-30 15:07:16 +02:00
2018-05-30 15:13:05 +02:00
< uib-tabset active = "appRestore.action" ng-show = "!appRestore.busyFetching" >
2018-05-29 22:05:18 +02:00
<!-- restore -->
< uib-tab index = "'restore'" heading = "Restore" >
< br / >
2018-05-30 15:13:05 +02:00
< p class = "text-danger" ng-hide = "appRestore.backups.length" > This app has no backups to restore or clone from yet.< / p >
< div ng-show = "appRestore.backups.length" >
< p > Restoring the app will lose all content generated since the backup.< / p >
< label class = "control-label" > Select Backup< / label >
< div class = "dropdown" >
< button type = "button" class = "btn btn-default" data-toggle = "dropdown" > {{ appRestore.selectedBackup.creationTime | prettyDate }} - v{{appRestore.selectedBackup.version}} ({{ appRestore.selectedBackup.creationTime | prettyLongDate }}) < span class = "caret" > < / span > < / button >
< ul class = "dropdown-menu" role = "menu" >
< li ng-repeat = "backup in appRestore.backups | orderBy:'-creationTime'" >
< a href = "" ng-click = "appRestore.selectBackup(backup)" > {{ backup.creationTime | prettyDate }} - v{{backup.version}} ({{ backup.creationTime | prettyLongDate }})< / a >
< / li >
< / ul >
2019-02-23 18:28:15 -08:00
< input type = "text" class = "offscreen" aria-hidden = "true" id = "appRestoreSelectedBackupId" value = "{{appRestore.selectedBackup.id}}" >
2019-02-24 19:18:43 +01:00
< i style = "margin-left: 10px;" class = "fa fa-copy hand" uib-tooltip = "{{ appRestore.copyBackupIdDone ? 'Copied to clipboard' : 'Click to copy backup id' }}" tooltip-placement = "right" ng-click = "appRestore.copyBackupId()" > < / i >
2018-05-30 15:13:05 +02:00
< / div >
< br / >
< fieldset >
< form role = "form" ng-submit = "appRestore.restore()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': appRestore.error.password }" >
< label class = "control-label" for = "appRestorePasswordInput" > Provide your password to confirm this action< / label >
< div ng-show = "appRestore.error.password" > < small > Wrong password< / small > < / div >
< input type = "password" class = "form-control" ng-model = "appRestore.password" id = "appRestorePasswordInput" name = "password" required autofocus >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "!appRestore.password || appRestore.busy || !appRestore.selectedBackup" / >
< / form >
< / fieldset >
2018-05-29 22:05:18 +02:00
< / div >
< / uib-tab >
<!-- clone -->
< uib-tab index = "'clone'" heading = "Clone" >
< br / >
2018-05-30 15:13:05 +02:00
< p class = "text-danger" ng-hide = "appRestore.backups.length" > This app has no backups to restore or clone from yet.< / p >
< div ng-show = "appRestore.backups.length" >
< label class = "control-label" > Select Backup< / label >
< div class = "dropdown" >
< button type = "button" class = "btn btn-default" data-toggle = "dropdown" > {{ appRestore.selectedBackup.creationTime | prettyDate }} - v{{appRestore.selectedBackup.version}} ({{ appRestore.selectedBackup.creationTime | prettyLongDate }}) < span class = "caret" > < / span > < / button >
< ul class = "dropdown-menu" role = "menu" >
< li ng-repeat = "backup in appRestore.backups | orderBy:'-creationTime'" >
< a href = "" ng-click = "appRestore.selectBackup(backup)" > {{ backup.creationTime | prettyDate }} - v{{backup.version}} ({{ backup.creationTime | prettyLongDate }})< / a >
< / li >
< / ul >
< / div >
< br / >
< fieldset >
2018-08-06 00:34:40 -07:00
< form role = "form" ng-submit = "appRestore.clone()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': appRestore.error.location }" >
< label class = "control-label" for = "appRestoreLocationInput" > Location< / label >
< div ng-show = "appRestore.error.location" > < small > {{ appRestore.error.location }}< / small > < / div >
2018-05-30 15:13:05 +02:00
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "appRestore.location" id = "appRestoreLocationInput" 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" >
2018-08-27 21:40:49 -07:00
<!-- the admin check is to check for spaces user -->
< span ng-if = "user.admin" > {{ (!appRestore.location ? '' : (appRestore.domain.config.hyphenatedSubdomains ? '-' : '.')) + appRestore.domain.domain }}< / span >
2018-08-28 14:22:40 -07:00
< span ng-if = "!user.admin" > {{ (!appRestore.location ? '' : '-') + spacesSuffix + (appRestore.domain.config.hyphenatedSubdomains ? '-' : '.') + appRestore.domain.domain }}< / span >
2018-05-30 15:13:05 +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 = "appRestore.domain = domain" > {{ domain.domain }}< / a >
< / li >
< / ul >
< / div >
2018-05-29 22:05:18 +02:00
< / div >
2018-05-28 00:47:53 -07:00
< / div >
2018-05-29 22:05:18 +02:00
2018-09-06 19:49:20 -07:00
< p class = "text-center" ng-show = "appRestore.location && appRestore.domain.provider === 'manual'" >
2018-05-30 15:13:05 +02:00
< b > Add an A record manually for {{ appRestore.location }} to this Cloudron's public IP< / b >
< br >
< / p >
2018-05-29 22:05:18 +02:00
2018-05-30 15:13:05 +02:00
< div class = "has-error text-center" ng-show = "appRestore.error.port" > {{ appRestore.error.port }}< / div >
< div ng-repeat = "(env, info) in appRestore.portBindingsInfo" >
< ng-form name = "portInfo_form" >
< div class = "form-group" ng-class = "{ 'has-error': (!appRestore.itemName{{$index}}.$dirty && appRestore.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }" >
2018-09-12 11:22:02 -07:00
< label class = "control-label" for = "inputPortInfo{{env}}" > < input type = "checkbox" ng-model = "appRestore.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 >
2018-05-30 15:13:05 +02:00
< input type = "number" class = "form-control" ng-model = "appRestore.portBindings[env]" ng-disabled = "!appRestore.portBindingsEnabled[env]" id = "inputPortInfo{{env}}" later-name = "itemName{{$index}}" min = "{{hostPortMin}}" max = "{{hostPortMax}}" required >
< / div >
< / ng-form >
< / div >
< / form >
< / fieldset >
< / div >
2018-05-29 22:05:18 +02:00
< / uib-tab >
< / uib-tabset >
2018-01-22 13:01:38 -08:00
< / div >
2018-05-23 20:36:54 -07:00
2018-01-22 13:01:38 -08:00
< div class = "modal-footer" >
2018-05-29 22:05:18 +02:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
2018-11-16 17:03:21 +01:00
< button type = "button" class = "btn btn-success" ng-click = "appRestore.clone()" ng-show = "appRestore.action === 'clone' && appRestore.backups.length !== 0" ng-disabled = "appRestore.busy || !appRestore.selectedBackup" > < i class = "fa fa-circle-notch fa-spin" ng-show = "appRestore.busy" > < / i > Clone< / button >
< button type = "button" class = "btn btn-danger" ng-click = "appRestore.restore()" ng-show = "appRestore.action === 'restore' && appRestore.backups.length !== 0" ng-disabled = "!appRestore.password || appRestore.busy || !appRestore.selectedBackup" > < i class = "fa fa-circle-notch fa-spin" ng-show = "appRestore.busy" > < / i > Restore< / button >
2018-01-22 13:01:38 -08:00
< / div >
< / div >
< / div >
< / div >
<!-- Modal information of app -->
< div class = "modal fade" id = "appInfoModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< img ng-src = "{{appInfo.app.iconUrl}}" onerror = "this.onerror=null;this.src='img/appicon_fallback.png'" class = "app-info-icon" / >
< h5 class = "app-info-title" >
{{ appInfo.app.manifest.title }}
2019-03-20 09:22:54 -07:00
< span class = "app-info-meta text-small" > {{ appInfo.app.upstreamVersion }} (Package < a ng-href = "/#/appstore/{{appInfo.app.manifest.id}}?version={{appInfo.app.manifest.version}}" > v{{ appInfo.app.manifest.version }}< / a > ) < / span >
2018-08-05 21:10:18 -07:00
< br / >
App ID < span class = "app-info-meta text-small" > {{ appInfo.app.id }}< / a > < / span >
< br / >
Last updated < span class = "app-info-meta text-small" > {{ appInfo.app.updateTime | prettyDate }}< / span >
2018-01-22 13:01:38 -08:00
< / h5 >
< / div >
< div class = "modal-body" >
< div class = "app-postinstall-message" ng-hide = "appInfo.app.manifest && appInfo.app.manifest.postInstallMessage" >
This package has no special usage information.
< / div >
< div class = "app-postinstall-message" ng-show = "appInfo.app.manifest && appInfo.app.manifest.postInstallMessage" >
< div ng-bind-html = "appInfo.message | postInstallMessage:appInfo.app | markdown2html" > < / div >
< / div >
2019-03-20 21:29:24 -07:00
< div ng-show = "appInfo.app.manifest.addons.localstorage.ftp" >
< br / >
< b > SFTP< / b > < 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 }}@{{ appInfo.app.fqdn }}< br / >
< / div >
2018-01-22 13:01:38 -08:00
< / div >
< div class = "modal-footer" >
2019-03-20 09:32:33 -07:00
< a ng-show = "appInfo.app.manifest.documentationUrl" target = "_blank" ng-href = "{{appInfo.app.manifest.documentationUrl}}" class = "btn btn-info pull-left" > Documentation< / a >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" autofocus > Close< / button >
2018-01-22 13:01:38 -08:00
< / div >
< / div >
< / div >
< / div >
2018-06-14 15:46:55 +02:00
<!-- Modal postinstall confirm -->
< div class = "modal fade" id = "appPostInstallConfirmModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< img ng-src = "{{appPostInstallConfirm.app.iconUrl}}" onerror = "this.onerror=null;this.src='img/appicon_fallback.png'" class = "app-info-icon" / >
< h5 class = "app-info-title" >
{{ appPostInstallConfirm.app.manifest.title }}
< span class = "app-info-meta text-small" > Package < a ng-href = "/#/appstore/{{appPostInstallConfirm.app.manifest.id}}?version={{appPostInstallConfirm.app.manifest.version}}" > v{{ appPostInstallConfirm.app.manifest.version }}< / a > < / span >
2018-08-05 21:36:40 -07:00
< br / >
< span ng-show = "appPostInstallConfirm.app.manifest.documentationUrl" > < a target = "_blank" ng-href = "{{appPostInstallConfirm.app.manifest.documentationUrl}}" > Documentation< / a > < / span >
< br / >
2018-06-14 15:46:55 +02:00
< / h5 >
< / div >
< div class = "modal-body" >
< div ng-bind-html = "appPostInstallConfirm.app.manifest.postInstallMessage | postInstallMessage:appPostInstallConfirm.app | markdown2html" > < / div >
2018-08-21 19:12:04 -07:00
< div ng-show = "appPostInstallConfirm.app.manifest.documentationUrl" >
Please see the < a target = "_blank" ng-href = "{{appPostInstallConfirm.app.manifest.documentationUrl}}" > documentation< / a > for more information.
< / div >
2018-06-14 15:46:55 +02:00
< / div >
< div class = "modal-footer" >
< div class = "form-group pull-left" >
< input type = "checkbox" id = "appPostInstallConfirmCheckbox" ng-model = "appPostInstallConfirm.confirmed" >
2018-08-05 21:09:16 -07:00
< label class = "control-label" for = "appPostInstallConfirmCheckbox" > Acknowledge instructions< / label >
2018-06-14 15:46:55 +02:00
< / div >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
< a class = "btn btn-success" ng-href = "{{ appPostInstallConfirm.confirmed ? ('https://' + appPostInstallConfirm.app.fqdn) : '' }}" target = "_blank" ng-disabled = "!appPostInstallConfirm.confirmed" ng-click = "appPostInstallConfirm.submit()" > Open {{ appPostInstallConfirm.app.manifest.title }}< / a >
< / div >
< / div >
< / div >
< / div >
2018-01-22 13:01:38 -08:00
<!-- Modal error app -->
< div class = "modal fade" id = "appErrorModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" > Error for {{ appError.app.fqdn }}< / h4 >
< / div >
< div class = "modal-body" >
< p > {{ appError.app.message | prettyAppMessage }}< / p >
< / div >
< div class = "modal-footer" >
2018-11-20 11:39:32 +01:00
< button type = "button" class = "btn btn-primary pull-left" ng-click = "appConfigure.show(appError.app)" autofocus > Repair< / button >
2018-12-08 21:45:49 -08:00
< a type = "button" class = "btn btn-default pull-left" ng-href = "{{ '/logs.html?appId=' + appError.app.id }}" target = "_blank" > Logs< / a >
2018-11-20 11:39:32 +01:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
2018-01-22 13:01:38 -08:00
< / div >
< / div >
< / div >
< / div >
<!-- Modal uninstall app -->
< div class = "modal fade" id = "appUninstallModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" > Really uninstall {{ appUninstall.app.fqdn }} ?< / h4 >
< / div >
< div class = "modal-body" >
< p > Deleting the app will also remove all content generated within this app!< / p >
< fieldset >
< form role = "form" name = "appUninstallForm" ng-submit = "doUninstall()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': (appUninstallForm.password.$dirty && appUninstallForm.password.$invalid) || (!appUninstallForm.password.$dirty && appUninstall.error.password) }" >
< label class = "control-label" for = "appUninstallPasswordInput" > Provide your password to confirm this action< / label >
< div class = "control-label" ng-show = "(appUninstallForm.password.$dirty && appUninstallForm.password.$invalid) || (!appUninstallForm.password.$dirty && appUninstall.error.password)" >
< small ng-show = " appUninstallForm.password.$dirty && appUninstallForm.password.$invalid" > Password required< / small >
< small ng-show = "!appUninstallForm.password.$dirty && appUninstall.error.password" > Wrong password< / small >
< / div >
< input type = "password" class = "form-control" ng-model = "appUninstall.password" id = "appUninstallPasswordInput" name = "password" required autofocus >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "appUninstallForm.$invalid || busy" / >
< / form >
< / fieldset >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
2018-11-16 17:03:21 +01:00
< button type = "button" class = "btn btn-danger" ng-click = "doUninstall()" ng-disabled = "appUninstallForm.$invalid || appUninstall.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "appUninstall.busy" > < / i > Uninstall< / button >
2018-01-22 13:01:38 -08:00
< / div >
< / div >
< / div >
< / div >
<!-- Modal update app -->
< div class = "modal fade" id = "appUpdateModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" > Update {{ appUpdate.app.fqdn }}< / h4 >
< / div >
< div class = "modal-body" >
< p > Recent Changes for new version < b > {{ appUpdate.manifest.version}}< / b > :< / p >
< div ng-bind-html = "appUpdate.manifest.changelog | markdown2html" > < / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Cancel< / button >
2018-11-16 17:03:21 +01:00
< button type = "button" class = "btn btn-danger" ng-click = "doUpdate()" ng-disabled = "appUpdate.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "appUpdate.busy" > < / i > Update< / button >
2018-01-22 13:01:38 -08:00
< / div >
< / div >
< / div >
< / div >
< script >
function imageErrorHandler(elem) {
'use strict';
var appstoreIconUrl = elem.getAttribute('appstore-icon');
var fallbackIconUrl = elem.getAttribute('fallback-icon');
if (elem.src === appstoreIconUrl) {
elem.src = fallbackIconUrl;
elem.onerror = null; // avoid retry after default icon cannot be loaded
} else {
elem.src = appstoreIconUrl;
}
}
< / script >
< div class = "content content-large" >
<!-- Workaround for select - all issue, see commit message -->
< div style = "font-size: 1px;" > < / div >
2018-08-28 21:35:17 -07:00
< div class = "animateMeOpacity ng-hide" ng-show = "installedApps.length === 0 && (user.admin || config.features.spaces)" >
2018-01-22 13:01:38 -08:00
< div class = "col-md-12" style = "text-align: center;" >
< br / > < br / > < br / > < br / >
< h1 > < i class = "fa fa-cloud-download fa-fw" > < / i > No apps installed yet!< / h1 >
< br / > < / br >
< h3 > How about installing some? Check out the < a href = "#/appstore" > App Store< / a > < / h3 >
< / div >
< / div >
2018-08-28 21:35:17 -07:00
< div class = "animateMeOpacity ng-hide" ng-show = "installedApps.length === 0 && !(user.admin || config.features.spaces)" >
2018-01-22 13:01:38 -08:00
< div class = "col-md-12" style = "text-align: center;" >
< br / > < br / > < br / > < br / >
< h1 > You don't have access to any apps on this Cloudron yet!< / h1 >
< br / > < / br >
< h3 > Once you do, they will show up here.< / h3 >
< / div >
< / div >
< div class = "animateMeOpacity ng-hide" ng-show = "installedApps.length > 0" >
2019-04-02 13:11:00 +02:00
< h1 class = "view-header" > Your Apps< / h1 >
2018-01-22 13:01:38 -08:00
< / div >
< div class = "animateMeOpacity ng-hide" ng-show = "installedApps.length > 0" >
2019-03-25 16:17:53 +01:00
< div ng-repeat = "domain in domains" ng-show = "domainHasApps(domain)" >
2019-03-25 16:33:49 +01:00
< h2 class = "domain-header" ng-show = "moreThanOneDomainHasApps()" > {{ domain.domain }}< / h2 >
2019-03-25 10:42:31 +01:00
< div class = "app-grid" >
2019-04-02 12:41:23 +02:00
< div class = "grid-item" ng-repeat = "app in installedApps | filter:{domain:domain.domain}:true | orderBy:'location'" >
2019-03-25 10:42:31 +01:00
< a ng-href = "{{ app | applicationLink }}" ng-click = "((app | installError) === true && showError(app)) || ((app | appIsInstalledAndHealthy) && app.pendingPostInstallConfirmation && appPostInstallConfirm.show(app))" target = "_blank" ng-class = "{ 'hand': (app | appIsInstalledAndHealthy) }" >
< div style = "background-color: white;" class = "highlight grid-item-content" uib-tooltip = "{{ app.fqdn }}" >
< div class = "grid-item-top" >
< div class = "row" >
< div class = "col-xs-12 text-center" style = "padding-left: 5px; padding-right: 5px;" >
< br / >
< img ng-src = "{{app.iconUrl || 'img/appicon_fallback.png'}}" fallback-icon = "img/appicon_fallback.png" appstore-icon = "{{ app.iconUrlStore }}" onerror = "imageErrorHandler(this)" class = "app-icon" / >
2018-01-22 13:01:38 -08:00
< / div >
2019-03-25 10:42:31 +01:00
< / div >
< br / >
< div class = "row" >
< div class = "col-xs-12 text-center" >
< div class = "grid-item-top-title" data-fittext > {{ app.location || app.fqdn }}< / div >
< div class = "text-muted status" style = "text-overflow: ellipsis; white-space: nowrap; overflow: hidden" uib-tooltip = "{{ app.message | shortAppMessage }}" >
{{ app | installationStateLabel }}
< / div >
< div class = "status" ng-style = "{ 'visibility': (app | installationActive) ? 'visible' : 'hidden' }" >
< div class = "progress progress-striped active" >
< div class = "progress-bar progress-bar-success" role = "progressbar" style = "width: {{ app.progress }}%" > < / div >
2018-01-22 13:01:38 -08:00
< / div >
2019-03-25 10:42:31 +01:00
< / div >
2018-01-22 13:01:38 -08:00
< / div >
2019-03-25 10:42:31 +01:00
< / div >
< / div >
2018-01-22 13:01:38 -08:00
2019-04-02 13:17:30 +02:00
< div class = "grid-item-actions" ng-show = "user.admin || (config.features.spaces && app.ownerId === user.id)" >
< a href = "" ng-click = "showUninstall(app)" uib-tooltip = "Uninstall" tooltip-placement = "right" tooltip-class = "app-tooltip" > < i class = "fa fa-times scale" > < / i > < / a >
< a href = "" ng-click = "appRestore.show(app)" ng-show = "backupConfig.provider !== 'noop'" uib-tooltip = "Backups" tooltip-placement = "right" tooltip-class = "app-tooltip" > < i class = "fa fa-archive scale" > < / i > < / a >
< a href = "" ng-click = "appConfigure.show(app)" ng-show = "(app.installationState === 'installed' || app.installationState === 'pending_configure') && !(app | installError)" uib-tooltip = "Configure" tooltip-placement = "right" tooltip-class = "app-tooltip" > < i class = "fa fa-pencil-alt scale" > < / i > < / a >
< a href = "" ng-click = "appConfigure.show(app)" ng-show = "app | installError" uib-tooltip = "Repair" tooltip-placement = "right" tooltip-class = "app-tooltip" > < i class = "fa fa-wrench scale" > < / i > < / a >
< a ng-href = "{{ '/terminal.html?id=' + app.id }}" target = "_blank" uib-tooltip = "Terminal" tooltip-placement = "right" tooltip-class = "app-tooltip" > < i class = "fa fa-terminal scale" > < / i > < / a >
< a ng-href = "{{ '/logs.html?appId=' + app.id }}" target = "_blank" uib-tooltip = "Logs" tooltip-placement = "right" tooltip-class = "app-tooltip" > < i class = "fa fa-file-alt scale" > < / i > < / a >
< a href = "" ng-click = "showInformation(app)" uib-tooltip = "Information" tooltip-placement = "right" tooltip-class = "app-tooltip" > < i class = "fa fa-info-circle scale" > < / i > < / a >
2019-03-25 10:42:31 +01:00
< / div >
2018-01-22 13:01:38 -08:00
2019-03-25 10:42:31 +01:00
<!-- we check the version here because the box updater does not know when an app gets updated -->
2019-04-02 13:11:00 +02:00
< div class = "app-update-badge" ng-click = "showUpdate(app, config.update.apps[app.id].manifest)" ng-show = "config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)" >
Update available
2019-03-25 10:42:31 +01:00
< / div >
< / div >
< / a >
< / div >
2018-01-22 13:01:38 -08:00
< / div >
2019-03-25 10:42:31 +01:00
< / div >
2018-01-22 13:01:38 -08:00
< / div >
< / div >