2018-01-22 13:01:38 -08:00
<!-- Modal install app -->
2019-07-31 08:09:36 +02:00
< div class = "modal fade appstore-install" id = "appInstallModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
2020-11-13 21:58:09 +01:00
< img ng-src = "{{appInstall.app.iconUrl}}" onerror = "this.onerror=null; this.src='img/appicon_fallback.png'" class = "app-icon" / >
< h3 class = "appstore-install-title" > {{ appInstall.app.manifest.title }}< / h3 >
2019-07-31 08:09:36 +02:00
< br / >
< span class = "appstore-install-meta" > < a href = "{{ appInstall.app.manifest.website }}" target = "_blank" > {{ appInstall.app.manifest.author }}< / a > < / span >
< br / >
2020-11-13 21:58:09 +01:00
< span class = "appstore-install-meta" > {{ 'appstore.installDialog.lastUpdated' | tr:{ date: (appInstall.app.creationDate | prettyDate) } }}< / span >
2019-07-31 08:09:36 +02:00
< br / >
Fix SI and Decimal unit usage
SI: For 1000, it is kB, MB, GB
IEC: For 1024, it is KiB, MiB, GiB
JEDEC: For 1024, it is KB, MB (conflicts with SI, of course)
Ultimately, what we decided is for RAM use IEC and for Disk use SI.
This is what docker does and also suggested here -
https://stackoverflow.com/questions/8632269/displaying-file-size-1000b-1kb-or-1024b-1kb
2022-10-13 21:48:03 +02:00
< span class = "appstore-install-meta" > {{ 'appstore.installDialog.memoryRequirement' | tr:{ size: (appInstall.app.manifest.memoryLimit | prettyBinarySize:'256 MB') } }}< / span >
2019-07-31 08:09:36 +02:00
< / div >
< div class = "modal-body" >
< div class = "collapse" id = "collapseInstallForm" data-toggle = "false" >
< form role = "form" name = "appInstallForm" ng-submit = "appInstall.submit()" autocomplete = "off" >
< div class = "has-error text-center" ng-show = "appInstall.error.other" ng-bind-html = "appInstall.error.other" > < / div >
< div class = "form-group" ng-class = "{ 'has-error': (appInstallForm.location.$dirty && appInstallForm.location.$invalid) || (!appInstallForm.location.$dirty && appInstall.error.location) }" >
2020-11-13 21:58:09 +01:00
< label class = "control-label" for = "appInstallLocationInput" > {{ 'appstore.installDialog.location' | tr }}< / label >
2019-07-31 08:09:36 +02:00
< div class = "input-group form-inline" >
2022-01-16 18:29:32 -08:00
< input type = "text" class = "form-control" ng-model = "appInstall.subdomain" id = "appInstallLocationInput" name = "location" placeholder = "{{ 'appstore.installDialog.locationPlaceholder' | tr }}" autofocus >
2019-07-31 08:09:36 +02:00
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
2022-01-16 18:29:32 -08:00
< span > {{ '.' + appInstall.domain.domain }}< / span >
2019-07-31 08:09:36 +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 = "appInstall.domain = domain" > {{ domain.domain }}< / a >
< / li >
< / ul >
< / div >
< / div >
2020-11-13 21:58:09 +01:00
< div ng-show = "appInstall.error.location" class = "text-small" > {{ appInstall.error.location }}< / div >
2018-01-22 13:01:38 -08:00
< / div >
2022-01-16 18:29:32 -08:00
< p class = "text-small text-warning" ng-show = "appInstall.domain.provider === 'noop' || appInstall.domain.provider === 'manual'" ng-bind-html = "'appstore.installDialog.manualWarning' | tr:{ location: ((appInstall.subdomain ? appInstall.subdomain + '.' : '') + appInstall.domain.domain) }" > < / p >
2018-01-22 13:01:38 -08:00
2022-01-20 16:58:00 -08:00
< div class = "has-error text-center" ng-show = "appInstall.error.secondaryDomain" > {{ appInstall.error.secondaryDomain }}< / div >
< div ng-repeat = "(env, info) in appInstall.app.manifest.httpPorts" >
< ng-form name = "secondaryDomainInfo_form" >
< div class = "form-group" ng-class = "{ 'has-error': (!secondaryDomainInfo_form.itemName{{$index}}.$dirty && appInstall.error.secondaryDomain) || (secondaryDomainInfo_form.itemName{{$index}}.$dirty && secondaryDomainInfo_form.itemName{{$index}}.$invalid) }" >
< label class = "control-label" for = "secondaryDomainInput{{env}}" >
{{ info.title }}
< sup >
< a popover-placement = "top-right" popover-trigger = "outsideClick" uib-popover = "{{info.description}}" > < i class = "fa fa-question-circle" > < / i > < / a >
< / sup >
< / label >
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "appInstall.secondaryDomains[env].subdomain" name = "location{{$index}}" placeholder = "{{ 'appstore.installDialog.locationPlaceholder' | tr }}" autofocus >
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
< span > .{{ appInstall.secondaryDomains[env].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 = "appInstall.secondaryDomains[env].domain = domain" > {{ domain.domain }}< / a >
< / li >
< / ul >
< / div >
< / div >
< / div >
< / ng-form >
< / div >
2019-07-31 08:09:36 +02:00
< div class = "has-error text-center" ng-show = "appInstall.error.port" > {{ appInstall.error.port }}< / div >
< div ng-repeat = "(env, info) in appInstall.portBindingsInfo" >
< ng-form name = "portInfo_form" >
< div class = "form-group" ng-class = "{ 'has-error': (!appInstallForm.itemName{{$index}}.$dirty && appInstall.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }" >
< label class = "control-label" for = "inputPortInfo{{env}}" > < input type = "checkbox" ng-model = "appInstall.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 >
2022-08-10 18:01:27 +02:00
< small style = "padding-left: 5px;" ng-show = "info.readOnly" > {{ 'appstore.installDialog.portReadOnly' | tr }}< / small >
2019-07-31 08:09:36 +02:00
< / label >
2022-08-10 18:01:27 +02:00
< input type = "number" class = "form-control" ng-model = "appInstall.portBindings[env]" ng-disabled = "!appInstall.portBindingsEnabled[env]" ng-readonly = "info.readOnly" id = "inputPortInfo{{env}}" later-name = "itemName{{$index}}" min = "{{hostPortMin}}" max = "{{hostPortMax}}" required >
2021-09-20 11:24:08 -07:00
< p class = "text-small text-warning text-bold" ng-show = "appInstall.domain.provider === 'cloudflare'" > {{ 'appstore.installDialog.cloudflarePortWarning' | tr }} < / p >
2019-07-31 08:09:36 +02:00
< / div >
< / ng-form >
< / div >
2018-01-22 13:01:38 -08:00
2022-06-09 14:21:50 +02:00
< div class = "form-group" ng-show = "isProxyApp(appInstall.app)" >
< label class = "control-label" for = "appInstallUpstreamUriInput" > Upstream URI< / label >
< input type = "text" class = "form-control" ng-model = "appInstall.upstreamUri" id = "appInstallUpstreamUriInput" name = "upstreamUri" ng-required = "isProxyApp(appInstall.app)" >
< / div >
2020-02-25 13:00:36 +01:00
< div class = "form-group" ng-show = "appInstall.app.manifest.addons.email" >
2020-11-13 21:58:09 +01:00
< label class = "control-label" > {{ 'appstore.installDialog.userManagement' | tr }}< / label >
2021-03-17 14:11:08 -07:00
< p > {{ 'appstore.installDialog.userManagementMailbox' | tr }}
< span ng-bind-html = "'appstore.installDialog.configuredForCloudronEmail' | tr:{ emailDocsLink: 'https://docs.cloudron.io/email/' }" >
< / p >
2019-07-31 08:09:36 +02:00
< / div >
2018-01-22 13:01:38 -08:00
2021-03-01 20:16:05 +01:00
< div class = "form-group" >
2021-03-17 14:11:08 -07:00
< label class = "control-label" ng-show = "!appInstall.customAuth && !appInstall.app.manifest.addons.email" > {{ 'appstore.installDialog.userManagement' | tr }} < sup > < a ng-href = "https://docs.cloudron.io/apps/#access-restriction" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
2021-03-02 20:49:47 -08:00
< label class = "control-label" ng-show = "appInstall.customAuth || appInstall.app.manifest.addons.email" > {{ 'app.accessControl.userManagement.dashboardVisibility' | tr }} < sup > < a ng-href = "https://docs.cloudron.io/apps/#dashboard-visibility" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
2021-03-01 18:52:10 +01:00
2022-05-25 17:38:47 +02:00
< p ng-show = "appInstall.customAuth || appInstall.app.manifest.addons.email" > {{ 'appstore.installDialog.userManagementNone' | tr }}< / p >
2019-07-31 08:09:36 +02:00
< div class = "radio" ng-show = "appInstall.optionalSso" >
< label >
2020-11-13 21:58:09 +01:00
< input type = "radio" ng-model = "appInstall.accessRestrictionOption" value = "nosso" > {{ 'appstore.installDialog.userManagementLeaveToApp' | tr }}
2019-07-31 08:09:36 +02:00
< / label >
< / div >
< div class = "radio" >
< label >
2021-03-01 18:52:10 +01:00
< input type = "radio" ng-model = "appInstall.accessRestrictionOption" value = "any" >
< span ng-show = "!appInstall.customAuth" > {{ 'appstore.installDialog.userManagementAllUsers' | tr }}< / span >
< span ng-show = "appInstall.customAuth" > {{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}< / span >
2019-07-31 08:09:36 +02:00
< / label >
< / div >
< div class = "radio" >
< label >
2021-03-01 18:52:10 +01:00
< input type = "radio" ng-model = "appInstall.accessRestrictionOption" value = "groups" >
< span ng-show = "!appInstall.customAuth" > {{ 'appstore.installDialog.userManagementSelectUsers' | tr }}< / span >
< span ng-show = "appInstall.customAuth" > {{ 'app.accessControl.userManagement.visibleForSelected' | tr }}< / span >
2020-11-13 21:58:09 +01:00
< span class = "label label-danger" ng-show = "appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()" > {{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}< / span >
2019-07-31 08:09:36 +02:00
< / label >
< / div >
< div >
< div style = "margin-left: 20px;" >
< div class = "col-md-5" >
2020-11-13 21:58:09 +01:00
{{ 'appstore.installDialog.users' | tr }}:
2022-03-04 13:36:25 +01:00
< multiselect ng-model = "appInstall.accessRestriction.users" ng-disabled = "appInstall.accessRestrictionOption !== 'groups'" options = "(user.username || user.email) for user in users" data-multiple = "true" filter-after-rows = "5" scroll-after-rows = "10" > < / multiselect >
2019-07-31 08:09:36 +02:00
< / div >
2018-01-22 13:01:38 -08:00
2019-07-31 08:09:36 +02:00
< div class = "col-md-5" >
2020-11-13 21:58:09 +01:00
{{ 'appstore.installDialog.groups' | tr }}:
2019-11-05 19:45:59 +01:00
< multiselect ng-model = "appInstall.accessRestriction.groups" ng-disabled = "appInstall.accessRestrictionOption !== 'groups'" options = "group.name for group in groups" data-multiple = "true" filter-after-rows = "5" scroll-after-rows = "10" > < / multiselect >
2019-07-31 08:09:36 +02:00
< / div >
< / div >
< / div >
2018-01-22 13:01:38 -08:00
2019-07-31 08:09:36 +02:00
< br / >
< br / >
< / div >
2018-01-22 13:01:38 -08:00
2019-07-31 08:09:36 +02:00
< div class = "hide" >
2021-01-12 19:48:48 -08:00
< label class = "control-label" for = "appInstallCertificateInput" > Certificate (optional)< / label >
< div class = "has-error text-center" ng-show = "appInstall.error.cert" > {{ appInstall.error.cert }}< / div >
< div class = "form-group" ng-class = "{ 'has-error': !appInstallForm.certificate.$dirty && appInstall.error.cert }" >
2019-07-31 08:09:36 +02:00
< div class = "input-group" >
< input type = "file" id = "appInstallCertificateFileInput" style = "display:none" / >
< input type = "text" class = "form-control" placeholder = "Certificate" ng-model = "appInstall.certificateFileName" id = "appInstallCertificateInput" name = "certificate" onclick = "getElementById('appInstallCertificateFileInput').click();" style = "cursor: pointer;" ng-required = "appInstall.keyFileName" >
< span class = "input-group-addon" >
< i class = "fa fa-upload" onclick = "getElementById('appInstallCertificateFileInput').click();" > < / i >
< / span >
2018-01-22 13:01:38 -08:00
< / div >
2019-07-31 08:09:36 +02:00
< / div >
2021-01-12 19:48:48 -08:00
< div class = "form-group" ng-class = "{ 'has-error': !appInstallForm.key.$dirty && appInstall.error.cert }" >
2019-07-31 08:09:36 +02:00
< div class = "input-group" >
< input type = "file" id = "appInstallKeyFileInput" style = "display:none" / >
< input type = "text" class = "form-control" placeholder = "Key" ng-model = "appInstall.keyFileName" id = "appInstallKeyInput" name = "key" onclick = "getElementById('appInstallKeyFileInput').click();" style = "cursor: pointer;" ng-required = "appInstall.certificateFileName" >
< span class = "input-group-addon" >
< i class = "fa fa-upload" onclick = "getElementById('appInstallKeyFileInput').click();" > < / i >
< / span >
2018-01-22 13:01:38 -08:00
< / div >
2019-07-31 08:09:36 +02:00
< / div >
2018-01-22 13:01:38 -08:00
< / div >
2019-07-31 08:09:36 +02:00
2021-02-17 17:09:46 +01:00
< input class = "ng-hide" type = "submit" ng-disabled = "(appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()) || !appInstall.accessRestrictionOption || appInstallForm.$invalid || busy" / >
2019-07-31 08:09:36 +02:00
< / form >
< / div >
< div class = "collapse" id = "collapseMediaLinksCarousel" data-toggle = "false" >
< div ng-repeat = "mediaLink in appInstall.mediaLinks" class = "slick-item" style = "background-image: url('{{mediaLink}}');" ng-show = "appInstall.mediaLinks.length == 1" > < / div >
< slick init-onload = "true" current-index = "0" autoplay = "true" arrows = "false" autoplay-speed = "2000" data = "appInstall.mediaLinks" ng-show = "appInstall.mediaLinks.length > 1" >
< div ng-repeat = "mediaLink in appInstall.mediaLinks" class = "slick-item" style = "background-image: url('{{mediaLink}}');" > < / div >
< / slick >
2021-01-22 12:35:09 -08:00
< br / >
2019-07-31 08:09:36 +02:00
< div class = "appstore-install-description" >
2022-02-22 16:20:27 +01:00
< p ng-show = "appInstall.app.manifest.upstreamVersion" > {{ 'appstore.installDialog.titleAndVersion' | tr:{ title: appInstall.app.manifest.title, version: appInstall.app.manifest.upstreamVersion } }}< / p >
2019-07-31 08:09:36 +02:00
< div ng-bind-html = "appInstall.app.manifest.description | markdown2html" > < / div >
< / div >
< / div >
< div class = "collapse" id = "collapseResourceConstraint" data-toggle = "false" >
2022-05-14 10:19:26 -07:00
< h4 class = "text-danger" > {{ 'appstore.installDialog.lowOnResources' | tr }}< sup > < a ng-href = "https://docs.cloudron.io/apps/#low-resource-warning" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / h4 >
2020-11-13 21:58:09 +01:00
< p > {{ 'appstore.installDialog.pleaseUpgradeServer' | tr }}< / p >
2019-07-31 08:09:36 +02:00
< / div >
2019-08-14 16:38:02 +02:00
< div class = "collapse" id = "collapseSubscriptionRequired" data-toggle = "false" >
2020-11-13 21:58:09 +01:00
< p > {{ 'appstore.installDialog.subscriptionRequired' | tr }}< / p >
2018-01-22 13:01:38 -08:00
< / div >
2019-07-31 08:09:36 +02:00
< / div >
< div class = "modal-footer" >
2020-11-13 21:58:09 +01:00
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > {{ 'main.dialog.close' | tr }}< / button >
2022-02-18 18:16:27 +01:00
< button type = "button" class = "btn btn-success" ng-click = "openSubscriptionSetup()" ng-show = "appInstall.state === 'subscriptionRequired'" > {{ 'appstore.installDialog.setupSubscriptionAction' | tr }}< / button >
2020-11-13 21:58:09 +01:00
< button type = "button" class = "btn btn-danger" ng-show = "appInstall.state === 'resourceConstraint'" ng-click = "appInstall.showForm(true)" > {{ 'appstore.installDialog.installAnywayAction' | tr }}< / button >
< button type = "button" class = "btn btn-success" ng-show = "appInstall.state === 'appInfo'" ng-click = "appInstall.showForm()" > {{ 'appstore.installDialog.installAction' | tr }}< / button >
2021-02-17 17:09:46 +01:00
< button type = "button" class = "btn btn-success" ng-show = "appInstall.state === 'installForm'" ng-click = "appInstall.submit()" ng-disabled = "(appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()) || !appInstall.accessRestrictionOption || appInstallForm.$invalid || appInstall.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "appInstall.busy" > < / i > {{ 'appstore.installDialog.doInstallAction' | tr:{ dnsOverwrite: appInstall.needsOverwrite } }}< / button >
2019-07-31 08:09:36 +02:00
< / div >
2018-01-22 13:01:38 -08:00
< / div >
2019-07-31 08:09:36 +02:00
< / div >
2018-01-22 13:01:38 -08:00
< / div >
<!-- Modal app not found -->
< div class = "modal fade" id = "appNotFoundModal" tabindex = "-1" role = "dialog" >
2020-11-13 21:58:09 +01:00
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" > {{ 'appstore.appNotFoundDialog.title' | tr }}< / h4 >
< / div >
< div class = "modal-body" ng-bind-html = "'appstore.appNotFoundDialog.description' | tr:{ appId: appNotFound.appId, version: appNotFound.version }" > < / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-primary" data-dismiss = "modal" > {{ 'main.dialog.close' | tr }}< / button >
< / div >
2018-01-22 13:01:38 -08:00
< / div >
2020-11-13 21:58:09 +01:00
< / div >
2018-01-22 13:01:38 -08:00
< / div >
2022-10-05 17:17:22 +02:00
<!-- Modal applinks add -->
< div class = "modal fade" id = "applinksAddModal" tabindex = "-1" role = "dialog" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" > {{ 'app.addApplinkDialog.title' | tr }}< / h4 >
< / div >
< div class = "modal-body" >
< form name = "applinksAddForm" role = "form" ng-submit = "applinksAdd.submit()" autocomplete = "off" >
< div class = "form-group" ng-class = "{ 'has-error': (applinksAddForm.upstreamUri.$dirty && applinksAddForm.upstreamUri.$invalid) || (!applinksAddForm.upstreamUri.$dirty && applinksAdd.error.upstreamUri) }" >
< label class = "control-label" > {{ 'app.applinks.upstreamUri' | tr }}< / label >
< input type = "text" class = "form-control" ng-model = "applinksAdd.upstreamUri" name = "upstreamUri" id = "inputUpstreamUri" autofocus autocomplete = "off" required >
2022-10-06 20:03:22 +02:00
< span class = "text-danger" ng-show = "applinksAdd.error.upstreamUri" > {{ applinksAdd.error.upstreamUri }}< / span >
2022-10-05 17:17:22 +02:00
< / div >
< div class = "form-group" >
< label class = "control-label" > {{ 'app.applinks.label' | tr }}< / label >
< input type = "text" class = "form-control" ng-model = "applinksAdd.label" name = "label" id = "inputLabel" autocomplete = "off" placeholder = "Leave empty for autodetection" >
< / div >
< div class = "form-group" >
< label class = "control-label" > {{ 'app.display.tags' | tr }}< / label >
< tag-input class = "form-control" placeholder = "{{ 'app.display.tagsPlaceholder' | tr }}" taglist = "applinksAdd.tags" name = "tags" uib-tooltip = "{{ 'app.display.tagsTooltip' | tr }}" > < / tag-input >
< / div >
< label class = "control-label" > {{ 'app.accessControl.userManagement.dashboardVisibility' | tr }} < sup > < a ng-href = "https://docs.cloudron.io/apps/#dashboard-visibility" class = "help" target = "_blank" > < i class = "fa fa-question-circle" > < / i > < / a > < / sup > < / label >
< div class = "radio" >
< label >
< input type = "radio" ng-model = "applinksAdd.accessRestrictionOption" value = "any" >
< span > {{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}< / span >
< / label >
< / div >
< div class = "radio" >
< label >
< input type = "radio" ng-model = "applinksAdd.accessRestrictionOption" value = "groups" >
< span > {{ 'app.accessControl.userManagement.visibleForSelected' | tr }}< / span >
< span class = "label label-danger" ng-show = "applinksAdd.accessRestrictionOption === 'groups' && !applinksAdd.isAccessRestrictionValid()" > {{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}< / span >
< / label >
< / div >
< div >
< div style = "margin-left: 20px; display: flex;" >
< div >
2022-10-24 17:34:20 +02:00
{{ 'appstore.installDialog.users' | tr }}: < multiselect name = "accessUsersSelect" class = "input-sm stretch" ng-model = "applinksAdd.accessRestriction.users" ng-disabled = "applinksAdd.accessRestrictionOption !== 'groups'" options = "(user.username || user.email) for user in users" data-multiple = "true" filter-after-rows = "5" scroll-after-rows = "10" > < / multiselect >
2022-10-05 17:17:22 +02:00
< / div >
< div >
2022-10-24 17:34:20 +02:00
{{ 'appstore.installDialog.groups' | tr }}: < multiselect name = "accessGroupsSelect" class = "input-sm stretch" ng-model = "applinksAdd.accessRestriction.groups" ng-disabled = "applinksAdd.accessRestrictionOption !== 'groups'" options = "group.name for group in groups" data-multiple = "true" filter-after-rows = "5" scroll-after-rows = "10" > < / multiselect >
2022-10-05 17:17:22 +02:00
< / div >
< / div >
< / div >
< input class = "ng-hide" type = "submit" ng-disabled = "applinksAddForm.$invalid || applinksAdd.busy" / >
< / form >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > {{ 'main.dialog.close' | tr }}< / button >
< button type = "button" class = "btn btn-success" ng-click = "applinksAdd.submit()" ng-disabled = "applinksAddForm.$invalid || applinksAdd.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "applinksAdd.busy" > < / i > {{ 'main.dialog.save' | tr }}< / button >
< / div >
< / div >
< / div >
< / div >
2018-01-22 13:01:38 -08:00
< div ng-show = "!ready" class = "loading-banner" >
2020-11-13 21:58:09 +01:00
< h1 > < i class = "fa fa-circle-notch fa-spin" > < / i > < / h1 >
2018-01-22 13:01:38 -08:00
< / div >
<!-- appstore login -->
2019-05-04 21:57:42 -07:00
< div ng-show = "ready && !validSubscription" class = "container card card-small appstore-login ng-cloak" >
2020-11-13 21:58:09 +01:00
< div class = "col-md-12 text-center" >
2023-12-02 11:23:03 +01:00
< h1 ng-show = "appstoreLogin.setupType === 'signup'" > {{ 'appstore.accountDialog.titleSignUp' | tr }}< / h1 >
< h1 ng-show = "appstoreLogin.setupType === 'login'" > {{ 'appstore.accountDialog.titleLogin' | tr }}< / h1 >
< h1 ng-show = "appstoreLogin.setupType === 'setupToken'" > {{ 'appstore.accountDialog.titleToken' | tr }}< / h1 >
2020-11-13 21:58:09 +01:00
< / div >
< div class = "col-md-12 text-center" >
< p > {{ 'appstore.accountDialog.description' | tr }}< / p >
< / div >
< div class = "col-md-12" style = "margin-bottom: 10px;" >
< small class = "text-danger" ng-show = "appstoreLogin.error.generic" > {{ appstoreLogin.error.generic }}< / small >
< / div >
< div class = "col-md-12" >
< br / >
2018-03-13 10:59:15 -07:00
2023-12-02 11:23:03 +01:00
< div ng-show = "appstoreLogin.setupType === 'signup'" >
< form name = "appstoreSignupForm" role = "form" novalidate ng-submit = "appstoreLogin.submit()" autocomplete = "off" >
< input type = "password" style = "display: none;" >
2018-01-22 13:01:38 -08:00
2023-12-02 11:23:03 +01:00
< div class = "form-group" ng-class = "{ 'has-error': (appstoreSignupForm.email.$dirty && appstoreSignupForm.email.$invalid) || appstoreLogin.error.generic }" >
< label class = "control-label" > {{ 'appstore.accountDialog.email' | tr }}< / label >
< input type = "email" class = "form-control" ng-model = "appstoreLogin.email" id = "inputAppstoreLoginEmail" name = "email" required autofocus >
< div class = "control-label" ng-show = "(!appstoreSignupForm.email.$dirty && appstoreLogin.error.email) || (appstoreSignupForm.email.$dirty && appstoreSignupForm.email.$invalid) || appstoreLogin.error.email" >
< small class = "text-danger" ng-show = "appstoreLogin.error.email" > {{ appstoreLogin.error.email }}< / small >
< / div >
< / div >
< div class = "form-group" ng-class = "{ 'has-error': (!appstoreSignupForm.password.$dirty && appstoreLogin.error.password) || (appstoreSignupForm.password.$dirty && appstoreSignupForm.password.$invalid) || appstoreLogin.error.generic }" >
< label class = "control-label" > {{ 'appstore.accountDialog.password' | tr }}< / label >
< input type = "password" class = "form-control" ng-model = "appstoreLogin.password" id = "inputAppstoreLoginPassword" name = "password" required password-reveal >
< div class = "control-label" ng-show = "(!appstoreSignupForm.password.$dirty && appstoreLogin.error.password) || (appstoreSignupForm.password.$dirty && appstoreSignupForm.password.$invalid)" >
< small ng-show = "!appstoreSignupForm.password.$dirty && appstoreLogin.error.password" > {{ 'appstore.accountDialog.errorWrongPassword' | tr }}< / small >
< / div >
< / div >
< div class = "checkbox" >
< label >
< input type = "checkbox" ng-model = "appstoreLogin.termsAccepted" > < span ng-bind-html = "'appstore.accountDialog.licenseCheckbox' | tr:{ licenseLink: 'https://cloudron.io/legal/license.html' }" > < / span >
< / label >
< / div >
< br / >
< center >
< button type = "submit" class = "btn btn-lg btn-success" ng-disabled = "appstoreSetupTokenForm.$invalid || appstoreLogin.busy" >
< i class = "fa fa-circle-notch fa-spin" ng-show = "appstoreLogin.busy" > < / i > {{ 'appstore.accountDialog.createAccountAction' | tr }}
< / button >
< / center >
< / form >
< / div >
< div ng-show = "appstoreLogin.setupType === 'login'" >
< form name = "appstoreLoginForm" role = "form" novalidate ng-submit = "appstoreLogin.submit()" autocomplete = "off" >
< input type = "password" style = "display: none;" >
< div class = "form-group" ng-class = "{ 'has-error': (appstoreLoginForm.email.$dirty && appstoreLoginForm.email.$invalid) || appstoreLogin.error.generic }" >
2020-11-13 21:58:09 +01:00
< label class = "control-label" > {{ 'appstore.accountDialog.email' | tr }}< / label >
< input type = "email" class = "form-control" ng-model = "appstoreLogin.email" id = "inputAppstoreLoginEmail" name = "email" required autofocus >
< div class = "control-label" ng-show = "(!appstoreLoginForm.email.$dirty && appstoreLogin.error.email) || (appstoreLoginForm.email.$dirty && appstoreLoginForm.email.$invalid) || appstoreLogin.error.email" >
2023-12-02 11:23:03 +01:00
< small class = "text-danger" ng-show = "appstoreLogin.error.email" > {{ appstoreLogin.error.email }}< / small >
2020-11-13 21:58:09 +01:00
< / div >
2023-12-02 11:23:03 +01:00
< / div >
2018-01-22 13:01:38 -08:00
2023-12-02 11:23:03 +01:00
< div class = "form-group" ng-class = "{ 'has-error': (!appstoreLoginForm.password.$dirty && appstoreLogin.error.password) || (appstoreLoginForm.password.$dirty && appstoreLoginForm.password.$invalid) || appstoreLogin.error.generic }" >
2020-11-13 21:58:09 +01:00
< label class = "control-label" > {{ 'appstore.accountDialog.password' | tr }}< / label >
2021-11-03 21:57:44 +01:00
< input type = "password" class = "form-control" ng-model = "appstoreLogin.password" id = "inputAppstoreLoginPassword" name = "password" required password-reveal >
2020-11-13 21:58:09 +01:00
< div class = "control-label" ng-show = "(!appstoreLoginForm.password.$dirty && appstoreLogin.error.password) || (appstoreLoginForm.password.$dirty && appstoreLoginForm.password.$invalid)" >
2023-12-02 11:23:03 +01:00
< small ng-show = "!appstoreLoginForm.password.$dirty && appstoreLogin.error.password" > {{ 'appstore.accountDialog.errorWrongPassword' | tr }}< / small >
2020-11-13 21:58:09 +01:00
< / div >
2023-12-02 11:23:03 +01:00
< / div >
2018-01-22 13:01:38 -08:00
2023-12-02 11:23:03 +01:00
< div class = "form-group" ng-class = "{ 'has-error': appstoreLogin.error.totpToken }" >
2020-11-13 21:58:09 +01:00
< label class = "control-label" > {{ 'appstore.accountDialog.2faToken' | tr }}< / label >
< input type = "text" class = "form-control" ng-model = "appstoreLogin.totpToken" id = "inputAppstoreLoginTotpToken" name = "totpToken" >
< div class = "control-label" ng-show = "appstoreLogin.error.totpToken" >
2023-12-02 11:23:03 +01:00
< small ng-show = "appstoreLogin.error.totpToken" > {{ appstoreLogin.error.totpToken }}< / small >
2020-11-13 21:58:09 +01:00
< / div >
2023-12-02 11:23:03 +01:00
< / div >
2018-04-22 18:52:37 +02:00
2023-12-02 11:23:03 +01:00
< div class = "checkbox" >
< label >
< input type = "checkbox" ng-model = "appstoreLogin.termsAccepted" > < span ng-bind-html = "'appstore.accountDialog.licenseCheckbox' | tr:{ licenseLink: 'https://cloudron.io/legal/license.html' }" > < / span >
< / label >
< / div >
2018-01-22 13:01:38 -08:00
2023-12-02 11:23:03 +01:00
< br / >
2018-01-22 13:01:38 -08:00
2023-12-02 11:23:03 +01:00
< center >
< button type = "submit" class = "btn btn-lg btn-success" ng-disabled = "appstoreSetupTokenForm.$invalid || appstoreLogin.busy" >
< i class = "fa fa-circle-notch fa-spin" ng-show = "appstoreLogin.busy" > < / i > {{ 'appstore.accountDialog.loginAction' | tr }}
< / button >
< / center >
< / form >
< / div >
< div ng-show = "appstoreLogin.setupType === 'setupToken'" >
< form name = "appstoreSetupTokenForm" role = "form" novalidate ng-submit = "appstoreLogin.submit()" autocomplete = "off" >
< input type = "password" style = "display: none;" >
< div class = "form-group" ng-class = "{ 'has-error': appstoreLogin.error.setupToken }" >
< label class = "control-label" > {{ 'appstore.accountDialog.setupToken' | tr }}< / label >
< input type = "text" class = "form-control" ng-model = "appstoreLogin.setupToken" id = "setupToken" name = "setupToken" ng-required = "true" >
< div class = "control-label" ng-show = "appstoreLogin.error.setupToken" >
< small ng-show = "appstoreLogin.error.setupToken" > {{ appstoreLogin.error.setupToken }}< / small >
< / div >
< / div >
< div class = "checkbox" >
< label >
< input type = "checkbox" ng-model = "appstoreLogin.termsAccepted" ng-required = "true" > < span ng-bind-html = "'appstore.accountDialog.licenseCheckbox' | tr:{ licenseLink: 'https://cloudron.io/legal/license.html' }" > < / span >
< / label >
< / div >
2018-03-13 10:21:25 +01:00
2020-11-13 21:58:09 +01:00
< br / >
2018-03-13 10:21:25 +01:00
2023-12-02 11:23:03 +01:00
< center >
< button type = "submit" class = "btn btn-lg btn-success" ng-disabled = "appstoreSetupTokenForm.$invalid || appstoreLogin.busy" >
< i class = "fa fa-circle-notch fa-spin" ng-show = "appstoreLogin.busy" > < / i > Setup
< / button >
< / center >
< / form >
< / div >
< center >
< br / >
< br / >
2018-01-22 13:01:38 -08:00
2023-12-02 11:23:03 +01:00
< a href = "" ng-click = "appstoreLogin.setupType = 'signup'" ng-show = "appstoreLogin.setupType === 'login'" > {{ 'appstore.accountDialog.switchToSignUpAction' | tr }}< / a >
< a href = "" ng-click = "appstoreLogin.setupType = 'login'" ng-show = "appstoreLogin.setupType === 'signup' || appstoreLogin.setupType === 'setupToken'" > {{ 'appstore.accountDialog.switchToLoginAction' | tr }}< / a >
< span ng-show = "appstoreLogin.setupType !== 'setupToken'" > or < a href = "" ng-click = "appstoreLogin.setupType = 'setupToken'" > use a setup token< / a > < / span >
< / center >
2020-11-13 21:58:09 +01:00
< / div >
2018-01-22 13:01:38 -08:00
< / div >
2018-03-14 09:04:05 -07:00
2018-04-05 22:03:40 +02:00
<!-- give more vertical spacing so the login form does not appear clipped -->
2019-05-04 21:57:42 -07:00
< div ng-show = "ready && !validSubscription" >
2020-06-12 15:02:41 +02:00
< br / >
< br / >
2018-04-05 22:03:40 +02:00
< / div >
2022-10-05 17:17:22 +02:00
< div class = "appstore-layout" >
< div ng-show = "ready && validSubscription" class = "ng-cloak appstore-toolbar" >
< div class = "appstore-toolbar-content" >
< div class = "dropdown" >
< button class = "btn dropdown-toggle" type = "button" data-toggle = "dropdown" ng-class = "{ 'btn-primary': '' !== category && 'recent' !== category && 'new' !== category }" >
{{ categoryButtonLabel(category) }}
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu" >
2022-11-09 15:45:34 +01:00
< li > < a href = "" ng-click = "showCategory('');" > < i class = "fas fa-home fa-fw" > < / i > {{ 'appstore.category.all' | tr }}< / a > < / li >
< li > < a href = "" ng-click = "showCategory('new');" > < i class = "fas fa-rss fa-fw" > < / i > {{ 'appstore.category.newApps' | tr }}< / a > < / li >
< li role = "separator" class = "divider" > < / li >
2022-10-05 17:17:22 +02:00
< li ng-repeat = "category in categories | orderBy:'label'" > < a href = "" ng-click = "showCategory(category.id);" > < i class = "{{ category.icon }} fa-fw" > < / i > {{ category.label }}< / a > < / li >
< / ul >
< / div >
2022-11-14 17:21:10 +01:00
< div class = "dropdown" >
2022-10-05 17:17:22 +02:00
< button class = "btn dropdown-toggle" type = "button" data-toggle = "dropdown" >
< i class = "{{ userManagementFilterOption.icon }} fa-fw" > < / i >
{{ 'appstore.ssofilter.label' | tr }}
< span class = "caret" > < / span >
< / button >
< ul class = "dropdown-menu" >
< li ng-repeat = "option in userManagementFilterOptions" ng-class = "{ 'active': userManagementFilterOption.id && userManagementFilterOption.id === option.id }" > < a href = "" ng-click = "applyUserMangamentFilter(option);" > < i class = "{{ option.icon }} fa-fw" > < / i > {{ option.label }}< / a > < / li >
< / ul >
2022-11-14 17:21:10 +01:00
< / div >
2022-10-05 17:17:22 +02:00
< input type = "text" id = "appstoreSearch" class = "form-control" style = "width: auto; flex-grow: 1;" placeholder = "{{ 'appstore.searchPlaceholder' | tr }}" ng-model = "searchString" ng-change = "search()" autofocus >
< div class = "btn-group" >
< button type = "button" class = "btn btn-default" ng-click = "openAppProxy()" > < i class = "fas fa-exchange-alt" > < / i > {{ 'apps.addAppproxyAction' | tr }}< / a > < / a >
< button type = "button" class = "btn btn-default" ng-click = "applinksAdd.show()" > < i class = "fas fa-link" > < / i > {{ 'apps.addApplinkAction' | tr }}< / button >
< / div >
2022-05-25 21:10:53 +02:00
< / div >
2020-06-12 15:02:41 +02:00
< / div >
2022-10-05 17:17:22 +02:00
< div ng-show = "ready && validSubscription" class = "ng-cloak appstore-grid" >
2022-11-09 15:45:34 +01:00
< div class = "text-center" ng-hide = "apps.length || popularApps.length" >
2020-06-12 15:02:41 +02:00
< br / >
< br / >
2020-06-16 11:20:28 +02:00
< br / >
2020-11-13 21:58:09 +01:00
< h3 class = "text-muted" > {{ 'appstore.noAppsFound' | tr }}< / h3 >
2020-06-16 11:20:28 +02:00
< br / >
2020-11-13 21:58:09 +01:00
< a href = "https://forum.cloudron.io/category/5/app-requests" target = "_blank" > {{ 'appstore.appMissing' | tr }}< / a >
2020-06-12 15:02:41 +02:00
< / div >
2022-10-05 17:17:22 +02:00
< div class = "" ng-show = "category === '' && popularApps.length" >
2021-03-18 14:14:22 +01:00
< div class = "row-no-margin" >
< div class = "col-sm-12" >
2022-11-10 17:44:11 +01:00
< h2 > {{ 'appstore.category.popular' | tr }}< / h2 >
2021-03-18 14:14:22 +01:00
< / div >
< / div >
< div class = "row-no-margin" >
2022-11-14 10:32:39 +01:00
< div class = "col-sm-3 appstore-item" ng-repeat = "app in popularApps | userManagementFilter:userManagementFilterOption" >
2021-03-18 14:14:22 +01:00
< div class = "appstore-item-content highlight" ng-click = "gotoApp(app)" ng-class = "{ 'appstore-item-content-testing': app.releaseState === 'unstable' }" >
< span class = "badge badge-danger appstore-item-badge-testing" ng-show = "app.releaseState === 'unstable'" > {{ 'appstore.unstable' | tr }}< / span >
< div class = "appstore-item-content-icon col-same-height" >
< img ng-src = "{{app.iconUrl}}" onerror = "this.onerror=null;this.src='img/appicon_fallback.png'" class = "app-icon" / >
< / div >
2022-11-14 10:32:39 +01:00
< div class = "col-same-height" >
2021-03-18 14:14:22 +01:00
< h4 class = "appstore-item-content-title" > {{ app.manifest.title }}< / h4 >
< div class = "appstore-item-content-tagline text-muted" > {{ app.manifest.tagline }}< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
2022-10-05 17:17:22 +02:00
< div class = "" ng-show = "apps.length" >
2021-03-18 14:28:22 +01:00
< div class = "row-no-margin" ng-show = "!category && !searchString" >
2021-03-18 14:14:22 +01:00
< div class = "col-sm-12" >
2023-02-25 20:03:24 +01:00
< h2 > {{ 'appstore.category.all' | tr }}< / h2 >
2021-03-18 14:14:22 +01:00
< / div >
< / div >
2020-06-12 15:02:41 +02:00
< div class = "row-no-margin" >
2022-11-14 10:32:39 +01:00
< div class = "col-sm-3 appstore-item" ng-repeat = "app in apps | userManagementFilter:userManagementFilterOption | orderBy:'-priority' " >
2020-06-12 15:02:41 +02:00
< div class = "appstore-item-content highlight" ng-click = "gotoApp(app)" ng-class = "{ 'appstore-item-content-testing': app.releaseState === 'unstable' }" >
2020-11-13 21:58:09 +01:00
< span class = "badge badge-danger appstore-item-badge-testing" ng-show = "app.releaseState === 'unstable'" > {{ 'appstore.unstable' | tr }}< / span >
2020-06-12 15:02:41 +02:00
< div class = "appstore-item-content-icon col-same-height" >
< img ng-src = "{{app.iconUrl}}" onerror = "this.onerror=null;this.src='img/appicon_fallback.png'" class = "app-icon" / >
2018-03-14 09:04:05 -07:00
< / div >
2020-06-12 15:02:41 +02:00
< div class = "appstore-item-content-description col-same-height" >
< h4 class = "appstore-item-content-title" > {{ app.manifest.title }}< / h4 >
< div class = "appstore-item-content-tagline text-muted" > {{ app.manifest.tagline }}< / div >
< / div >
< / div >
2018-03-14 09:04:05 -07:00
< / div >
2020-06-12 15:02:41 +02:00
< / div >
2018-03-14 09:04:05 -07:00
< / div >
2020-06-12 15:02:41 +02:00
< / div >
2022-10-05 17:17:22 +02:00
2018-03-14 09:04:05 -07:00
< / div >