/* eslint-disable */ // @ts-nocheck import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation"; import { AlertVariant, Button, Checkbox, Divider, Dropdown, DropdownItem, DropdownList, InputGroup, InputGroupItem, MenuToggle, Spinner, Tooltip, TreeView, TreeViewDataItem, } from "../../../shared/@patternfly/react-core"; import { PaginatingTableToolbar, useAlerts, useFetch, } from "../../../shared/keycloak-ui-shared"; import { AngleRightIcon, EllipsisVIcon } from "../../../shared/@patternfly/react-icons"; import { useGroupResource } from "../../context/group-resource/GroupResourceContext"; import { unionBy } from "lodash-es"; import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { useAdminClient } from "../../admin-client"; import { KeycloakSpinner } from "../../../shared/keycloak-ui-shared"; import { useAccess } from "../../context/access/Access"; import { fetchAdminUI } from "../../context/auth/admin-ui-endpoint"; import { useRealm } from "../../context/realm-context/RealmContext"; import useToggle from "../../utils/useToggle"; import { GroupsModal } from "../GroupsModal"; import { useSubGroups } from "../SubGroupsContext"; import { toGroups } from "../routes/Groups"; import { DeleteGroup } from "./DeleteGroup"; import { MoveDialog } from "./MoveDialog"; import "./group-tree.css"; type ExtendedTreeViewDataItem = TreeViewDataItem & { access?: Record; }; type GroupTreeContextMenuProps = { group: GroupRepresentation; refresh: () => void; }; export function countGroups(groups: GroupRepresentation[]) { let count = groups.length; for (const group of groups) { if (group.subGroups) { count += countGroups(group.subGroups); } } return count; } const GroupTreeContextMenu = ({ group, refresh, }: GroupTreeContextMenuProps) => { const { t } = useTranslation(); const [isOpen, toggleOpen] = useToggle(); const [renameOpen, toggleRenameOpen] = useToggle(); const [createOpen, toggleCreateOpen] = useToggle(); const [moveOpen, toggleMoveOpen] = useToggle(); const [deleteOpen, toggleDeleteOpen] = useToggle(); const navigate = useNavigate(); const { realm } = useRealm(); const orgId = useGroupResource().getOrgId(); return ( <> {renameOpen && ( { navigate(toGroups({ realm, orgId })); refresh(); }} handleModalToggle={toggleRenameOpen} /> )} {createOpen && ( )} {moveOpen && ( )} { navigate(toGroups({ realm, orgId })); refresh(); }} /> ( )} isOpen={isOpen} > {t("edit")} {t("moveTo")} {t("createChildGroup")} {t("delete")} ); }; type GroupTreeProps = { refresh: () => void; canViewDetails: boolean; }; const SUBGROUP_COUNT = 50; const TreeLoading = () => { const { t } = useTranslation(); return ( <> {t("spinnerLoading")} ); }; const LOADING_TREE = [ { name: , }, ]; export const GroupTree = ({ refresh: viewRefresh, canViewDetails, }: GroupTreeProps) => { const { adminClient } = useAdminClient(); const isOrgGroups = useGroupResource().isOrgGroups(); const orgId = useGroupResource().getOrgId(); const { t } = useTranslation(); const { realm } = useRealm(); const navigate = useNavigate(); const { addAlert } = useAlerts(); const { hasAccess } = useAccess(); const [data, setData] = useState(); const { subGroups, clear } = useSubGroups(); const [search, setSearch] = useState(""); const [max, setMax] = useState(20); const [first, setFirst] = useState(0); const prefFirst = useRef(0); const prefMax = useRef(20); const prefSearch = useRef(""); const [count, setCount] = useState(0); const [exact, setExact] = useState(false); const [activeItem, setActiveItem] = useState(); const [firstSub, setFirstSub] = useState(0); const [key, setKey] = useState(0); const refresh = () => { setKey(key + 1); viewRefresh(); }; const mapGroup = ( group: GroupRepresentation, refresh: () => void, ): ExtendedTreeViewDataItem => { const hasSubGroups = group.subGroupCount; return { id: group.id, name: ( {group.name} ), access: group.access || {}, children: hasSubGroups ? search.length === 0 ? LOADING_TREE : group.subGroups?.map((g) => mapGroup(g, refresh)) : undefined, action: (hasAccess("manage-users") || group.access?.manage) && ( ), defaultExpanded: subGroups.map((g) => g.id).includes(group.id), }; }; useFetch( async () => { const groupsEndpoint = isOrgGroups ? `organizations/${orgId}/groups` : "groups"; const groups = await fetchAdminUI( adminClient, groupsEndpoint, Object.assign( { first: `${first}`, max: `${max + 1}`, exact: `${exact}`, global: `${search !== ""}`, ...(isOrgGroups ? { subGroupsCount: "true", ...(search && { populateHierarchy: "true" }), } : {}), }, search === "" ? null : { search }, ), ); let subGroups: GroupRepresentation[] = []; if (activeItem) { subGroups = await fetchAdminUI( adminClient, `${groupsEndpoint}/${activeItem.id}/children`, { first: `${firstSub}`, max: `${SUBGROUP_COUNT}`, ...(isOrgGroups ? { subGroupsCount: "true" } : {}), }, ); } return { groups, subGroups }; }, ({ groups, subGroups }) => { if (activeItem) { const found = findGroup(data || [], activeItem.id!, []); if (found.length && subGroups.length) { const foundTreeItem = found.pop()!; foundTreeItem.children = [ ...(unionBy(foundTreeItem.children || []).splice(0, SUBGROUP_COUNT), subGroups.map((g) => mapGroup(g, refresh), "id")), ...(subGroups.length === SUBGROUP_COUNT ? [ { id: "next", name: ( ), }, ] : []), ]; } } if ( search || prefSearch.current !== search || prefFirst.current !== first || prefMax.current !== max ) { setData(groups.map((g) => mapGroup(g, refresh))); } else { setData( unionBy( data, groups.map((g) => mapGroup(g, refresh)), "id", ), ); } setCount(countGroups(groups)); prefFirst.current = first; prefMax.current = max; prefSearch.current = search; }, [key, first, firstSub, max, search, exact, activeItem], ); const findGroup = ( groups: ExtendedTreeViewDataItem[], id: string, path: ExtendedTreeViewDataItem[], ) => { for (let index = 0; index < groups.length; index++) { const group = groups[index]; if (group.id === id) { path.push(group); return path; } if (group.children) { path.push(group); findGroup(group.children, id, path); if (path[path.length - 1].id !== id) { path.pop(); } } } return path; }; const nav = (item: TreeViewDataItem, data: ExtendedTreeViewDataItem[]) => { if (item.id === "next") return; setActiveItem(item); const path = findGroup(data, item.id!, []); if (!subGroups.every(({ id }) => path.find((t) => t.id === id))) clear(); if ( canViewDetails || path.at(-1)?.access?.view || subGroups.at(-1)?.access?.view ) { navigate( toGroups({ realm, id: path.map((g) => g.id).join("/"), orgId, }), ); } else { addAlert(t("noViewRights"), AlertVariant.warning); navigate(toGroups({ realm, orgId })); } }; return data ? ( { setFirst(first); setMax(max); }} inputGroupName="searchForGroups" inputGroupPlaceholder={t("searchForGroups")} inputGroupOnEnter={setSearch} toolbarItem={ setExact(value)} className="pf-v5-u-mr-xs" /> } > {data.length > 0 && ( 0} activeItems={activeItem ? [activeItem] : undefined} hasGuides hasSelectableNodes className="keycloak_groups_treeview" onExpand={(_, item) => { nav(item, data); }} onSelect={(_, item) => { nav(item, data); }} /> )} ) : ( ); };