Merge remote-tracking branch 'origin/master' into feature/gcs

This commit is contained in:
Aleksandr Bogdanov
2017-11-14 20:16:12 +01:00
69 changed files with 2107 additions and 1875 deletions
+41 -51
View File
@@ -3,8 +3,8 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" ng-show="(appConfigure.app | installError)">Repair</h4>
<h4 class="modal-title" ng-hide="(appConfigure.app | installError)">Configure</h4>
<h4 class="modal-title" ng-show="(appConfigure.app | installError)">Repair {{ appConfigure.app.fqdn }}</h4>
<h4 class="modal-title" ng-hide="(appConfigure.app | installError)">Configure {{ appConfigure.app.fqdn }}</h4>
</div>
<div class="modal-body">
<fieldset>
@@ -88,18 +88,26 @@
</div>
<div class="radio">
<label>
<input type="radio" ng-model="appConfigure.accessRestrictionOption" value="groups" ng-disabled="groups.length <= 1">
Only allow the following user groups <span class="label label-danger" ng-show="appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid()">Select at least one group</span>
<input type="radio" ng-model="appConfigure.accessRestrictionOption" value="groups">
Only allow the following users and groups <span class="label label-danger" ng-show="appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid()">Select at least one user or group</span>
</label>
</div>
<div ng-show="groups.length <= 1" style="margin-left: 20px;">No groups available. <a href="#/users">Create groups</a></div>
<div>
<div style="margin-left: 20px;">
<span ng-repeat="group in groups | ignoreAdminGroup">
<button class="btn btn-default" type="button" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" ng-click="appConfigureToggleGroup(group);" ng-class="{ 'btn-primary': (appConfigure.accessRestriction.groups && appConfigure.accessRestriction.groups.indexOf(group.id) !== -1) }">{{ group.name }}</button>
</span>
<div class="col-md-5">
Users:
<multiselect class="input-sm stretch" ng-model="appConfigure.accessRestriction.users" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" options="user.username for user in users" data-multiple="true"></multiselect>
</div>
<div class="col-md-5">
Groups:
<multiselect class="input-sm stretch" ng-model="appConfigure.accessRestriction.groups" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" options="group.name for group in (groups | ignoreAdminGroup)" data-multiple="true"></multiselect>
</div>
</div>
</div>
<br/>
<br/>
</div>
<p ng-show="!mailConfig.enabled && appConfigure.app.manifest.addons.email" class="text-danger">
@@ -109,23 +117,19 @@
<a href="" ng-click="appConfigure.advancedVisible = true" ng-hide="appConfigure.advancedVisible">Advanced settings...</a>
<div uib-collapse="!appConfigure.advancedVisible">
<div class="form-group">
<label class="control-label" for="memoryLimit">Maximum Memory Limit: <b>{{ appConfigure.memoryLimit ? appConfigure.memoryLimit / 1024 / 1024 + 'MB' : 'Default (256 MB)' }}</b></label>
<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>{{ appConfigure.memoryLimit ? appConfigure.memoryLimit / 1024 / 1024 + 'MB' : 'Default (256 MB)' }}</b></label>
<br/>
<div style="padding: 0 10px;">
<slider id="memoryLimit" ng-model="appConfigure.memoryLimit" step="134217728" tooltip="hide" ticks="appConfigure.memoryTicks" ticks-snap-bounds="67108864"></slider>
</div>
</div>
<br/>
<div class="form-group" ng-class="{ 'has-error': !appConfigureForm.xFrameOptions.$dirty && appConfigure.error.xFrameOptions }">
<label class="control-label">Allow embedding from the following site</label>
<div class="control-label" ng-show="appConfigure.error.xFrameOptions"><small>Must be empty of a valid URL</small></div>
<input type="text" class="form-control" id="appConfigureXFrameOptionsInput" name="xFrameOptions" placeholder="https://example.com" ng-model="appConfigure.xFrameOptions" uib-tooltip="Leave blank to not allow embedding">
</div>
<br/>
<div class="form-group">
<label class="control-label">Specify robots.txt file content</label>
<textarea ng-model="appConfigure.robotsTxt" placeholder="Leave empty to allow all bots to index this app." class="form-control" rows="3"></textarea>
@@ -179,8 +183,11 @@
<div class="modal-header">
<h4 class="modal-title">Restore {{ appRestore.app.fqdn }}</h4>
</div>
<div class="modal-body" ng-show="appRestore.backups.length === 0">
<p class="text-danger">This app has no backups.</p>
<div class="modal-body" ng-show="appRestore.busyFetching">
<h4 class="text-center"><i class="fa fa-circle-o-notch fa-spin"></i> Fetching backups</h4>
</div>
<div class="modal-body" ng-show="appRestore.backups.length === 0 && !appRestore.busyFetching">
<h4 class="text-danger">This app has no backups.</h4>
</div>
<div class="modal-body" ng-show="appRestore.backups.length !== 0">
<p>Restoring the app will lose all content generated since the backup.</p>
@@ -195,7 +202,7 @@
</div>
<br/>
<fieldset>
<form role="form" name="appRestoreForm" ng-submit="doRestore()" autocomplete="off">
<form role="form" name="appRestoreForm" ng-submit="appRestore.submit()" autocomplete="off">
<div class="form-group" ng-class="{ 'has-error': (appRestoreForm.password.$dirty && appRestoreForm.password.$invalid) || (!appRestoreForm.password.$dirty && appRestore.error.password) }">
<label class="control-label" for="appRestorePasswordInput">Provide your password to confirm this action</label>
<div class="control-label" ng-show="(appRestoreForm.password.$dirty && appRestoreForm.password.$invalid) || (!appRestoreForm.password.$dirty && appRestore.error.password)">
@@ -211,22 +218,28 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" ng-click="doRestore()" ng-disabled="appRestoreForm.$invalid || appRestore.busy || !appRestore.selectedBackup"><i class="fa fa-circle-o-notch fa-spin" ng-show="appRestore.busy"></i> Restore</button>
<button type="button" class="btn btn-success" ng-click="appRestore.submit()" ng-show="appRestore.backups.length !== 0" ng-disabled="appRestoreForm.$invalid || appRestore.busy || !appRestore.selectedBackup"><i class="fa fa-circle-o-notch fa-spin" ng-show="appRestore.busy"></i> Restore</button>
</div>
</div>
</div>
</div>
<!-- Modal post install message app -->
<div class="modal fade" id="appPostInstallModal" tabindex="-1" role="dialog">
<!-- Modal information of app -->
<div class="modal fade" id="appInfoModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Installation notes for {{ appPostInstall.app.manifest.title }}</h4>
<img ng-src="{{appInfo.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-info-icon"/>
<h5 class="app-info-title">{{ appInfo.app.manifest.title }}</h5>
<br/>
<span class="app-info-meta">Package version <a ng-href="/#/appstore/{{appInfo.app.manifest.id}}?version={{appInfo.app.manifest.version}}">{{ appInfo.app.manifest.version }}</a> </span>
</div>
<div class="modal-body">
<div class="app-postinstall-message">
<div ng-bind-html="appPostInstall.message | postInstallMessage:appPostInstall.app | markdown2html"></div>
<div class="app-postinstall-message" ng-hide="appInfo.app.manifest && appInfo.app.manifest.postInstallMessage">
This package has no special usage information.
</div>
<div class="app-postinstall-message" ng-show="appInfo.app.manifest && appInfo.app.manifest.postInstallMessage">
<div ng-bind-html="appInfo.message | postInstallMessage:appInfo.app | markdown2html"></div>
</div>
</div>
<div class="modal-footer">
@@ -295,34 +308,11 @@
</div>
<div class="modal-body">
<p>Recent Changes for new version <b>{{ appUpdate.manifest.version}}</b>:</p>
<pre>{{ appUpdate.manifest.changelog }}</pre>
<br/>
<fieldset>
<form role="form" name="appUpdateForm" ng-submit="doUpdate(appUpdateForm)" autocomplete="off">
<div ng-repeat="(env, info) in appUpdate.portBindingsInfo" ng-class="{ 'newPort': info.isNew }">
<ng-form name="portInfo_form">
<div class="form-group" ng-class="{ 'has-error': portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid }">
<label class="control-label" for="inputPortInfo{{env}}"><input type="checkbox" ng-model="appUpdate.portBindingsEnabled[env]"> <span ng-show="info.isNew">New - </span> {{ info.description }} ({{ HOST_PORT_MIN }} - {{ HOST_PORT_MAX }})</label>
<input type="number" class="form-control" ng-model="appUpdate.portBindings[env]" ng-disabled="!appUpdate.portBindingsEnabled[env]" id="inputPortInfo{{env}}" later-name="itemName{{$index}}" min="{{HOST_PORT_MIN}}" max="{{HOST_PORT_MAX}}" required>
</div>
</ng-form>
</div>
<div ng-repeat="(env, port) in appUpdate.obsoletePortBindings" class="obsoletePort">
<ng-form name="obsoletePortInfo_form">
<div class="form-group">
Obsolete -
<label class="control-label">{{ env }}</label>
<input type="number" class="form-control" ng-model="port" disabled>
</div>
</ng-form>
</div>
<input class="ng-hide" type="submit" ng-disabled="appUpdateForm.$invalid || busy"/>
</form>
</fieldset>
<div ng-bind-html="appUpdate.manifest.changelog | markdown2html"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" ng-click="doUpdate(appUpdateForm)" ng-disabled="appUpdateForm.$invalid || appUpdate.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="appUpdate.busy"></i> Update</button>
<button type="button" class="btn btn-danger" ng-click="doUpdate()" ng-disabled="appUpdate.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="appUpdate.busy"></i> Update</button>
</div>
</div>
</div>
@@ -401,7 +391,7 @@
<div class="grid-item-bottom-mobile" ng-show="user.admin">
<div class="row">
<div class="col-xs-4 text-left">
<a href="" ng-click="showRestore(app)" ng-show="backupConfig.provider !== 'noop'">
<a href="" ng-click="appRestore.show(app)" ng-show="backupConfig.provider !== 'noop'">
<i class="fa fa-undo scale"></i>
</a>
@@ -424,7 +414,7 @@
</div>
<div>
<a href="" ng-click="showRestore(app)" ng-show="backupConfig.provider !== 'noop'" title="Restore App"><i class="fa fa-undo scale"></i></a>
<a href="" ng-click="appRestore.show(app)" ng-show="backupConfig.provider !== 'noop'" title="Restore App"><i class="fa fa-undo scale"></i></a>
</div>
<div ng-show="(app.installationState === 'installed' || app.installationState === 'pending_configure') && !(app | installError)">
@@ -435,8 +425,8 @@
<a href="" ng-click="showConfigure(app)" title="Repair App"><i class="fa fa-wrench scale"></i></a>
</div>
<div ng-show="hasPostInstallMessage(app)">
<a href="" ng-click="showPostInstall(app)" title="Information"><i class="fa fa-info-circle scale"></i></a>
<div>
<a href="" ng-click="showInformation(app)" title="Information"><i class="fa fa-info-circle scale"></i></a>
</div>
</div>
+77 -150
View File
@@ -63,6 +63,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
$scope.appRestore = {
busy: false,
busyFetching: false,
error: {},
app: {},
password: '',
@@ -71,10 +72,51 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
selectBackup: function (backup) {
$scope.appRestore.selectedBackup = backup;
},
show: function (app) {
$scope.reset();
$scope.appRestore.app = app;
$scope.appRestore.busyFetching = true;
$('#appRestoreModal').modal('show');
Client.getAppBackups(app.id, function (error, backups) {
if (error) {
Client.error(error);
} else {
$scope.appRestore.backups = backups;
if (backups.length) $scope.appRestore.selectedBackup = backups[0]; // pre-select first backup
$scope.appRestore.busyFetching = false;
}
});
return false; // prevent propagation and default
},
submit: function () {
$scope.appRestore.busy = true;
$scope.appRestore.error.password = null;
Client.restoreApp($scope.appRestore.app.id, $scope.appRestore.selectedBackup.id, $scope.appRestore.password, function (error) {
if (error && error.statusCode === 403) {
$scope.appRestore.password = '';
$scope.appRestore.error.password = true;
$scope.appRestoreForm.password.$setPristine();
$('#appRestorePasswordInput').focus();
} else if (error) {
Client.error(error);
} else {
$('#appRestoreModal').modal('hide');
}
$scope.appRestore.busy = false;
});
}
};
$scope.appPostInstall = {
$scope.appInfo = {
app: {},
message: ''
};
@@ -97,7 +139,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
$('#appConfigureModal').modal('hide');
$('#appRestoreModal').modal('hide');
$('#appUpdateModal').modal('hide');
$('#appPostInstallModal').modal('hide');
$('#appInfoModal').modal('hide');
$('#appUninstallModal').modal('hide');
// reset configure dialog
@@ -136,10 +178,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
$scope.appUpdate.error = {};
$scope.appUpdate.app = {};
$scope.appUpdate.manifest = {};
$scope.appUpdate.portBindings = {};
$scope.appUpdateForm.$setPristine();
$scope.appUpdateForm.$setUntouched();
// reset restore dialog
$scope.appRestore.error = {};
@@ -180,14 +218,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
});
};
$scope.appConfigureToggleGroup = function (group) {
var groups = $scope.appConfigure.accessRestriction.groups;
var pos = groups.indexOf(group.id);
if (pos === -1) groups.push(group.id);
else groups.splice(pos, 1);
};
$scope.useAltDomain = function (use) {
$scope.appConfigure.usingAltDomain = use;
@@ -206,27 +236,36 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
$scope.appConfigure.location = app.altDomain || app.location;
$scope.appConfigure.usingAltDomain = !!app.altDomain;
$scope.appConfigure.portBindingsInfo = app.manifest.tcpPorts || {}; // Portbinding map only for information
$scope.appConfigure.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any';
$scope.appConfigure.accessRestriction = app.accessRestriction || { users: [], groups: [] };
$scope. Option = app.accessRestriction ? 'groups' : 'any';
$scope.appConfigure.memoryLimit = app.memoryLimit || app.manifest.memoryLimit || (256 * 1024 * 1024);
$scope.appConfigure.xFrameOptions = app.xFrameOptions.indexOf('ALLOW-FROM') === 0 ? app.xFrameOptions.split(' ')[1] : '';
$scope.appConfigure.customAuth = !(app.manifest.addons['ldap'] || app.manifest.addons['oauth']);
$scope.appConfigure.robotsTxt = app.robotsTxt;
$scope.appConfigure.enableBackup = app.enableBackup;
// create ticks starting from manifest memory limit
$scope.appConfigure.memoryTicks = [
256 * 1024 * 1024,
512 * 1024 * 1024,
1024 * 1024 * 1024,
2048 * 1024 * 1024,
4096 * 1024 * 1024
].filter(function (t) { return t >= (app.manifest.memoryLimit || 0); });
// 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.appConfigure.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.appConfigure.memoryTicks.push(i * 1024 * 1024);
}
if (app.manifest.memoryLimit && $scope.appConfigure.memoryTicks[0] !== app.manifest.memoryLimit) {
$scope.appConfigure.memoryTicks.unshift(app.manifest.memoryLimit);
}
$scope.appConfigure.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any';
$scope.appConfigure.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.appConfigure.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.appConfigure.accessRestriction.groups.push(g); });
}
// fill the portBinding structures. There might be holes in the app.portBindings, which signalizes a disabled port
for (var env in $scope.appConfigure.portBindingsInfo) {
@@ -256,11 +295,18 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
}
}
var finalAccessRestriction = null;
if ($scope.appConfigure.accessRestrictionOption === 'groups') {
finalAccessRestriction = { users: [], groups: [] };
finalAccessRestriction.users = $scope.appConfigure.accessRestriction.users.map(function (u) { return u.id; });
finalAccessRestriction.groups = $scope.appConfigure.accessRestriction.groups.map(function (g) { return g.id; });
}
var data = {
location: $scope.appConfigure.usingAltDomain ? $scope.appConfigure.app.location : $scope.appConfigure.location,
altDomain: $scope.appConfigure.usingAltDomain ? $scope.appConfigure.location : null,
portBindings: finalPortBindings,
accessRestriction: $scope.appConfigure.accessRestrictionOption === 'groups' ? $scope.appConfigure.accessRestriction : null,
accessRestriction: finalAccessRestriction,
cert: $scope.appConfigure.certificateFile,
key: $scope.appConfigure.keyFile,
xFrameOptions: $scope.appConfigure.xFrameOptions ? ('ALLOW-FROM ' + $scope.appConfigure.xFrameOptions) : 'SAMEORIGIN',
@@ -303,13 +349,13 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
});
};
$scope.showPostInstall = function (app) {
$scope.showInformation = function (app) {
$scope.reset();
$scope.appPostInstall.app = app;
$scope.appPostInstall.message = app.manifest.postInstallMessage;
$scope.appInfo.app = app;
$scope.appInfo.message = app.manifest.postInstallMessage;
$('#appPostInstallModal').modal('show');
$('#appInfoModal').modal('show');
return false; // prevent propagation and default
};
@@ -324,48 +370,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
return false; // prevent propagation and default
};
$scope.showRestore = function (app) {
$scope.reset();
$scope.appRestore.app = app;
$scope.appRestore.busy = true;
$('#appRestoreModal').modal('show');
Client.getAppBackups(app.id, function (error, backups) {
if (error) {
Client.error(error);
} else {
$scope.appRestore.backups = backups;
if (backups.length) $scope.appRestore.selectedBackup = backups[0]; // pre-select first backup
$scope.appRestore.busy = false;
}
});
return false; // prevent propagation and default
};
$scope.doRestore = function () {
$scope.appRestore.busy = true;
$scope.appRestore.error.password = null;
Client.restoreApp($scope.appRestore.app.id, $scope.appRestore.selectedBackup.id, $scope.appRestore.password, function (error) {
if (error && error.statusCode === 403) {
$scope.appRestore.password = '';
$scope.appRestore.error.password = true;
$scope.appRestoreForm.password.$setPristine();
$('#appRestorePasswordInput').focus();
} else if (error) {
Client.error(error);
} else {
$('#appRestoreModal').modal('hide');
$scope.reset();
}
$scope.appRestore.busy = false;
});
};
$scope.showUninstall = function (app) {
$scope.reset();
@@ -408,90 +412,17 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
$scope.appUpdate.app = app;
$scope.appUpdate.manifest = angular.copy(updateManifest);
// ensure we always operate on objects here
app.portBindings = app.portBindings || {};
app.manifest.tcpPorts = app.manifest.tcpPorts || {};
updateManifest.tcpPorts = updateManifest.tcpPorts || {};
// Activate below two lines for testing the UI
// updateManifest.tcpPorts['TEST_HTTP'] = { defaultValue: 1337, description: 'HTTP server'};
// app.manifest.tcpPorts['TEST_FOOBAR'] = { defaultValue: 1338, description: 'FOOBAR server'};
// app.portBindings['TEST_SSH'] = 1339;
var portBindingsInfo = {}; // Portbinding map only for information
var portBindings = {}; // This is the actual model holding the env:port pair
var portBindingsEnabled = {}; // This is the actual model holding the enabled/disabled flag
var obsoletePortBindings = {}; // Info map for obsolete port bindings, this is for display use only and thus not in the model
var portsChanged = false;
var env;
// detect new portbindings and copy all from manifest.tcpPorts
for (env in updateManifest.tcpPorts) {
portBindingsInfo[env] = updateManifest.tcpPorts[env];
if (!app.manifest.tcpPorts[env]) {
portBindingsInfo[env].isNew = true;
portBindingsEnabled[env] = true;
// use default integer port value in model
portBindings[env] = updateManifest.tcpPorts[env].defaultValue || 0;
portsChanged = true;
} else {
// detect if the port binding was enabled
if (app.portBindings[env]) {
portBindings[env] = app.portBindings[env];
portBindingsEnabled[env] = true;
} else {
portBindings[env] = updateManifest.tcpPorts[env].defaultValue || 0;
portBindingsEnabled[env] = false;
}
}
}
// detect obsolete portbindings (mappings in app.portBindings, but not anymore in updateManifest.tcpPorts)
for (env in app.manifest.tcpPorts) {
// only list the port if it is not in the new manifest and was enabled previously
if (!updateManifest.tcpPorts[env] && app.portBindings[env]) {
obsoletePortBindings[env] = app.portBindings[env];
portsChanged = true;
}
}
// now inject the maps into the $scope, we only show those if ports have changed
$scope.appUpdate.portBindings = portBindings; // always inject the model, so it gets used in the actual update call
$scope.appUpdate.portBindingsEnabled = portBindingsEnabled; // always inject the model, so it gets used in the actual update call
if (portsChanged) {
$scope.appUpdate.portBindingsInfo = portBindingsInfo;
$scope.appUpdate.obsoletePortBindings = obsoletePortBindings;
} else {
$scope.appUpdate.portBindingsInfo = {};
$scope.appUpdate.obsoletePortBindings = {};
}
$('#appUpdateModal').modal('show');
};
$scope.doUpdate = function (form) {
$scope.doUpdate = function () {
$scope.appUpdate.busy = true;
// only use enabled ports from portBindings
var finalPortBindings = {};
for (var env in $scope.appUpdate.portBindings) {
if ($scope.appUpdate.portBindingsEnabled[env]) {
finalPortBindings[env] = $scope.appUpdate.portBindings[env];
}
}
Client.updateApp($scope.appUpdate.app.id, $scope.appUpdate.manifest, finalPortBindings, function (error) {
Client.updateApp($scope.appUpdate.app.id, $scope.appUpdate.manifest, function (error) {
if (error) {
Client.error(error);
} else {
$scope.appUpdate.app = {};
form.$setPristine();
form.$setUntouched();
$('#appUpdateModal').modal('hide');
}
@@ -512,10 +443,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
window.history.back();
};
$scope.hasPostInstallMessage = function (app) {
return app.manifest && app.manifest.postInstallMessage;
};
function fetchUsers() {
Client.getUsers(function (error, users) {
if (error) {
+14 -6
View File
@@ -72,18 +72,26 @@
</div>
<div class="radio">
<label>
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="groups" ng-disabled="groups.length <= 1">
Only allow the following user groups <span class="label label-danger" ng-show="appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()">Select at least one group</span>
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="groups">
Only allow the following users and groups <span class="label label-danger" ng-show="appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()">Select at least one user or group</span>
</label>
</div>
<div ng-show="groups.length <= 1" style="margin-left: 20px;">No groups available. <a href="" ng-click="showView('/users')">Create groups</a></div>
<div>
<div style="margin-left: 20px;">
<span ng-repeat="group in groups | ignoreAdminGroup">
<button class="btn btn-default" type="button" ng-disabled="appInstall.accessRestrictionOption !== 'groups'" ng-click="appInstall.toggleGroup(group);" ng-class="{ 'btn-primary': (appInstall.accessRestriction.groups && appInstall.accessRestriction.groups.indexOf(group.id) !== -1) }">{{ group.name }}</button>
</span>
<div class="col-md-5">
Users: &nbsp;
<multiselect ng-model="appInstall.accessRestriction.users" ng-disabled="appInstall.accessRestrictionOption !== 'groups'" options="user.username for user in users" data-multiple="true"></multiselect>
</div>
<div class="col-md-5">
Groups: &nbsp;
<multiselect ng-model="appInstall.accessRestriction.groups" ng-disabled="appInstall.accessRestrictionOption !== 'groups'" options="group.name for group in (groups | ignoreAdminGroup)" data-multiple="true"></multiselect>
</div>
</div>
</div>
<br/>
<br/>
</div>
<p ng-show="!mailConfig.enabled && appInstall.app.manifest.addons.email" class="text-danger">
+7 -23
View File
@@ -53,14 +53,6 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
return !!(tmp.users.length || tmp.groups.length);
},
toggleGroup: function (group) {
var groups = $scope.appInstall.accessRestriction.groups;
var pos = groups.indexOf(group.id);
if (pos === -1) groups.push(group.id);
else groups.splice(pos, 1);
},
reset: function () {
$scope.appInstall.app = {};
$scope.appInstall.error = {};
@@ -144,13 +136,17 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
}
}
// translate to accessRestriction object
var accessRestriction = $scope.appInstall.accessRestrictionOption === 'groups' ? $scope.appInstall.accessRestriction : null;
var finalAccessRestriction = null;
if ($scope.appInstall.accessRestrictionOption === 'groups') {
finalAccessRestriction = { users: [], groups: [] };
finalAccessRestriction.users = $scope.appInstall.accessRestriction.users.map(function (u) { return u.id; });
finalAccessRestriction.groups = $scope.appInstall.accessRestriction.groups.map(function (g) { return g.id; });
}
var data = {
location: $scope.appInstall.location || '',
portBindings: finalPortBindings,
accessRestriction: accessRestriction,
accessRestriction: finalAccessRestriction,
cert: $scope.appInstall.certificateFile,
key: $scope.appInstall.keyFile,
sso: !$scope.appInstall.optionalSso ? undefined : ($scope.appInstall.accessRestrictionOption !== 'nosso')
@@ -353,18 +349,6 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
});
return callback(null, apps);
// Client.getNonApprovedApps(function (error, result) {
// if (error) return callback(error);
// // add testing tag to the manifest for UI and search reasons
// result.forEach(function (app) {
// if (!app.manifest.tags) app.manifest.tags = [];
// app.manifest.tags.push('testing');
// });
// callback(null, apps.concat(result));
// });
});
}
+2 -2
View File
@@ -33,7 +33,7 @@
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="dnsCredentials.provider === 'gcdns'">
<div class="input-group">
<input type="file" id="gcdnsKeyFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Service Account Key" ng-model="dnsCredentials.gcdnsKey.keyFileName" id="gcdnsKeyInput" name="cert" onclick="getElementById('gcdnsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="dnsCredentials.busy" required>
<input type="text" class="form-control" placeholder="Service Account Key" ng-model="dnsCredentials.gcdnsKey.keyFileName" id="gcdnsKeyInput" name="cert" onclick="getElementById('gcdnsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="dnsCredentials.busy" ng-required="dnsCredentials.provider === 'gcdns'">
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('gcdnsKeyFileInput').click();"></i>
</span>
@@ -90,7 +90,7 @@
</p>
<p ng-show="dnsCredentials.provider === 'manual'">
Setup an <i>A</i> record for <b>my.{{ dnsCredentials.customDomain || 'example.com' }}</b> to this server's IP. All DNS records have to be setup manually <i>before</i> each app installation.
Setup an <i>A</i> record for <b>{{ config.adminLocation }}.{{ dnsCredentials.customDomain || 'example.com' }}</b> to this server's IP. All DNS records have to be setup manually <i>before</i> each app installation.
</p>
</div>
<div class="modal-footer ">
+3 -3
View File
@@ -65,9 +65,9 @@
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#mail_settings">Mail server settings for email clients</a>
<div id="mail_settings" class="panel-collapse collapse">
<br/>
<p><b>Incoming Mail (IMAP)</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 993 (TLS)</p>
<p><b>Outgoing Mail (SMTP)</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 587 (STARTTLS)</p>
<p><b>ManageSieve</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 4190 (TLS)</p>
<p><b>Incoming Mail (IMAP)</b><br/>Server: <span ng-click-select>{{config.mailFqdn}}</span><br/>Port: 993 (TLS)</p>
<p><b>Outgoing Mail (SMTP)</b><br/>Server: <span ng-click-select>{{config.mailFqdn}}</span><br/>Port: 587 (STARTTLS)</p>
<p><b>ManageSieve</b><br/>Server: <span ng-click-select>{{config.mailFqdn}}</span><br/>Port: 4190 (TLS)</p>
<p>All the servers require your Cloudron credentials for authentication.</p>
</div>
</div>
+9 -1
View File
@@ -142,10 +142,18 @@
<input type="text" class="form-control" ng-model="configureBackup.backupFolder" id="inputConfigureBackupFolder" name="backupFolder" ng-disabled="configureBackup.busy" placeholder="Directory for backups" ng-required="configureBackup.provider === 'filesystem'">
<p class="has-error" ng-show="configureBackup.provider === 'filesystem'">
Please ensure that the backup directory is an external disk
Please ensure that the backup directory is an external ext4 disk
</p>
</div>
<div class="checkbox" ng-show="configureBackup.provider === 'filesystem'">
<label>
<input type="checkbox" ng-model="configureBackup.useHardlinks" id="inputConfigureUseHardlinks">
Use hardlinks
</input>
</label>
</div>
<!-- S3/Minio/SOS/GCS -->
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.endpoint }" ng-show="configureBackup.provider === 'minio' || configureBackup.provider === 's3-v4-compat'">
<label class="control-label" for="inputConfigureBackupEndpoint">Endpoint</label>
+6 -2
View File
@@ -345,6 +345,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
backupFolder: '',
retentionSecs: 7 * 24 * 60 * 60,
acceptSelfSignedCerts: false,
useHardlinks: true,
format: 'tgz',
clearForm: function () {
@@ -360,6 +361,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
$scope.configureBackup.retentionSecs = 7 * 24 * 60 * 60;
$scope.configureBackup.format = 'tgz';
$scope.configureBackup.acceptSelfSignedCerts = false;
$scope.configureBackup.useHardlinks = true;
},
show: function () {
@@ -385,6 +387,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
$scope.configureBackup.retentionSecs = $scope.backupConfig.retentionSecs;
$scope.configureBackup.format = $scope.backupConfig.format;
$scope.configureBackup.acceptSelfSignedCerts = !!$scope.backupConfig.acceptSelfSignedCerts;
$scope.configureBackup.useHardlinks = !$scope.backupConfig.noHardlinks;
$('#configureBackupModal').modal('show');
},
@@ -443,6 +446,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
}
} else if (backupConfig.provider === 'filesystem') {
backupConfig.backupFolder = $scope.configureBackup.backupFolder;
backupConfig.noHardlinks = !$scope.configureBackup.useHardlinks;
}
Client.setBackupConfig(backupConfig, function (error) {
@@ -559,8 +563,8 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
$scope.backupConfig = backupConfig;
// Check if a proper storage backend is configured
if (backupConfig.provider === 'filesystem') {
// Check if a proper storage backend is configured. TODO: this check fails if /var/backups is actually external
if (backupConfig.provider === 'filesystem' && backupConfig.backupFolder === '/var/backups') {
var actionScope = $scope.$new(true);
actionScope.action = '/#/settings';