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