UNPKG

@keycloakify/keycloak-admin-ui

Version:
281 lines 17.1 kB
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { HelpItem, useAlerts, useFetch } from "../ui-shared"; import { AlertVariant, Button, ButtonVariant, Divider, PageSection, PageSectionVariants, } from "@patternfly/react-core"; import styles from "@patternfly/react-styles/css/components/Table/table"; import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table"; import { Fragment, useMemo, useRef, useState, } from "react"; import { useTranslation } from "react-i18next"; import { useAdminClient } from "../admin-client"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { KeycloakSpinner } from "../ui-shared"; import { ListEmptyState } from "../ui-shared"; import { toUpperCase } from "../util"; import { FederatedUserLink } from "../user/FederatedUserLink"; import { CredentialRow } from "../user/user-credentials/CredentialRow"; import { InlineLabelEdit } from "../user/user-credentials/InlineLabelEdit"; import { ResetCredentialDialog } from "../user/user-credentials/ResetCredentialDialog"; import { ResetPasswordDialog } from "../user/user-credentials/ResetPasswordDialog"; import "./user-credentials.css"; const UserCredentialsRow = ({ credential, userId, toggleDelete, resetPassword, isUserLabelEdit, setIsUserLabelEdit, refresh, }) => (_jsx(CredentialRow, { credential: credential, toggleDelete: () => toggleDelete(credential), resetPassword: resetPassword, children: _jsx(InlineLabelEdit, { credential: credential, userId: userId, isEditable: ((isUserLabelEdit === null || isUserLabelEdit === void 0 ? void 0 : isUserLabelEdit.status) && isUserLabelEdit.rowKey === credential.id) || false, toggle: () => { setIsUserLabelEdit({ status: !(isUserLabelEdit === null || isUserLabelEdit === void 0 ? void 0 : isUserLabelEdit.status), rowKey: credential.id, }); if (isUserLabelEdit === null || isUserLabelEdit === void 0 ? void 0 : isUserLabelEdit.status) { refresh(); } } }) }, credential.id)); export const UserCredentials = ({ user, setUser }) => { const { adminClient } = useAdminClient(); const { t } = useTranslation(); const { addAlert, addError } = useAlerts(); const [key, setKey] = useState(0); const refresh = () => setKey(key + 1); const [isOpen, setIsOpen] = useState(false); const [openCredentialReset, setOpenCredentialReset] = useState(false); const [userCredentials, setUserCredentials] = useState([]); const [groupedUserCredentials, setGroupedUserCredentials] = useState([]); const [selectedCredential, setSelectedCredential] = useState({}); const [isResetPassword, setIsResetPassword] = useState(false); const [isUserLabelEdit, setIsUserLabelEdit] = useState(); const bodyRef = useRef(null); const [state, setState] = useState({ draggedItemId: "", draggingToItemIndex: -1, dragging: false, tempItemOrder: [""], }); useFetch(() => adminClient.users.getCredentials({ id: user.id }), (credentials) => { setUserCredentials(credentials); const groupedCredentials = credentials.reduce((r, a) => { r[a.type] = r[a.type] || []; r[a.type].push(a); return r; }, Object.create(null)); const groupedCredentialsArray = Object.keys(groupedCredentials).map((key) => ({ key, value: groupedCredentials[key] })); setGroupedUserCredentials(groupedCredentialsArray.map((groupedCredential) => ({ ...groupedCredential, isExpanded: false, }))); }, [key]); const passwordTypeFinder = userCredentials.find((credential) => credential.type === "password"); const toggleModal = () => setIsOpen(!isOpen); const toggleCredentialsResetModal = () => { setOpenCredentialReset(!openCredentialReset); }; const resetPassword = () => { setIsResetPassword(true); toggleModal(); }; const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ titleKey: t("deleteCredentialsConfirmTitle"), messageKey: t("deleteCredentialsConfirm"), continueButtonLabel: t("delete"), continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { try { await adminClient.users.deleteCredential({ id: user.id, credentialId: selectedCredential.id, }); addAlert(t("deleteCredentialsSuccess"), AlertVariant.success); setKey((key) => key + 1); } catch (error) { addError("deleteCredentialsError", error); } }, }); const itemOrder = useMemo(() => groupedUserCredentials.flatMap((groupedCredential) => [ groupedCredential.value.map(({ id }) => id).toString(), ...(groupedCredential.isExpanded ? groupedCredential.value.map((c) => c.id) : []), ]), [groupedUserCredentials]); const onDragStart = (evt) => { evt.dataTransfer.effectAllowed = "move"; evt.dataTransfer.setData("text/plain", evt.currentTarget.id); const draggedItemId = evt.currentTarget.id; evt.currentTarget.classList.add(styles.modifiers.ghostRow); evt.currentTarget.setAttribute("aria-pressed", "true"); setState({ ...state, draggedItemId, dragging: true }); }; const moveItem = (items, targetItem, toIndex) => { const fromIndex = items.indexOf(targetItem); if (fromIndex === toIndex) { return items; } const result = [...items]; result.splice(toIndex, 0, result.splice(fromIndex, 1)[0]); return result; }; const move = (itemOrder) => { if (!bodyRef.current) return; const ulNode = bodyRef.current; const nodes = Array.from(ulNode.children); if (nodes.every(({ id }, i) => id === itemOrder[i])) { return; } ulNode.replaceChildren(); itemOrder.forEach((itemId) => { ulNode.appendChild(nodes.find(({ id }) => id === itemId)); }); }; const onDragCancel = () => { if (!bodyRef.current) return; Array.from(bodyRef.current.children).forEach((el) => { el.classList.remove(styles.modifiers.ghostRow); el.setAttribute("aria-pressed", "false"); }); setState({ ...state, draggedItemId: "", draggingToItemIndex: -1, dragging: false, }); }; const onDragLeave = (evt) => { if (!isValidDrop(evt)) { move(itemOrder); setState({ ...state, draggingToItemIndex: -1 }); } }; const isValidDrop = (evt) => { if (!bodyRef.current) return false; const ulRect = bodyRef.current.getBoundingClientRect(); return (evt.clientX > ulRect.x && evt.clientX < ulRect.x + ulRect.width && evt.clientY > ulRect.y && evt.clientY < ulRect.y + ulRect.height); }; const onDrop = (evt) => { if (isValidDrop(evt)) { onDragFinish(state.draggedItemId, state.tempItemOrder); } else { onDragCancel(); } }; const onDragOver = (evt) => { var _a; evt.preventDefault(); const td = evt.target; const curListItem = td.closest("tr"); if (!curListItem || (bodyRef.current && !bodyRef.current.contains(curListItem)) || curListItem.id === state.draggedItemId) { return; } else { const dragId = curListItem.id; const draggingToItemIndex = Array.from(((_a = bodyRef.current) === null || _a === void 0 ? void 0 : _a.children) || []).findIndex((item) => item.id === dragId); if (draggingToItemIndex === state.draggingToItemIndex) { return; } const tempItemOrder = moveItem(itemOrder, state.draggedItemId, draggingToItemIndex); move(tempItemOrder); setState({ ...state, draggingToItemIndex, tempItemOrder, }); } }; const onAddRequiredActions = (requiredActions) => { var _a; setUser({ ...user, requiredActions: [...((_a = user.requiredActions) !== null && _a !== void 0 ? _a : []), ...requiredActions], }); }; const onDragEnd = ({ target }) => { if (!(target instanceof HTMLTableRowElement)) { return; } target.classList.remove(styles.modifiers.ghostRow); target.setAttribute("aria-pressed", "false"); setState({ ...state, draggedItemId: "", draggingToItemIndex: -1, dragging: false, }); }; const onDragFinish = async (dragged, newOrder) => { const oldIndex = itemOrder.findIndex((key) => key === dragged); const newIndex = newOrder.findIndex((key) => key === dragged); const times = newIndex - oldIndex; const ids = dragged.split(","); try { for (const id of ids) for (let index = 0; index < Math.abs(times); index++) { if (times > 0) { await adminClient.users.moveCredentialPositionDown({ id: user.id, credentialId: id, newPreviousCredentialId: itemOrder[newIndex], }); } else { await adminClient.users.moveCredentialPositionUp({ id: user.id, credentialId: id, }); } } refresh(); addAlert(t("updatedCredentialMoveSuccess"), AlertVariant.success); } catch (error) { addError("updatedCredentialMoveError", error); } }; const onToggleDelete = (credential) => { setSelectedCredential(credential); toggleDeleteDialog(); }; const useFederatedCredentials = user.federationLink; const [credentialTypes, setCredentialTypes] = useState([]); useFetch(() => adminClient.users.getUserStorageCredentialTypes({ id: user.id }), setCredentialTypes, []); if (!credentialTypes) { return _jsx(KeycloakSpinner, {}); } const hasCredentialTypes = credentialTypes.length > 0; const noCredentials = groupedUserCredentials.length === 0; const noFederatedCredentials = !user.credentials || user.credentials.length === 0; const emptyState = noCredentials && noFederatedCredentials && !hasCredentialTypes; return (_jsxs(_Fragment, { children: [isOpen && (_jsx(ResetPasswordDialog, { user: user, isResetPassword: isResetPassword, onAddRequiredActions: onAddRequiredActions, refresh: refresh, onClose: () => setIsOpen(false) })), openCredentialReset && (_jsx(ResetCredentialDialog, { userId: user.id, onClose: () => setOpenCredentialReset(false) })), _jsx(DeleteConfirm, {}), user.email && !emptyState && (_jsx(Button, { className: "kc-resetCredentialBtn-header", variant: "primary", "data-testid": "credentialResetBtn", onClick: () => setOpenCredentialReset(true), children: t("credentialResetBtn") })), userCredentials.length !== 0 && passwordTypeFinder === undefined && (_jsxs(_Fragment, { children: [_jsx(Button, { className: "kc-setPasswordBtn-tbl", "data-testid": "setPasswordBtn-table", variant: "primary", form: "userCredentials-form", onClick: () => { setIsOpen(true); }, children: t("setPassword") }), _jsx(Divider, {})] })), groupedUserCredentials.length !== 0 && (_jsx(PageSection, { variant: PageSectionVariants.light, children: _jsxs(Table, { variant: "compact", children: [_jsx(Thead, { children: _jsxs(Tr, { className: "kc-table-header", children: [_jsx(Th, { children: _jsx(HelpItem, { helpText: t("userCredentialsHelpText"), fieldLabelId: "userCredentialsHelpTextLabel" }) }), _jsx(Th, { "aria-hidden": "true" }), _jsx(Th, { children: t("type") }), _jsx(Th, { children: t("userLabel") }), _jsx(Th, { children: t("createdAt") }), _jsx(Th, { children: t("data") }), _jsx(Th, { "aria-hidden": "true" }), _jsx(Th, { "aria-hidden": "true" })] }) }), _jsx(Tbody, { ref: bodyRef, onDragOver: onDragOver, onDrop: onDragOver, onDragLeave: onDragLeave, children: groupedUserCredentials.map((groupedCredential, rowIndex) => (_jsxs(Fragment, { children: [_jsxs(Tr, { id: groupedCredential.value.map(({ id }) => id).toString(), draggable: groupedUserCredentials.length > 1, onDrop: onDrop, onDragEnd: onDragEnd, onDragStart: onDragStart, children: [_jsx(Td, { className: groupedUserCredentials.length === 1 ? "one-row" : "", draggableRow: { id: `draggable-row-${groupedCredential.value.map(({ id }) => id)}`, } }), groupedCredential.value.length > 1 ? (_jsx(Td, { className: "kc-expandRow-btn", expand: { rowIndex, isExpanded: groupedCredential.isExpanded, onToggle: (_, rowIndex) => { const rows = groupedUserCredentials.map((credential, index) => index === rowIndex ? { ...credential, isExpanded: !credential.isExpanded, } : credential); setGroupedUserCredentials(rows); }, } })) : (_jsx(Td, {})), _jsx(Td, { dataLabel: `columns-${groupedCredential.key}`, className: "kc-notExpandableRow-credentialType", "data-testid": "credentialType", children: toUpperCase(groupedCredential.key) }), groupedCredential.value.length <= 1 && groupedCredential.value.map((credential) => (_jsx(UserCredentialsRow, { credential: credential, userId: user.id, toggleDelete: onToggleDelete, resetPassword: resetPassword, isUserLabelEdit: isUserLabelEdit, setIsUserLabelEdit: setIsUserLabelEdit, refresh: refresh }, credential.id)))] }), groupedCredential.isExpanded && groupedCredential.value.map((credential) => (_jsxs(Tr, { id: credential.id, draggable: true, onDrop: onDrop, onDragEnd: onDragEnd, onDragStart: onDragStart, children: [_jsx(Td, {}), _jsx(Td, { className: "kc-draggable-dropdown-type-icon", draggableRow: { id: `draggable-row-${groupedCredential.value.map(({ id }) => id)}`, } }), _jsx(Td, { dataLabel: `child-columns-${credential.id}`, className: "kc-expandableRow-credentialType", children: toUpperCase(credential.type) }), _jsx(UserCredentialsRow, { credential: credential, userId: user.id, toggleDelete: onToggleDelete, resetPassword: resetPassword, isUserLabelEdit: isUserLabelEdit, setIsUserLabelEdit: setIsUserLabelEdit, refresh: refresh })] }, credential.id)))] }, groupedCredential.key))) })] }) })), useFederatedCredentials && hasCredentialTypes && (_jsx(PageSection, { variant: PageSectionVariants.light, children: _jsxs(Table, { variant: "compact", children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: t("type") }), _jsx(Th, { children: t("providedBy") }), _jsx(Th, { "aria-hidden": "true" })] }) }), _jsx(Tbody, { children: credentialTypes.map((credential) => (_jsxs(Tr, { children: [_jsx(Td, { children: _jsx("b", { children: credential }) }), _jsx(Td, { children: _jsx(FederatedUserLink, { user: user }) }), credential === "password" && (_jsx(Td, { modifier: "fitContent", children: _jsx(Button, { variant: "secondary", onClick: toggleModal, children: t("setPassword") }) }))] }, credential))) })] }) })), emptyState && (_jsx(ListEmptyState, { hasIcon: true, message: t("noCredentials"), instructions: t("noCredentialsText"), primaryActionText: t("setPassword"), onPrimaryAction: toggleModal, secondaryActions: user.email ? [ { text: t("credentialResetBtn"), onClick: toggleCredentialsResetModal, type: ButtonVariant.link, }, ] : undefined }))] })); }; //# sourceMappingURL=UserCredentials.js.map