UNPKG

@keycloakify/keycloak-admin-ui

Version:
189 lines 9.88 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { AlertVariant, Button, Checkbox, Divider, Dropdown, DropdownItem, DropdownList, InputGroup, InputGroupItem, MenuToggle, Spinner, Tooltip, TreeView, } from "@patternfly/react-core"; import { PaginatingTableToolbar, useAlerts, useFetch, } from "../../ui-shared"; import { AngleRightIcon, EllipsisVIcon } from "@patternfly/react-icons"; 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 "../../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 "../../groups/GroupsModal"; import { useSubGroups } from "../../groups/SubGroupsContext"; import { toGroups } from "../../groups/routes/Groups"; import { DeleteGroup } from "../../groups/components/DeleteGroup"; import { MoveDialog } from "../../groups/components/MoveDialog"; import "./group-tree.css"; export function countGroups(groups) { let count = groups.length; for (const group of groups) { if (group.subGroups) { count += countGroups(group.subGroups); } } return count; } const GroupTreeContextMenu = ({ group, refresh, }) => { 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(); return (_jsxs(_Fragment, { children: [renameOpen && (_jsx(GroupsModal, { id: group.id, rename: group, refresh: () => { navigate(toGroups({ realm })); refresh(); }, handleModalToggle: toggleRenameOpen })), createOpen && (_jsx(GroupsModal, { id: group.id, handleModalToggle: toggleCreateOpen, refresh: refresh })), moveOpen && (_jsx(MoveDialog, { source: group, refresh: refresh, onClose: toggleMoveOpen })), _jsx(DeleteGroup, { show: deleteOpen, toggleDialog: toggleDeleteOpen, selectedRows: [group], refresh: () => { navigate(toGroups({ realm })); refresh(); } }), _jsx(Dropdown, { popperProps: { position: "right", }, onOpenChange: toggleOpen, toggle: (ref) => (_jsx(MenuToggle, { ref: ref, onClick: toggleOpen, isExpanded: isOpen, variant: "plain", "aria-label": "Actions", children: _jsx(EllipsisVIcon, {}) })), isOpen: isOpen, children: _jsxs(DropdownList, { children: [_jsx(DropdownItem, { onClick: toggleRenameOpen, children: t("rename") }, "rename"), _jsx(DropdownItem, { onClick: toggleMoveOpen, children: t("moveTo") }, "move"), _jsx(DropdownItem, { onClick: toggleCreateOpen, children: t("createChildGroup") }, "create"), _jsx(Divider, {}, "separator"), ",", _jsx(DropdownItem, { onClick: toggleDeleteOpen, children: t("delete") }, "delete")] }) })] })); }; const SUBGROUP_COUNT = 50; const TreeLoading = () => { const { t } = useTranslation(); return (_jsxs(_Fragment, { children: [_jsx(Spinner, { size: "sm" }), " ", t("spinnerLoading")] })); }; const LOADING_TREE = [ { name: _jsx(TreeLoading, {}), }, ]; export const GroupTree = ({ refresh: viewRefresh, canViewDetails, }) => { const { adminClient } = useAdminClient(); 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 [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, refresh) => { var _a, _b; const hasSubGroups = group.subGroupCount; return { id: group.id, name: (_jsx(Tooltip, { content: group.name, children: _jsx("span", { children: group.name }) })), access: group.access || {}, children: hasSubGroups ? search.length === 0 ? LOADING_TREE : (_a = group.subGroups) === null || _a === void 0 ? void 0 : _a.map((g) => mapGroup(g, refresh)) : undefined, action: (hasAccess("manage-users") || ((_b = group.access) === null || _b === void 0 ? void 0 : _b.manage)) && (_jsx(GroupTreeContextMenu, { group: group, refresh: refresh })), defaultExpanded: subGroups.map((g) => g.id).includes(group.id), }; }; useFetch(async () => { const groups = await fetchAdminUI(adminClient, "groups", Object.assign({ first: `${first}`, max: `${max + 1}`, exact: `${exact}`, global: `${search !== ""}`, }, search === "" ? null : { search })); let subGroups = []; if (activeItem) { subGroups = await fetchAdminUI(adminClient, `groups/${activeItem.id}/children`, { first: `${firstSub}`, max: `${SUBGROUP_COUNT}`, }); } 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: (_jsx(Button, { variant: "plain", onClick: () => setFirstSub(firstSub + SUBGROUP_COUNT), children: _jsx(AngleRightIcon, {}) })), }, ] : []), ]; } } if (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; }, [key, first, firstSub, max, search, exact, activeItem]); const findGroup = (groups, id, path) => { 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, data) => { var _a, _b, _c, _d; 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 || ((_b = (_a = path.at(-1)) === null || _a === void 0 ? void 0 : _a.access) === null || _b === void 0 ? void 0 : _b.view) || ((_d = (_c = subGroups.at(-1)) === null || _c === void 0 ? void 0 : _c.access) === null || _d === void 0 ? void 0 : _d.view)) { navigate(toGroups({ realm, id: path.map((g) => g.id).join("/"), })); } else { addAlert(t("noViewRights"), AlertVariant.warning); navigate(toGroups({ realm })); } }; return data ? (_jsx(PaginatingTableToolbar, { count: count, first: first, max: max, onNextClick: setFirst, onPreviousClick: setFirst, onPerPageSelect: (first, max) => { setFirst(first); setMax(max); }, inputGroupName: "searchForGroups", inputGroupPlaceholder: t("searchForGroups"), inputGroupOnEnter: setSearch, toolbarItem: _jsxs(InputGroup, { className: "pf-v5-u-pt-sm", children: [_jsx(InputGroupItem, { children: _jsx(Checkbox, { id: "exact", "data-testid": "exact-search", name: "exact", isChecked: exact, onChange: (_event, value) => setExact(value) }) }), _jsx(InputGroupItem, { children: _jsx("label", { htmlFor: "exact", className: "pf-v5-u-pl-sm", children: t("exactSearch") }) })] }), children: data.length > 0 && (_jsx(TreeView, { data: data.slice(0, max), allExpanded: search.length > 0, activeItems: activeItem ? [activeItem] : undefined, hasGuides: true, hasSelectableNodes: true, className: "keycloak_groups_treeview", onExpand: (_, item) => { nav(item, data); }, onSelect: (_, item) => { nav(item, data); } })) })) : (_jsx(KeycloakSpinner, {})); }; //# sourceMappingURL=GroupTree.js.map