diff --git a/contrib/auth/acl/controller.go b/contrib/auth/acl/controller.go
index 2917f5b1a8b..57a40909a48 100644
--- a/contrib/auth/acl/controller.go
+++ b/contrib/auth/acl/controller.go
@@ -151,6 +151,7 @@ func (c *Controller) ListGroupMembers(w http.ResponseWriter, r *http.Request, gr
Username: u.Username,
CreationDate: u.CreatedAt.Unix(),
Email: u.Email,
+ FriendlyName: u.FriendlyName,
})
}
writeResponse(w, http.StatusOK, response)
diff --git a/pkg/api/controller.go b/pkg/api/controller.go
index 7004db34abe..89133c4d407 100644
--- a/pkg/api/controller.go
+++ b/pkg/api/controller.go
@@ -1112,6 +1112,7 @@ func (c *Controller) ListGroupMembers(w http.ResponseWriter, r *http.Request, gr
Id: u.Username,
Email: u.Email,
CreationDate: u.CreatedAt.Unix(),
+ FriendlyName: u.FriendlyName,
})
}
writeResponse(w, r, http.StatusOK, response)
diff --git a/webui/src/lib/components/auth/forms.jsx b/webui/src/lib/components/auth/forms.jsx
index e5771961b53..12608461c78 100644
--- a/webui/src/lib/components/auth/forms.jsx
+++ b/webui/src/lib/components/auth/forms.jsx
@@ -9,15 +9,8 @@ import {SearchIcon} from "@primer/octicons-react";
import {useAPI} from "../../hooks/api";
import {Checkbox, DataTable, DebouncedFormControl, AlertError, Loading} from "../controls";
-const resolveEntityDisplayName = (ent) => {
- // for users
- if (ent?.email?.length) return ent.email;
- // for groups
- if (ent?.name?.length) return ent.name;
- return ent.id;
-}
-
-export const AttachModal = ({ show, searchFn, onAttach, onHide, addText = "Add",
+
+export const AttachModal = ({ show, searchFn, resolveEntityFn = (ent => ent.id), onAttach, onHide , addText = "Add",
emptyState = 'No matches', modalTitle = 'Add', headers = ['Select', 'ID'],
filterPlaceholder = 'Filter...'}) => {
const search = useRef(null);
@@ -49,7 +42,7 @@ export const AttachModal = ({ show, searchFn, onAttach, onHide, addText = "Add",
onAdd={() => setSelected([...selected, ent])}
onRemove={() => setSelected(selected.filter(selectedEnt => selectedEnt.id !== ent.id))}
name={'selected'}/>,
- {resolveEntityDisplayName(ent)}
+ {resolveEntityFn(ent)}
]}/>
@@ -58,7 +51,7 @@ export const AttachModal = ({ show, searchFn, onAttach, onHide, addText = "Add",
Selected:
{(selected.map(item => (
- {resolveEntityDisplayName(item)}
+ {resolveEntityFn(item)}
)))}
diff --git a/webui/src/lib/components/auth/users.jsx b/webui/src/lib/components/auth/users.jsx
new file mode 100644
index 00000000000..8f317148359
--- /dev/null
+++ b/webui/src/lib/components/auth/users.jsx
@@ -0,0 +1,20 @@
+import {auth, MAX_LISTING_AMOUNT} from "../../api";
+
+export const allUsersFromLakeFS = async (resolveUserDisplayNameFN = (user => user.id)) => {
+ let after = ""
+ let hasMore = true
+ let usersList = []
+ try {
+ do {
+ const results = await auth.listUsers("", after, MAX_LISTING_AMOUNT);
+ usersList = usersList.concat(results.results);
+ after = results.pagination.next_offset;
+ hasMore = results.pagination.has_more;
+ } while (hasMore);
+ usersList.sort((a, b) => resolveUserDisplayNameFN(a).localeCompare(resolveUserDisplayNameFN(b)));
+ return usersList;
+ } catch (error) {
+ console.error("Error fetching users:", error);
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/webui/src/lib/utils.ts b/webui/src/lib/utils.ts
index 3be59c67120..266dfbd4817 100644
--- a/webui/src/lib/utils.ts
+++ b/webui/src/lib/utils.ts
@@ -4,7 +4,7 @@ interface User {
friendly_name: string;
}
-export const resolveDisplayName = (user: User): string => {
+export const resolveUserDisplayName = (user: User): string => {
if (!user) return "";
if (user?.email?.length) return user.email;
if (user?.friendly_name?.length) return user.friendly_name;
diff --git a/webui/src/pages/auth/credentials.jsx b/webui/src/pages/auth/credentials.jsx
index 344ab46c146..33dab61cc7c 100644
--- a/webui/src/pages/auth/credentials.jsx
+++ b/webui/src/pages/auth/credentials.jsx
@@ -12,7 +12,7 @@ import {auth} from "../../lib/api";
import {useState} from "react";
import {CredentialsShowModal, CredentialsTable} from "../../lib/components/auth/credentials";
import {useRouter} from "../../lib/hooks/router";
-import {resolveDisplayName} from "../../lib/utils";
+import {resolveUserDisplayName} from "../../lib/utils";
const CredentialsContainer = () => {
const router = useRouter();
@@ -40,7 +40,7 @@ const CredentialsContainer = () => {
Create a new Access Key for user {resolveDisplayName(user)}?}
+ msg={Create a new Access Key for user {resolveUserDisplayName(user)}?}
onConfirm={hide => {
createKey()
.then(key => { setCreatedKey(key) })
diff --git a/webui/src/pages/auth/groups/group/members.jsx b/webui/src/pages/auth/groups/group/members.jsx
index f8cab5ed014..3cdef8a439d 100644
--- a/webui/src/pages/auth/groups/group/members.jsx
+++ b/webui/src/pages/auth/groups/group/members.jsx
@@ -19,22 +19,32 @@ import {
} from "../../../../lib/components/controls";
import {useRouter} from "../../../../lib/hooks/router";
import {Link} from "../../../../lib/components/nav";
-import {resolveDisplayName} from "../../../../lib/utils";
+import {resolveUserDisplayName} from "../../../../lib/utils";
+import {allUsersFromLakeFS} from "../../../../lib/components/auth/users";
const GroupMemberList = ({ groupId, after, onPaginate }) => {
const [refresh, setRefresh] = useState(false);
const [showAddModal, setShowAddModal] = useState(false);
const [attachError, setAttachError] = useState(null);
-
+ const [allUsers, setAllUsers] = useState([]);
const {results, loading, error, nextPage} = useAPIWithPagination(() => {
return auth.listGroupMembers(groupId, after);
}, [groupId, after, refresh]);
-
useEffect(() => {
setAttachError(null);
}, [refresh]);
+
+ const searchUsers = async (prefix, maxResults, resolveUserDisplayNameFN = (user => user.id)) => {
+ let allUsersList = allUsers;
+ if (allUsersList.length == 0) {
+ allUsersList = await allUsersFromLakeFS(resolveUserDisplayNameFN)
+ setAllUsers(allUsersList)
+ }
+ let filteredUsers = allUsersList.filter(user => resolveUserDisplayNameFN(user).startsWith(prefix));
+ return filteredUsers.slice(0, maxResults);
+ };
let content;
if (loading) content = ;
else if (error) content= ;
@@ -45,7 +55,7 @@ const GroupMemberList = ({ groupId, after, onPaginate }) => {
user.id}
rowFn={user => [
- {resolveDisplayName(user)},
+ {resolveUserDisplayName(user)},
]}
headers={['User ID', 'Created At']}
@@ -54,7 +64,7 @@ const GroupMemberList = ({ groupId, after, onPaginate }) => {
buttonFn: user => Are you sure you{'\''}d like to remove user {resolveDisplayName(user)} from group {groupId}?}
+ msg={Are you sure you{'\''}d like to remove user {resolveUserDisplayName(user)} from group {groupId}?}
onConfirm={() => {
auth.removeUserFromGroup(user.id, groupId)
.catch(error => alert(error))
@@ -75,7 +85,8 @@ const GroupMemberList = ({ groupId, after, onPaginate }) => {
filterPlaceholder={'Find User...'}
modalTitle={'Add to Group'}
addText={'Add to Group'}
- searchFn={prefix => auth.listUsers(prefix, "", 5).then(res => res.results)}
+ resolveEntityFn={resolveUserDisplayName}
+ searchFn={prefix => searchUsers(prefix, 5, resolveUserDisplayName).then(res => res)}
onHide={() => setShowAddModal(false)}
onAttach={(selected) => {
Promise.all(selected.map(user => auth.addUserToGroup(user.id, groupId)))
diff --git a/webui/src/pages/auth/users/index.jsx b/webui/src/pages/auth/users/index.jsx
index da45a7218dd..969f58064b5 100644
--- a/webui/src/pages/auth/users/index.jsx
+++ b/webui/src/pages/auth/users/index.jsx
@@ -24,7 +24,7 @@ import {
} from "../../../lib/components/controls";
import validator from "validator/es";
import { disallowPercentSign, INVALID_USER_NAME_ERROR_MESSAGE } from "../validation";
-import { resolveDisplayName } from "../../../lib/utils";
+import { resolveUserDisplayName } from "../../../lib/utils";
const USER_NOT_FOUND = "unknown";
export const GetUserEmailByIdContext = createContext();
@@ -119,7 +119,7 @@ const UsersContainer = ({nextPage, refresh, setRefresh, error, loading, userList
onRemove={() => setSelected(selected.filter(u => u !== user))}
/>,
- { resolveDisplayName(user) }
+ { resolveUserDisplayName(user) }
,
]}/>
diff --git a/webui/src/pages/auth/users/user/groups.jsx b/webui/src/pages/auth/users/user/groups.jsx
index 7e2fa6fc94a..bb8b0adc540 100644
--- a/webui/src/pages/auth/users/user/groups.jsx
+++ b/webui/src/pages/auth/users/user/groups.jsx
@@ -21,6 +21,12 @@ import { ConfirmationButton } from "../../../../lib/components/modals";
import { useRouter } from "../../../../lib/hooks/router";
import { Link } from "../../../../lib/components/nav";
+const resolveGroupDisplayName = (group) => {
+ if(!group) return "";
+ if (group?.name?.length) return group.name;
+ return group.id;
+}
+
const UserGroupsList = ({ userId, after, onPaginate }) => {
const [refresh, setRefresh] = useState(false);
const [showAddModal, setShowAddModal] = useState(false);
@@ -97,6 +103,7 @@ const UserGroupsList = ({ userId, after, onPaginate }) => {
searchFn={(prefix) =>
auth.listGroups(prefix, "", 5).then((res) => res.results)
}
+ resolveEntityFn={resolveGroupDisplayName}
onHide={() => setShowAddModal(false)}
onAttach={(selected) => {
Promise.all(
@@ -112,7 +119,8 @@ const UserGroupsList = ({ userId, after, onPaginate }) => {
.finally(() => {
setShowAddModal(false);
});
- }}
+ }
+ }
/>
)}
>