diff --git a/apps/admin-ui/public/resources/en/groups.json b/apps/admin-ui/public/resources/en/groups.json
index 42e1276a53..011beedfc5 100644
--- a/apps/admin-ui/public/resources/en/groups.json
+++ b/apps/admin-ui/public/resources/en/groups.json
@@ -22,6 +22,7 @@
"usersAdded_other": "{{count}} users added to the group",
"usersAddedError": "Could not add users to the group: {{error}}",
"search": "Search",
+ "exactSearch": "Exact search",
"members": "Members",
"searchMembers": "Search members",
"addMember": "Add member",
diff --git a/apps/admin-ui/src/groups/components/GroupTree.tsx b/apps/admin-ui/src/groups/components/GroupTree.tsx
index d864939129..772c2b484c 100644
--- a/apps/admin-ui/src/groups/components/GroupTree.tsx
+++ b/apps/admin-ui/src/groups/components/GroupTree.tsx
@@ -2,10 +2,12 @@ import { useState } from "react";
import { Link } from "react-router-dom-v5-compat";
import { useTranslation } from "react-i18next";
import {
+ Checkbox,
Dropdown,
DropdownItem,
DropdownPosition,
DropdownSeparator,
+ InputGroup,
KebabToggle,
TreeView,
TreeViewDataItem,
@@ -109,6 +111,7 @@ export const GroupTree = ({ refresh: viewRefresh }: GroupTreeProps) => {
const [search, setSearch] = useState("");
const [max, setMax] = useState(20);
const [first, setFirst] = useState(0);
+ const [exact, setExact] = useState(false);
const [key, setKey] = useState(0);
const refresh = () => {
@@ -151,12 +154,13 @@ export const GroupTree = ({ refresh: viewRefresh }: GroupTreeProps) => {
{
first: `${first}`,
max: `${max + 1}`,
+ exact: `${exact}`,
},
search === "" ? null : { search }
)
),
(groups) => setData(groups.map((g) => mapGroup(g, [], refresh))),
- [key, first, max, search]
+ [key, first, max, search, exact]
);
return data ? (
@@ -173,6 +177,18 @@ export const GroupTree = ({ refresh: viewRefresh }: GroupTreeProps) => {
inputGroupName="searchForGroups"
inputGroupPlaceholder={t("groups:searchForGroups")}
inputGroupOnEnter={setSearch}
+ toolbarItem={
+
+ setExact(value)}
+ />
+ {t("exactSearch")}
+
+ }
>
{data.length > 0 && (
0} hasGuides />
diff --git a/keycloak-theme/src/main/java/org/keycloak/admin/ui/rest/GroupsResource.java b/keycloak-theme/src/main/java/org/keycloak/admin/ui/rest/GroupsResource.java
index b56a4ecba0..9a12d8124a 100644
--- a/keycloak-theme/src/main/java/org/keycloak/admin/ui/rest/GroupsResource.java
+++ b/keycloak-theme/src/main/java/org/keycloak/admin/ui/rest/GroupsResource.java
@@ -10,6 +10,7 @@ import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
+
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Content;
@@ -53,22 +54,23 @@ public class GroupsResource {
)}
)
public final Stream listGroups(@QueryParam("search") @DefaultValue("") final String search, @QueryParam("first")
- @DefaultValue("0") int first, @QueryParam("max") @DefaultValue("10") int max, @QueryParam("global") @DefaultValue("true") boolean global) {
+ @DefaultValue("0") int first, @QueryParam("max") @DefaultValue("10") int max, @QueryParam("global") @DefaultValue("true") boolean global,
+ @QueryParam("exact") @DefaultValue("false") boolean exact) {
this.auth.groups().requireList();
final Stream stream;
if (!"".equals(search)) {
if (global) {
- stream = this.realm.searchForGroupByNameStream(search, first, max);
+ stream = session.groups().searchForGroupByNameStream(realm, search, exact, first, max);
} else {
stream = this.realm.getTopLevelGroupsStream().filter(g -> g.getName().contains(search)).skip(first).limit(max);
}
} else {
stream = this.realm.getTopLevelGroupsStream(first, max);
}
- return stream.map(g -> toGroupHierarchy(g, search));
+ return stream.map(g -> toGroupHierarchy(g, search, exact));
}
- private GroupRepresentation toGroupHierarchy(GroupModel group, final String search) {
+ private GroupRepresentation toGroupHierarchy(GroupModel group, final String search, boolean exact) {
GroupRepresentation rep = toRepresentation(group, true);
rep.setAccess(auth.groups().getAccess(group));
rep.setSubGroups(group.getSubGroupsStream().filter(g ->
@@ -76,9 +78,9 @@ public class GroupsResource {
g, search
)
).map(subGroup ->
- ModelToRepresentation.toGroupHierarchy(
- subGroup, true, search
- )
+ ModelToRepresentation.toGroupHierarchy(
+ subGroup, true, search, exact
+ )
).collect(Collectors.toList()));
return rep;