diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java index 2a4b40af24..e704c399d6 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/GroupsResource.java @@ -90,6 +90,19 @@ public interface GroupsResource { @Consumes(MediaType.APPLICATION_JSON) Response count(@QueryParam("search") String search); + /** + * Counts groups by name search. + * @param search max number of occurrences + * @param onlyTopGroups true or false for filter only top level groups count + * @return The number of group containing search therm. + */ + @GET + @NoCache + @Path("/count") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + Response count(@QueryParam("search") String search, @QueryParam("top") String onlyTopGroups); + /** * create or add a top level realm groupSet or create child. This will update the group and set the parent if it exists. Create it and set the parent * if the group doesn't exist. diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index af7159cad6..7144c3b474 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -1213,8 +1213,8 @@ public class RealmAdapter implements CachedRealmModel { } @Override - public Long getGroupsCount() { - return cacheSession.getGroupsCount(this); + public Long getGroupsCount(Boolean onlyTopGroups) { + return cacheSession.getGroupsCount(this, onlyTopGroups); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java index 77f8981ed1..bcff0ba591 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java @@ -843,8 +843,8 @@ public class RealmCacheSession implements CacheRealmProvider { } @Override - public Long getGroupsCount(RealmModel realm) { - return getDelegate().getGroupsCount(realm); + public Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups) { + return getDelegate().getGroupsCount(realm, onlyTopGroups); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index b64a6a6624..2e15fd04af 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -17,6 +17,7 @@ package org.keycloak.models.jpa; +import com.sun.org.apache.xpath.internal.operations.Bool; import org.jboss.logging.Logger; import org.keycloak.common.util.Time; import org.keycloak.connections.jpa.util.JpaUtils; @@ -339,8 +340,12 @@ public class JpaRealmProvider implements RealmProvider { } @Override - public Long getGroupsCount(RealmModel realm) { - Long count = em.createNamedQuery("getGroupCount", Long.class) + public Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups) { + String query = "getGroupCount"; + if(Objects.equals(onlyTopGroups, Boolean.TRUE)) { + query = "getTopLevelGroupCount"; + } + Long count = em.createNamedQuery(query, Long.class) .setParameter("realm", realm.getId()) .getSingleResult(); @@ -577,7 +582,7 @@ public class JpaRealmProvider implements RealmProvider { @Override public List searchForGroupByName(RealmModel realm, String search, Integer first, Integer max) { - TypedQuery query = em.createNamedQuery("getGroupIdsByNameContaining", String.class) + TypedQuery query = em.createNamedQuery("getTopLevelGroupIdsByNameContaining", String.class) .setParameter("realm", realm.getId()) .setParameter("search", search); if(Objects.nonNull(first) && Objects.nonNull(max)) { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 05d7517af5..3b07a73e10 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1687,8 +1687,8 @@ public class RealmAdapter implements RealmModel, JpaModel { } @Override - public Long getGroupsCount() { - return session.realms().getGroupsCount(this); + public Long getGroupsCount(Boolean onlyTopGroups) { + return session.realms().getGroupsCount(this, onlyTopGroups); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java index 7215df6a6e..eff0340704 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java @@ -27,9 +27,10 @@ import java.util.Collection; */ @NamedQueries({ @NamedQuery(name="getGroupIdsByParent", query="select u.id from GroupEntity u where u.parent = :parent"), - @NamedQuery(name="getGroupIdsByNameContaining", query="select u.id from GroupEntity u where u.realm.id = :realm and u.name like concat('%',:search,'%') order by u.name ASC"), + @NamedQuery(name="getTopLevelGroupIdsByNameContaining", query="select u.id from GroupEntity u where u.realm.id = :realm and u.name like concat('%',:search,'%') and u.parent is null order by u.name ASC"), @NamedQuery(name="getTopLevelGroupIds", query="select u.id from GroupEntity u where u.parent is null and u.realm.id = :realm"), @NamedQuery(name="getGroupCount", query="select count(u) from GroupEntity u where u.realm.id = :realm"), + @NamedQuery(name="getTopLevelGroupCount", query="select count(u) from GroupEntity u where u.realm.id = :realm and u.parent is null"), @NamedQuery(name="getGroupCountByNameContaining", query="select count(u) from GroupEntity u where u.realm.id = :realm and u.name like concat('%',:name,'%')"), }) @Entity diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index f8d32e1f64..e88c59ff37 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -400,7 +400,7 @@ public interface RealmModel extends RoleContainerModel { GroupModel getGroupById(String id); List getGroups(); - Long getGroupsCount(); + Long getGroupsCount(Boolean onlyTopGroups); Long getGroupsCountByNameContaining(String search); List getTopLevelGroups(); List getTopLevelGroups(Integer first, Integer max); diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java index 3b043fc7ad..e1a5d5f7f7 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java @@ -40,7 +40,7 @@ public interface RealmProvider extends Provider { List getGroups(RealmModel realm); - Long getGroupsCount(RealmModel realm); + Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups); Long getGroupsCountByNameContaining(RealmModel realm, String search); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java index 8d927cfb21..ef704552e9 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupsResource.java @@ -16,6 +16,7 @@ */ package org.keycloak.services.resources.admin; +import org.apache.http.HttpStatus; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; @@ -38,6 +39,8 @@ import java.util.List; import java.util.Objects; import org.keycloak.services.ErrorResponse; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; +import twitter4j.JSONException; +import twitter4j.JSONObject; /** * @resource Groups @@ -111,15 +114,22 @@ public class GroupsResource { @GET @NoCache @Path("/count") - public Response getGroupCount(@QueryParam("search") String search) { - auth.requireView(); + @Produces(MediaType.APPLICATION_JSON) + public Response getGroupCount(@QueryParam("search") String search, @QueryParam("top") String onlyTopGroups) { Long results; + JSONObject response = new JSONObject(); if (Objects.nonNull(search)) { results = realm.getGroupsCountByNameContaining(search); } else { - results = realm.getGroupsCount(); + results = realm.getGroupsCount(Objects.equals(onlyTopGroups, Boolean.TRUE.toString())); } - return Response.ok(results).build(); + try { + response.put("count", results); + } catch (JSONException e) { + e.printStackTrace(); + return ErrorResponse.error("Cannot create response object", Response.Status.INTERNAL_SERVER_ERROR); + } + return Response.ok(response.toString(), MediaType.APPLICATION_JSON).build(); } /** diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index f261105214..a7601d2f0d 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1031,6 +1031,7 @@ group-membership.tooltip=Groups user is a member of. Select a listed group and c membership.available-groups.tooltip=Groups a user can join. Select a group and click the join button. table-of-realm-users=Table of Realm Users view-all-users=View all users +view-all-groups=View all groups unlock-users=Unlock users no-users-available=No users available users.instruction=Please enter a search, or click on view all users diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index c650d00681..2efa53ff52 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -790,6 +790,9 @@ module.config([ '$routeProvider', function($routeProvider) { }, groups : function(GroupListLoader) { return GroupListLoader(); + }, + groupsCount : function(GroupCountLoader) { + return GroupCountLoader(); } }, controller : 'GroupListCtrl' @@ -1924,7 +1927,7 @@ module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location $('#loading').hide(); } return response; - }, + }, responseError: function(response) { resourceRequests--; if (resourceRequests == 0) { @@ -1944,7 +1947,7 @@ module.factory('errorInterceptor', function($q, $window, $rootScope, $location, return { response: function(response) { return response; - }, + }, responseError: function(response) { if (response.status == 401) { Auth.authz.logout(); @@ -2186,17 +2189,17 @@ module.directive('kcEnter', function() { module.directive('kcSave', function ($compile, $timeout, Notifications) { var clickDelay = 500; // 500 ms - + return { restrict: 'A', link: function ($scope, elem, attr, ctrl) { elem.addClass("btn btn-primary"); elem.attr("type","submit"); - + var disabled = false; elem.on('click', function(evt) { if ($scope.hasOwnProperty("changed") && !$scope.changed) return; - + // KEYCLOAK-4121: Prevent double form submission if (disabled) { evt.preventDefault(); @@ -2206,7 +2209,7 @@ module.directive('kcSave', function ($compile, $timeout, Notifications) { disabled = true; $timeout(function () { disabled = false; }, clickDelay, false); } - + $scope.$apply(function() { var form = elem.closest('form'); if (form && form.attr('name')) { @@ -2869,35 +2872,35 @@ module.directive('kcOnReadFile', function ($parse) { module.controller('PagingCtrl', function ($scope) { $scope.currentPageInput = 1; - + $scope.firstPage = function() { if (!$scope.hasPrevious()) return; $scope.currentPage = 1; $scope.currentPageInput = 1; }; - + $scope.lastPage = function() { if (!$scope.hasNext()) return; $scope.currentPage = $scope.numberOfPages; $scope.currentPageInput = $scope.numberOfPages; }; - + $scope.previousPage = function() { if (!$scope.hasPrevious()) return; $scope.currentPage--; $scope.currentPageInput = $scope.currentPage; }; - + $scope.nextPage = function() { if (!$scope.hasNext()) return; $scope.currentPage++; $scope.currentPageInput = $scope.currentPage; }; - + $scope.hasNext = function() { return $scope.currentPage < $scope.numberOfPages; }; - + $scope.hasPrevious = function() { return $scope.currentPage > 1; }; @@ -2927,11 +2930,11 @@ module.directive('kcValidPage', function() { if (viewValue >= 1 && viewValue <= scope.numberOfPages) { scope.currentPage = viewValue; } - + return true; } } - } + } }); // filter used for paged tables diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js index aa0cfadd15..fed4d94078 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js @@ -1,31 +1,104 @@ -module.controller('GroupListCtrl', function($scope, $route, realm, groups, Groups, Group, GroupChildren, Notifications, $location, Dialog) { +module.controller('GroupListCtrl', function($scope, $route, $q, realm, groups, groupsCount, Groups, GroupsCount, Group, GroupChildren, Notifications, $location, Dialog) { $scope.realm = realm; $scope.groupList = [ - {"id" : "realm", "name": "Groups", - "subGroups" : groups} + { + "id" : "realm", + "name": "Groups", + "subGroups" : groups + } ]; + $scope.searchTerms = ''; + $scope.currentPage = 1; + $scope.currentPageInput = $scope.currentPage; + $scope.pageSize = groups.length; + $scope.numberOfPages = Math.ceil(groupsCount.count/$scope.pageSize); + $scope.tree = []; + var refreshGroups = function (search) { + var queryParams = { + realm : realm.id, + first : ($scope.currentPage * $scope.pageSize) - $scope.pageSize, + max : $scope.pageSize + }; + var countParams = { + realm : realm.id, + top : 'true' + }; + + if(angular.isDefined(search) && search !== '') { + queryParams.search = search; + countParams.search = search; + } + + var promiseGetGroups = $q.defer(); + Groups.query(queryParams, function(entry) { + promiseGetGroups.resolve(entry); + }, function() { + promiseGetGroups.reject('Unable to fetch ' + i); + }); + var promiseGetGroupsChain = promiseGetGroups.promise.then(function(entry) { + groups = entry; + $scope.groupList = [ + { + "id" : "realm", + "name": "Groups", + "subGroups" : groups + } + ]; + }); + + var promiseCount = $q.defer(); + GroupsCount.query(countParams, function(entry) { + promiseCount.resolve(entry); + }, function() { + promiseCount.reject('Unable to fetch ' + i); + }); + var promiseCountChain = promiseCount.promise.then(function(entry) { + groupsCount = entry; + $scope.numberOfPages = Math.ceil(groupsCount.count/$scope.pageSize); + }); + + $q.all([promiseGetGroupsChain, promiseCountChain]); + }; + + $scope.$watch('currentPage', function(newValue, oldValue) { + if(newValue !== oldValue) { + refreshGroups(); + } + }); + + $scope.clearSearch = function() { + $scope.searchTerms = ''; + $scope.currentPage = 1; + refreshGroups(); + }; + + $scope.searchGroup = function() { + $scope.currentPage = 1; + refreshGroups($scope.searchTerms); + }; + $scope.edit = function(selected) { - if (selected.id == 'realm') return; + if (selected.id === 'realm') return; $location.url("/realms/" + realm.realm + "/groups/" + selected.id); - } + }; $scope.cut = function(selected) { $scope.cutNode = selected; - } + }; $scope.isDisabled = function() { if (!$scope.tree.currentNode) return true; - return $scope.tree.currentNode.id == 'realm'; - } + return $scope.tree.currentNode.id === 'realm'; + }; $scope.paste = function(selected) { - if (selected == null) return; - if ($scope.cutNode == null) return; - if (selected.id == $scope.cutNode.id) return; - if (selected.id == 'realm') { + if (selected === null) return; + if ($scope.cutNode === null) return; + if (selected.id === $scope.cutNode.id) return; + if (selected.id === 'realm') { Groups.save({realm: realm.realm}, {id:$scope.cutNode.id}, function() { $route.reload(); Notifications.success("Group moved."); @@ -41,10 +114,10 @@ module.controller('GroupListCtrl', function($scope, $route, realm, groups, Group } - } + }; $scope.remove = function(selected) { - if (selected == null) return; + if (selected === null) return; Dialog.confirmDelete(selected.name, 'group', function() { Group.remove({ realm: realm.realm, groupId : selected.id }, function() { $route.reload(); @@ -52,7 +125,7 @@ module.controller('GroupListCtrl', function($scope, $route, realm, groups, Group }); }); - } + }; $scope.createGroup = function(selected) { var parent = 'realm'; @@ -61,13 +134,13 @@ module.controller('GroupListCtrl', function($scope, $route, realm, groups, Group } $location.url("/create/group/" + realm.realm + '/parent/' + parent); - } + }; var isLeaf = function(node) { - return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0); - } + return node.id !== "realm" && (!node.subGroups || node.subGroups.length === 0); + }; $scope.getGroupClass = function(node) { - if (node.id == "realm") { + if (node.id === "realm") { return 'pficon pficon-users'; } if (isLeaf(node)) { @@ -77,12 +150,12 @@ module.controller('GroupListCtrl', function($scope, $route, realm, groups, Group if (node.subGroups.length && !node.collapsed) return 'expanded'; return 'collapsed'; - } + }; $scope.getSelectedClass = function(node) { if (node.selected) { return 'selected'; - } else if ($scope.cutNode && $scope.cutNode.id == node.id) { + } else if ($scope.cutNode && $scope.cutNode.id === node.id) { return 'cut'; } return undefined; @@ -95,8 +168,8 @@ module.controller('GroupCreateCtrl', function($scope, $route, realm, parentId, G $scope.group = {}; $scope.save = function() { console.log('save!!!'); - if (parentId == 'realm') { - console.log('realm') + if (parentId === 'realm') { + console.log('realm'); Groups.save({realm: realm.realm}, $scope.group, function(data, headers) { var l = headers().location; @@ -120,7 +193,7 @@ module.controller('GroupCreateCtrl', function($scope, $route, realm, parentId, G } - } + }; $scope.cancel = function() { $location.url("/realms/" + realm.realm + "/groups"); }; @@ -176,8 +249,7 @@ module.controller('GroupDetailCtrl', function(Dialog, $scope, realm, group, Grou var attrs = $scope.group.attributes; for (var attribute in attrs) { if (typeof attrs[attribute] === "string") { - var attrVals = attrs[attribute].split("##"); - attrs[attribute] = attrVals; + attrs[attribute] = attrs[attribute].split("##"); } } } @@ -186,8 +258,7 @@ module.controller('GroupDetailCtrl', function(Dialog, $scope, realm, group, Grou var attrs = group.attributes; for (var attribute in attrs) { if (typeof attrs[attribute] === "object") { - var attrVals = attrs[attribute].join("##"); - attrs[attribute] = attrVals; + attrs[attribute] = attrs[attribute].join("##"); } } } @@ -212,8 +283,8 @@ module.controller('GroupDetailCtrl', function(Dialog, $scope, realm, group, Grou }); module.controller('GroupRoleMappingCtrl', function($scope, $http, realm, group, clients, client, Notifications, GroupRealmRoleMapping, - GroupClientRoleMapping, GroupAvailableRealmRoleMapping, GroupAvailableClientRoleMapping, - GroupCompositeRealmRoleMapping, GroupCompositeClientRoleMapping) { + GroupClientRoleMapping, GroupAvailableRealmRoleMapping, GroupAvailableClientRoleMapping, + GroupCompositeRealmRoleMapping, GroupCompositeClientRoleMapping) { $scope.realm = realm; $scope.group = group; $scope.selectedRealmRoles = []; @@ -237,70 +308,70 @@ module.controller('GroupRoleMappingCtrl', function($scope, $http, realm, group, $scope.selectedRealmRoles = []; $http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm', roles).then(function() { - $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - $scope.selectedRealmMappings = []; - $scope.selectRealmRoles = []; - if ($scope.targetClient) { - console.log('load available'); - $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.selectedClientRoles = []; - $scope.selectedClientMappings = []; - } - Notifications.success("Role mappings updated."); + $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.selectedRealmMappings = []; + $scope.selectRealmRoles = []; + if ($scope.targetClient) { + console.log('load available'); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + } + Notifications.success("Role mappings updated."); - }); + }); }; $scope.deleteRealmRole = function() { $http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/realm', {data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).then(function() { - $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - $scope.selectedRealmMappings = []; - $scope.selectRealmRoles = []; - if ($scope.targetClient) { - console.log('load available'); - $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.selectedClientRoles = []; - $scope.selectedClientMappings = []; - } - Notifications.success("Role mappings updated."); - }); + $scope.realmMappings = GroupRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.selectedRealmMappings = []; + $scope.selectRealmRoles = []; + if ($scope.targetClient) { + console.log('load available'); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + } + Notifications.success("Role mappings updated."); + }); }; $scope.addClientRole = function() { $http.post(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.targetClient.id, $scope.selectedClientRoles).then(function() { - $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.selectedClientRoles = []; - $scope.selectedClientMappings = []; - $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - Notifications.success("Role mappings updated."); - }); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + Notifications.success("Role mappings updated."); + }); }; $scope.deleteClientRole = function() { $http.delete(authUrl + '/admin/realms/' + realm.realm + '/groups/' + group.id + '/role-mappings/clients/' + $scope.targetClient.id, {data : $scope.selectedClientMappings, headers : {"content-type" : "application/json"}}).then(function() { - $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); - $scope.selectedClientRoles = []; - $scope.selectedClientMappings = []; - $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); - Notifications.success("Role mappings updated."); - }); + $scope.clientMappings = GroupClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientRoles = GroupAvailableClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.clientComposite = GroupCompositeClientRoleMapping.query({realm : realm.realm, groupId : group.id, client : $scope.targetClient.id}); + $scope.selectedClientRoles = []; + $scope.selectedClientMappings = []; + $scope.realmComposite = GroupCompositeRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + $scope.realmRoles = GroupAvailableRealmRoleMapping.query({realm : realm.realm, groupId : group.id}); + Notifications.success("Role mappings updated."); + }); }; @@ -332,13 +403,13 @@ module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMember groupId: group.id, max : 5, first : 0 - } + }; $scope.firstPage = function() { $scope.query.first = 0; $scope.searchQuery(); - } + }; $scope.previousPage = function() { $scope.query.first -= parseInt($scope.query.max); @@ -346,12 +417,12 @@ module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMember $scope.query.first = 0; } $scope.searchQuery(); - } + }; $scope.nextPage = function() { $scope.query.first += parseInt($scope.query.max); $scope.searchQuery(); - } + }; $scope.searchQuery = function() { console.log("query.search: " + $scope.query.search); @@ -368,7 +439,7 @@ module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMember }); -module.controller('DefaultGroupsCtrl', function($scope, $route, realm, groups, DefaultGroups, Notifications, $location, Dialog) { +module.controller('DefaultGroupsCtrl', function($scope, $route, realm, groups, DefaultGroups, Notifications) { $scope.realm = realm; $scope.groupList = groups; $scope.selectedGroup = null; @@ -383,7 +454,7 @@ module.controller('DefaultGroupsCtrl', function($scope, $route, realm, groups, D if (!$scope.tree.currentNode) { Notifications.error('Please select a group to add'); return; - }; + } DefaultGroups.update({realm: realm.realm, groupId: $scope.tree.currentNode.id}, function() { Notifications.success('Added default group'); @@ -401,11 +472,11 @@ module.controller('DefaultGroupsCtrl', function($scope, $route, realm, groups, D }; var isLeaf = function(node) { - return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0); + return node.id !== "realm" && (!node.subGroups || node.subGroups.length === 0); }; $scope.getGroupClass = function(node) { - if (node.id == "realm") { + if (node.id === "realm") { return 'pficon pficon-users'; } if (isLeaf(node)) { @@ -415,12 +486,12 @@ module.controller('DefaultGroupsCtrl', function($scope, $route, realm, groups, D if (node.subGroups.length && !node.collapsed) return 'expanded'; return 'collapsed'; - } + }; $scope.getSelectedClass = function(node) { if (node.selected) { return 'selected'; - } else if ($scope.cutNode && $scope.cutNode.id == node.id) { + } else if ($scope.cutNode && $scope.cutNode.id === node.id) { return 'cut'; } return undefined; diff --git a/themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/themes/src/main/resources/theme/base/admin/resources/js/loaders.js index 044414c478..9b7bab3bda 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/loaders.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/loaders.js @@ -15,7 +15,7 @@ module.factory('Loader', function($q) { }); return delay.promise; }; - } + }; loader.query = function(service, id) { return function() { var i = id && id(); @@ -27,7 +27,7 @@ module.factory('Loader', function($q) { }); return delay.promise; }; - } + }; return loader; }); @@ -490,7 +490,18 @@ module.factory('AuthenticationConfigLoader', function(Loader, AuthenticationConf module.factory('GroupListLoader', function(Loader, Groups, $route, $q) { return Loader.query(Groups, function() { return { - realm : $route.current.params.realm + realm : $route.current.params.realm, + first : 1, + max : 20 + } + }); +}); + +module.factory('GroupCountLoader', function(Loader, GroupsCount, $route, $q) { + return Loader.query(GroupsCount, function() { + return { + realm : $route.current.params.realm, + top : 'true' } }); }); diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index fca7b334f1..2fb10dd0ec 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -3,7 +3,7 @@ var module = angular.module('keycloak.services', [ 'ngResource', 'ngRoute' ]); module.service('Dialog', function($modal) { - var dialog = {}; + var dialog = {}; var openDialog = function(title, message, btns, template) { var controller = function($scope, $modalInstance, title, message, btns) { @@ -36,15 +36,15 @@ module.service('Dialog', function($modal) { }).result; } - var escapeHtml = function(str) { - var div = document.createElement('div'); - div.appendChild(document.createTextNode(str)); - return div.innerHTML; - }; + var escapeHtml = function(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + }; - dialog.confirmDelete = function(name, type, success) { - var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1)); - var msg = 'Are you sure you want to permanently delete the ' + type + ' ' + name + '?'; + dialog.confirmDelete = function(name, type, success) { + var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1)); + var msg = 'Are you sure you want to permanently delete the ' + type + ' ' + name + '?'; var btns = { ok: { label: 'Delete', @@ -57,7 +57,7 @@ module.service('Dialog', function($modal) { } openDialog(title, msg, btns, '/templates/kc-modal.html').then(success); - } + } dialog.confirmGenerateKeys = function(name, type, success) { var title = 'Generate new keys for realm'; @@ -138,10 +138,10 @@ module.service('CopyDialog', function($modal) { }); module.factory('Notifications', function($rootScope, $timeout) { - // time (in ms) the notifications are shown - var delay = 5000; + // time (in ms) the notifications are shown + var delay = 5000; - var notifications = {}; + var notifications = {}; notifications.current = { display: false }; notifications.current.remove = function() { if (notifications.scheduled) { @@ -157,9 +157,9 @@ module.factory('Notifications', function($rootScope, $timeout) { $rootScope.notification = notifications.current; - notifications.message = function(type, header, message) { + notifications.message = function(type, header, message) { notifications.current.remove(); - + notifications.current.type = type; notifications.current.header = header; notifications.current.message = message; @@ -170,25 +170,25 @@ module.factory('Notifications', function($rootScope, $timeout) { }, delay); console.debug("Added message"); - } + } - notifications.info = function(message) { - notifications.message("info", "Info!", message); - }; + notifications.info = function(message) { + notifications.message("info", "Info!", message); + }; - notifications.success = function(message) { - notifications.message("success", "Success!", message); - }; + notifications.success = function(message) { + notifications.message("success", "Success!", message); + }; - notifications.error = function(message) { - notifications.message("danger", "Error!", message); - }; + notifications.error = function(message) { + notifications.message("danger", "Error!", message); + }; - notifications.warn = function(message) { - notifications.message("warning", "Warning!", message); - }; + notifications.warn = function(message) { + notifications.message("warning", "Warning!", message); + }; - return notifications; + return notifications; }); @@ -237,12 +237,12 @@ module.factory('ComponentUtils', function() { }); module.factory('Realm', function($resource) { - return $resource(authUrl + '/admin/realms/:id', { - id : '@realm' - }, { - update : { - method : 'PUT' - }, + return $resource(authUrl + '/admin/realms/:id', { + id : '@realm' + }, { + update : { + method : 'PUT' + }, create : { method : 'POST', params : { id : ''} @@ -325,9 +325,9 @@ module.factory('RealmSMTPConnectionTester', function($resource) { realm : '@realm', config : '@config' }, { - send: { - method: 'POST' - } + send: { + method: 'POST' + } }); }); @@ -777,67 +777,67 @@ function roleControl($scope, realm, role, roles, clients, $scope.addRealmRole = function() { $scope.compositeSwitchDisabled=true; $http.post(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites', - $scope.selectedRealmRoles).then(function() { - for (var i = 0; i < $scope.selectedRealmRoles.length; i++) { - var role = $scope.selectedRealmRoles[i]; - var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]); - if (idx != -1) { - $scope.realmRoles.splice(idx, 1); - $scope.realmMappings.push(role); - } + $scope.selectedRealmRoles).then(function() { + for (var i = 0; i < $scope.selectedRealmRoles.length; i++) { + var role = $scope.selectedRealmRoles[i]; + var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]); + if (idx != -1) { + $scope.realmRoles.splice(idx, 1); + $scope.realmMappings.push(role); } - $scope.selectedRealmRoles = []; - Notifications.success("Role added to composite."); - }); + } + $scope.selectedRealmRoles = []; + Notifications.success("Role added to composite."); + }); }; $scope.deleteRealmRole = function() { $scope.compositeSwitchDisabled=true; $http.delete(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites', {data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).then(function() { - for (var i = 0; i < $scope.selectedRealmMappings.length; i++) { - var role = $scope.selectedRealmMappings[i]; - var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]); - if (idx != -1) { - $scope.realmMappings.splice(idx, 1); - $scope.realmRoles.push(role); - } + for (var i = 0; i < $scope.selectedRealmMappings.length; i++) { + var role = $scope.selectedRealmMappings[i]; + var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]); + if (idx != -1) { + $scope.realmMappings.splice(idx, 1); + $scope.realmRoles.push(role); } - $scope.selectedRealmMappings = []; - Notifications.success("Role removed from composite."); - }); + } + $scope.selectedRealmMappings = []; + Notifications.success("Role removed from composite."); + }); }; $scope.addClientRole = function() { $scope.compositeSwitchDisabled=true; $http.post(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites', - $scope.selectedClientRoles).then(function() { - for (var i = 0; i < $scope.selectedClientRoles.length; i++) { - var role = $scope.selectedClientRoles[i]; - var idx = $scope.clientRoles.indexOf($scope.selectedClientRoles[i]); - if (idx != -1) { - $scope.clientRoles.splice(idx, 1); - $scope.clientMappings.push(role); - } + $scope.selectedClientRoles).then(function() { + for (var i = 0; i < $scope.selectedClientRoles.length; i++) { + var role = $scope.selectedClientRoles[i]; + var idx = $scope.clientRoles.indexOf($scope.selectedClientRoles[i]); + if (idx != -1) { + $scope.clientRoles.splice(idx, 1); + $scope.clientMappings.push(role); } - $scope.selectedClientRoles = []; - }); + } + $scope.selectedClientRoles = []; + }); }; $scope.deleteClientRole = function() { $scope.compositeSwitchDisabled=true; $http.delete(authUrl + '/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites', {data : $scope.selectedClientMappings, headers : {"content-type" : "application/json"}}).then(function() { - for (var i = 0; i < $scope.selectedClientMappings.length; i++) { - var role = $scope.selectedClientMappings[i]; - var idx = $scope.clientMappings.indexOf($scope.selectedClientMappings[i]); - if (idx != -1) { - $scope.clientMappings.splice(idx, 1); - $scope.clientRoles.push(role); - } + for (var i = 0; i < $scope.selectedClientMappings.length; i++) { + var role = $scope.selectedClientMappings[i]; + var idx = $scope.clientMappings.indexOf($scope.selectedClientMappings[i]); + if (idx != -1) { + $scope.clientMappings.splice(idx, 1); + $scope.clientRoles.push(role); } - $scope.selectedClientMappings = []; - }); + } + $scope.selectedClientMappings = []; + }); }; @@ -1054,10 +1054,10 @@ module.factory('ClientTestNodesAvailable', function($resource) { module.factory('ClientCertificate', function($resource) { return $resource(authUrl + '/admin/realms/:realm/clients/:client/certificates/:attribute', { - realm : '@realm', - client : "@client", - attribute: "@attribute" - }); + realm : '@realm', + client : "@client", + attribute: "@attribute" + }); }); module.factory('ClientCertificateGenerate', function($resource) { @@ -1075,10 +1075,10 @@ module.factory('ClientCertificateGenerate', function($resource) { module.factory('ClientCertificateDownload', function($resource) { return $resource(authUrl + '/admin/realms/:realm/clients/:client/certificates/:attribute/download', { - realm : '@realm', - client : "@client", - attribute: "@attribute" - }, + realm : '@realm', + client : "@client", + attribute: "@attribute" + }, { download : { method : 'POST', @@ -1142,9 +1142,9 @@ module.factory('ClientInstallationJBoss', function($resource) { var url = authUrl + '/admin/realms/:realm/clients/:client/installation/jboss'; return { url : function(parameters) - { - return url.replace(':realm', parameters.realm).replace(':client', parameters.client); - } + { + return url.replace(':realm', parameters.realm).replace(':client', parameters.client); + } } }); @@ -1606,10 +1606,26 @@ module.factory('GroupChildren', function($resource) { }); }); +module.factory('GroupsCount', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/count', { + realm : '@realm' + }, + { + query: { + isArray: false, + method: 'GET', + params: {}, + transformResponse: function (data) { + return angular.fromJson(data) + } + } + }); +}); + module.factory('Groups', function($resource) { return $resource(authUrl + '/admin/realms/:realm/groups', { realm : '@realm' - }); + }) }); module.factory('GroupRealmRoleMapping', function($resource) { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-list.html index efb62bfc9b..7ad6f657b4 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/group-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-list.html @@ -1,37 +1,50 @@ -
- +
+ - - - - - - - - - + + + + + + + - - -
-
-
- - - - - -
-
-
+
+
+
+
+ +
+ +
+
+
+ +
+
+ + + + + +
+
+
+
+
-
-
- -
+
+ + + + +
+ +
+ \ No newline at end of file