562 lines
32 KiB
HTML
562 lines
32 KiB
HTML
<!-- TODO use this once we enable custom SSL certificate ui again -->
|
|
<!-- <div class="hide">
|
|
<label class="control-label" for="appConfigureCertificateInput" ng-show="appConfigure.domain.provider !== 'caas'">Certificate (optional)</label>
|
|
<div class="has-error text-center" ng-show="appConfigure.error.cert && appConfigure.domain.provider !== 'caas'">{{ appConfigure.error.cert }}</div>
|
|
<div class="form-group" ng-class="{ 'has-error': !appConfigureForm.certificate.$dirty && appConfigure.error.cert }" ng-show="appConfigure.domain.provider !== 'caas'">
|
|
<div class="input-group">
|
|
<input type="file" id="appConfigureCertificateFileInput" onchange="readCertificate()" style="display:none"/>
|
|
<input type="text" class="form-control" placeholder="Certificate" ng-model="appConfigure.certificateFileName" id="appConfigureCertificateInput" name="certificate" onclick="getElementById('appConfigureCertificateFileInput').click();" style="cursor: pointer;" ng-required="appConfigure.keyFileName">
|
|
<span class="input-group-addon">
|
|
<i class="fa fa-upload" onclick="getElementById('appConfigureCertificateFileInput').click();"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="form-group" ng-class="{ 'has-error': !appConfigureForm.key.$dirty && appConfigure.error.cert }" ng-show="appConfigure.domain.provider !== 'caas'">
|
|
<div class="input-group">
|
|
<input type="file" id="appConfigureKeyFileInput" onchange="readKey()" style="display:none"/>
|
|
<input type="text" class="form-control" placeholder="Key" ng-model="appConfigure.keyFileName" id="appConfigureKeyInput" name="key" onclick="getElementById('appConfigureKeyFileInput').click();" style="cursor: pointer;" ng-required="appConfigure.certificateFileName">
|
|
<span class="input-group-addon">
|
|
<i class="fa fa-upload" onclick="getElementById('appConfigureKeyFileInput').click();"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div> -->
|
|
|
|
<script>
|
|
function imageErrorHandler(elem) {
|
|
'use strict';
|
|
|
|
elem.src = elem.getAttribute('fallback-icon');
|
|
elem.onerror = null; // avoid retry after default icon cannot be loaded
|
|
}
|
|
</script>
|
|
|
|
<!-- Modal uninstall app -->
|
|
<div class="modal fade" id="uninstallModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-body">
|
|
<br/>
|
|
<p>Really uninstall <b>{{ app.fqdn }}</b> ?</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">No</button>
|
|
<button type="button" class="btn btn-danger" ng-click="uninstall.submit()" ng-disabled="uninstall.busy"><i class="fa fa-circle-notch fa-spin" ng-show="uninstall.busy"></i> Yes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal clone app -->
|
|
<div class="modal fade" id="cloneModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">
|
|
Clone - {{ app.fqdn }}
|
|
</h4>
|
|
</div>
|
|
<div class="modal-body" style="padding: 0 15px">
|
|
<p>Using backup from <b>{{ clone.backup.creationTime | prettyDate }}</b> and version <b>v{{ clone.backup.version }}</b></p>
|
|
<fieldset>
|
|
<form role="form" ng-submit="clone.submit()" autocomplete="off">
|
|
<div class="form-group" ng-class="{ 'has-error': clone.error.location }">
|
|
<label class="control-label" for="cloneLocationInput">Location</label>
|
|
<div ng-show="clone.error.location"><small>{{ clone.error.location }}</small></div>
|
|
<div class="input-group form-inline">
|
|
<input type="text" class="form-control" ng-model="clone.location" id="cloneLocationInput" name="location" placeholder="Leave empty to use bare domain" autofocus>
|
|
<div class="input-group-btn">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
<span>{{ (!clone.location ? '' : (clone.domain.config.hyphenatedSubdomains ? '-' : '.')) + clone.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="clone.domain = domain">{{ domain.domain }}</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-center" ng-show="appClone.location && appClone.domain.provider === 'manual'">
|
|
<b>Add an A record manually for {{ appClone.location }} to this Cloudron's public IP</b>
|
|
<br>
|
|
</p>
|
|
|
|
<div class="has-error text-center" ng-show="appClone.error.port">{{ appClone.error.port }}</div>
|
|
<div ng-repeat="(env, info) in appClone.portBindingsInfo">
|
|
<ng-form name="portInfo_form">
|
|
<div class="form-group" ng-class="{ 'has-error': (!appClone.itemName{{$index}}.$dirty && appClone.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
|
|
<label class="control-label" for="inputPortInfo{{env}}"><input type="checkbox" ng-model="appClone.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="appClone.portBindings[env]" ng-disabled="!appClone.portBindingsEnabled[env]" id="inputPortInfo{{env}}" later-name="itemName{{$index}}" min="{{hostPortMin}}" max="{{hostPortMax}}" required>
|
|
</div>
|
|
</ng-form>
|
|
</div>
|
|
</form>
|
|
</fieldset>
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
<button type="button" class="btn btn-success" ng-click="appClone.submit()"><i class="far fa-clone"></i> Clone</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content content-large app-configure">
|
|
|
|
<a href="/#/apps" class="back-to-apps-link"><i class="fas fa-arrow-left"></i> Back to My Apps</a>
|
|
|
|
<div class="task-indicator animateMe" ng-show="app.taskId">
|
|
<i class="fa fa-circle-notch fa-spin"></i>
|
|
<span>{{ app | installationStateLabel:user }}</span>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<div class="row">
|
|
<div class="col-md-2 text-center">
|
|
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-icon"/>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<h1 style="line-height: 0.7;">
|
|
{{ app.label || app.fqdn }}
|
|
<a class="btn btn-sm btn-outline btn-primary pull-right" ng-href="https://{{ app.fqdn }}" target="_blank"><i class="fas fa-external-link-alt"></i> Open App</a>
|
|
<a class="btn btn-sm btn-outline btn-primary pull-right" ng-href="{{ app.manifest.documentationUrl }}" target="_blank">Documentation</a>
|
|
<br/>
|
|
<span class="text-small">
|
|
{{ app | installationStateLabel:user }}
|
|
</span>
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<div class="row">
|
|
<div class="col-md-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('security')" ng-class="{ 'active': view === 'security' }">Security</div>
|
|
<div ng-click="setView('email')" ng-class="{ 'active': view === 'email' }">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('debug')" ng-class="{ 'active': view === 'debug' }">Debug</div>
|
|
<div ng-click="setView('uninstall')" ng-class="{ 'active': view === 'uninstall' }">Uninstall</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<div class="card" ng-show="view === 'display'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<fieldset>
|
|
<form role="form" name="displayForm" ng-submit="display.submit()" autocomplete="off">
|
|
<div class="form-group" ng-class="{ 'has-error': !displayForm.label.$dirty && display.error.label }">
|
|
<label class="control-label">Label</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 comma to separate tags" taglist="display.tags" name="tags" uib-tooltip="For grouping in the dashboard"></tag-input>
|
|
</div>
|
|
<div class="form-group">
|
|
<div>
|
|
<label class="control-label">Icon</label>
|
|
</div>
|
|
<div id="previewIcon" class="app-custom-icon" ng-click="display.showCustomIconSelector()" style="background-image: url('{{ display.iconUrl() }}');">
|
|
<div class="overlay"></div>
|
|
</div>
|
|
<a href="" style="font-weight: normal;" ng-click="display.resetCustomIcon()">Reset Icon</a>
|
|
<input type="file" id="iconFileInput" style="display: none" accept="image/png"/>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="(!display.icon.data && !displayForm.$dirty) || displayForm.$invalid || display.busy"/>
|
|
</form>
|
|
</fieldset>
|
|
</div>
|
|
</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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'location'">
|
|
<div class="task-active-overlay" ng-show="app.taskId">App task in progress...</div>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<fieldset>
|
|
<form role="form" name="locationForm" ng-submit="location.submit()" autocomplete="off">
|
|
<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="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-btn">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
<span>{{ (!location.location ? '' : (location.domain.config.hyphenatedSubdomains ? '-' : '.')) + 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="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>
|
|
|
|
<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">Redirections <sup><a ng-href="{{ config.webServerOrigin }}/documentation/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="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.config.hyphenatedSubdomains ? '-' : '.')) + 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>
|
|
<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 alternate domains are configured. <a href="" ng-click="location.addAlternateDomain($event)">Add a domain</a>
|
|
</div>
|
|
<div ng-show="location.alternateDomains.length > 0" style="margin-top: 5px;">
|
|
<a href="" ng-click="location.addAlternateDomain($event)">Add another domain</a>
|
|
</div>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="locationForm.$invalid || location.busy"/>
|
|
</form>
|
|
</fieldset>
|
|
</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"><i class="fa fa-circle-notch fa-spin" ng-show="location.busy"></i> Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'access'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<fieldset>
|
|
<form role="form" name="accessForm" ng-submit="access.submit()" autocomplete="off">
|
|
<div class="form-group">
|
|
<div ng-show="access.ssoAuth">
|
|
<label class="control-label">User management</label>
|
|
<p class="text-small" ng-show="access.ftp">This setting also controls SFTP access.</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 href="https://cloudron.io/documentation/email/" target="_blank">Cloudron Email</a>.
|
|
</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">
|
|
|
|
<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 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"></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"></multiselect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="accessForm.$invalid || access.busy"/>
|
|
</div>
|
|
</form>
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="access.submit()" ng-disabled="access.$invalid || access.busy"><i class="fa fa-circle-notch fa-spin" ng-show="access.busy"></i> Save</button>
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row" ng-show="app.manifest.addons.localstorage.ftp">
|
|
<div class="col-md-12">
|
|
<label>SFTP</label> <sup><a ng-href="{{ config.webServerOrigin }}/documentation/apps/#ftp-access" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup><br/>
|
|
Server: {{ config.adminFqdn }}<br/>
|
|
Port: 222<br/>
|
|
Username: {{ user.username }}@{{ app.fqdn }}<br/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'resources'">
|
|
<div class="task-active-overlay" ng-show="app.taskId">App task in progress...</div>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<fieldset>
|
|
<form role="form" name="resourcesForm" ng-submit="resources.submit()" autocomplete="off">
|
|
<div class="form-group">
|
|
<label class="control-label" for="memoryLimit">Memory Limit <sup><a ng-href="{{ config.webServerOrigin }}/documentation/apps/#increasing-the-memory-limit-of-an-app" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ resources.memoryLimit ? resources.memoryLimit / 1024 / 1024 + 'MB' : 'Default (256 MB)' }}</b></label>
|
|
<br/>
|
|
<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>
|
|
|
|
<!-- We do not show this currently -->
|
|
<div ng-if="false" class="form-group" ng-class="{ 'has-error': !resourcesForm.dataDir.$dirty && resources.error.dataDir }">
|
|
<input type="checkbox" id="resourcesEnableDataDir" ng-model="resources.dataDirEnabled">
|
|
<label class="control-label" for="resourcesEnableDataDir">Custom Data Directory</label>
|
|
<div class="control-label" ng-show="resources.error.dataDir">{{resources.error.dataDir}}</div>
|
|
<input type="text" class="form-control" id="resourcesDataDirInput" name="dataDir" ng-disabled="!resources.dataDirEnabled" placeholder="/mnt/appdata" ng-model="resources.dataDir">
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="resources.memoryLimit === resources.currentMemoryLimit || resourcesForm.$invalid || resources.busy"/>
|
|
</form>
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12 text-right">
|
|
<button class="btn btn-outline btn-primary pull-right" ng-click="resources.submit()" ng-disabled="resources.memoryLimit === resources.currentMemoryLimit || resources.$invalid || resources.busy"><i class="fa fa-circle-notch fa-spin" ng-show="resources.busy"></i> Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'email'">
|
|
<div class="task-active-overlay" ng-show="app.taskId">App task in progress...</div>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<fieldset>
|
|
<form role="form" name="emailForm" ng-submit="email.submit()" autocomplete="off">
|
|
<!-- recvmail currently only works with cloudron email -->
|
|
<div class="form-group">
|
|
<input type="checkbox" id="emailMailboxNameEnabled" ng-model="email.mailboxNameEnabled">
|
|
<label class="control-label" for="emailMailboxNameEnabled">Custom Mail FROM</label>
|
|
<p class="text-small">Addresses ending with '.app' are reserved.</p>
|
|
<div class="has-error" 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" ng-required="email.mailboxNameEnabled" name="mailboxName" ng-model="email.mailboxName" ng-disabled="!email.mailboxNameEnabled">
|
|
|
|
<div class="input-group-btn">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" ng-disabled="!email.mailboxNameEnabled">
|
|
@{{ email.domain.domain }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<br/>
|
|
|
|
<p class="text-small">This app is configured to send mail using <a ng-href="/#/email/{{ app.domain }}">{{app.domain}}'s Outbound Email</a> settings.</p>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="emailForm.$invalid || email.busy"/>
|
|
</form>
|
|
</fieldset>
|
|
</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.$invalid || email.busy"><i class="fa fa-circle-notch fa-spin" ng-show="email.busy"></i> Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'security'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<fieldset>
|
|
<form role="form" name="securityForm" ng-submit="security.submit()" autocomplete="off">
|
|
<div class="form-group">
|
|
<label class="control-label" style="width: 100%">Specify robots.txt file content <sup><a ng-href="{{ config.webServerOrigin }}/documentation/apps/#indexing-by-search-engines-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 = disableIndexingTemplate">Disable indexing</a></label>
|
|
<textarea ng-model="security.robotsTxt" placeholder="Leave empty to allow all bots to index this app." class="form-control" rows="4"></textarea>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="security.robotsTxt === security.currentRobotsTxt || securityForm.$invalid || security.busy"/>
|
|
</form>
|
|
</fieldset>
|
|
</div>
|
|
</div>
|
|
<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.robotsTxt === security.currentRobotsTxt || security.$invalid || security.busy"><i class="fa fa-circle-notch fa-spin" ng-show="security.busy"></i> Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'updates'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
Last updated <span class="app-info-meta text-small">{{ app.updateTime | prettyDate }}</span>
|
|
<br/>
|
|
Automatic Updates <span class="app-info-meta text-small">{{ updates.enableAutomaticUpdate ? 'Enabled' : 'Disabled' }}</span>
|
|
<br/>
|
|
<br/>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<button class="btn btn-danger" ng-click="updates.toggleAutomaticUpdates()" ng-disabled="updates.busy"><i class="fa fa-circle-notch fa-spin" ng-show="updates.busy"></i> {{ updates.enableAutomaticUpdate ? 'Disable' : 'Enable' }} Automatic Updates</button>
|
|
<button class="btn btn-primary pull-right" 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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card"ng-show="view === 'backups'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<!-- backup id copy helper -->
|
|
<input type="text" class="offscreen" aria-hidden="true" id="backupIdHelper" value="">
|
|
|
|
<table class="table table-hover" style="margin: 0;">
|
|
<thead>
|
|
<tr>
|
|
<th width="25px"> </th>
|
|
<th>Created</th>
|
|
<th>Version</th>
|
|
<th class="text-right" width="180px">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr ng-hide="backups.backups.length">
|
|
<td colspan="4" class="text-center">This app has no backups yet.</td>
|
|
</tr>
|
|
<tr ng-repeat="backup in backups.backups">
|
|
<td><div ng-click="backups.copyBackupId(backup)" class="hand" uib-tooltip="{{ backups.copyBackupIdDone ? 'Copied to clipboard' : 'Click to copy backup id' }}" tooltip-placement="right"><i class="fa fa-copy"></i></div></td>
|
|
<td>{{ backup.creationTime | prettyDate }}</td>
|
|
<td>v{{ backup.version }}</td>
|
|
<td class="text-right no-wrap" style="vertical-align: bottom">
|
|
<button class="btn btn-xs btn-default" ng-hide="backup.ackRestore" 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-hide="backup.ackRestore" ng-disabled="app.taskId" ng-click="backup.ackRestore = true" uib-tooltip="Restore to this Backup"><i class="fas fa-history"></i></button>
|
|
<button class="btn btn-xs btn-danger" ng-show="backup.ackRestore" ng-click="backups.restore(backup)">Yes restore now</button>
|
|
<button class="btn btn-xs btn-default" ng-show="backup.ackRestore" ng-click="backup.ackRestore = false">Back</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<br/>
|
|
</div>
|
|
</div>
|
|
<hr/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<button class="btn btn-danger" ng-click="backups.toggleAutomaticBackups()" ng-disabled="backups.busy"><i class="fa fa-circle-notch fa-spin" ng-show="backups.busy"></i> {{ backups.enableBackup ? 'Disable' : 'Enable' }} Automatic Daily Backups</button>
|
|
<button type="button" class="btn btn-primary pull-right" ng-click="backups.createBackup()" ng-disabled="app.taskId"><i class="fa fa-circle-notch fa-spin" ng-show="app.installationState === 'pending_backup'"></i> Create Backup</button>
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div ng-show="app.installationState === 'pending_backup'">
|
|
<div class="progress progress-striped active animateMe" style="margin-bottom: 10px;">
|
|
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress }}%"></div>
|
|
</div>
|
|
<div><center>{{ app.message }}</center></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'debug'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<p>
|
|
{{ app.manifest.title }}
|
|
<span class="app-info-meta text-small">{{ app.upstreamVersion }} (Package <a ng-href="/#/appstore/{{app.manifest.id}}?version={{app.manifest.version}}">v{{ app.manifest.version }}</a>) </span>
|
|
<br/>
|
|
App ID <span class="app-info-meta text-small">{{ app.id }}</a> </span>
|
|
</p>
|
|
|
|
<button class="btn btn-danger" ng-click="debug.stopAppTask(app.taskId)" ng-disabled="!app.taskId">Cancel Current Task</button>
|
|
<button class="btn btn-danger" ng-click="debug.restartApp()" ng-disabled="app.taskId || appIsRestarting"><i class="fa fa-circle-notch fa-spin" ng-show="appIsRestarting"></i> Restart App</button>
|
|
<a class="btn btn-primary pull-right" ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank">Terminal</a>
|
|
<a class="btn btn-primary pull-right" ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank">Logs</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" ng-show="view === 'uninstall'">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<p class="text-danger">Deleting the app will also remove all it's data.</p>
|
|
<p>App backups are not removed and will be cleaned up based on the backup policy.</p>
|
|
|
|
<button class="btn btn-danger pull-right" ng-click="uninstall.ask()">Uninstall</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|