2021-01-12 14:00:44 +00:00
|
|
|
import React, {
|
|
|
|
createContext,
|
|
|
|
ReactNode,
|
|
|
|
useContext,
|
|
|
|
useEffect,
|
|
|
|
useState,
|
|
|
|
} from "react";
|
|
|
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
2020-09-15 19:54:52 +00:00
|
|
|
import { useTranslation } from "react-i18next";
|
2020-09-28 15:58:03 +00:00
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
Dropdown,
|
|
|
|
DropdownItem,
|
|
|
|
KebabToggle,
|
|
|
|
PageSection,
|
|
|
|
PageSectionVariants,
|
|
|
|
ToolbarItem,
|
2020-10-27 23:45:35 +00:00
|
|
|
AlertVariant,
|
2020-09-28 15:58:03 +00:00
|
|
|
} from "@patternfly/react-core";
|
2020-12-14 08:57:05 +00:00
|
|
|
import { UsersIcon } from "@patternfly/react-icons";
|
2020-11-12 12:55:52 +00:00
|
|
|
import GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
|
2020-09-09 09:07:17 +00:00
|
|
|
|
2020-12-14 08:57:05 +00:00
|
|
|
import { GroupsCreateModal } from "./GroupsCreateModal";
|
|
|
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
|
|
|
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
|
|
|
import { useAdminClient } from "../context/auth/AdminClient";
|
|
|
|
import { useAlerts } from "../components/alert/Alerts";
|
|
|
|
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
|
|
|
|
|
|
|
import "./GroupsSection.css";
|
2021-01-12 14:00:44 +00:00
|
|
|
import { useRealm } from "../context/realm-context/RealmContext";
|
2020-12-14 08:57:05 +00:00
|
|
|
|
|
|
|
type GroupTableData = GroupRepresentation & {
|
|
|
|
membersLength?: number;
|
|
|
|
};
|
|
|
|
|
2021-01-12 14:00:44 +00:00
|
|
|
type SubGroupsProps = {
|
|
|
|
subGroups: GroupRepresentation[];
|
|
|
|
setSubGroups: (group: GroupRepresentation[]) => void;
|
|
|
|
clear: () => void;
|
|
|
|
remove: (group: GroupRepresentation) => void;
|
|
|
|
};
|
|
|
|
|
|
|
|
const SubGroupContext = createContext<SubGroupsProps>({
|
|
|
|
subGroups: [],
|
|
|
|
setSubGroups: () => {},
|
|
|
|
clear: () => {},
|
|
|
|
remove: () => {},
|
|
|
|
});
|
|
|
|
|
|
|
|
export const SubGroups = ({ children }: { children: ReactNode }) => {
|
|
|
|
const [subGroups, setSubGroups] = useState<GroupRepresentation[]>([]);
|
|
|
|
|
|
|
|
const clear = () => setSubGroups([]);
|
|
|
|
const remove = (group: GroupRepresentation) =>
|
|
|
|
setSubGroups(
|
|
|
|
subGroups.slice(
|
|
|
|
0,
|
|
|
|
subGroups.findIndex((g) => g.id === group.id)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return (
|
|
|
|
<SubGroupContext.Provider
|
|
|
|
value={{ subGroups, setSubGroups, clear, remove }}
|
|
|
|
>
|
|
|
|
{children}
|
|
|
|
</SubGroupContext.Provider>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const useSubGroups = () => useContext(SubGroupContext);
|
|
|
|
|
|
|
|
const getId = (pathname: string) => {
|
|
|
|
const pathParts = pathname.substr(1).split("/");
|
|
|
|
return pathParts.length > 1 ? pathParts.splice(2) : undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
const getLastId = (pathname: string) => {
|
|
|
|
const pathParts = getId(pathname);
|
|
|
|
return pathParts ? pathParts[pathParts.length - 1] : undefined;
|
|
|
|
};
|
|
|
|
|
2020-09-10 18:04:03 +00:00
|
|
|
export const GroupsSection = () => {
|
2020-09-15 19:54:52 +00:00
|
|
|
const { t } = useTranslation("groups");
|
2020-11-12 12:55:52 +00:00
|
|
|
const adminClient = useAdminClient();
|
2020-09-28 15:58:03 +00:00
|
|
|
const [isKebabOpen, setIsKebabOpen] = useState(false);
|
2020-10-13 20:52:23 +00:00
|
|
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
2020-12-14 08:57:05 +00:00
|
|
|
const [selectedRows, setSelectedRows] = useState<GroupRepresentation[]>([]);
|
2021-01-12 14:00:44 +00:00
|
|
|
const { subGroups, setSubGroups } = useSubGroups();
|
2020-10-27 23:45:35 +00:00
|
|
|
const { addAlert } = useAlerts();
|
2021-01-12 14:00:44 +00:00
|
|
|
const { realm } = useRealm();
|
|
|
|
const history = useHistory();
|
|
|
|
|
|
|
|
const location = useLocation();
|
|
|
|
const id = getLastId(location.pathname);
|
|
|
|
|
2020-12-14 08:57:05 +00:00
|
|
|
const [key, setKey] = useState("");
|
|
|
|
const refresh = () => setKey(`${new Date().getTime()}`);
|
|
|
|
|
|
|
|
const getMembers = async (id: string) => {
|
|
|
|
const response = await adminClient.groups.listMembers({ id });
|
|
|
|
return response ? response.length : 0;
|
|
|
|
};
|
2020-09-15 19:54:52 +00:00
|
|
|
|
|
|
|
const loader = async () => {
|
2021-01-12 14:00:44 +00:00
|
|
|
let groupsData;
|
|
|
|
if (!id) {
|
|
|
|
groupsData = await adminClient.groups.find();
|
|
|
|
} else {
|
|
|
|
const ids = getId(location.pathname);
|
|
|
|
const isNavigationStateInValid = ids && ids.length !== subGroups.length;
|
|
|
|
if (isNavigationStateInValid) {
|
|
|
|
const groups = [];
|
|
|
|
for (const i of ids!) {
|
|
|
|
const group = await adminClient.groups.findOne({ id: i });
|
|
|
|
if (group) groups.push(group);
|
|
|
|
}
|
|
|
|
setSubGroups(groups);
|
|
|
|
groupsData = groups.pop()?.subGroups!;
|
|
|
|
} else {
|
|
|
|
const group = await adminClient.groups.findOne({ id });
|
|
|
|
if (group) {
|
|
|
|
setSubGroups([...subGroups, group]);
|
|
|
|
groupsData = group.subGroups!;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (groupsData) {
|
|
|
|
const memberPromises = groupsData.map((group) => getMembers(group.id!));
|
|
|
|
const memberData = await Promise.all(memberPromises);
|
|
|
|
return groupsData.map((group: GroupTableData, i) => {
|
|
|
|
group.membersLength = memberData[i];
|
|
|
|
return group;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
history.push(`/${realm}/groups`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
2020-09-15 19:54:52 +00:00
|
|
|
};
|
|
|
|
|
2021-01-12 14:00:44 +00:00
|
|
|
useEffect(() => {
|
|
|
|
refresh();
|
|
|
|
}, [id]);
|
|
|
|
|
2020-10-01 18:52:18 +00:00
|
|
|
const handleModalToggle = () => {
|
2020-10-01 19:15:23 +00:00
|
|
|
setIsCreateModalOpen(!isCreateModalOpen);
|
2020-10-01 18:52:18 +00:00
|
|
|
};
|
|
|
|
|
2021-01-05 19:56:16 +00:00
|
|
|
const deleteGroup = async (group: GroupRepresentation) => {
|
2020-12-14 08:57:05 +00:00
|
|
|
try {
|
2021-01-05 19:56:16 +00:00
|
|
|
await adminClient.groups.del({
|
2020-12-14 08:57:05 +00:00
|
|
|
id: group.id!,
|
2020-10-27 23:45:35 +00:00
|
|
|
});
|
2021-01-05 19:56:16 +00:00
|
|
|
addAlert(t("groupDelete"), AlertVariant.success);
|
2020-12-14 08:57:05 +00:00
|
|
|
} catch (error) {
|
|
|
|
addAlert(t("groupDeleteError", { error }), AlertVariant.danger);
|
|
|
|
}
|
2021-01-05 19:56:16 +00:00
|
|
|
return true;
|
2020-12-14 08:57:05 +00:00
|
|
|
};
|
2020-10-27 23:45:35 +00:00
|
|
|
|
2020-12-14 08:57:05 +00:00
|
|
|
const multiDelete = async () => {
|
|
|
|
if (selectedRows!.length !== 0) {
|
|
|
|
const chainedPromises = selectedRows!.map((group) => deleteGroup(group));
|
|
|
|
|
|
|
|
await Promise.all(chainedPromises);
|
|
|
|
addAlert(t("groupsDeleted"), AlertVariant.success);
|
|
|
|
setSelectedRows([]);
|
|
|
|
refresh();
|
2020-10-27 23:45:35 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-12-14 08:57:05 +00:00
|
|
|
const GroupNameCell = (group: GroupTableData) => (
|
|
|
|
<>
|
2021-01-12 14:00:44 +00:00
|
|
|
<Link key={group.id} to={`${location.pathname}/${group.id}`}>
|
2020-12-14 08:57:05 +00:00
|
|
|
{group.name}
|
|
|
|
</Link>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
|
|
|
|
const GroupMemberCell = (group: GroupTableData) => (
|
|
|
|
<div className="keycloak-admin--groups__member-count">
|
|
|
|
<UsersIcon key={`user-icon-${group.id}`} />
|
|
|
|
{group.membersLength}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
2020-09-15 19:54:52 +00:00
|
|
|
return (
|
2020-10-14 20:47:29 +00:00
|
|
|
<>
|
2021-03-01 15:06:04 +00:00
|
|
|
<ViewHeader
|
|
|
|
titleKey="groups:groups"
|
|
|
|
subKey="groups:groupsDescription"
|
|
|
|
dropdownItems={[
|
|
|
|
<DropdownItem
|
|
|
|
data-testid="searchGroup"
|
|
|
|
key="searchGroup"
|
|
|
|
onClick={() => history.push(`/${realm}/groups/search`)}
|
|
|
|
>
|
|
|
|
{t("searchGroup")}
|
|
|
|
</DropdownItem>,
|
|
|
|
<DropdownItem
|
|
|
|
data-testid="renameGroup"
|
|
|
|
key="renameGroup"
|
|
|
|
onClick={() => addAlert("Not implemented")}
|
|
|
|
>
|
|
|
|
{t("renameGroup")}
|
|
|
|
</DropdownItem>,
|
|
|
|
<DropdownItem
|
|
|
|
data-testid="deleteGroup"
|
|
|
|
key="deleteGroup"
|
|
|
|
onClick={() => deleteGroup({ id })}
|
|
|
|
>
|
|
|
|
{t("deleteGroup")}
|
|
|
|
</DropdownItem>,
|
|
|
|
]}
|
|
|
|
/>
|
2020-09-28 15:58:03 +00:00
|
|
|
<PageSection variant={PageSectionVariants.light}>
|
2020-12-14 08:57:05 +00:00
|
|
|
<KeycloakDataTable
|
|
|
|
key={key}
|
|
|
|
onSelect={(rows) => setSelectedRows([...rows])}
|
|
|
|
canSelectAll={false}
|
|
|
|
loader={loader}
|
2021-01-12 14:00:44 +00:00
|
|
|
ariaLabelKey="groups:groups"
|
|
|
|
searchPlaceholderKey="groups:searchForGroups"
|
2020-12-14 08:57:05 +00:00
|
|
|
toolbarItem={
|
|
|
|
<>
|
|
|
|
<ToolbarItem>
|
2021-03-01 15:06:04 +00:00
|
|
|
<Button
|
|
|
|
data-testid="openCreateGroupModal"
|
|
|
|
variant="primary"
|
|
|
|
onClick={handleModalToggle}
|
|
|
|
>
|
2020-12-14 08:57:05 +00:00
|
|
|
{t("createGroup")}
|
|
|
|
</Button>
|
|
|
|
</ToolbarItem>
|
|
|
|
<ToolbarItem>
|
|
|
|
<Dropdown
|
|
|
|
toggle={
|
|
|
|
<KebabToggle
|
|
|
|
onToggle={() => setIsKebabOpen(!isKebabOpen)}
|
2020-10-13 21:06:58 +00:00
|
|
|
/>
|
2020-12-14 08:57:05 +00:00
|
|
|
}
|
|
|
|
isOpen={isKebabOpen}
|
|
|
|
isPlain
|
|
|
|
dropdownItems={[
|
|
|
|
<DropdownItem
|
|
|
|
key="action"
|
|
|
|
component="button"
|
|
|
|
onClick={() => {
|
|
|
|
multiDelete();
|
|
|
|
setIsKebabOpen(false);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t("common:delete")}
|
|
|
|
</DropdownItem>,
|
|
|
|
]}
|
2020-10-14 20:53:32 +00:00
|
|
|
/>
|
2020-12-14 08:57:05 +00:00
|
|
|
</ToolbarItem>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
actions={[
|
|
|
|
{
|
|
|
|
title: t("moveTo"),
|
|
|
|
onRowClick: () => console.log("TO DO: Add move to functionality"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: t("common:delete"),
|
|
|
|
onRowClick: async (group: GroupRepresentation) => {
|
2021-01-05 19:56:16 +00:00
|
|
|
return deleteGroup(group);
|
2020-12-14 08:57:05 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
]}
|
|
|
|
columns={[
|
|
|
|
{
|
|
|
|
name: "name",
|
|
|
|
displayKey: "groups:groupName",
|
|
|
|
cellRenderer: GroupNameCell,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "members",
|
|
|
|
displayKey: "groups:members",
|
|
|
|
cellRenderer: GroupMemberCell,
|
|
|
|
},
|
|
|
|
]}
|
|
|
|
emptyState={
|
|
|
|
<ListEmptyState
|
|
|
|
hasIcon={true}
|
2021-03-01 15:06:04 +00:00
|
|
|
message={t(`noGroupsInThis${id ? "SubGroup" : "Realm"}`)}
|
|
|
|
instructions={t(
|
|
|
|
`noGroupsInThis${id ? "SubGroup" : "Realm"}Instructions`
|
|
|
|
)}
|
2020-12-14 08:57:05 +00:00
|
|
|
primaryActionText={t("createGroup")}
|
|
|
|
onPrimaryAction={() => handleModalToggle()}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
|
2021-03-01 15:06:04 +00:00
|
|
|
{isCreateModalOpen && (
|
|
|
|
<GroupsCreateModal
|
|
|
|
id={id}
|
|
|
|
handleModalToggle={handleModalToggle}
|
|
|
|
refresh={refresh}
|
|
|
|
/>
|
|
|
|
)}
|
2020-09-18 08:04:55 +00:00
|
|
|
</PageSection>
|
2020-10-14 20:47:29 +00:00
|
|
|
</>
|
2020-09-15 19:54:52 +00:00
|
|
|
);
|
2020-09-09 09:07:17 +00:00
|
|
|
};
|