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" >
< img ng-src = "{{appInstall.app.iconUrl}}" onerror = "this.onerror=null;this.src='img/appicon_fallback.png'" class = "app-icon" / >
< h3 class = "appstore-install-title" title = "Version {{ appInstall.app.manifest.version }}" > {{ appInstall.app.manifest.title }} < span class = "badge badge-danger" ng-show = "appInstall.app.publishState === 'testing'" > Testing< / span > < / h3 >
< br / >
< span class = "appstore-install-meta" > < a href = "{{ appInstall.app.manifest.website }}" target = "_blank" > {{ appInstall.app.manifest.author }}< / a > < / span >
< br / >
< span class = "appstore-install-meta" > Last updated {{ appInstall.app.creationDate | prettyDate }}< / span >
< br / >
< span class = "appstore-install-meta hand" > Requires atleast {{ appInstall.app.manifest.memoryLimit | prettyMemory }}MB memory< / span >
< / 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) }" >
2019-09-24 00:04:31 -07:00
< label class = "control-label" for = "appInstallLocationInput" > Location< / label >
< div ng-show = "appInstall.error.location" > < small > {{ appInstall.error.location }}< / small > < / div >
2019-07-31 08:09:36 +02:00
< div class = "input-group form-inline" >
< input type = "text" class = "form-control" ng-model = "appInstall.location" id = "appInstallLocationInput" name = "location" placeholder = "Leave empty to use bare domain" autofocus >
< div class = "input-group-btn" >
< button type = "button" class = "btn btn-default dropdown-toggle" data-toggle = "dropdown" >
< span > {{ (appInstall.location ? (appInstall.domain.config.hyphenatedSubdomains ? '-' : '.') : '') + appInstall.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.domain = domain" > {{ domain.domain }}< / a >
< / li >
< / ul >
< / div >
< / div >
2018-01-22 13:01:38 -08:00
< / div >
2019-07-31 08:09:36 +02:00
< p class = "text-center" ng-show = "appInstall.location && appInstall.domain.provider === 'manual'" >
< b > Add an A record manually for {{ appInstall.location }} to this Cloudron's public IP< / b >
< br >
< / p >
2018-01-22 13:01:38 -08:00
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 >
< / label >
< input type = "number" class = "form-control" ng-model = "appInstall.portBindings[env]" ng-disabled = "!appInstall.portBindingsEnabled[env]" id = "inputPortInfo{{env}}" later-name = "itemName{{$index}}" min = "{{hostPortMin}}" max = "{{hostPortMax}}" required >
< / div >
< / ng-form >
< / div >
2018-01-22 13:01:38 -08:00
2019-07-31 08:09:36 +02:00
<!-- for spaces users, the User management is hidden. thus the admin flag check -->
2020-02-24 17:29:20 +01:00
< div class = "form-group" ng-show = "user.isAtLeastAdmin && appInstall.customAuth && !appInstall.app.manifest.addons.email" >
2019-07-31 08:09:36 +02:00
< label class = "control-label" > User management< / label >
< p > This app has it's own user management.< / p >
< / div >
2018-01-22 13:01:38 -08:00
2020-02-24 17:29:20 +01:00
< div class = "form-group" ng-show = "user.isAtLeastAdmin && appInstall.app.manifest.addons.email" >
2019-07-31 08:09:36 +02:00
< label class = "control-label" > User management< / label >
< p > All users with a mailbox on this Cloudron have access.< / p >
< / div >
2018-01-22 13:01:38 -08:00
2020-02-24 17:29:20 +01:00
< div class = "form-group" ng-show = "user.isAtLeastAdmin && !appInstall.customAuth && !appInstall.app.manifest.addons.email" >
2019-07-31 08:09:36 +02:00
< label class = "control-label" > User management< / label >
< div class = "radio" ng-show = "appInstall.optionalSso" >
< label >
< input type = "radio" ng-model = "appInstall.accessRestrictionOption" value = "nosso" >
Leave user management to the app
< / label >
< / div >
< div class = "radio" >
< label >
< input type = "radio" ng-model = "appInstall.accessRestrictionOption" value = "any" >
Allow all users from this Cloudron
< / label >
< / div >
< div class = "radio" >
< label >
< input type = "radio" ng-model = "appInstall.accessRestrictionOption" value = "groups" >
Only allow the following users and groups < span class = "label label-danger" ng-show = "appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()" > Select at least one user or group< / span >
< / label >
< / div >
< div >
< div style = "margin-left: 20px;" >
< div class = "col-md-5" >
Users:
2019-11-05 19:45:59 +01:00
< multiselect ng-model = "appInstall.accessRestriction.users" ng-disabled = "appInstall.accessRestrictionOption !== 'groups'" options = "user.username 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" >
Groups:
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
< p ng-show = "appInstall.app.manifest.addons.email" class = "text-info" >
2019-12-18 14:29:42 -08:00
This app is pre-configured for use with < a ng-href = "{{ config.webServerOrigin }}/documentation/email/" target = "_blank" > Cloudron Email< / a > .
2019-07-31 08:09:36 +02:00
< / p >
2018-01-22 13:01:38 -08:00
2019-07-31 08:09:36 +02:00
< div class = "hide" >
< label class = "control-label" for = "appInstallCertificateInput" ng-show = "appInstall.domain.provider !== 'caas'" > Certificate (optional)< / label >
< div class = "has-error text-center" ng-show = "appInstall.error.cert && appInstall.domain.provider !== 'caas'" > {{ appInstall.error.cert }}< / div >
< div class = "form-group" ng-class = "{ 'has-error': !appInstallForm.certificate.$dirty && appInstall.error.cert }" ng-show = "appInstall.domain.provider !== 'caas'" >
< 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 >
< div class = "form-group" ng-class = "{ 'has-error': !appInstallForm.key.$dirty && appInstall.error.cert }" ng-show = "appInstall.domain.provider !== 'caas'" >
< 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
< input class = "ng-hide" type = "submit" ng-disabled = "appInstallForm.$invalid || busy" / >
< / 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 >
< div class = "appstore-install-description" >
< div ng-bind-html = "appInstall.app.manifest.description | markdown2html" > < / div >
< / div >
< / div >
< div class = "collapse" id = "collapseResourceConstraint" data-toggle = "false" >
< h4 class = "text-danger" > This Cloudron is running low on resources.< / h4 >
< p > Please upgrade to a server instance with more memory. Alternately, free up resources by uninstalling unused applications.< / p >
< / div >
2019-08-14 16:38:02 +02:00
< div class = "collapse" id = "collapseSubscriptionRequired" data-toggle = "false" >
2020-02-21 14:07:46 +01:00
< p class = "text-bold" > A subscription for this Cloudron is required to install more apps.< / p >
2018-01-22 13:01:38 -08:00
< / div >
2019-07-31 08:09:36 +02:00
< / div >
< div class = "modal-footer" >
2020-02-21 14:07:46 +01:00
< button type = "button" class = "btn btn-success pull-left" ng-click = "openSubscriptionSetup()" ng-show = "appInstall.state === 'subscriptionRequired'" > Setup Subscription< / button >
< button type = "button" class = "btn btn-default" data-dismiss = "modal" > Close< / button >
2020-02-24 17:29:20 +01:00
< button type = "button" class = "btn btn-danger" ng-show = "user.isAtLeastAdmin && appInstall.state === 'resourceConstraint'" ng-click = "appInstall.showForm(true)" > Install anyway< / button >
2019-07-31 08:09:36 +02:00
< button type = "button" class = "btn btn-success" ng-show = "appInstall.state === 'appInfo'" ng-click = "appInstall.showForm()" > Install< / button >
2019-09-24 00:21:01 +02:00
< button type = "button" class = "btn btn-success" ng-show = "appInstall.state === 'installForm'" ng-click = "appInstall.submit()" ng-disabled = "appInstallForm.$invalid || appInstall.busy" > < i class = "fa fa-circle-notch fa-spin" ng-show = "appInstall.busy" > < / i > Install {{ appInstall.needsOverwrite ? 'and overwrite DNS' : '' }}< / 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" >
< div class = "modal-dialog" >
< div class = "modal-content" >
< div class = "modal-header" >
< h4 class = "modal-title" > App not found< / h4 >
< / div >
< div class = "modal-body" >
There is no such app < b > {{ appNotFound.appId }}< / b > < span ng-show = "appNotFound.version" > with version < b > {{ appNotFound.version }}< / b > < / span > .
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-primary" data-dismiss = "modal" > OK< / button >
< / div >
< / div >
< / div >
< / div >
< div ng-show = "!ready" class = "loading-banner" >
2018-11-16 17:03:21 +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" >
2018-03-13 10:21:25 +01:00
< div class = "col-md-12 text-center" >
2018-03-13 10:59:15 -07:00
< h1 ng-show = "appstoreLogin.register" > Sign up with Cloudron.io< / h1 >
< h1 ng-hide = "appstoreLogin.register" > Login to Cloudron.io< / h1 >
2018-01-22 13:01:38 -08:00
< / div >
2018-06-04 15:45:32 -07:00
< div class = "col-md-12 text-center" >
2018-06-07 19:42:08 -07:00
< p > This account is used to access the App Store and manage your subscription< / p >
2018-03-13 10:59:15 -07:00
< / div >
< div class = "col-md-12" style = "margin-bottom: 10px;" >
2018-01-22 13:01:38 -08:00
< small class = "text-danger" ng-show = "appstoreLogin.error.generic" > {{ appstoreLogin.error.generic }}< / small >
< / div >
< div class = "col-md-12" >
2018-03-13 10:59:15 -07:00
< br / >
2018-01-22 13:01:38 -08:00
< 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 }" >
2018-06-07 19:42:08 -07:00
< label class = "control-label" > Email< / label >
2018-01-22 13:01:38 -08:00
< 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" >
< small class = "text-danger" ng-show = "appstoreLogin.error.email" > {{ appstoreLogin.error.email }}< / small >
< / div >
< / div >
< div class = "form-group" ng-class = "{ 'has-error': (!appstoreLoginForm.password.$dirty && appstoreLogin.error.password) || (appstoreLoginForm.password.$dirty && appstoreLoginForm.password.$invalid) || appstoreLogin.error.generic }" >
< label class = "control-label" > Password< / label >
< input type = "password" class = "form-control" ng-model = "appstoreLogin.password" id = "inputAppstoreLoginPassword" name = "password" required >
< div class = "control-label" ng-show = "(!appstoreLoginForm.password.$dirty && appstoreLogin.error.password) || (appstoreLoginForm.password.$dirty && appstoreLoginForm.password.$invalid)" >
< small ng-show = "!appstoreLoginForm.password.$dirty && appstoreLogin.error.password" > Wrong password< / small >
< / div >
< / div >
2018-04-22 18:52:37 +02:00
< div class = "form-group" ng-hide = "appstoreLogin.register" ng-class = "{ 'has-error': appstoreLogin.error.totpToken }" >
< label class = "control-label" > 2FA Token (if enabled)< / label >
< input type = "text" class = "form-control" ng-model = "appstoreLogin.totpToken" id = "inputAppstoreLoginTotpToken" name = "totpToken" >
< div class = "control-label" ng-show = "appstoreLogin.error.totpToken" >
< small ng-show = "appstoreLogin.error.totpToken" > {{ appstoreLogin.error.totpToken }}< / small >
< / div >
< / div >
2020-01-30 15:36:05 +01:00
< div class = "form-group" >
2020-01-30 21:11:33 -08:00
< label class = "control-label" > Intended Use< / label >
< select class = "purpose form-control" ng-model = "appstoreLogin.purpose" required >
< option value = "" disabled selected hidden > Please choose an option...< / option >
< option value = "personal_cloud" > Personal use< / option >
< option value = "business_cloud" > Business use< / option >
< option value = "website_hosting" > Website hosting< / option >
< option value = "paas" > PaaS - Develop & deploy apps< / option >
< option value = "single_app" > Host only one app< / option >
< option value = "exploring" > Just exploring< / option >
< / select >
2020-01-30 15:36:05 +01:00
< / div >
2018-03-13 11:06:49 -07:00
< div class = "checkbox" >
2018-01-22 13:01:38 -08:00
< label >
2018-02-23 14:58:20 -08:00
< input type = "checkbox" ng-model = "appstoreLogin.termsAccepted" > I accept the Cloudron < a href = "https://cloudron.io/legal/license.html" target = "_blank" > license< / a >
2018-01-22 13:01:38 -08:00
< / label >
< / div >
< br / >
2018-03-13 10:21:25 +01:00
< center >
2018-03-13 11:06:49 -07:00
< button type = "submit" class = "btn btn-lg btn-success" ng-disabled = "appstoreLoginForm.$invalid || appstoreLogin.busy || !appstoreLogin.termsAccepted" >
2018-11-16 17:03:21 +01:00
< i class = "fa fa-circle-notch fa-spin" ng-show = "appstoreLogin.busy" > < / i > < span ng-hide = "appstoreLogin.register" > Login< / span > < span ng-show = "appstoreLogin.register" > Create Account< / span >
2018-03-13 10:21:25 +01:00
< / button >
< br / >
< br / >
2018-06-07 19:42:08 -07:00
< a href = "" ng-click = "appstoreLogin.register = true" ng-hide = "appstoreLogin.register" > Don't have an account yet? Sign up< / a >
< a href = "" ng-click = "appstoreLogin.register = false" ng-show = "appstoreLogin.register" > Already have an account? Log in< / a >
2018-03-13 10:21:25 +01:00
< / center >
2018-01-22 13:01:38 -08:00
< / form >
< / div >
< / 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" >
2018-04-05 22:03:40 +02:00
< br / >
< br / >
< / div >
2019-05-04 21:57:42 -07:00
< div ng-show = "ready && validSubscription" class = "ng-cloak" id = "appstoreGrid" >
2018-03-14 09:04:05 -07:00
< div class = "col-md-2" >
< br / >
< div >
< form ng-submit = "search()" >
< div class = "input-group" >
< input type = "text" id = "appstoreSearch" class = "form-control" style = "height: 40px" placeholder = "Search" ng-model = "searchString" ng-change = "search()" autofocus >
< / div >
< / form >
< / div >
< br / >
2018-06-08 09:48:34 +02:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'featured' }" category = "featured" > Popular< / a >
2019-11-18 22:43:33 +01:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'new' }" category = "new" > New Apps< / a >
2019-10-22 17:51:09 +02:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'recent' }" category = "recent" > Recently Updated< / a >
2018-03-14 09:04:05 -07:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === '' }" category = "" > All< / a >
< br / >
2018-11-15 18:07:18 +01:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'analytics' }" category = "analytics" > < i class = "fa fa-chart-line" > < / i > Analytics< / a >
2018-03-14 09:04:05 -07:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'blog' }" category = "blog" > < i class = "fa fa-font" > < / i > Blog< / a >
2018-11-15 18:07:18 +01:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'chat' }" category = "chat" > < i class = "fa fa-comments" > < / i > Chat< / a >
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'git' }" category = "git" > < i class = "fa fa-code-branch" > < / i > Code Hosting< / a >
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'CRM' }" category = "crm" > < i class = "fab fa-connectdevelop" > < / i > CRM< / a >
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'document' }" category = "document" > < i class = "fa fa-file-word" > < / i > Documents< / a >
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'email' }" category = "email" > < i class = "fa fa-envelope" > < / i > Email< / a >
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'sync' }" category = "sync" > < i class = "fa fa-sync-alt" > < / i > File Sync< / a >
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'finance' }" category = "finance" > < i class = "fa fa-hand-holding-usd" > < / i > Finance< / a >
2018-03-14 09:04:05 -07:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'forum' }" category = "forum" > < i class = "fa fa-users" > < / i > Forum< / a >
2018-11-15 18:07:18 +01:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'gallery' }" category = "gallery" > < i class = "fa fa-images" > < / i > Gallery< / a >
2019-02-21 10:15:19 -08:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'game' }" category = "game" > < i class = "fa fa-gamepad" > < / i > Games< / a >
2018-11-15 18:07:18 +01:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'notes' }" category = "notes" > < i class = "fa fa-sticky-note" > < / i > Notes< / a >
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'project' }" category = "project" > < i class = "fas fa-project-diagram" > < / i > Project Management< / a >
2018-03-14 09:04:05 -07:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'vpn' }" category = "vpn" > < i class = "fa fa-user-secret" > < / i > VPN< / a >
2018-11-15 18:07:18 +01:00
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'hosting' }" category = "hosting" > < i class = "fa fa-server" > < / i > Web Hosting< / a >
< a href = "" class = "appstore-category-link" ng-click = "showCategory($event);" ng-class = "{'category-active': category === 'wiki' }" category = "wiki" > < i class = "fab fa-wikipedia-w" > < / i > Wiki< / a >
2018-03-14 09:04:05 -07:00
< br / >
< br / >
< a href = "https://forum.cloudron.io/category/5/app-requests" target = "_blank" > Missing an app? Let us know.< / a >
< / div >
< div class = "col-md-10" ng-show = "apps.length" >
< div class = "row-no-margin" >
< div class = "col-sm-1 appstore-item" ng-repeat = "app in apps | orderBy:'installCount':true" >
2019-05-03 15:56:59 +02: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'" > Unstable< / span >
2018-03-14 09:04:05 -07: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" / >
< / div >
< 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 class="appstore - item - rating"><i class="fa fa - star"></i><i class="fa fa - star"></i><i class="fa fa - star"></i><i class="fa fa - star - half - o"></i><i class="fa fa - star - o"></i></div> -->
< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "col-md-10 animateMeOpacity loading-banner" ng-show = "!apps.length" >
< h3 class = "text-muted" > No apps found.< / h3 >
< a href = "https://forum.cloudron.io/category/5/app-requests" target = "_blank" > < h3 > Request an app or vote for one in our forum.< / h3 > < / a >
< / div >
< / div >