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 >
2024-07-16 22:21:36 +02:00
< div ng-repeat = "(env, info) in appInstall.portInfo" >
2019-07-31 08:09:36 +02:00
< 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) }" >
2024-07-16 22:21:36 +02:00
< label class = "control-label" for = "inputPortInfo{{env}}" > < input type = "checkbox" ng-model = "appInstall.portsEnabled[env]" >
2019-07-31 08:09:36 +02:00
{{ info.title }}
< sup >
2024-08-20 18:16:57 +02:00
< a popover-placement = "top-right" popover-trigger = "outsideClick" uib-popover = "{{info.description}}. {{info.portCount >=1 ? (info.portCount + ' ports. ') : ''}}" > < i class = "fa fa-question-circle" > < / i > < / a >
2019-07-31 08:09:36 +02:00
< / 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 >
2024-07-16 22:21:36 +02:00
< input type = "number" class = "form-control" ng-model = "appInstall.ports[env]" ng-disabled = "!appInstall.portsEnabled[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 >
2023-12-02 18:20:11 +01:00
< input type = "email" class = "form-control" ng-model = "appstoreLogin.email" id = "inputAppstoreSignupEmail" name = "email" required autofocus >
2023-12-02 11:23:03 +01:00
< 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 >
2023-12-02 18:20:11 +01:00
< div class = "form-group" ng-class = "{ 'has-error': (!appstoreSignupForm.password.$dirty && appstoreLogin.error.signupPassword) || (appstoreSignupForm.password.$dirty && appstoreSignupForm.password.$invalid) || appstoreLogin.error.generic }" >
2023-12-02 11:23:03 +01:00
< label class = "control-label" > {{ 'appstore.accountDialog.password' | tr }}< / label >
2023-12-02 18:20:11 +01:00
< input type = "password" class = "form-control" ng-model = "appstoreLogin.password" id = "inputAppstoreSignupPassword" name = "password" required password-reveal >
< div class = "control-label" ng-show = "(!appstoreSignupForm.password.$dirty && appstoreLogin.error.signupPassword) || (appstoreSignupForm.password.$dirty && appstoreSignupForm.password.$invalid)" >
< small ng-show = "!appstoreSignupForm.password.$dirty && appstoreLogin.error.signupPassword" > {{ 'appstore.accountDialog.errorWrongPassword' | tr }}< / small >
2023-12-02 11:23:03 +01:00
< / div >
< / div >
< div class = "checkbox" >
< label >
2023-12-02 18:20:11 +01:00
< 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 >
2023-12-02 11:23:03 +01:00
< / label >
< / div >
< br / >
< center >
2023-12-02 18:20:11 +01:00
< button type = "submit" class = "btn btn-lg btn-success" ng-disabled = "appstoreSignupForm.$invalid || appstoreLogin.busy" >
2023-12-02 11:23:03 +01:00
< 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 >
2023-12-02 18:20:11 +01:00
< input type = "email" class = "form-control" ng-model = "appstoreLogin.email" name = "email" required autofocus >
2020-11-13 21:58:09 +01:00
< 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 18:20:11 +01:00
< div class = "form-group" ng-class = "{ 'has-error': (!appstoreLoginForm.password.$dirty && appstoreLogin.error.loginPassword) || (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 >
2023-12-02 18:20:11 +01:00
< div class = "control-label" ng-show = "(!appstoreLoginForm.password.$dirty && appstoreLogin.error.loginPassword) || (appstoreLoginForm.password.$dirty && appstoreLoginForm.password.$invalid)" >
< small ng-show = "!appstoreLoginForm.password.$dirty && appstoreLogin.error.loginPassword" > {{ '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 >
2023-12-02 18:20:11 +01:00
< 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 >
2023-12-02 11:23:03 +01:00
< / 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 >
2023-12-02 18:20:11 +01:00
< button type = "submit" class = "btn btn-lg btn-success" ng-disabled = "appstoreLoginForm.$invalid || appstoreLogin.busy" >
2023-12-02 11:23:03 +01:00
< 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 >
2023-12-02 16:42:03 +01:00
< input type = "text" class = "form-control" ng-model = "appstoreLogin.setupToken" id = "inputAppstoreSetupToken" name = "setupToken" ng-required = "true" >
2023-12-02 11:23:03 +01:00
< 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" >
2023-12-02 17:53:40 +01:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "appstoreLogin.busy" > < / i > {{ 'appstore.accountDialog.setupWithTokenAction' | tr }}
2023-12-02 11:23:03 +01:00
< / button >
< / center >
< / form >
< / div >
2023-12-02 17:53:40 +01:00
< br / >
2018-01-22 13:01:38 -08:00
2023-12-02 17:53:40 +01:00
< center >
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 >
2024-08-20 18:16:57 +02:00
< span ng-show = "appstoreLogin.setupType !== 'setupToken'" > or < a href = "" ng-click = "appstoreLogin.setupType = 'setupToken'" > Use a setup token< / a > < / span >
2023-12-02 11:23:03 +01:00
< / 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" >
2024-10-15 17:47:59 +02:00
< 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" >
< 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 >
< 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 >
< div class = "dropdown" >
< 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-05-25 21:10:53 +02:00
< / div >
2024-10-15 17:47:59 +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 >
< 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 >
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 >