Move all app configure tasks to separate view
This commit is contained in:
@@ -460,12 +460,12 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.configureApp = function (id, data, callback) {
|
Client.prototype.configureApp = function (id, setting, data, callback) {
|
||||||
post('/api/v1/apps/' + id + '/configure', data, null, function (error, data, status) {
|
post('/api/v1/apps/' + id + '/configure/' + setting, data, null, function (error, data, status) {
|
||||||
if (error) return callback(error);
|
if (error) return callback(error);
|
||||||
if (status !== 202) return callback(new ClientError(status, data));
|
if (status !== 200 && status !== 202) return callback(new ClientError(status, data));
|
||||||
|
|
||||||
callback(null);
|
callback(null, data);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1027,12 +1027,13 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
|||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.getApp = function (appId, callback) {
|
Client.prototype.getApp = function (appId, callback) {
|
||||||
|
var that = this;
|
||||||
|
|
||||||
get('/api/v1/apps/' + appId, null, function (error, data, status) {
|
get('/api/v1/apps/' + appId, null, function (error, data, status) {
|
||||||
if (error) return callback(error);
|
if (error) return callback(error);
|
||||||
if (status !== 200) return callback(new ClientError(status, data));
|
if (status !== 200) return callback(new ClientError(status, data));
|
||||||
|
|
||||||
var tmp = data.manifest.description.match(/\<upstream\>(.*)\<\/upstream\>/i);
|
that._appPostProcess(data);
|
||||||
data.upstreamVersion = (tmp && tmp[1]) ? tmp[1] : '';
|
|
||||||
|
|
||||||
callback(null, data);
|
callback(null, data);
|
||||||
});
|
});
|
||||||
@@ -1458,6 +1459,9 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
|||||||
// amend the post install confirm state
|
// amend the post install confirm state
|
||||||
app.pendingPostInstallConfirmation = !!localStorage['confirmPostInstall_' + app.id];
|
app.pendingPostInstallConfirmation = !!localStorage['confirmPostInstall_' + app.id];
|
||||||
|
|
||||||
|
var tmp = app.manifest.description.match(/\<upstream\>(.*)\<\/upstream\>/i);
|
||||||
|
app.upstreamVersion = (tmp && tmp[1]) ? tmp[1] : '';
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ app.config(['$routeProvider', function ($routeProvider) {
|
|||||||
}).when('/users', {
|
}).when('/users', {
|
||||||
controller: 'UsersController',
|
controller: 'UsersController',
|
||||||
templateUrl: 'views/users.html?<%= revision %>'
|
templateUrl: 'views/users.html?<%= revision %>'
|
||||||
|
}).when('/app/:appId', {
|
||||||
|
controller: 'AppController',
|
||||||
|
templateUrl: 'views/app.html?<%= revision %>'
|
||||||
}).when('/appstore', {
|
}).when('/appstore', {
|
||||||
controller: 'AppStoreController',
|
controller: 'AppStoreController',
|
||||||
templateUrl: 'views/appstore.html?<%= revision %>'
|
templateUrl: 'views/appstore.html?<%= revision %>'
|
||||||
|
|||||||
415
src/views/app.html
Normal file
415
src/views/app.html
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
<script>
|
||||||
|
function imageErrorHandler(elem) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
elem.src = elem.getAttribute('fallback-icon');
|
||||||
|
elem.onerror = null; // avoid retry after default icon cannot be loaded
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1>
|
||||||
|
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-icon"/>
|
||||||
|
{{ app.label || app.location || app.fqdn }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<table width="100%">
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="vertical-align: top;">App ID</td>
|
||||||
|
<td class="text-right" style="vertical-align: top;">{{ app.id }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="vertical-align: top;">Upstream Version</td>
|
||||||
|
<td class="text-right" style="vertical-align: top;">{{ app.upstreamVersion }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="vertical-align: top;">App Package</td>
|
||||||
|
<td class="text-right" style="vertical-align: top;">{{ app.manifest.title }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="vertical-align: top;">App Package Version</td>
|
||||||
|
<td class="text-right" style="vertical-align: top;">{{ app.manifest.version }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br/>
|
||||||
|
<a class="btn btn-outline btn-primary pull-right" ng-href="{{ app.manifest.documentationUrl }}" target="_blank">Documentation</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><h3>Display</h3></div>
|
||||||
|
<div class="card">
|
||||||
|
<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="displayForm.$invalid || display.busy"/>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<span class="text-success text-bold" ng-show="display.success">Saved</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
<button class="btn btn-outline btn-primary pull-right" ng-click="display.submit()" ng-disabled="display.$invalid || display.busy"><i class="fa fa-circle-notch fa-spin" ng-show="display.busy"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><h3>Location</h3></div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<fieldset>
|
||||||
|
<form role="form" name="locationForm" ng-submit="location.submit()" autocomplete="off">
|
||||||
|
<div class="has-error text-center" ng-show="location.error.other">{{ location.error.other }}</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" for="locationLocationInput">Location {{ location.error.location }} </label>
|
||||||
|
<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-6">
|
||||||
|
<span class="text-success text-bold" ng-show="location.success">Saved</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 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><h3>Access Control</h3></div>
|
||||||
|
<div class="card">
|
||||||
|
<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-6">
|
||||||
|
<span class="text-success text-bold" ng-show="access.success">Saved</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><h3>Resources</h3></div>
|
||||||
|
<div class="card">
|
||||||
|
<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="resourcesForm.$invalid || resources.busy"/>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<span class="text-success text-bold" ng-show="resources.success">Saved</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
<button class="btn btn-outline btn-primary pull-right" ng-click="resources.submit()" ng-disabled="resources.$invalid || resources.busy"><i class="fa fa-circle-notch fa-spin" ng-show="resources.busy"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="app.manifest.addons.sendmail || app.manifest.addons.recvmail"><h3>Email</h3></div>
|
||||||
|
<div class="card" ng-if="app.manifest.addons.sendmail || app.manifest.addons.recvmail">
|
||||||
|
<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" ng-class="{ 'has-error': !emailForm.mailboxName.$dirty && email.error.mailboxName }">
|
||||||
|
<input type="checkbox" id="emailMailboxNameEnabled" ng-model="email.mailboxNameEnabled">
|
||||||
|
<label class="control-label" for="emailMailboxNameEnabled">Custom Mail FROM</label>
|
||||||
|
<div class="has-error" ng-show="email.error.mailboxName">{{ email.error.mailboxName }}</div>
|
||||||
|
|
||||||
|
<div class="input-group form-inline">
|
||||||
|
<input type="text" class="form-control" ng-required="email.mailboxNameEnabled" name="mailboxName" ng-model="email.mailboxName" uib-tooltip="App FROM email address. Addresses ending with '.app' are reserved." tooltip-class="long" 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>
|
||||||
|
|
||||||
|
<p class="text-small">
|
||||||
|
<br/>
|
||||||
|
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-6">
|
||||||
|
<span class="text-success text-bold" ng-show="email.success">Saved</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 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><h3>Security</h3></div>
|
||||||
|
<div class="card">
|
||||||
|
<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="securityForm.$invalid || security.busy"/>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<span class="text-success text-bold" ng-show="security.success">Saved</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
<button class="btn btn-outline btn-primary pull-right" ng-click="security.submit()" ng-disabled="security.$invalid || security.busy"><i class="fa fa-circle-notch fa-spin" ng-show="security.busy"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><h3>Updates</h3></div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<fieldset>
|
||||||
|
<form role="form" name="updatesForm" ng-submit="updates.submit()" autocomplete="off">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="updatesEnableAutomaticUpdate" ng-model="updates.enableAutomaticUpdate">
|
||||||
|
<label class="control-label" for="updatesEnableAutomaticUpdate">Enable automatic updates</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input class="ng-hide" type="submit" ng-disabled="updatesForm.$invalid || updates.busy"/>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<span class="text-success text-bold" ng-show="updates.success">Saved</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
<button class="btn btn-outline btn-primary pull-right" ng-click="updates.submit()" ng-disabled="updates.$invalid || updates.busy"><i class="fa fa-circle-notch fa-spin" ng-show="updates.busy"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><h3>Backups</h3></div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<fieldset>
|
||||||
|
<form role="form" name="backupsForm" ng-submit="backups.submit()" autocomplete="off">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="backupsEnableBackup" ng-model="backups.enableBackup">
|
||||||
|
<label class="control-label" for="backupsEnableBackup">Enable automatic daily backups</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input class="ng-hide" type="submit" ng-disabled="backupsForm.$invalid || backups.busy"/>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<span class="text-success text-bold" ng-show="backups.success">Saved</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 text-right">
|
||||||
|
<button class="btn btn-outline btn-primary pull-right" ng-click="backups.submit()" ng-disabled="backupsForm.$invalid || backups.busy"><i class="fa fa-circle-notch fa-spin" ng-show="backups.busy"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
464
src/views/app.js
Normal file
464
src/views/app.js
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* global angular */
|
||||||
|
/* global $ */
|
||||||
|
/* global asyncSeries */
|
||||||
|
|
||||||
|
angular.module('Application').controller('AppController', ['$scope', '$location', '$timeout', '$interval', 'Client', function ($scope, $location, $timeout, $interval, Client) {
|
||||||
|
Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); });
|
||||||
|
|
||||||
|
var appId = $location.path().slice('/app/'.length);
|
||||||
|
|
||||||
|
$scope.app = null;
|
||||||
|
$scope.ready = false;
|
||||||
|
$scope.HOST_PORT_MIN = 1024;
|
||||||
|
$scope.HOST_PORT_MAX = 65535;
|
||||||
|
$scope.config = Client.getConfig();
|
||||||
|
$scope.user = Client.getUserInfo();
|
||||||
|
$scope.domains = [];
|
||||||
|
$scope.groups = [];
|
||||||
|
$scope.users = [];
|
||||||
|
$scope.backupsEnabled = true;
|
||||||
|
$scope.disableIndexingTemplate = '# Disable search engine indexing\n\nUser-agent: *\nDisallow: /';
|
||||||
|
|
||||||
|
$scope.display = {
|
||||||
|
busy: false,
|
||||||
|
error: {},
|
||||||
|
success: false,
|
||||||
|
|
||||||
|
tags: '',
|
||||||
|
label: '',
|
||||||
|
icon: { data: null },
|
||||||
|
|
||||||
|
iconUrl: function () {
|
||||||
|
if (!$scope.app) return '';
|
||||||
|
|
||||||
|
if ($scope.display.icon.data === '__original__') { // user clicked reset
|
||||||
|
return $scope.app.iconUrl + '&original=true';
|
||||||
|
} else if ($scope.display.icon.data) { // user uploaded icon
|
||||||
|
return $scope.display.icon.data;
|
||||||
|
} else { // current icon
|
||||||
|
return $scope.app.iconUrl;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resetCustomIcon: function () {
|
||||||
|
$scope.display.icon.data = '__original__';
|
||||||
|
},
|
||||||
|
|
||||||
|
showCustomIconSelector: function () {
|
||||||
|
$('#iconFileInput').click();
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function () {
|
||||||
|
var app = $scope.app;
|
||||||
|
|
||||||
|
// translate for tag-input
|
||||||
|
$scope.display.tags = app.tags ? app.tags.join(',') : '';
|
||||||
|
|
||||||
|
$scope.display.label = $scope.app.label || '';
|
||||||
|
$scope.display.icon = { data: null };
|
||||||
|
|
||||||
|
$('#iconFileInput').get(0).onchange = function (event) {
|
||||||
|
var fr = new FileReader();
|
||||||
|
fr.onload = function () {
|
||||||
|
$scope.$apply(function () {
|
||||||
|
// var file = event.target.files[0];
|
||||||
|
$scope.display.icon.data = fr.result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
fr.readAsDataURL(event.target.files[0]);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function () {
|
||||||
|
$scope.display.busy = true;
|
||||||
|
$scope.display.error = {};
|
||||||
|
|
||||||
|
// TODO break those apart
|
||||||
|
Client.configureApp($scope.app.id, 'label', { label: $scope.display.label }, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
var tags = $scope.display.tags.split(',').map(function (t) { return t.trim(); }).filter(function (t) { return !!t; });
|
||||||
|
|
||||||
|
Client.configureApp($scope.app.id, 'tags', { tags: tags }, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
// skip if icon is unchanged
|
||||||
|
if ($scope.display.icon.data === null) {
|
||||||
|
$scope.display.busy = false;
|
||||||
|
$scope.display.success = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon;
|
||||||
|
if ($scope.display.icon.data === '__original__') { // user reset the icon
|
||||||
|
icon = '';
|
||||||
|
} else if ($scope.display.icon.data) { // user loaded custom icon
|
||||||
|
icon = $scope.display.icon.data.replace(/^data:image\/[a-z]+;base64,/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.configureApp($scope.app.id, 'icon', { icon: icon }, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.display.busy = false;
|
||||||
|
$scope.display.success = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.location = {
|
||||||
|
busy: false,
|
||||||
|
error: {},
|
||||||
|
success: false,
|
||||||
|
|
||||||
|
domain: null,
|
||||||
|
location: '',
|
||||||
|
alternateDomains: [],
|
||||||
|
portBindings: {},
|
||||||
|
portBindingsEnabled: {},
|
||||||
|
portBindingsInfo: {},
|
||||||
|
|
||||||
|
addAlternateDomain: function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
$scope.location.alternateDomains.push({
|
||||||
|
domain: $scope.domains[0],
|
||||||
|
subdomain: ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
delAlternateDomain: function (event, index) {
|
||||||
|
event.preventDefault();
|
||||||
|
$scope.location.alternateDomains.splice(index, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function () {
|
||||||
|
var app = $scope.app;
|
||||||
|
|
||||||
|
$scope.location.location = app.location;
|
||||||
|
$scope.location.domain = $scope.domains.filter(function (d) { return d.domain === app.domain; })[0];
|
||||||
|
$scope.location.portBindingsInfo = angular.extend({}, app.manifest.tcpPorts, app.manifest.udpPorts); // Portbinding map only for information
|
||||||
|
$scope.location.alternateDomains = app.alternateDomains.map(function (a) { return { subdomain: a.subdomain, domain: $scope.domains.filter(function (d) { return d.domain === a.domain; })[0] };});
|
||||||
|
|
||||||
|
// fill the portBinding structures. There might be holes in the app.portBindings, which signalizes a disabled port
|
||||||
|
for (var env in $scope.location.portBindingsInfo) {
|
||||||
|
if (app.portBindings && app.portBindings[env]) {
|
||||||
|
$scope.location.portBindings[env] = app.portBindings[env];
|
||||||
|
$scope.location.portBindingsEnabled[env] = true;
|
||||||
|
} else {
|
||||||
|
$scope.location.portBindings[env] = $scope.location.portBindingsInfo[env].defaultValue || 0;
|
||||||
|
$scope.location.portBindingsEnabled[env] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function () {
|
||||||
|
$scope.location.busy = true;
|
||||||
|
$scope.location.error = {};
|
||||||
|
|
||||||
|
// only use enabled ports from portBindings
|
||||||
|
var portBindings = {};
|
||||||
|
for (var env in $scope.location.portBindings) {
|
||||||
|
if ($scope.location.portBindingsEnabled[env]) {
|
||||||
|
portBindings[env] = $scope.location.portBindings[env];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
location: $scope.location.location,
|
||||||
|
domain: $scope.location.domain.domain,
|
||||||
|
portBindings: portBindings,
|
||||||
|
alternateDomains: $scope.location.alternateDomains.map(function (a) { return { subdomain: a.subdomain, domain: a.domain.domain };})
|
||||||
|
};
|
||||||
|
|
||||||
|
Client.configureApp($scope.app.id, 'location', data, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.location.success = true;
|
||||||
|
$scope.location.busy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.access = {
|
||||||
|
busy: false,
|
||||||
|
error: {},
|
||||||
|
success: false,
|
||||||
|
|
||||||
|
ftp: false,
|
||||||
|
ssoAuth: false,
|
||||||
|
accessRestrictionOption: 'any',
|
||||||
|
accessRestriction: { users: [], groups: [] },
|
||||||
|
|
||||||
|
isAccessRestrictionValid: function () {
|
||||||
|
var tmp = $scope.access.accessRestriction;
|
||||||
|
return !!(tmp.users.length || tmp.groups.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function () {
|
||||||
|
var app = $scope.app;
|
||||||
|
|
||||||
|
$scope.access.ftp = app.manifest.addons.localstorage && app.manifest.addons.localstorage.ftp;
|
||||||
|
$scope.access.ssoAuth = (app.manifest.addons['ldap'] || app.manifest.addons['oauth']) && app.sso;
|
||||||
|
$scope.access.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any';
|
||||||
|
$scope.access.accessRestriction = { users: [], groups: [] };
|
||||||
|
|
||||||
|
if (app.accessRestriction) {
|
||||||
|
var userSet = { };
|
||||||
|
app.accessRestriction.users.forEach(function (uid) { userSet[uid] = true; });
|
||||||
|
$scope.users.forEach(function (u) { if (userSet[u.id] === true) $scope.access.accessRestriction.users.push(u); });
|
||||||
|
|
||||||
|
var groupSet = { };
|
||||||
|
app.accessRestriction.groups.forEach(function (gid) { groupSet[gid] = true; });
|
||||||
|
$scope.groups.forEach(function (g) { if (groupSet[g.id] === true) $scope.access.accessRestriction.groups.push(g); });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function () {
|
||||||
|
$scope.access.busy = true;
|
||||||
|
$scope.access.error = {};
|
||||||
|
|
||||||
|
var accessRestriction = null;
|
||||||
|
if ($scope.access.accessRestrictionOption === 'groups') {
|
||||||
|
accessRestriction = { users: [], groups: [] };
|
||||||
|
accessRestriction.users = $scope.access.accessRestriction.users.map(function (u) { return u.id; });
|
||||||
|
accessRestriction.groups = $scope.access.accessRestriction.groups.map(function (g) { return g.id; });
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.configureApp($scope.app.id, 'access_restriction', { accessRestriction: accessRestriction }, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.access.success = true;
|
||||||
|
$scope.access.busy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.resources = {
|
||||||
|
busy: false,
|
||||||
|
error: {},
|
||||||
|
success: false,
|
||||||
|
|
||||||
|
memoryLimit: 0,
|
||||||
|
memoryTicks: [],
|
||||||
|
dataDir: null,
|
||||||
|
dataDirEnabled: false,
|
||||||
|
|
||||||
|
show: function () {
|
||||||
|
var app = $scope.app;
|
||||||
|
|
||||||
|
$scope.resources.memoryLimit = app.memoryLimit || app.manifest.memoryLimit || (256 * 1024 * 1024);
|
||||||
|
$scope.resources.dataDirEnabled = !!app.dataDir;
|
||||||
|
$scope.resources.dataDir = app.dataDir;
|
||||||
|
|
||||||
|
// create ticks starting from manifest memory limit. the memory limit here is currently split into ram+swap (and thus *2 below)
|
||||||
|
// TODO: the *2 will overallocate since 4GB is max swap that cloudron itself allocates
|
||||||
|
$scope.resources.memoryTicks = [];
|
||||||
|
var npow2 = Math.pow(2, Math.ceil(Math.log($scope.config.memory)/Math.log(2)));
|
||||||
|
for (var i = 256; i <= (npow2*2/1024/1024); i *= 2) {
|
||||||
|
if (i >= (app.manifest.memoryLimit/1024/1024 || 0)) $scope.resources.memoryTicks.push(i * 1024 * 1024);
|
||||||
|
}
|
||||||
|
if (app.manifest.memoryLimit && $scope.resources.memoryTicks[0] !== app.manifest.memoryLimit) {
|
||||||
|
$scope.resources.memoryTicks.unshift(app.manifest.memoryLimit);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function () {
|
||||||
|
$scope.resources.busy = true;
|
||||||
|
$scope.resources.error = {};
|
||||||
|
|
||||||
|
var memoryLimit = $scope.resources.memoryLimit === $scope.resources.memoryTicks[0] ? 0 : $scope.resources.memoryLimit;
|
||||||
|
|
||||||
|
Client.configureApp($scope.app.id, 'memory_limit', { memoryLimit: memoryLimit }, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.resources.success = true;
|
||||||
|
$scope.resources.busy = false;
|
||||||
|
|
||||||
|
// TODO handle data dir once we show it
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.email = {
|
||||||
|
busy: false,
|
||||||
|
error: {},
|
||||||
|
success: false,
|
||||||
|
|
||||||
|
mailboxNameEnabled: false,
|
||||||
|
mailboxName: '',
|
||||||
|
domain: '',
|
||||||
|
|
||||||
|
show: function () {
|
||||||
|
var app = $scope.app;
|
||||||
|
|
||||||
|
$scope.email.mailboxNameEnabled = app.mailboxName && (app.mailboxName.match(/\.app$/) === null);
|
||||||
|
$scope.email.mailboxName = app.mailboxName || '';
|
||||||
|
$scope.email.domain = $scope.domains.filter(function (d) { return d.domain === app.domain; })[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function () {
|
||||||
|
$scope.email.busy = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.security = {
|
||||||
|
busy: false,
|
||||||
|
error: {},
|
||||||
|
success: false,
|
||||||
|
|
||||||
|
robotsTxt: '',
|
||||||
|
|
||||||
|
show: function () {
|
||||||
|
var app = $scope.app;
|
||||||
|
|
||||||
|
$scope.security.robotsTxt = app.robotsTxt;
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function () {
|
||||||
|
$scope.security.busy = true;
|
||||||
|
$scope.security.error = {};
|
||||||
|
|
||||||
|
Client.configureApp($scope.app.id, 'robots_txt', { robotsTxt: $scope.security.robotsTxt }, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.security.success = true;
|
||||||
|
$scope.security.busy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.updates = {
|
||||||
|
busy: false,
|
||||||
|
error: {},
|
||||||
|
success: false,
|
||||||
|
|
||||||
|
enableAutomaticUpdate: false,
|
||||||
|
|
||||||
|
show: function () {
|
||||||
|
var app = $scope.app;
|
||||||
|
|
||||||
|
$scope.updates.enableAutomaticUpdate = app.enableAutomaticUpdate;
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function () {
|
||||||
|
$scope.updates.busy = true;
|
||||||
|
$scope.updates.error = {};
|
||||||
|
|
||||||
|
Client.configureApp($scope.app.id, 'automatic_update', { enable: $scope.updates.enableAutomaticUpdate }, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.updates.success = true;
|
||||||
|
$scope.updates.busy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.backups = {
|
||||||
|
busy: false,
|
||||||
|
error: {},
|
||||||
|
success: false,
|
||||||
|
|
||||||
|
enableBackup: false,
|
||||||
|
|
||||||
|
show: function () {
|
||||||
|
var app = $scope.app;
|
||||||
|
|
||||||
|
$scope.backups.enableBackup = app.enableBackup;
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function () {
|
||||||
|
$scope.backups.busy = true;
|
||||||
|
$scope.backups.error = {};
|
||||||
|
|
||||||
|
Client.configureApp($scope.app.id, 'automatic_backup', { enable: $scope.backups.enableBackup }, function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.backups.success = true;
|
||||||
|
$scope.backups.busy = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function fetchUsers(callback) {
|
||||||
|
Client.getUsers(function (error, users) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
|
||||||
|
// ensure we have something to work with in the access restriction dropdowns
|
||||||
|
users.forEach(function (user) { user.display = user.username || user.email; });
|
||||||
|
|
||||||
|
$scope.users = users;
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchGroups(callback) {
|
||||||
|
Client.getGroups(function (error, groups) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
|
||||||
|
$scope.groups = groups;
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDomains(callback) {
|
||||||
|
Client.getDomains(function (error, result) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
|
||||||
|
$scope.domains = result;
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBackupConfig(callback) {
|
||||||
|
Client.getBackupConfig(function (error, backupConfig) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
|
||||||
|
$scope.backupEnabled = backupConfig.provider !== 'noop';
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Client.onReady(function () {
|
||||||
|
$scope.app = Client.getApp(appId, function (error, app) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.app = app;
|
||||||
|
|
||||||
|
asyncSeries([
|
||||||
|
fetchUsers,
|
||||||
|
fetchGroups,
|
||||||
|
getDomains,
|
||||||
|
getBackupConfig
|
||||||
|
], function (error) {
|
||||||
|
if (error) return Client.error(error);
|
||||||
|
|
||||||
|
$scope.display.show();
|
||||||
|
$scope.location.show();
|
||||||
|
$scope.resources.show();
|
||||||
|
$scope.access.show();
|
||||||
|
$scope.email.show();
|
||||||
|
$scope.security.show();
|
||||||
|
$scope.backups.show();
|
||||||
|
$scope.updates.show();
|
||||||
|
|
||||||
|
$scope.ready = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// setup all the dialog focus handling
|
||||||
|
['appConfigureModal', 'appUninstallModal', 'appUpdateModal', 'appRestoreModal', 'appInfoModal', 'appErrorModal'].forEach(function (id) {
|
||||||
|
$('#' + id).on('shown.bs.modal', function () {
|
||||||
|
$(this).find("[autofocus]:first").focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.modal-backdrop').remove();
|
||||||
|
}]);
|
||||||
@@ -608,7 +608,7 @@
|
|||||||
<a href="" ng-show="app | activeTask" ng-click="appCancel.show(app)" uib-tooltip="Cancel {{app | installationStateCancelTooltip}}" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-ban scale"></i></a>
|
<a href="" ng-show="app | activeTask" ng-click="appCancel.show(app)" uib-tooltip="Cancel {{app | installationStateCancelTooltip}}" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-ban scale"></i></a>
|
||||||
<a href="" ng-hide="app | activeTask" ng-click="appUninstall.show(app)" uib-tooltip="Uninstall" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-times scale"></i></a>
|
<a href="" ng-hide="app | activeTask" ng-click="appUninstall.show(app)" uib-tooltip="Uninstall" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-times scale"></i></a>
|
||||||
<a href="" ng-hide="!backupsEnabled || (app | activeTask)" ng-click="appRestore.show(app)" uib-tooltip="Backups" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-archive scale"></i></a>
|
<a href="" ng-hide="!backupsEnabled || (app | activeTask)" ng-click="appRestore.show(app)" uib-tooltip="Backups" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-archive scale"></i></a>
|
||||||
<a href="" ng-hide="(app | activeTask) || (app | installError)" ng-click="appConfigure.show(app)" uib-tooltip="Configure" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-pencil-alt scale"></i></a>
|
<a ng-href="#/app/{{ app.id}}" ng-hide="(app | activeTask) || (app | installError)" uib-tooltip="Configure" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-pencil-alt scale"></i></a>
|
||||||
<a href="" ng-hide="(app | activeTask) || !(app | installError)" ng-click="appConfigure.show(app)" uib-tooltip="Repair" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-wrench scale"></i></a>
|
<a href="" ng-hide="(app | activeTask) || !(app | installError)" ng-click="appConfigure.show(app)" uib-tooltip="Repair" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-wrench scale"></i></a>
|
||||||
<a ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank" uib-tooltip="Terminal" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-terminal scale"></i></a>
|
<a ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank" uib-tooltip="Terminal" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-terminal scale"></i></a>
|
||||||
<a ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank" uib-tooltip="Logs" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-file-alt scale"></i></a>
|
<a ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank" uib-tooltip="Logs" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-file-alt scale"></i></a>
|
||||||
|
|||||||
@@ -36,14 +36,14 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
|||||||
certificateFileName: '',
|
certificateFileName: '',
|
||||||
keyFile: null,
|
keyFile: null,
|
||||||
keyFileName: '',
|
keyFileName: '',
|
||||||
memoryLimit: 0,
|
|
||||||
memoryTicks: [],
|
|
||||||
mailboxName: '',
|
mailboxName: '',
|
||||||
accessRestrictionOption: 'any',
|
accessRestrictionOption: 'any',
|
||||||
accessRestriction: { users: [], groups: [] },
|
accessRestriction: { users: [], groups: [] },
|
||||||
dataDir: null,
|
|
||||||
alternateDomains: [],
|
alternateDomains: [],
|
||||||
mailboxNameEnabled: false,
|
mailboxNameEnabled: false,
|
||||||
|
memoryLimit: 0,
|
||||||
|
memoryTicks: [],
|
||||||
|
dataDir: null,
|
||||||
dataDirEnabled: false,
|
dataDirEnabled: false,
|
||||||
ssoAuth: false,
|
ssoAuth: false,
|
||||||
ftp: false,
|
ftp: false,
|
||||||
@@ -100,7 +100,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
|||||||
$scope.appConfigure.mailboxName = app.mailboxName || '';
|
$scope.appConfigure.mailboxName = app.mailboxName || '';
|
||||||
$scope.appConfigure.label = app.label || '';
|
$scope.appConfigure.label = app.label || '';
|
||||||
|
|
||||||
$scope.appConfigure.ssoAuth = (app.manifest.addons['ldap'] || app.manifest.addons['oauth']) && app.sso;
|
|
||||||
$scope.appConfigure.ftp = app.manifest.addons.localstorage && app.manifest.addons.localstorage.ftp;
|
$scope.appConfigure.ftp = app.manifest.addons.localstorage && app.manifest.addons.localstorage.ftp;
|
||||||
|
|
||||||
// create ticks starting from manifest memory limit. the memory limit here is currently split into ram+swap (and thus *2 below)
|
// create ticks starting from manifest memory limit. the memory limit here is currently split into ram+swap (and thus *2 below)
|
||||||
@@ -114,6 +113,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
|||||||
$scope.appConfigure.memoryTicks.unshift(app.manifest.memoryLimit);
|
$scope.appConfigure.memoryTicks.unshift(app.manifest.memoryLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.appConfigure.ssoAuth = (app.manifest.addons['ldap'] || app.manifest.addons['oauth']) && app.sso;
|
||||||
$scope.appConfigure.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any';
|
$scope.appConfigure.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any';
|
||||||
$scope.appConfigure.accessRestriction = { users: [], groups: [] };
|
$scope.appConfigure.accessRestriction = { users: [], groups: [] };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user