First bunch of app configure translations
This commit is contained in:
+235
-288
@@ -427,7 +427,7 @@
|
||||
|
||||
<div class="content content-large app-configure">
|
||||
|
||||
<a href="/#/apps" class="back-to-view-link"><i class="fas fa-arrow-left"></i> Back to My Apps</a>
|
||||
<a href="/#/apps" class="back-to-view-link"><i class="fas fa-arrow-left"></i> {{ 'app.backAction' | tr }}</a>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -444,20 +444,20 @@
|
||||
<span class="text-small" ng-show="app.error"> : {{ app.error.reason + ' - ' + app.error.message }}</span>
|
||||
</h1>
|
||||
<div>
|
||||
<a class="btn btn-sm btn-default" ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank" uib-tooltip="Logs" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fas fa-align-left"></i></a>
|
||||
<a class="btn btn-sm btn-default" ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank" uib-tooltip="Terminal" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fa fa-terminal"></i></a>
|
||||
<a class="btn btn-sm btn-default" ng-href="{{ '/filemanager.html?appId=' + app.id }}" target="_blank" uib-tooltip="File Manager" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fas fa-folder"></i></a>
|
||||
<a class="btn btn-sm btn-default" ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank" uib-tooltip="{{ 'app.logsActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fas fa-align-left"></i></a>
|
||||
<a class="btn btn-sm btn-default" ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank" uib-tooltip="{{ 'app.terminalActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fa fa-terminal"></i></a>
|
||||
<a class="btn btn-sm btn-default" ng-href="{{ '/filemanager.html?appId=' + app.id }}" target="_blank" uib-tooltip="{{ 'app.filemanagerActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom"><i class="fas fa-folder"></i></a>
|
||||
<div class="dropdown" style="display: inline-block">
|
||||
<button class="btn btn-sm btn-default dropdown-toggle" type="button" data-toggle="dropdown" uib-tooltip="Documentation" tooltip-append-to-body="true" tooltip-placement="bottom">
|
||||
<button class="btn btn-sm btn-default dropdown-toggle" type="button" data-toggle="dropdown" uib-tooltip="{{ 'app.docsActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom">
|
||||
<i class="fas fa-book"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li ng-class="{ 'disabled': !app.manifest.postInstallMessage }"><a href="" ng-click="postInstallMessage.show(false)">First Time Setup</a></li>
|
||||
<li ng-class="{ 'disabled': !app.manifest.documentationUrl }"><a ng-href="{{ app.manifest.documentationUrl }}" target="_blank">Documentation</a></li>
|
||||
<li ng-class="{ 'disabled': (!app.manifest.configurePath || !(app | applicationLink)) }"><a ng-href="{{ (app.manifest.configurePath && (app | applicationLink)) ? ((app | applicationLink) + app.manifest.configurePath) : ''}}" target="_blank">Admin Page</a></li>
|
||||
<li ng-class="{ 'disabled': !app.manifest.postInstallMessage }"><a href="" ng-click="postInstallMessage.show(false)">{{ 'app.firstTimeSetupAction' | tr }}</a></li>
|
||||
<li ng-class="{ 'disabled': !app.manifest.documentationUrl }"><a ng-href="{{ app.manifest.documentationUrl }}" target="_blank">{{ 'app.docsAction' | tr }}</a></li>
|
||||
<li ng-class="{ 'disabled': (!app.manifest.configurePath || !(app | applicationLink)) }"><a ng-href="{{ (app.manifest.configurePath && (app | applicationLink)) ? ((app | applicationLink) + app.manifest.configurePath) : ''}}" target="_blank">{{ 'app.adminPageAction' | tr }}</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li ng-class="{ 'disabled': !app.manifest.website }"><a ng-href="{{ app.manifest.website }}" target="_blank">Project Website</a></li>
|
||||
<li ng-class="{ 'disabled': !app.manifest.website }"><a ng-href="{{ app.manifest.website }}" target="_blank">{{ 'app.projectWebsiteAction' | tr }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -482,18 +482,18 @@
|
||||
<div class="row app-configure-links-container" ng-show="view">
|
||||
<div class="col-sm-2">
|
||||
<div class="app-configure-links">
|
||||
<div ng-click="setView('display')" ng-class="{ 'active': view === 'display' }">Display</div>
|
||||
<div ng-click="setView('location')" ng-class="{ 'active': view === 'location' }">Location</div>
|
||||
<div ng-click="setView('access')" ng-class="{ 'active': view === 'access' }">Access Control</div>
|
||||
<div ng-click="setView('resources')" ng-class="{ 'active': view === 'resources' }">Resources</div>
|
||||
<div ng-click="setView('storage')" ng-class="{ 'active': view === 'storage' }">Storage</div>
|
||||
<div ng-click="setView('graphs')" ng-class="{ 'active': view === 'graphs' }">Graphs</div>
|
||||
<div ng-click="setView('security')" ng-class="{ 'active': view === 'security' }">Security</div>
|
||||
<div ng-click="setView('email')" ng-class="{ 'active': view === 'email' }" ng-show="app.manifest.addons.sendmail || app.manifest.addons.recvmail">Email</div>
|
||||
<div ng-click="setView('updates')" ng-class="{ 'active': view === 'updates' }">Updates</div>
|
||||
<div ng-click="setView('backups')" ng-class="{ 'active': view === 'backups' }">Backups</div>
|
||||
<div ng-click="setView('repair')" ng-class="{ 'active': view === 'repair' }">Repair</div>
|
||||
<div ng-click="setView('uninstall')" ng-class="{ 'active': view === 'uninstall' }">Uninstall</div>
|
||||
<div ng-click="setView('display')" ng-class="{ 'active': view === 'display' }">{{ 'app.displayTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('location')" ng-class="{ 'active': view === 'location' }">{{ 'app.locationTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('access')" ng-class="{ 'active': view === 'access' }">{{ 'app.accessControlTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('resources')" ng-class="{ 'active': view === 'resources' }">{{ 'app.resourcesTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('storage')" ng-class="{ 'active': view === 'storage' }">{{ 'app.storageTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('graphs')" ng-class="{ 'active': view === 'graphs' }">{{ 'app.graphsTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('security')" ng-class="{ 'active': view === 'security' }">{{ 'app.securityTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('email')" ng-class="{ 'active': view === 'email' }" ng-show="app.manifest.addons.sendmail || app.manifest.addons.recvmail">{{ 'app.emailTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('updates')" ng-class="{ 'active': view === 'updates' }">{{ 'app.updatesTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('backups')" ng-class="{ 'active': view === 'backups' }">{{ 'app.backupsTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('repair')" ng-class="{ 'active': view === 'repair' }">{{ 'app.repairTabTitle' | tr }}</div>
|
||||
<div ng-click="setView('uninstall')" ng-class="{ 'active': view === 'uninstall' }">{{ 'app.uninstallTabTitle' | tr }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 card-container">
|
||||
@@ -503,23 +503,23 @@
|
||||
<form role="form" name="displayForm" ng-submit="display.submit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group" ng-class="{ 'has-error': !displayForm.label.$dirty && display.error.label }">
|
||||
<label class="control-label">Label</label>
|
||||
<label class="control-label">{{ 'app.display.label' | tr }}</label>
|
||||
<div class="control-label" ng-show="display.error.label">{{display.error.label}}</div>
|
||||
<input type="text" class="form-control" id="displayLabelInput" name="label" ng-model="display.label">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">Tags</label>
|
||||
<tag-input class="form-control" placeholder="Use space to separate tags" taglist="display.tags" name="tags" uib-tooltip="For grouping in the dashboard"></tag-input>
|
||||
<label class="control-label">{{ 'app.display.tags' | tr }}</label>
|
||||
<tag-input class="form-control" placeholder="{{ 'app.display.tagsPlaceholder' | tr }}" taglist="display.tags" name="tags" uib-tooltip="{{ 'app.display.tagsTooltip' | tr }}"></tag-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<label class="control-label">Icon</label>
|
||||
<label class="control-label">{{ 'app.display.icon' | tr }}</label>
|
||||
</div>
|
||||
<div id="previewIcon" class="app-custom-icon" ng-click="display.showCustomIconSelector()">
|
||||
<img ng-src="{{ display.iconUrl() || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)"/>
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
<a href="" style="font-weight: normal;" ng-click="display.resetCustomIcon()">Reset Icon</a>
|
||||
<a href="" style="font-weight: normal;" ng-click="display.resetCustomIcon()">{{ 'app.display.iconResetAction' | tr }}</a>
|
||||
<input type="file" id="iconFileInput" style="display: none" accept="image/png"/>
|
||||
</div>
|
||||
|
||||
@@ -530,7 +530,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="display.submit()" ng-disabled="(!display.icon.data && !displayForm.$dirty) || display.$invalid || display.busy"><i class="fa fa-circle-notch fa-spin" ng-show="display.busy"></i> Save</button>
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="display.submit()" ng-disabled="(!display.icon.data && !displayForm.$dirty) || display.$invalid || display.busy"><i class="fa fa-circle-notch fa-spin" ng-show="display.busy"></i> {{ 'app.display.saveAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -539,99 +539,84 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form role="form" name="locationForm" ng-submit="location.submit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group" ng-class="{ 'has-error': (locationForm.location.$dirty && locationForm.location.$invalid) || (!locationForm.location.$dirty && location.error.location) }">
|
||||
<label class="control-label">Location</label>
|
||||
<div class="has-error" ng-show="location.error.location">{{ location.error.location }}</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': (locationForm.location.$dirty && locationForm.location.$invalid) || (!locationForm.location.$dirty && location.error.location) }">
|
||||
<label class="control-label">{{ 'app.location.location' | tr }}</label>
|
||||
<div class="has-error" ng-show="location.error.location">{{ location.error.location }}</div>
|
||||
|
||||
<div class="input-group form-inline">
|
||||
<input type="text" class="form-control" ng-model="location.location" id="locationLocationInput" name="location" placeholder="{{ 'Leave empty to use bare domain' }}" autofocus>
|
||||
<div class="input-group form-inline">
|
||||
<input type="text" class="form-control" ng-model="location.location" name="location" placeholder="{{ 'app.location.locationPlaceholder' | tr }}" autofocus>
|
||||
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>{{ (!location.location ? '' : '.') + location.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="location.domain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>{{ (!location.location ? '' : '.') + location.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="location.domain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="small text-center text-warning" ng-show="location.domain.provider === 'linode'">
|
||||
<b>Linode DNS average <a target="_blank" ng-href="https://docs.cloudron.io/domains/#linode-dns">propagation time</a> is 30 minutes.
|
||||
Changing the location will take a while.</b>
|
||||
<br>
|
||||
</p>
|
||||
<p class="text-small text-bold text-warning" ng-show="location.domain.provider === 'linode'" ng-bind-html="'appstore.installDialog.linodeWarning' | tr:{ linodeDocsLink: 'https://docs.cloudron.io/domains/#linode-dns' }"></p>
|
||||
<p class="text-small text-bold text-warning" ng-show="location.location && location.domain.provider === 'manual'" ng-bind-html="'appstore.installDialog.manualWarning' | tr:{ location: (location.location + '.' + location.domain.domain) }"></p>
|
||||
|
||||
<p class="text-center" ng-show="location.location && location.domain.provider === 'manual'">
|
||||
<b>Add an A record manually for {{ location.location }} to this Cloudron's public IP</b>
|
||||
<br>
|
||||
</p>
|
||||
<!-- hidden submit has to be prior to other button elements, otherwise firefox will treat them as the "enter" key action, in this case the alternate domain delete button! -->
|
||||
<input class="ng-hide" type="submit" ng-disabled="locationForm.$invalid || location.busy"/>
|
||||
|
||||
<!-- hidden submit has to be prior to other button elements, otherwise firefox will treat them as the "enter" key action, in this case the alternate domain delete button! -->
|
||||
<input class="ng-hide" type="submit" ng-disabled="locationForm.$invalid || location.busy"/>
|
||||
<div class="has-error text-center" ng-show="location.error.port">{{ location.error.port }}</div>
|
||||
<div ng-repeat="(env, info) in location.portBindingsInfo">
|
||||
<ng-form name="portInfo_form">
|
||||
<div class="form-group" ng-class="{ 'has-error': (!locationForm.itemName{{$index}}.$dirty && location.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
|
||||
<label class="control-label" for="locationPortInput{{env}}"><input type="checkbox" ng-model="location.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="location.portBindings[env]" ng-disabled="!location.portBindingsEnabled[env]" id="locationPortInput{{env}}" later-name="itemName{{$index}}" min="{{HOST_PORT_MIN}}" max="{{HOST_PORT_MAX}}" required>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
|
||||
<div class="has-error text-center" ng-show="location.error.port">{{ location.error.port }}</div>
|
||||
<div ng-repeat="(env, info) in location.portBindingsInfo">
|
||||
<ng-form name="portInfo_form">
|
||||
<div class="form-group" ng-class="{ 'has-error': (!locationForm.itemName{{$index}}.$dirty && location.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
|
||||
<label class="control-label" for="locationPortInput{{env}}"><input type="checkbox" ng-model="location.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="location.portBindings[env]" ng-disabled="!location.portBindingsEnabled[env]" id="locationPortInput{{env}}" later-name="itemName{{$index}}" min="{{HOST_PORT_MIN}}" max="{{HOST_PORT_MAX}}" required>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
<div class="form-group alternate-domains">
|
||||
<label class="control-label">{{ 'app.location.redirections' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#redirections" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<div class="has-error" ng-show="location.error.alternateDomains">{{ location.error.alternateDomains }}</div>
|
||||
|
||||
<div class="form-group alternate-domains">
|
||||
<label class="control-label">Redirections <sup><a ng-href="https://docs.cloudron.io/apps/#redirections" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<div class="has-error" ng-show="location.error.alternateDomains">{{ location.error.alternateDomains }}</div>
|
||||
<div class="row" ng-repeat="alternateDomain in location.alternateDomains">
|
||||
<div class="col col-lg-11">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" ng-model="alternateDomain.subdomain" placeholder="{{ 'app.location.redirectionsPlaceholder' | tr }}">
|
||||
|
||||
<div class="row" ng-repeat="alternateDomain in location.alternateDomains">
|
||||
<div class="col col-lg-11">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" ng-model="alternateDomain.subdomain" placeholder="Leave empty to use bare domain">
|
||||
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>{{ (!alternateDomain.subdomain ? '' : '.') + alternateDomain.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="alternateDomain.domain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>{{ (!alternateDomain.subdomain ? '' : '.') + alternateDomain.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="alternateDomain.domain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-lg-1">
|
||||
<button class="btn btn-danger btn-sm" ng-click="location.delAlternateDomain($event, $index)"><i class="far fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="location.alternateDomains.length === 0">
|
||||
No redirect domains are configured. <a href="" ng-click="location.addAlternateDomain($event)">Add a redirection</a>
|
||||
</div>
|
||||
<div ng-show="location.alternateDomains.length > 0" style="margin-top: 5px;">
|
||||
<a href="" ng-click="location.addAlternateDomain($event)">Add another redirection</a>
|
||||
<div class="col col-lg-1">
|
||||
<button class="btn btn-danger btn-sm" ng-click="location.delAlternateDomain($event, $index)"><i class="far fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
<div ng-show="location.alternateDomains.length === 0">{{ 'app.location.noRedirections' | tr }}</div>
|
||||
<div style="margin-top: 5px;"><a href="" ng-click="location.addAlternateDomain($event)">{{ 'app.location.addRedirectionAction' | tr }}</a></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="location.submit()" ng-disabled="location.$invalid || location.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="location.busy"></i> Save
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="location.busy"></i> {{ 'app.location.saveAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -641,58 +626,48 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form role="form" name="accessForm" ng-submit="access.submit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<div ng-show="access.ssoAuth">
|
||||
<label class="control-label">User management</label>
|
||||
<p>
|
||||
This app is configured to authenticate with the Cloudron User Directory.
|
||||
<span class="text-small text-warning" ng-show="access.ftp">Also controls SFTP access.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div ng-show="!access.ssoAuth">
|
||||
<label class="control-label">Dashboard visibility</label>
|
||||
<p ng-show="!access.app.manifest.addons.email" class="text-small">
|
||||
This app has it's own user management.
|
||||
<span ng-show="access.ftp">This setting also controls SFTP access.</span>
|
||||
</p>
|
||||
<p ng-show="access.app.manifest.addons.email" class="text-small">
|
||||
This app is pre-configured for use with <a ng-href="https://docs.cloudron.io/email/" target="_blank">Cloudron Email</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div ng-show="access.ssoAuth">
|
||||
<label class="control-label">{{ 'app.accessControl.userManagement.title' | tr }}</label>
|
||||
<p>{{ 'app.accessControl.userManagement.description' | tr }} <span class="text-small text-warning" ng-show="access.ftp">{{ 'app.accessControl.userManagement.descriptionSftp' | tr }}</span></p>
|
||||
</div>
|
||||
<div ng-show="!access.ssoAuth">
|
||||
<label class="control-label">{{ 'app.accessControl.userManagement.dashboardVisibility' | tr }}</label>
|
||||
<p ng-show="!access.app.manifest.addons.email" class="text-small">{{ 'appstore.installDialog.userManagementNone' | tr }} <span ng-show="access.ftp">{{ 'app.accessControl.userManagement.sftpAccessControl' | tr }}</span></p>
|
||||
<p ng-show="access.app.manifest.addons.email" class="text-small">{{ 'appstore.installDialog.configuredForCloudronEmail' | tr:{ emailDocsLink: 'https://docs.cloudron.io/email/' } }}</p>
|
||||
</div>
|
||||
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="access.accessRestrictionOption" value="any">
|
||||
<span ng-show="access.ssoAuth">Allow all users on this Cloudron</span>
|
||||
<span ng-show="!access.ssoAuth">Visible to all users on this Cloudron</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="access.accessRestrictionOption" value="groups">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="access.accessRestrictionOption" value="any">
|
||||
<span ng-show="access.ssoAuth">{{ 'appstore.installDialog.userManagementAllUsers' | tr }}</span>
|
||||
<span ng-show="!access.ssoAuth">{{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="access.accessRestrictionOption" value="groups">
|
||||
|
||||
<span ng-show="access.ssoAuth">Only allow the following users and groups</span>
|
||||
<span ng-show="!access.ssoAuth">Only visible to the following users and groups</span>
|
||||
<span ng-show="access.ssoAuth">{{ 'appstore.installDialog.userManagementSelectUsers' | tr }}</span>
|
||||
<span ng-show="!access.ssoAuth">{{ 'app.accessControl.userManagement.visibleForSelected' | tr }}</span>
|
||||
|
||||
<span class="label label-danger" ng-show="access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()">Select at least one user or group</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin-left: 20px;">
|
||||
<div class="col-md-5">
|
||||
Users: <multiselect class="input-sm stretch" ng-model="access.accessRestriction.users" ng-disabled="access.accessRestrictionOption !== 'groups'" options="user.display for user in users" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
<span class="label label-danger" ng-show="access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()">{{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div style="margin-left: 20px;">
|
||||
<div class="col-md-5">
|
||||
{{ 'appstore.installDialog.users' | tr }}: <multiselect class="input-sm stretch" ng-model="access.accessRestriction.users" ng-disabled="access.accessRestrictionOption !== 'groups'" options="user.display for user in users" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
Groups: <multiselect class="input-sm stretch" ng-model="access.accessRestriction.groups" ng-disabled="access.accessRestrictionOption !== 'groups'" options="group.name for group in groups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
{{ 'appstore.installDialog.groups' | tr }}: <multiselect class="input-sm stretch" ng-model="access.accessRestriction.groups" ng-disabled="access.accessRestrictionOption !== 'groups'" options="group.name for group in groups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="(access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()) || accessForm.$invalid || access.busy"/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="(access.accessRestrictionOption === 'groups' && !access.isAccessRestrictionValid()) || accessForm.$invalid || access.busy"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -704,13 +679,13 @@
|
||||
<div class="row" ng-show="app.manifest.addons.localstorage.ftp">
|
||||
<hr/>
|
||||
<div class="col-md-12">
|
||||
<label>SFTP</label> <sup><a ng-href="https://docs.cloudron.io/apps/#ftp-access" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup><br/>
|
||||
<label>{{ 'app.accessControl.sftp.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#ftp-access" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">Server</span>
|
||||
<span class="text-muted">{{ 'app.accessControl.sftp.server' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span>{{ config.adminFqdn }}</span>
|
||||
@@ -719,7 +694,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">Port</span>
|
||||
<span class="text-muted">{{ 'app.accessControl.sftp.port' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span>222</span>
|
||||
@@ -728,7 +703,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">Username</span>
|
||||
<span class="text-muted">{{ 'app.accessControl.sftp.username' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span>{{ user.username }}@{{ app.fqdn }}</span>
|
||||
@@ -742,27 +717,25 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form role="form" name="resourcesForm" ng-submit="resources.submitMemoryLimit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="memoryLimit">Memory Limit <sup><a ng-href="https://docs.cloudron.io/apps/#memory-limit" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ resources.memoryLimit | prettyByteSize:'Default (256 MB)' }}</b></label>
|
||||
<p>Cloudron allocates 50% of this value as RAM and 50% as swap.</p>
|
||||
<div style="padding: 0 10px;">
|
||||
<slider id="memoryLimit" ng-model="resources.memoryLimit" step="134217728" tooltip="hide" ticks="resources.memoryTicks" ticks-snap-bounds="67108864"></slider>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="memoryLimit">{{ 'app.resources.memory.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#memory-limit" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ resources.memoryLimit | prettyByteSize:'Default (256 MB)' }}</b></label>
|
||||
<p>{{ 'app.resources.memory.description' | tr }}</p>
|
||||
<div style="padding: 0 10px;">
|
||||
<slider id="memoryLimit" ng-model="resources.memoryLimit" step="134217728" tooltip="hide" ticks="resources.memoryTicks" ticks-snap-bounds="67108864"></slider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="resources.memoryLimit === resources.currentMemoryLimit || resourcesForm.$invalid || resources.busy"/>
|
||||
</fieldset>
|
||||
<input class="ng-hide" type="submit" ng-disabled="resources.memoryLimit === resources.currentMemoryLimit || resourcesForm.$invalid || resources.busy"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<span ng-show="resources.error.memoryLimit" class="text-danger">Unable to set memory limit, try less.</span>
|
||||
<span ng-show="resources.error.memoryLimit" class="text-danger">{{ 'app.resources.memory.error' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-md-4 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="resources.submitMemoryLimit()" ng-disabled="resources.memoryLimit === resources.currentMemoryLimit || resourcesForm.$invalid || resources.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="resources.busy"></i> Resize
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="resources.busy"></i> {{ 'app.resources.memory.resizeAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -772,8 +745,8 @@
|
||||
<form role="form" name="resourcesForm" ng-submit="resources.submitCpuShares()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="cpuShares">CPU Shares <sup><a ng-href="https://docs.cloudron.io/apps/#cpu-shares" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ (resources.cpuShares * 100 / 1024 | number:0) + ' %' }}</b></label>
|
||||
<p>Percent of CPU time when system is under heavy load.</p>
|
||||
<label class="control-label" for="cpuShares">{{ 'app.resources.cpu.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#cpu-shares" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ (resources.cpuShares * 100 / 1024 | number:0) + ' %' }}</b></label>
|
||||
<p>{{ 'app.resources.cpu.description' | tr }}</p>
|
||||
<div style="padding: 0 10px;">
|
||||
<slider id="cpuShares" ng-model="resources.cpuShares" ticks="[32, 256, 512, 768, 1024]" step="32" ticks-snap-bounds="32" min="32" max="1024" tooltip="hide"></slider>
|
||||
</div>
|
||||
@@ -787,7 +760,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="resources.submitCpuShares()" ng-disabled="resources.cpuShares === resources.currentCpuShares || resourcesForm.$invalid || resources.busyCpuShares || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="resources.busyCpuShares"></i> Set
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="resources.busyCpuShares"></i> {{ 'app.resources.cpu.setAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -796,27 +769,22 @@
|
||||
<div class="card" ng-show="view === 'storage'">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label" for="storageEnableDataDir">App Data<sup><a ng-href="https://docs.cloudron.io/storage/#app-data-directory" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p>
|
||||
By default, this app's data is located at <code>/home/yellowtent/appsdata/{{ app.id }}</code>. If the server is running out of disk space,
|
||||
you can bind an external disk and move this app's data there. Only Ext4 and NFS binds are supported.
|
||||
</p>
|
||||
<label class="control-label">{{ 'app.storage.appdata.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/storage/#app-data-directory" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p ng-bind-html="'app.storage.appdata.description' | tr:{ storagePath: ('/home/yellowtent/appsdata/' + app.id) }"></p>
|
||||
<form role="form" name="storageDataDirForm" ng-submit="storage.submitDataDir()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group" ng-class="{ 'has-error': storageDataDirForm.$dirty && storage.error.dataDir }">
|
||||
<div ng-show="storage.error.dataDir">{{ storage.error.dataDir }}</div>
|
||||
<input type="text" class="form-control" name="dataDir" placeholder="Leave empty to use platform default" ng-model="storage.dataDir">
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': storageDataDirForm.$dirty && storage.error.dataDir }">
|
||||
<div ng-show="storage.error.dataDir">{{ storage.error.dataDir }}</div>
|
||||
<input type="text" class="form-control" name="dataDir" placeholder="{{ 'app.storage.appdata.dataDirPlaceholder' | tr }}" ng-model="storage.dataDir">
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="!storageDataDirForm.$dirty || storageDataDirForm.$invalid || storage.busyDataDir"/>
|
||||
</fieldset>
|
||||
<input class="ng-hide" type="submit" ng-disabled="!storageDataDirForm.$dirty || storageDataDirForm.$invalid || storage.busyDataDir"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="storage.submitDataDir()" ng-disabled="!storageDataDirForm.$dirty || storageDataDirForm.$invalid || storage.busyDataDir || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="storage.busyDataDir"></i> Move Data
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="storage.busyDataDir"></i> {{ 'app.storage.appdata.moveAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -824,14 +792,14 @@
|
||||
<hr>
|
||||
|
||||
<div class="form-group mounts">
|
||||
<label class="control-label">Mounts <sup><a ng-href="https://docs.cloudron.io/apps/#volumes" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<label class="control-label">{{ 'app.storage.mounts.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#volumes" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<div class="has-error" ng-show="storage.error.mounts">{{ storage.error.mounts }}</div>
|
||||
|
||||
<table class="table table-hover" style="margin-top: 10px;" ng-show="storage.mounts.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40%">Volume</th>
|
||||
<th class="text-left hidden-xs hidden-sm">Read Only</th>
|
||||
<th style="width: 40%">{{ 'app.storage.mounts.volume' | tr }}</th>
|
||||
<th class="text-left hidden-xs hidden-sm">{{ 'app.storage.mounts.readOnly' | tr }}</th>
|
||||
<th style="width: 100px" class="text-right">{{ 'main.actions' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -850,18 +818,14 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div ng-show="storage.mounts.length === 0">
|
||||
No volumes are mounted. <a href="" ng-click="storage.addMount($event)">Add a mount</a>
|
||||
</div>
|
||||
<div ng-show="storage.mounts.length > 0" style="margin-top: 5px;">
|
||||
<a href="" ng-click="storage.addMount($event)">Add another mount</a>
|
||||
</div>
|
||||
<div ng-show="storage.mounts.length === 0">{{ 'app.storage.mounts.noMounts' | tr }}</div>
|
||||
<div style="margin-top: 5px;"><a href="" ng-click="storage.addMount($event)">{{ 'app.storage.mounts.addMountAction' | tr }}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="storage.submitMounts()" ng-disabled="storage.busyMounts || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="storage.busyMounts"></i> Save
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="storage.busyMounts"></i> {{ 'app.storage.mounts.saveAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -872,17 +836,17 @@
|
||||
<div class="col-md-12">
|
||||
<div class="dropdown pull-right">
|
||||
<button class="btn btn-sm btn-primary dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
Select Period {{ graphs.periodLabel }}
|
||||
{{ 'app.graphs.selectPeriod' | tr:{ period: graphs.periodLabel } }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="" ng-click="graphs.setPeriod(12, '12 hours')">12 hours</a></li>
|
||||
<li><a href="" ng-click="graphs.setPeriod(24, '24 hours')">24 hours</a></li>
|
||||
<li><a href="" ng-click="graphs.setPeriod(24*7, '7 days')">7 days</a></li>
|
||||
<li><a href="" ng-click="graphs.setPeriod(24*30, '30 days')">30 days</a></li>
|
||||
<li><a href="" ng-click="graphs.setPeriod(12, '12 hours')">{{ 'app.graphs.period.12h' | tr }}</a></li>
|
||||
<li><a href="" ng-click="graphs.setPeriod(24, '24 hours')">{{ 'app.graphs.period.24h' | tr }}</a></li>
|
||||
<li><a href="" ng-click="graphs.setPeriod(24*7, '7 days')">{{ 'app.graphs.period.7d' | tr }}</a></li>
|
||||
<li><a href="" ng-click="graphs.setPeriod(24*30, '30 days')">{{ 'app.graphs.period.30d' | tr }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<label style="margin-top: 10px;">Memory (RAM + Swap) in MB</label>
|
||||
<label style="margin-top: 10px;">{{ 'app.graphs.memoryTitle' | tr }}</label>
|
||||
<canvas id="graphsMemoryChart" style="width: 100%;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
@@ -891,41 +855,39 @@
|
||||
<div class="card" ng-show="view === 'email'">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label" for="emailMailboxNameEnabled">Mail FROM Address</label>
|
||||
<p>This sets the address from which this app sends email. This app is already configured to send mail using {{app.domain}}'s <a ng-href="/#/email/{{ app.domain }}">Outbound Email</a> settings.</p>
|
||||
<label class="control-label">{{ 'app.email.from.title' | tr }}</label>
|
||||
<p ng-bind-html="'app.email.from.description' | tr:{ domain: app.domain, domainConfigLink: ('/#/email/' + app.domain) }"></p>
|
||||
|
||||
<form role="form" name="emailForm" ng-submit="email.submit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<!-- recvmail currently only works with cloudron email -->
|
||||
<div class="form-group" ng-class="{ 'has-error': emailForm.$dirty && email.error.mailboxName }">
|
||||
<div ng-show="email.error.mailboxName">{{ email.error.mailboxName }}</div>
|
||||
<!-- recvmail currently only works with cloudron email -->
|
||||
<div class="form-group" ng-class="{ 'has-error': emailForm.$dirty && email.error.mailboxName }">
|
||||
<div ng-show="email.error.mailboxName">{{ email.error.mailboxName }}</div>
|
||||
|
||||
<div class="input-group form-inline" ng-class="{ 'has-error': !emailForm.mailboxName.$dirty && email.error.mailboxName }">
|
||||
<input type="text" class="form-control" name="mailboxName" placeholder="Leave empty to use platform default" ng-model="email.mailboxName">
|
||||
<div class="input-group form-inline" ng-class="{ 'has-error': !emailForm.mailboxName.$dirty && email.error.mailboxName }">
|
||||
<input type="text" class="form-control" name="mailboxName" placeholder="{{ 'app.email.from.mailboxPlaceholder' | tr }}" ng-model="email.mailboxName">
|
||||
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>{{ '@' + email.mailboxDomain.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="email.mailboxDomain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span>{{ '@' + email.mailboxDomain.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="email.mailboxDomain = domain">{{ domain.domain }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
<input class="ng-hide" type="submit" ng-disabled="(email.currentMailboxDomainName === email.mailboxDomain.domain && email.currentMailboxName === email.mailboxName) || email.busy || app.error || app.taskId"/>
|
||||
</fieldset>
|
||||
<br/>
|
||||
</div>
|
||||
<input class="ng-hide" type="submit" ng-disabled="(email.currentMailboxDomainName === email.mailboxDomain.domain && email.currentMailboxName === email.mailboxName) || email.busy || app.error || app.taskId"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="email.submit()" ng-disabled="(email.currentMailboxDomainName === email.mailboxDomain.domain && email.currentMailboxName === email.mailboxName) || email.busy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="email.busy"></i> Save
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="email.busy"></i> {{ 'app.email.from.saveAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -935,20 +897,18 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form role="form" name="securityForm" ng-submit="security.submit()" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="control-label" style="width: 100%">Robots.txt <sup><a ng-href="https://docs.cloudron.io/apps/#robotstxt" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> <a href="" class="pull-right" style="font-weight: normal;" ng-click="security.robotsTxt = ROBOTS_DISABLE_INDEXING_TEMPLATE">Disable indexing</a></label>
|
||||
<textarea ng-trim="false" style="white-space: pre-wrap" ng-model="security.robotsTxt" placeholder="Leave empty to allow all bots to index this app" class="form-control" rows="4"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" style="width: 100%">{{ 'app.security.robots.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#robotstxt" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> <a href="" class="pull-right" style="font-weight: normal;" ng-click="security.robotsTxt = ROBOTS_DISABLE_INDEXING_TEMPLATE">Disable indexing</a></label>
|
||||
<textarea ng-trim="false" style="white-space: pre-wrap" ng-model="security.robotsTxt" placeholder="{{ 'app.security.robots.txtPlaceholder' | tr }}" class="form-control" rows="4"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" style="width: 100%">Content Security Policy <sup><a ng-href="https://docs.cloudron.io/apps/#custom-csp" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> </label>
|
||||
<p>Setting this option will override any CSP headers sent by the app itself</p>
|
||||
<textarea ng-model="security.csp" placeholder="default-src 'self'; frame-ancestors 'none';" class="form-control" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" style="width: 100%">{{ 'app.security.csp.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#custom-csp" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> </label>
|
||||
<p>{{ 'app.security.csp.description' | tr }}</p>
|
||||
<textarea ng-model="security.csp" placeholder="default-src 'self'; frame-ancestors 'none';" class="form-control" rows="2"></textarea>
|
||||
</div>
|
||||
|
||||
<input class="ng-hide" type="submit" ng-disabled="securityForm.$invalid || security.busy"/>
|
||||
</fieldset>
|
||||
<input class="ng-hide" type="submit" ng-disabled="securityForm.$invalid || security.busy"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -956,17 +916,17 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="security.submit()" ng-disabled="security.$invalid || security.busy || app.error" tooltip-enable="app.error" uib-tooltip="App is in error state">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="security.busy"></i> Save
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="security.busy"></i> {{ 'app.security.csp.saveAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" ng-show="view === 'updates'">
|
||||
<p><label class="control-label">App Info</label></p>
|
||||
<p><label class="control-label">{{ 'app.updates.info.title' | tr }}</label></p>
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<span class="text-muted">App Title & Version</span>
|
||||
<span class="text-muted">{{ 'app.updates.info.description' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-8 text-right">
|
||||
<span ng-show="app.appStoreId">{{ app.manifest.title }} {{ app.upstreamVersion }}</span>
|
||||
@@ -976,7 +936,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">App ID</span>
|
||||
<span class="text-muted">{{ 'app.updates.info.appId' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span>{{ app.id }}</span>
|
||||
@@ -985,7 +945,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">Package Version</span>
|
||||
<span class="text-muted">{{ 'app.updates.info.packageVersion' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span ng-show="app.appStoreId"><a ng-href="/#/appstore/{{app.manifest.id}}?version={{app.manifest.version}}">v{{ app.manifest.version }}</a></span>
|
||||
@@ -995,7 +955,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<span class="text-muted">Last Updated</span>
|
||||
<span class="text-muted">{{ 'app.updates.info.lastUpdated' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-xs-6 text-right">
|
||||
<span>{{ app.updateTime | prettyDate }}</span>
|
||||
@@ -1006,20 +966,21 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12" ng-show="!config.update.apps[app.id].manifest.version || config.update.apps[app.id].manifest.version === app.manifest.version">
|
||||
<button class="btn btn-primary pull-right" ng-show="app.appStoreId" ng-click="updates.check()" ng-disabled="updates.busyCheck"><i class="fa fa-circle-notch fa-spin" ng-show="updates.busyCheck"></i> Check for Updates</button>
|
||||
<span ng-show="!app.appStoreId" class="text-danger pull-right">Updates are not available for custom apps</span>
|
||||
<button class="btn btn-primary pull-right" ng-show="app.appStoreId" ng-click="updates.check()" ng-disabled="updates.busyCheck"><i class="fa fa-circle-notch fa-spin" ng-show="updates.busyCheck"></i> {{ 'app.updates.info.checkForUpdatesAction' | tr }}</button>
|
||||
<span ng-show="!app.appStoreId" class="text-danger pull-right">{{ 'app.updates.info.customAppUpdateInfo' | tr }}</span>
|
||||
</div>
|
||||
<div class="col-md-12" ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && app.installationState !== 'pending_update'">
|
||||
<button type="button" class="btn btn-success pull-right" ng-click="updates.askUpdate()" ng-disabled="app.taskId || app.error || app.runState === 'stopped'" tooltip-enable="app.error || app.taskId || app.runState === 'stopped'" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is not running' }}">Update Available</button>
|
||||
<button type="button" class="btn btn-success pull-right" ng-click="updates.askUpdate()" ng-disabled="app.taskId || app.error || app.runState === 'stopped'" tooltip-enable="app.error || app.taskId || app.runState === 'stopped'" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is not running' }}">{{ 'app.updates.info.updateAvailableAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label">Automatic Updates</label>
|
||||
<p>Cloudron periodically polls the App Store to check for updates. If you disable automatic updates, be sure to manually check for updates.</p>
|
||||
<p>Automatic Updates is currently <b>{{ updates.enableAutomaticUpdate ? 'enabled' : 'disabled' }}</b>.</p>
|
||||
<button class="btn btn-primary pull-right" uib-tooltip="{{ app.appStoreId ? '' : 'Not available for custom apps' }}" ng-class="{ 'btn-danger': updates.enableAutomaticUpdate }" ng-click="updates.toggleAutomaticUpdates()" ng-disabled="updates.busyAutomaticUpdates || !app.appStoreId"><i class="fa fa-circle-notch fa-spin" ng-show="updates.busyAutomaticUpdates"></i> {{ updates.enableAutomaticUpdate ? 'Disable' : 'Enable' }} Automatic Updates</button>
|
||||
<label class="control-label">{{ 'app.updates.auto.title' | tr }}</label>
|
||||
<p>{{ 'app.updates.auto.description' | tr }}</p>
|
||||
<p ng-show="updates.enableAutomaticUpdate">{{ 'app.updates.auto.enabled' | tr }}</p>
|
||||
<p ng-hide="updates.enableAutomaticUpdate">{{ 'app.updates.auto.disabled' | tr }}</p>
|
||||
<button class="btn btn-primary pull-right" uib-tooltip="{{ app.appStoreId ? '' : 'Not available for custom apps' }}" ng-class="{ 'btn-danger': updates.enableAutomaticUpdate }" ng-click="updates.toggleAutomaticUpdates()" ng-disabled="updates.busyAutomaticUpdates || !app.appStoreId"><i class="fa fa-circle-notch fa-spin" ng-show="updates.busyAutomaticUpdates"></i> {{ updates.enableAutomaticUpdate ? ('app.updates.auto.disableAction' | tr) : ('app.updates.auto.enableAction' | tr) }} </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1027,11 +988,8 @@
|
||||
<div class="card" ng-show="view === 'backups'">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label">Backups</label>
|
||||
|
||||
<div>
|
||||
<span>Backups are complete snapshots of the app. You can use app backups to restore or clone this app.</span>
|
||||
</div>
|
||||
<label class="control-label">{{ 'app.backups.backups.title' | tr }}</label>
|
||||
<div>{{ 'app.backups.backups.description' | tr }}</div>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -1039,19 +997,21 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="25px"> </th>
|
||||
<th>Backup</th>
|
||||
<th>{{ 'app.backups.backups.packageVersion' | tr }}</th>
|
||||
<th>{{ 'app.backups.backups.time' | tr }}</th>
|
||||
<th class="text-right" width="180px">{{ 'main.actions' | tr }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="backup in backups.backups">
|
||||
<td><div class="hand clipboard" data-clipboard-text="{{ backup.id }}" uib-tooltip="{{ copyBackupIdDone ? 'Copied to clipboard' : 'Click to copy backup id' }}" tooltip-placement="right"><i class="fa fa-copy"></i></div></td>
|
||||
<td><div uib-tooltip="{{ backup.creationTime | prettyLongDate }}">v{{ backup.packageVersion }} - {{ backup.creationTime | prettyDate }}</div></td>
|
||||
<td><div class="hand clipboard" data-clipboard-text="{{ backup.id }}" uib-tooltip="{{ copyBackupIdDone ? ('main.clipboard.copied' | tr) : ('main.clipboard.clickToCopyBackupId' | tr) }}" tooltip-placement="right"><i class="fa fa-copy"></i></div></td>
|
||||
<td><div>v{{ backup.packageVersion }}</div></td>
|
||||
<td><div uib-tooltip="{{ backup.creationTime | prettyLongDate }}">{{ backup.creationTime | prettyDate }}</div></td>
|
||||
<td class="text-right no-wrap" style="vertical-align: bottom">
|
||||
<button class="btn btn-xs btn-default" ng-click="downloadConfig(backup)" uib-tooltip="Download Backup Configuration"><i class="fas fa-file-alt"></i></button>
|
||||
<button class="btn btn-xs btn-default" ng-click="downloadConfig(backup)" uib-tooltip="{{ 'app.backups.backups.downloadConfigTooltip' | tr }}"><i class="fas fa-file-alt"></i></button>
|
||||
|
||||
<button class="btn btn-xs btn-default" ng-click="clone.show(backup)" uib-tooltip="Clone from this Backup"><i class="far fa-clone"></i></button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="restore.show(backup)" ng-disabled="app.taskId || app.runState === 'stopped'" uib-tooltip="Restore to this Backup"><i class="fas fa-history"></i></button>
|
||||
<button class="btn btn-xs btn-default" ng-click="clone.show(backup)" uib-tooltip="{{ 'app.backups.backups.cloneTooltip' | tr }}"><i class="far fa-clone"></i></button>
|
||||
<button class="btn btn-xs btn-danger" ng-click="restore.show(backup)" ng-disabled="app.taskId || app.runState === 'stopped'" uib-tooltip="{{ 'app.backups.backups.restoreTooltip' | tr }}"><i class="fas fa-history"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -1059,7 +1019,7 @@
|
||||
|
||||
<br/>
|
||||
<button type="button" class="btn btn-primary pull-right" ng-click="backups.createBackup()" ng-disabled="app.taskId || backups.busyCreate || app.error || app.runState === 'stopped'" tooltip-enable="app.error || app.taskId || app.runState === 'stopped'" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is not running' }}">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="app.installationState === 'pending_backup' || backups.busyCreate"></i> Create Backup
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="app.installationState === 'pending_backup' || backups.busyCreate"></i> {{ 'app.backups.backups.createBackupAction' | tr }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
@@ -1067,25 +1027,24 @@
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label">Import From External Backup</label>
|
||||
<p>Use this to migrate an app from another Cloudron. The other app must have the same package version and access
|
||||
control setting as this one.
|
||||
</p>
|
||||
<label class="control-label">{{ 'app.backups.import.title' | tr }}</label>
|
||||
<p>{{ 'app.backups.import.description' | tr }}</p>
|
||||
|
||||
<button class="btn btn-primary pull-right" class="btn-primary" ng-click="importBackup.show()" ng-disabled="importBackup.busy || app.taskId || app.runState === 'stopped'" tooltip-enable="app.taskId" uib-tooltip="App is not running">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="backups.busy"></i> Import Backup
|
||||
<button class="btn btn-primary pull-right" class="btn-primary" ng-click="importBackup.show()" ng-disabled="importBackup.busy || app.taskId || app.runState === 'stopped'" tooltip-enable="app.taskId" uib-tooltip="App is not running">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="backups.busy"></i> {{ 'app.backups.backups.importAction' | tr }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label">Automatic Backups</label>
|
||||
<label class="control-label">{{ 'app.backups.auto.title' | tr }}</label>
|
||||
|
||||
<p>Cloudron periodically creates a backup based on the <a href="/#/backups">backup</a> settings.
|
||||
Automatic Backups is currently <b ng-class="backups.enableBackup ? 'text-success' : 'text-danger'">{{ backups.enableBackup ? 'enabled' : 'disabled' }}</b>.</p>
|
||||
<p ng-bind-html="'app.backups.auto.description' | tr:{ backupLink: '/#/backups' }"></p>
|
||||
<p class="text-success" ng-show="backups.enableBackup">{{ 'app.backups.auto.enabled' | tr }}</p>
|
||||
<p class="text-danger" ng-hide="backups.enableBackup">{{ 'app.backups.auto.disabled' | tr }}</p>
|
||||
|
||||
<button class="btn btn-primary pull-right" ng-class="{ 'btn-danger': backups.enableBackup }" ng-click="backups.toggleAutomaticBackups()" ng-disabled="backups.busyAutomaticBackups"><i class="fa fa-circle-notch fa-spin" ng-show="backups.busyAutomaticBackups"></i> {{ backups.enableBackup ? 'Disable' : 'Enable' }} Automatic Backups</button>
|
||||
<button class="btn btn-primary pull-right" ng-class="{ 'btn-danger': backups.enableBackup }" ng-click="backups.toggleAutomaticBackups()" ng-disabled="backups.busyAutomaticBackups"><i class="fa fa-circle-notch fa-spin" ng-show="backups.busyAutomaticBackups"></i> {{ backups.enableBackup ? ('app.backups.auto.disableAction' | tr) : ('app.backups.auto.enableAction' | tr) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1093,28 +1052,20 @@
|
||||
<div class="card" ng-show="view === 'repair'">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label">Crash Recovery</label>
|
||||
<p>
|
||||
If the app is not responding, try restarting the app. If the app is constantly restarting because of a broken plugin or misconfiguration,
|
||||
place the app in recovery mode in order to access the console.
|
||||
Use the following <a target="_blank" ng-href="https://docs.cloudron.io/troubleshooting/#unresponsive-app">instructions</a>
|
||||
to get the app running again.
|
||||
</p>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.pauseAppBegin()" ng-show="!app.debugMode" ng-disabled="repair.pauseBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">Enable Recovery Mode</button>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.pauseAppDone()" ng-show="app.debugMode" ng-disabled="repair.pauseBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">Disable Recovery Mode</button>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.restartApp()" ng-disabled="repair.restartBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">
|
||||
<i ng-show="repair.restartBusy" class="fa fa-circle-notch fa-spin"></i>
|
||||
Restart App
|
||||
</button>
|
||||
<label class="control-label">{{ 'app.repair.recovery.title' | tr }}</label>
|
||||
<p ng-bind-html="'app.repair.recovery.description' | tr:{ docsLink: 'https://docs.cloudron.io/troubleshooting/#unresponsive-app' }"></p>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.pauseAppBegin()" ng-show="!app.debugMode" ng-disabled="repair.pauseBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">{{ 'app.repair.recovery.enableRecoveryModeAction' | tr }}</button>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.pauseAppDone()" ng-show="app.debugMode" ng-disabled="repair.pauseBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}">{{ 'app.repair.recovery.disableRecoveryModeAction' | tr }}</button>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.restartApp()" ng-disabled="repair.restartBusy || app.error || app.taskId" tooltip-enable="app.error || app.taskId" uib-tooltip="{{ app.error ? 'App is in error state' : 'App is busy' }}"><i ng-show="repair.restartBusy" class="fa fa-circle-notch fa-spin"></i> {{ 'app.repair.recovery.restartAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label">Task Error</label>
|
||||
<p>If a configuration, update, restore or backup action resulted in an error, you can retry the task.</p>
|
||||
<label class="control-label">{{ 'app.repair.taskError.title' | tr }}</label>
|
||||
<p>{{ 'app.repair.taskError.description' | tr }}</p>
|
||||
<p ng-show="app.error">An error occurred during the <b>{{ app.error.installationState | taskName }}</b> operation: <span class="text-danger"><b>{{ app.error.reason + ': ' + app.error.message }}</b></span></p>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.confirm()" ng-disabled="app.taskId || !app.error" tooltip-enable="app.taskId" uib-tooltip="App is busy">Retry {{ app.error.installationState | taskName }}</button>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.confirm()" ng-disabled="app.taskId || !app.error" tooltip-enable="app.taskId" uib-tooltip="App is busy">{{ 'app.repair.taskError.retryAction' | tr:{ task: (app.error.installationState | taskName) } }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1122,25 +1073,21 @@
|
||||
<div class="card" ng-show="view === 'uninstall'">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label">Start / Stop</label>
|
||||
<p>
|
||||
Apps can be stopped to conserve server resources instead of unistallation. Future app backups will not include any app changes between now and the most recent app backup. For this reason, it is recommended to trigger a backup before stopping the app.
|
||||
</p>
|
||||
<label class="control-label">{{ 'app.uninstall.startStop.title' | tr }}</label>
|
||||
<p>{{ 'app.uninstall.startStop.description' | tr }}</p>
|
||||
<button class="btn btn-primary pull-right" ng-class="{ 'btn-danger': !uninstall.startButton }" ng-click="uninstall.toggleRunState()" ng-disabled="app.taskId || app.error || app.installationState === 'pending_start' || app.installationState === 'pending_stop'">
|
||||
<i ng-show="app.installationState === 'pending_start' || app.installationState === 'pending_stop'" class="fa fa-circle-notch fa-spin"></i>
|
||||
{{ uninstall.startButton ? 'Start App' : 'Stop App' }}
|
||||
{{ uninstall.startButton ? ('app.uninstall.startStop.startAction' | tr) : ('app.uninstall.startStop.stopAction' | tr) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="control-label">Uninstall</label>
|
||||
<p>This will uninstall the app immediately and remove all it's data. The site will be inaccessible.
|
||||
</p>
|
||||
<p>App backups are not removed and will be cleaned up based on the backup policy. You can resurrect this app from an existing
|
||||
app backup using the following <a target="_blank" ng-href="https://docs.cloudron.io/backups/#import-app-backup">instructions</a>.</p>
|
||||
<button class="btn btn-danger pull-right" ng-click="uninstall.ask()">Uninstall</button>
|
||||
<label class="control-label">{{ 'app.uninstall.uninstall.title' | tr }}</label>
|
||||
<p>{{ 'app.uninstall.uninstall.description' | tr }}</p>
|
||||
<p ng-bind-html="'app.uninstall.uninstall.backupWarning' | tr:{ importBackupDocsLink: 'https://docs.cloudron.io/backups/#import-app-backup' }"></p>
|
||||
<button class="btn btn-danger pull-right" ng-click="uninstall.ask()">{{ 'app.uninstall.uninstall.uninstallAction' | tr }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user