505 lines
33 KiB
HTML
505 lines
33 KiB
HTML
|
|
<!-- Modal install app -->
|
|
<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">{{ appInstall.app.manifest.title }}</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">{{ 'appstore.installDialog.lastUpdated' | tr:{ date: (appInstall.app.creationDate | prettyDate) } }}</span>
|
|
<br/>
|
|
<span class="appstore-install-meta">{{ 'appstore.installDialog.memoryRequirement' | tr:{ size: (appInstall.app.manifest.memoryLimit | prettyBinarySize:'256 MB') } }}</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) }">
|
|
<label class="control-label" for="appInstallLocationInput">{{ 'appstore.installDialog.location' | tr }}</label>
|
|
<div class="input-group form-inline">
|
|
<input type="text" class="form-control" ng-model="appInstall.subdomain" id="appInstallLocationInput" name="location" 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.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>
|
|
<div ng-show="appInstall.error.location" class="text-small">{{ appInstall.error.location }}</div>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
<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>
|
|
|
|
<div class="has-error text-center" ng-show="appInstall.error.port">{{ appInstall.error.port }}</div>
|
|
<div ng-repeat="(env, info) in appInstall.portInfo">
|
|
<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.portsEnabled[env]">
|
|
{{ info.title }}
|
|
<sup>
|
|
<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>
|
|
</sup>
|
|
<small style="padding-left: 5px;" ng-show="info.readOnly">{{ 'appstore.installDialog.portReadOnly' | tr }}</small>
|
|
</label>
|
|
<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>
|
|
<p class="text-small text-warning text-bold" ng-show="appInstall.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p>
|
|
</div>
|
|
</ng-form>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
<div class="form-group" ng-show="appInstall.app.manifest.addons.email">
|
|
<label class="control-label">{{ 'appstore.installDialog.userManagement' | tr }}</label>
|
|
<p>{{ 'appstore.installDialog.userManagementMailbox' | tr }}
|
|
<span ng-bind-html="'appstore.installDialog.configuredForCloudronEmail' | tr:{ emailDocsLink: 'https://docs.cloudron.io/email/' }">
|
|
</p>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<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>
|
|
<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>
|
|
|
|
<p ng-show="appInstall.customAuth || appInstall.app.manifest.addons.email">{{ 'appstore.installDialog.userManagementNone' | tr }}</p>
|
|
<div class="radio" ng-show="appInstall.optionalSso">
|
|
<label>
|
|
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="nosso"> {{ 'appstore.installDialog.userManagementLeaveToApp' | tr }}
|
|
</label>
|
|
</div>
|
|
<div class="radio">
|
|
<label>
|
|
<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>
|
|
</label>
|
|
</div>
|
|
<div class="radio">
|
|
<label>
|
|
<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>
|
|
<span class="label label-danger" ng-show="appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()">{{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<div style="margin-left: 20px;">
|
|
<div class="col-md-5">
|
|
{{ 'appstore.installDialog.users' | tr }}:
|
|
<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>
|
|
</div>
|
|
|
|
<div class="col-md-5">
|
|
{{ 'appstore.installDialog.groups' | tr }}:
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
<br/>
|
|
</div>
|
|
|
|
<div class="hide">
|
|
<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 }">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
<div class="form-group" ng-class="{ 'has-error': !appInstallForm.key.$dirty && appInstall.error.cert }">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="(appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()) || !appInstall.accessRestrictionOption || 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>
|
|
<br/>
|
|
<div class="appstore-install-description">
|
|
<p ng-show="appInstall.app.manifest.upstreamVersion">{{ 'appstore.installDialog.titleAndVersion' | tr:{ title: appInstall.app.manifest.title, version: appInstall.app.manifest.upstreamVersion } }}</p>
|
|
<div ng-bind-html="appInstall.app.manifest.description | markdown2html"></div>
|
|
</div>
|
|
</div>
|
|
<div class="collapse" id="collapseResourceConstraint" data-toggle="false">
|
|
<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>
|
|
<p>{{ 'appstore.installDialog.pleaseUpgradeServer' | tr }}</p>
|
|
</div>
|
|
<div class="collapse" id="collapseSubscriptionRequired" data-toggle="false">
|
|
<p>{{ 'appstore.installDialog.subscriptionRequired' | tr }}</p>
|
|
</div>
|
|
</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="openSubscriptionSetup()" ng-show="appInstall.state === 'subscriptionRequired'">{{ 'appstore.installDialog.setupSubscriptionAction' | tr }}</button>
|
|
<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>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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">{{ '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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
<span class="text-danger" ng-show="applinksAdd.error.upstreamUri">{{ applinksAdd.error.upstreamUri }}</span>
|
|
</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>
|
|
{{ '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>
|
|
</div>
|
|
|
|
<div>
|
|
{{ '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>
|
|
</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>
|
|
|
|
<div ng-show="!ready" class="loading-banner">
|
|
<h1><i class="fa fa-circle-notch fa-spin"></i></h1>
|
|
</div>
|
|
|
|
<!-- appstore login -->
|
|
<div ng-show="ready && !validSubscription" class="container card card-small appstore-login ng-cloak">
|
|
<div class="col-md-12 text-center">
|
|
<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>
|
|
</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/>
|
|
|
|
<div ng-show="appstoreLogin.setupType === 'signup'">
|
|
<form name="appstoreSignupForm" role="form" novalidate ng-submit="appstoreLogin.submit()" autocomplete="off">
|
|
<input type="password" style="display: none;">
|
|
|
|
<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="inputAppstoreSignupEmail" 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.signupPassword) || (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="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>
|
|
</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>
|
|
|
|
<br/>
|
|
|
|
<center>
|
|
<button type="submit" class="btn btn-lg btn-success" ng-disabled="appstoreSignupForm.$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 }">
|
|
<label class="control-label">{{ 'appstore.accountDialog.email' | tr }}</label>
|
|
<input type="email" class="form-control" ng-model="appstoreLogin.email" 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.loginPassword) || (appstoreLoginForm.password.$dirty && appstoreLoginForm.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="(!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>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-class="{ 'has-error': appstoreLogin.error.totpToken }">
|
|
<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">
|
|
<small ng-show="appstoreLogin.error.totpToken">{{ appstoreLogin.error.totpToken }}</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>
|
|
|
|
<br/>
|
|
|
|
<center>
|
|
<button type="submit" class="btn btn-lg btn-success" ng-disabled="appstoreLoginForm.$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="inputAppstoreSetupToken" 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>
|
|
|
|
<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.setupWithTokenAction' | tr }}
|
|
</button>
|
|
</center>
|
|
</form>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<center>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- give more vertical spacing so the login form does not appear clipped -->
|
|
<div ng-show="ready && !validSubscription">
|
|
<br/>
|
|
<br/>
|
|
</div>
|
|
|
|
<div class="appstore-layout">
|
|
|
|
<div ng-show="ready && validSubscription" class="ng-cloak appstore-toolbar">
|
|
<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>
|
|
</div>
|
|
<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>
|
|
</div>
|
|
|
|
<div ng-show="ready && validSubscription" class="ng-cloak appstore-grid">
|
|
<div class="text-center" ng-hide="apps.length || popularApps.length">
|
|
<br/>
|
|
<br/>
|
|
<br/>
|
|
<h3 class="text-muted">{{ 'appstore.noAppsFound' | tr }}</h3>
|
|
<br/>
|
|
<a href="https://forum.cloudron.io/category/5/app-requests" target="_blank">{{ 'appstore.appMissing' | tr }}</a>
|
|
</div>
|
|
<div class="" ng-show="category === '' && popularApps.length">
|
|
<div class="row-no-margin">
|
|
<div class="col-sm-12">
|
|
<h2>{{ 'appstore.category.popular' | tr }}</h2>
|
|
</div>
|
|
</div>
|
|
<div class="row-no-margin">
|
|
<div class="col-sm-3 appstore-item" ng-repeat="app in popularApps | userManagementFilter:userManagementFilterOption">
|
|
<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>
|
|
<div class="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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="" ng-show="apps.length">
|
|
<div class="row-no-margin" ng-show="!category && !searchString">
|
|
<div class="col-sm-12">
|
|
<h2>{{ 'appstore.category.all' | tr }}</h2>
|
|
</div>
|
|
</div>
|
|
<div class="row-no-margin">
|
|
<div class="col-sm-3 appstore-item" ng-repeat="app in apps | userManagementFilter:userManagementFilterOption | orderBy:'-priority' ">
|
|
<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>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|