@keycloakify/keycloak-admin-ui
Version:
Repackaged Keycloak Admin UI
189 lines • 9.88 kB
JavaScript
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