diff --git a/src/translation/en.json b/src/translation/en.json index 239b9e079..62dcedae7 100644 --- a/src/translation/en.json +++ b/src/translation/en.json @@ -116,7 +116,7 @@ "locationPlaceholder": "Leave empty to use bare domain", "manualWarning": "Add an A record manually for {{ location }} to this Cloudron's public IP", "userManagement": "User management", - "userManagementNone": "This app has its own user management.", + "userManagementNone": "This app has its own user management. This setting determines whether this app is visible in the user's dashboard.", "userManagementMailbox": "All users with a mailbox on this Cloudron have access.", "userManagementLeaveToApp": "Leave user management to the app", "userManagementAllUsers": "Allow all users from this Cloudron", @@ -1227,7 +1227,7 @@ "accessControl": { "userManagement": { "title": "User management", - "description": "This app is configured to authenticate with the Cloudron User Directory.", + "description": "This app is configured to authenticate with the Cloudron User Directory. This setting controls who can log in and use the app.", "descriptionSftp": "This setting also controls SFTP access.", "dashboardVisibility": "Dashboard visibility", "sftpAccessControl": "This setting also controls SFTP access.", @@ -1239,6 +1239,10 @@ "server": "Server", "port": "Port", "username": "Username" + }, + "operators": { + "title": "Operators", + "description": "Operators can configure and maintain this app." } }, "resources": { diff --git a/src/views/app.html b/src/views/app.html index b45200607..8fc4cb9ba 100644 --- a/src/views/app.html +++ b/src/views/app.html @@ -508,10 +508,10 @@
@@ -682,58 +682,77 @@
-
+
+ +

{{ 'appstore.installDialog.userManagementMailbox' | tr }} + +

+
-
- -

{{ 'appstore.installDialog.userManagementMailbox' | tr }} - -

-
+
+ +

{{ 'app.accessControl.userManagement.description' | tr }} {{ 'app.accessControl.userManagement.descriptionSftp' | tr }}

+
+
+ +

{{ 'appstore.installDialog.userManagementNone' | tr }} {{ 'app.accessControl.userManagement.sftpAccessControl' | tr }}

+
-
- -

{{ 'app.accessControl.userManagement.description' | tr }} {{ 'app.accessControl.userManagement.descriptionSftp' | tr }}

-
-
- -

{{ 'appstore.installDialog.userManagementNone' | tr }} {{ 'app.accessControl.userManagement.sftpAccessControl' | tr }}

-
+
+ +
+
+
+
diff --git a/src/views/app.js b/src/views/app.js index 3c3e518c8..941454bde 100644 --- a/src/views/app.js +++ b/src/views/app.js @@ -11,8 +11,6 @@ /* global SECRET_PLACEHOLDER */ angular.module('Application').controller('AppController', ['$scope', '$location', '$timeout', '$interval', '$route', '$routeParams', 'Client', function ($scope, $location, $timeout, $interval, $route, $routeParams, Client) { - Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); }); - // List is from http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region $scope.s3Regions = [ { name: 'Asia Pacific (Mumbai)', value: 'ap-south-1' }, @@ -452,6 +450,8 @@ angular.module('Application').controller('AppController', ['$scope', '$location' accessRestrictionOption: 'any', accessRestriction: { users: [], groups: [] }, + operators: { users: [], groups: [] }, + isAccessRestrictionValid: function () { var tmp = $scope.access.accessRestriction; return !!(tmp.users.length || tmp.groups.length); @@ -466,15 +466,29 @@ angular.module('Application').controller('AppController', ['$scope', '$location' $scope.access.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any'; $scope.access.accessRestriction = { users: [], groups: [] }; + $scope.access.operators = { users: [], groups: [] }; + + var userSet, groupSet; + if (app.accessRestriction) { - var userSet = { }; + 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; }); + groupSet = {}; + if (app.accessRestriction.groups) 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); }); } + + if (app.operators) { + userSet = {}; + app.operators.users.forEach(function (uid) { userSet[uid] = true; }); + $scope.users.forEach(function (u) { if (userSet[u.id] === true) $scope.access.operators.users.push(u); }); + + groupSet = {}; + if (app.operators.groups) app.operators.groups.forEach(function (gid) { groupSet[gid] = true; }); + $scope.groups.forEach(function (g) { if (groupSet[g.id] === true) $scope.access.operators.groups.push(g); }); + } }, submit: function () { @@ -488,13 +502,24 @@ angular.module('Application').controller('AppController', ['$scope', '$location' accessRestriction.groups = $scope.access.accessRestriction.groups.map(function (g) { return g.id; }); } + var operators = null; + if ($scope.access.operators.users.length || $scope.access.operators.groups.length) { + operators = { users: [], groups: [] }; + operators.users = $scope.access.operators.users.map(function (u) { return u.id; }); + operators.groups = $scope.access.operators.groups.map(function (g) { return g.id; }); + } + Client.configureApp($scope.app.id, 'access_restriction', { accessRestriction: accessRestriction }, function (error) { if (error) return Client.error(error); - $timeout(function () { - $scope.access.success = true; - $scope.access.busy = false; - }, 1000); + Client.configureApp($scope.app.id, 'operators', { operators: operators }, function (error) { + if (error) return Client.error(error); + + $timeout(function () { + $scope.access.success = true; + $scope.access.busy = false; + }, 1000); + }); }); } }; @@ -1681,8 +1706,6 @@ angular.module('Application').controller('AppController', ['$scope', '$location' return; } - console.log(backupConfig) - $scope.$apply(function () { // we assume property names match here, this does not yet work for gcs keys Object.keys(backupConfig).forEach(function (k) { @@ -1699,6 +1722,8 @@ angular.module('Application').controller('AppController', ['$scope', '$location' refreshApp(appId, function (error) { if (error) return Client.error(error); + if ($scope.app.accessLevel !== 'admin' && $scope.app.accessLevel !== 'operator') return $location.path('/'); + // skipViewShow because we don't have all the values like domains/users to init the view yet if ($routeParams.view) { // explicit route in url bar $scope.setView($routeParams.view, true /* skipViewShow */); @@ -1706,6 +1731,17 @@ angular.module('Application').controller('AppController', ['$scope', '$location' $scope.setView($scope.app.error ? 'repair' : 'display', true /* skipViewShow */); } + function done() { + $scope[$scope.view].show(); // initialize now that we have all the values + + var refreshTimer = $interval(function () { refreshApp($scope.app.id); }, 5000); // call with inline function to avoid iteration argument passed see $interval docs + $scope.$on('$destroy', function () { + $interval.cancel(refreshTimer); + }); + } + + if ($scope.app.accessLevel !== 'admin') return done(); + async.series([ fetchUsers, fetchGroups, @@ -1715,12 +1751,7 @@ angular.module('Application').controller('AppController', ['$scope', '$location' ], function (error) { if (error) return Client.error(error); - $scope[$scope.view].show(); // initialize now that we have all the values - - var refreshTimer = $interval(function () { refreshApp($scope.app.id); }, 5000); // call with inline function to avoid iteration argument passed see $interval docs - $scope.$on('$destroy', function () { - $interval.cancel(refreshTimer); - }); + done(); }); }); }); diff --git a/src/views/apps.html b/src/views/apps.html index ae6ea887b..9cb0e80a5 100644 --- a/src/views/apps.html +++ b/src/views/apps.html @@ -73,8 +73,8 @@