310 lines
20 KiB
HTML
310 lines
20 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 | prettyByteSize:'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.location" 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.location ? '.' : '') + 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.location && appInstall.domain.provider === 'manual'" ng-bind-html="'appstore.installDialog.manualWarning' | tr:{ location: (appInstall.location + '.' + appInstall.domain.domain) }"></p>
|
|
|
|
<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>
|
|
|
|
<div class="form-group" ng-show="appInstall.customAuth && !appInstall.app.manifest.addons.email">
|
|
<label class="control-label">{{ 'appstore.installDialog.userManagement' | tr }}</label>
|
|
<p>{{ 'appstore.installDialog.userManagementNone' | tr }}</p>
|
|
</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 }}</p>
|
|
</div>
|
|
|
|
<div class="form-group" ng-show="!appInstall.customAuth && !appInstall.app.manifest.addons.email">
|
|
<label class="control-label">{{ 'appstore.installDialog.userManagement' | tr }}</label>
|
|
<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"> {{ 'appstore.installDialog.userManagementAllUsers' | tr }}
|
|
</label>
|
|
</div>
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="groups"> {{ 'appstore.installDialog.userManagementSelectUsers' | tr }}
|
|
<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 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>
|
|
|
|
<p ng-show="appInstall.app.manifest.addons.email" class="text-info" ng-bind-html="'appstore.installDialog.configuredForCloudronEmail' | tr:{ emailDocsLink: 'https://docs.cloudron.io/email/' }"></p>
|
|
|
|
<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">
|
|
<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 }}</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-success pull-left" ng-click="openSubscriptionSetup()" ng-show="appInstall.state === 'subscriptionRequired'">{{ 'appstore.installDialog.setupSubscriptionAction' | tr }}</button>
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | 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>
|
|
|
|
<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.register">{{ 'appstore.accountDialog.titleSignUp' | tr }}</h1>
|
|
<h1 ng-hide="appstoreLogin.register">{{ 'appstore.accountDialog.titleLogin' | 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/>
|
|
|
|
<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" 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">{{ 'appstore.accountDialog.password' | tr }}</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">{{ 'appstore.accountDialog.errorWrongPassword' | tr }}</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" ng-hide="appstoreLogin.register" 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="form-group">
|
|
<label class="control-label">{{ 'appstore.accountDialog.intendedUse' | tr }}</label>
|
|
<select class="purpose form-control" ng-model="appstoreLogin.purpose" required>
|
|
<option value="" disabled selected hidden>{{ 'appstore.accountDialog.chooseAnOption' | tr }}</option>
|
|
<option value="personal_cloud">Personal use</option>
|
|
<option value="business_cloud">Business use</option>
|
|
<option value="website_hosting">Website hosting</option>
|
|
<option value="msp">Managed Service Provider</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>
|
|
</div>
|
|
|
|
<div class="checkbox">
|
|
<label>
|
|
<input type="checkbox" ng-model="appstoreLogin.termsAccepted"><span ng-bind-html="'appstore.accountDialog.licenseCheckbox' | tr:{ licenseLink: 'https://cloudron.io/legal/license.html' }"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<center>
|
|
<button type="submit" class="btn btn-lg btn-success" ng-disabled="appstoreLoginForm.$invalid || appstoreLogin.busy || !appstoreLogin.termsAccepted">
|
|
<i class="fa fa-circle-notch fa-spin" ng-show="appstoreLogin.busy"></i> <span ng-hide="appstoreLogin.register">{{ 'appstore.accountDialog.loginAction' | tr }}</span><span ng-show="appstoreLogin.register">{{ 'appstore.accountDialog.createAccountAction' | tr }}</span>
|
|
</button>
|
|
|
|
<br/>
|
|
<br/>
|
|
|
|
<a href="" ng-click="appstoreLogin.register = true" ng-hide="appstoreLogin.register">{{ 'appstore.accountDialog.switchToSignUpAction' | tr }}</a>
|
|
<a href="" ng-click="appstoreLogin.register = false" ng-show="appstoreLogin.register">{{ 'appstore.accountDialog.switchToLoginAction' | tr }}</a>
|
|
</center>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- give more vertical spacing so the login form does not appear clipped -->
|
|
<div ng-show="ready && !validSubscription">
|
|
<br/>
|
|
<br/>
|
|
</div>
|
|
|
|
<div ng-show="ready && validSubscription" class="ng-cloak appstore-toolbar">
|
|
<div class="appstore-toolbar-content">
|
|
<button class="btn" type="button" ng-click="showCategory('');" ng-class="{ 'btn-primary': '' === category }">{{ 'appstore.category.all' | tr }}</button>
|
|
<button class="btn" type="button" ng-click="showCategory('featured');" ng-class="{ 'btn-primary': 'featured' === category }">{{ 'appstore.category.popular' | tr }}</button>
|
|
<button class="btn" type="button" ng-click="showCategory('new');" ng-class="{ 'btn-primary': 'new' === category }">{{ 'appstore.category.newApps' | tr }}</button>
|
|
<div class="dropdown">
|
|
<button class="btn dropdown-toggle" type="button" data-toggle="dropdown" ng-class="{ 'btn-primary': '' !== category && 'recent' !== category && 'featured' !== category && 'new' !== category }">
|
|
{{ categoryButtonLabel(category) }}
|
|
<span class="caret"></span>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li ng-repeat="category in categories"><a href="" ng-click="showCategory(category.id);"><i class="{{ category.icon }} fa-fw"></i> {{ category.label }}</a></li>
|
|
</ul>
|
|
</div>
|
|
<input type="text" id="appstoreSearch" class="form-control" placeholder="{{ 'appstore.searchPlaceholder' | tr }}" ng-model="searchString" ng-change="search()" autofocus>
|
|
</div>
|
|
</div>
|
|
|
|
<div ng-show="ready && validSubscription" class="ng-cloak appstore-grid">
|
|
<div class="row">
|
|
<div class="col-md-12 text-center" ng-hide="apps.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="col-md-12" ng-show="apps.length">
|
|
<div class="row-no-margin">
|
|
<div class="col-sm-1 appstore-item" ng-repeat="app in apps">
|
|
<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>
|