Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
theme.keycloak.v2.admin.resources.assets.EditUser-Ad2pc0MX.js.map Maven / Gradle / Ivy
Go to download
The user inferface to administrate the Keycloak server.
{"version":3,"file":"EditUser-Ad2pc0MX.js","sources":["../../../../../node_modules/.pnpm/[email protected] /node_modules/lodash-es/_baseIntersection.js","../../../../../node_modules/.pnpm/[email protected] /node_modules/lodash-es/_castArrayLikeObject.js","../../../../../node_modules/.pnpm/[email protected] /node_modules/lodash-es/intersectionBy.js","../../../../../node_modules/.pnpm/[email protected] /node_modules/lodash-es/isEmpty.js","../../src/user/UserAttributes.tsx","../../src/user/UserConsents.tsx","../../src/user/user-credentials/CredentialDataDialog.tsx","../../src/user/user-credentials/CredentialRow.tsx","../../src/user/user-credentials/InlineLabelEdit.tsx","../../src/user/user-credentials/LifespanField.tsx","../../src/user/user-credentials/ResetCredentialDialog.tsx","../../src/user/user-credentials/ResetPasswordDialog.tsx","../../src/user/UserCredentials.tsx","../../src/user/UserGroups.tsx","../../src/user/UserIdPModal.tsx","../../src/user/UserIdentityProviderLinks.tsx","../../src/user/UserRoleMapping.tsx","../../src/user/UserSessions.tsx","../../src/user/EditUser.tsx"],"sourcesContent":["import SetCache from './_SetCache.js';\nimport arrayIncludes from './_arrayIncludes.js';\nimport arrayIncludesWith from './_arrayIncludesWith.js';\nimport arrayMap from './_arrayMap.js';\nimport baseUnary from './_baseUnary.js';\nimport cacheHas from './_cacheHas.js';\n\n/* Built-in method references for those with the same name as other `lodash` methods. */\nvar nativeMin = Math.min;\n\n/**\n * The base implementation of methods like `_.intersection`, without support\n * for iteratee shorthands, that accepts an array of arrays to inspect.\n *\n * @private\n * @param {Array} arrays The arrays to inspect.\n * @param {Function} [iteratee] The iteratee invoked per element.\n * @param {Function} [comparator] The comparator invoked per element.\n * @returns {Array} Returns the new array of shared values.\n */\nfunction baseIntersection(arrays, iteratee, comparator) {\n var includes = comparator ? arrayIncludesWith : arrayIncludes,\n length = arrays[0].length,\n othLength = arrays.length,\n othIndex = othLength,\n caches = Array(othLength),\n maxLength = Infinity,\n result = [];\n\n while (othIndex--) {\n var array = arrays[othIndex];\n if (othIndex && iteratee) {\n array = arrayMap(array, baseUnary(iteratee));\n }\n maxLength = nativeMin(array.length, maxLength);\n caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))\n ? new SetCache(othIndex && array)\n : undefined;\n }\n array = arrays[0];\n\n var index = -1,\n seen = caches[0];\n\n outer:\n while (++index < length && result.length < maxLength) {\n var value = array[index],\n computed = iteratee ? iteratee(value) : value;\n\n value = (comparator || value !== 0) ? value : 0;\n if (!(seen\n ? cacheHas(seen, computed)\n : includes(result, computed, comparator)\n )) {\n othIndex = othLength;\n while (--othIndex) {\n var cache = caches[othIndex];\n if (!(cache\n ? cacheHas(cache, computed)\n : includes(arrays[othIndex], computed, comparator))\n ) {\n continue outer;\n }\n }\n if (seen) {\n seen.push(computed);\n }\n result.push(value);\n }\n }\n return result;\n}\n\nexport default baseIntersection;\n","import isArrayLikeObject from './isArrayLikeObject.js';\n\n/**\n * Casts `value` to an empty array if it's not an array like object.\n *\n * @private\n * @param {*} value The value to inspect.\n * @returns {Array|Object} Returns the cast array-like object.\n */\nfunction castArrayLikeObject(value) {\n return isArrayLikeObject(value) ? value : [];\n}\n\nexport default castArrayLikeObject;\n","import arrayMap from './_arrayMap.js';\nimport baseIntersection from './_baseIntersection.js';\nimport baseIteratee from './_baseIteratee.js';\nimport baseRest from './_baseRest.js';\nimport castArrayLikeObject from './_castArrayLikeObject.js';\nimport last from './last.js';\n\n/**\n * This method is like `_.intersection` except that it accepts `iteratee`\n * which is invoked for each element of each `arrays` to generate the criterion\n * by which they're compared. The order and references of result values are\n * determined by the first array. The iteratee is invoked with one argument:\n * (value).\n *\n * @static\n * @memberOf _\n * @since 4.0.0\n * @category Array\n * @param {...Array} [arrays] The arrays to inspect.\n * @param {Function} [iteratee=_.identity] The iteratee invoked per element.\n * @returns {Array} Returns the new array of intersecting values.\n * @example\n *\n * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);\n * // => [2.1]\n *\n * // The `_.property` iteratee shorthand.\n * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');\n * // => [{ 'x': 1 }]\n */\nvar intersectionBy = baseRest(function(arrays) {\n var iteratee = last(arrays),\n mapped = arrayMap(arrays, castArrayLikeObject);\n\n if (iteratee === last(mapped)) {\n iteratee = undefined;\n } else {\n mapped.pop();\n }\n return (mapped.length && mapped[0] === arrays[0])\n ? baseIntersection(mapped, baseIteratee(iteratee, 2))\n : [];\n});\n\nexport default intersectionBy;\n","import baseKeys from './_baseKeys.js';\nimport getTag from './_getTag.js';\nimport isArguments from './isArguments.js';\nimport isArray from './isArray.js';\nimport isArrayLike from './isArrayLike.js';\nimport isBuffer from './isBuffer.js';\nimport isPrototype from './_isPrototype.js';\nimport isTypedArray from './isTypedArray.js';\n\n/** `Object#toString` result references. */\nvar mapTag = '[object Map]',\n setTag = '[object Set]';\n\n/** Used for built-in method references. */\nvar objectProto = Object.prototype;\n\n/** Used to check objects for own properties. */\nvar hasOwnProperty = objectProto.hasOwnProperty;\n\n/**\n * Checks if `value` is an empty object, collection, map, or set.\n *\n * Objects are considered empty if they have no own enumerable string keyed\n * properties.\n *\n * Array-like values such as `arguments` objects, arrays, buffers, strings, or\n * jQuery-like collections are considered empty if they have a `length` of `0`.\n * Similarly, maps and sets are considered empty if they have a `size` of `0`.\n *\n * @static\n * @memberOf _\n * @since 0.1.0\n * @category Lang\n * @param {*} value The value to check.\n * @returns {boolean} Returns `true` if `value` is empty, else `false`.\n * @example\n *\n * _.isEmpty(null);\n * // => true\n *\n * _.isEmpty(true);\n * // => true\n *\n * _.isEmpty(1);\n * // => true\n *\n * _.isEmpty([1, 2, 3]);\n * // => false\n *\n * _.isEmpty({ 'a': 1 });\n * // => false\n */\nfunction isEmpty(value) {\n if (value == null) {\n return true;\n }\n if (isArrayLike(value) &&\n (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' ||\n isBuffer(value) || isTypedArray(value) || isArguments(value))) {\n return !value.length;\n }\n var tag = getTag(value);\n if (tag == mapTag || tag == setTag) {\n return !value.size;\n }\n if (isPrototype(value)) {\n return !baseKeys(value).length;\n }\n for (var key in value) {\n if (hasOwnProperty.call(value, key)) {\n return false;\n }\n }\n return true;\n}\n\nexport default isEmpty;\n","import type UserRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/userRepresentation\";\nimport { PageSection, PageSectionVariants } from \"@patternfly/react-core\";\nimport { UseFormReturn, useFormContext } from \"react-hook-form\";\n\nimport {\n AttributeForm,\n AttributesForm,\n} from \"../components/key-value-form/AttributeForm\";\nimport { UserFormFields, toUserFormFields } from \"./form-state\";\nimport {\n UnmanagedAttributePolicy,\n UserProfileConfig,\n} from \"@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata\";\n\ntype UserAttributesProps = {\n user: UserRepresentation;\n save: (user: UserFormFields) => void;\n upConfig?: UserProfileConfig;\n};\n\nexport const UserAttributes = ({\n user,\n save,\n upConfig,\n}: UserAttributesProps) => {\n const form = useFormContext();\n\n return (\n \n }\n save={save}\n fineGrainedAccess={user.access?.manage}\n reset={() =>\n form.reset({\n ...form.getValues(),\n attributes: toUserFormFields(user).attributes,\n })\n }\n name=\"unmanagedAttributes\"\n isDisabled={\n UnmanagedAttributePolicy.AdminView ==\n upConfig?.unmanagedAttributePolicy\n }\n />\n \n );\n};\n","import type UserConsentRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/userConsentRepresentation\";\nimport {\n AlertVariant,\n ButtonVariant,\n Chip,\n ChipGroup,\n} from \"@patternfly/react-core\";\nimport { CubesIcon } from \"@patternfly/react-icons\";\nimport { cellWidth } from \"@patternfly/react-table\";\nimport { sortBy } from \"lodash-es\";\nimport { useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { useAdminClient } from \"../admin-client\";\nimport { useAlerts } from \"../components/alert/Alerts\";\nimport { useConfirmDialog } from \"../components/confirm-dialog/ConfirmDialog\";\nimport { ListEmptyState } from \"../components/list-empty-state/ListEmptyState\";\nimport {\n Action,\n KeycloakDataTable,\n} from \"../components/table-toolbar/KeycloakDataTable\";\nimport { emptyFormatter } from \"../util\";\nimport useFormatDate from \"../utils/useFormatDate\";\nimport { useParams } from \"../utils/useParams\";\n\nexport const UserConsents = () => {\n const { adminClient } = useAdminClient();\n\n const [selectedClient, setSelectedClient] =\n useState();\n const { t } = useTranslation();\n const { addAlert, addError } = useAlerts();\n const formatDate = useFormatDate();\n const [key, setKey] = useState(0);\n\n const { id } = useParams<{ id: string }>();\n const alphabetize = (consentsList: UserConsentRepresentation[]) => {\n return sortBy(consentsList, (client) => client.clientId?.toUpperCase());\n };\n\n const refresh = () => setKey(new Date().getTime());\n\n const loader = async () => {\n const getConsents = await adminClient.users.listConsents({ id });\n\n return alphabetize(getConsents);\n };\n\n const clientScopesRenderer = ({\n grantedClientScopes,\n }: UserConsentRepresentation) => {\n return (\n \n {grantedClientScopes!.map((currentChip) => (\n \n {currentChip}\n \n ))}\n \n );\n };\n\n const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({\n titleKey: \"revokeClientScopesTitle\",\n messageKey: t(\"revokeClientScopes\", {\n clientId: selectedClient?.clientId,\n }),\n continueButtonLabel: \"revoke\",\n continueButtonVariant: ButtonVariant.danger,\n onConfirm: async () => {\n try {\n await adminClient.users.revokeConsent({\n id,\n clientId: selectedClient!.clientId!,\n });\n\n refresh();\n\n addAlert(t(\"deleteGrantsSuccess\"), AlertVariant.success);\n } catch (error) {\n addError(\"deleteGrantsError\", error);\n }\n },\n });\n\n return (\n <>\n \n \n createDate ? formatDate(new Date(createDate)) : \"—\",\n },\n {\n name: \"lastUpdatedDate\",\n displayKey: \"lastUpdated\",\n transforms: [cellWidth(10)],\n cellRenderer: ({ lastUpdatedDate }) =>\n lastUpdatedDate ? formatDate(new Date(lastUpdatedDate)) : \"—\",\n },\n ]}\n actions={[\n {\n title: t(\"revoke\"),\n onRowClick: (client) => {\n setSelectedClient(client);\n toggleDeleteDialog();\n },\n } as Action,\n ]}\n emptyState={\n \n }\n />\n >\n );\n};\n","import { useTranslation } from \"react-i18next\";\nimport { Modal, ModalVariant } from \"@patternfly/react-core\";\nimport {\n Table,\n TableVariant,\n Tbody,\n Td,\n Th,\n Thead,\n Tr,\n} from \"@patternfly/react-table\";\n\ntype CredentialDataDialogProps = {\n credentialData: [string, string][];\n onClose: () => void;\n};\n\nexport const CredentialDataDialog = ({\n credentialData,\n onClose,\n}: CredentialDataDialogProps) => {\n const { t } = useTranslation();\n return (\n \n \n \n \n {t(\"showPasswordDataName\")} \n {t(\"showPasswordDataValue\")} \n \n \n \n {credentialData.map((cred, index) => {\n return (\n \n {cred[0]} \n {cred[1]} \n \n );\n })}\n \n
\n \n );\n};\n","import { ReactNode, useMemo } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { Td } from \"@patternfly/react-table\";\nimport {\n Button,\n Dropdown,\n DropdownItem,\n DropdownList,\n MenuToggle,\n} from \"@patternfly/react-core\";\nimport type CredentialRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation\";\nimport useToggle from \"../../utils/useToggle\";\nimport useLocaleSort from \"../../utils/useLocaleSort\";\nimport { CredentialDataDialog } from \"./CredentialDataDialog\";\nimport useFormatDate from \"../../utils/useFormatDate\";\nimport { EllipsisVIcon } from \"@patternfly/react-icons\";\n\ntype CredentialRowProps = {\n credential: CredentialRepresentation;\n resetPassword: () => void;\n toggleDelete: () => void;\n children: ReactNode;\n};\n\nexport const CredentialRow = ({\n credential,\n resetPassword,\n toggleDelete,\n children,\n}: CredentialRowProps) => {\n const formatDate = useFormatDate();\n const { t } = useTranslation();\n const [showData, toggleShow] = useToggle();\n const [kebabOpen, toggleKebab] = useToggle();\n const localeSort = useLocaleSort();\n\n const rows = useMemo(() => {\n if (!credential.credentialData) {\n return [];\n }\n\n const credentialData: Record = JSON.parse(\n credential.credentialData,\n );\n return localeSort(Object.entries(credentialData), ([key]) => key).map<\n [string, string]\n >(([key, value]) => {\n if (typeof value === \"string\") {\n return [key, value];\n }\n\n return [key, JSON.stringify(value)];\n });\n }, [credential.credentialData]);\n\n return (\n <>\n {showData && Object.keys(credential).length !== 0 && (\n {\n toggleShow();\n }}\n />\n )}\n\n {children} \n {formatDate(new Date(credential.createdDate!))} \n \n \n {t(\"showDataBtn\")}\n \n \n {credential.type === \"password\" ? (\n \n \n {t(\"resetPasswordBtn\")}\n \n \n ) : (\n \n )}\n \n (\n \n \n \n )}\n isOpen={kebabOpen}\n >\n \n {\n toggleDelete();\n toggleKebab();\n }}\n >\n {t(\"deleteBtn\")}\n \n \n \n \n >\n );\n};\n","import type CredentialRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation\";\nimport {\n AlertVariant,\n Button,\n Form,\n FormGroup,\n TextInput,\n} from \"@patternfly/react-core\";\nimport { CheckIcon, PencilAltIcon, TimesIcon } from \"@patternfly/react-icons\";\nimport { useForm } from \"react-hook-form\";\nimport { useTranslation } from \"react-i18next\";\nimport { useAdminClient } from \"../../admin-client\";\nimport { useAlerts } from \"../../components/alert/Alerts\";\n\ntype UserLabelForm = {\n userLabel: string;\n};\n\ntype InlineLabelEditProps = {\n userId: string;\n credential: CredentialRepresentation;\n isEditable: boolean;\n toggle: () => void;\n};\n\nexport const InlineLabelEdit = ({\n userId,\n credential,\n isEditable,\n toggle,\n}: InlineLabelEditProps) => {\n const { adminClient } = useAdminClient();\n\n const { t } = useTranslation();\n const { register, handleSubmit } = useForm();\n\n const { addAlert, addError } = useAlerts();\n\n const saveUserLabel = async (userLabel: UserLabelForm) => {\n try {\n await adminClient.users.updateCredentialLabel(\n {\n id: userId,\n credentialId: credential.id!,\n },\n userLabel.userLabel || \"\",\n );\n addAlert(t(\"updateCredentialUserLabelSuccess\"), AlertVariant.success);\n toggle();\n } catch (error) {\n addError(\"updateCredentialUserLabelError\", error);\n }\n };\n\n return (\n \n );\n};\n","import { useTranslation } from \"react-i18next\";\nimport { TimeSelectorControl } from \"../../components/time-selector/TimeSelectorControl\";\nimport { credResetFormDefaultValues } from \"./ResetCredentialDialog\";\n\nexport const LifespanField = () => {\n const { t } = useTranslation();\n\n return (\n \n );\n};\n","import type { RequiredActionAlias } from \"@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation\";\nimport { AlertVariant, Form, ModalVariant } from \"@patternfly/react-core\";\nimport { isEmpty } from \"lodash-es\";\nimport { FormProvider, useForm, useWatch } from \"react-hook-form\";\nimport { useTranslation } from \"react-i18next\";\nimport { useAdminClient } from \"../../admin-client\";\nimport { useAlerts } from \"../../components/alert/Alerts\";\nimport { ConfirmDialogModal } from \"../../components/confirm-dialog/ConfirmDialog\";\nimport { LifespanField } from \"./LifespanField\";\nimport { RequiredActionMultiSelect } from \"./RequiredActionMultiSelect\";\n\ntype ResetCredentialDialogProps = {\n userId: string;\n onClose: () => void;\n};\n\ntype CredentialResetForm = {\n actions: RequiredActionAlias[];\n lifespan: number;\n};\n\nexport const credResetFormDefaultValues: CredentialResetForm = {\n actions: [],\n lifespan: 43200, // 12 hours\n};\n\nexport const ResetCredentialDialog = ({\n userId,\n onClose,\n}: ResetCredentialDialogProps) => {\n const { adminClient } = useAdminClient();\n\n const { t } = useTranslation();\n const form = useForm({\n defaultValues: credResetFormDefaultValues,\n });\n const { handleSubmit, control } = form;\n\n const resetActionWatcher = useWatch({\n control,\n name: \"actions\",\n });\n const resetIsNotDisabled = !isEmpty(resetActionWatcher);\n\n const { addAlert, addError } = useAlerts();\n\n const sendCredentialsResetEmail = async ({\n actions,\n lifespan,\n }: CredentialResetForm) => {\n if (isEmpty(actions)) {\n return;\n }\n\n try {\n await adminClient.users.executeActionsEmail({\n id: userId,\n actions,\n lifespan,\n });\n addAlert(t(\"credentialResetEmailSuccess\"), AlertVariant.success);\n onClose();\n } catch (error) {\n addError(\"credentialResetEmailError\", error);\n }\n };\n\n return (\n {\n handleSubmit(sendCredentialsResetEmail)();\n }}\n confirmButtonDisabled={!resetIsNotDisabled}\n >\n \n \n );\n};\n","import { RequiredActionAlias } from \"@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation\";\nimport type UserRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/userRepresentation\";\nimport {\n AlertVariant,\n ButtonVariant,\n Form,\n FormGroup,\n} from \"@patternfly/react-core\";\nimport { FormProvider, useForm } from \"react-hook-form\";\nimport { useTranslation } from \"react-i18next\";\nimport { FormErrorText, PasswordInput } from \"@keycloak/keycloak-ui-shared\";\nimport { useAdminClient } from \"../../admin-client\";\nimport { DefaultSwitchControl } from \"../../components/SwitchControl\";\nimport { useAlerts } from \"../../components/alert/Alerts\";\nimport {\n ConfirmDialogModal,\n useConfirmDialog,\n} from \"../../components/confirm-dialog/ConfirmDialog\";\nimport useToggle from \"../../utils/useToggle\";\n\ntype ResetPasswordDialogProps = {\n user: UserRepresentation;\n isResetPassword: boolean;\n onAddRequiredActions?: (requiredActions: string[]) => void;\n refresh: () => void;\n onClose: () => void;\n};\n\nexport type CredentialsForm = {\n password: string;\n passwordConfirmation: string;\n temporaryPassword: boolean;\n};\n\nconst credFormDefaultValues: CredentialsForm = {\n password: \"\",\n passwordConfirmation: \"\",\n temporaryPassword: true,\n};\n\nexport const ResetPasswordDialog = ({\n user,\n isResetPassword,\n onAddRequiredActions,\n refresh,\n onClose,\n}: ResetPasswordDialogProps) => {\n const { adminClient } = useAdminClient();\n\n const { t } = useTranslation();\n const form = useForm({\n defaultValues: credFormDefaultValues,\n mode: \"onChange\",\n });\n const {\n register,\n formState: { isValid, errors },\n watch,\n handleSubmit,\n clearErrors,\n setError,\n } = form;\n\n const [confirm, toggle] = useToggle(true);\n const password = watch(\"password\", \"\");\n const passwordConfirmation = watch(\"passwordConfirmation\", \"\");\n\n const { addAlert, addError } = useAlerts();\n\n const [toggleConfirmSaveModal, ConfirmSaveModal] = useConfirmDialog({\n titleKey: isResetPassword ? \"resetPasswordConfirm\" : \"setPasswordConfirm\",\n messageKey: isResetPassword\n ? t(\"resetPasswordConfirmText\", { username: user.username })\n : t(\"setPasswordConfirmText\", { username: user.username }),\n continueButtonLabel: isResetPassword ? \"resetPassword\" : \"savePassword\",\n continueButtonVariant: ButtonVariant.danger,\n onConfirm: () => handleSubmit(saveUserPassword)(),\n });\n\n const saveUserPassword = async ({\n password,\n temporaryPassword,\n }: CredentialsForm) => {\n try {\n await adminClient.users.resetPassword({\n id: user.id!,\n credential: {\n temporary: temporaryPassword,\n type: \"password\",\n value: password,\n },\n });\n if (temporaryPassword) {\n onAddRequiredActions?.([RequiredActionAlias.UPDATE_PASSWORD]);\n }\n const credentials = await adminClient.users.getCredentials({\n id: user.id!,\n });\n const credentialLabel = credentials.find((c) => c.type === \"password\");\n if (credentialLabel) {\n await adminClient.users.updateCredentialLabel(\n {\n id: user.id!,\n credentialId: credentialLabel.id!,\n },\n t(\"defaultPasswordLabel\"),\n );\n }\n addAlert(\n isResetPassword\n ? t(\"resetCredentialsSuccess\")\n : t(\"savePasswordSuccess\"),\n AlertVariant.success,\n );\n refresh();\n } catch (error) {\n addError(\n isResetPassword ? \"resetPasswordError\" : \"savePasswordError\",\n error,\n );\n }\n\n onClose();\n };\n\n const { onChange, ...rest } = register(\"password\", { required: true });\n return (\n <>\n \n \n \n \n >\n );\n};\n","import type CredentialRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation\";\nimport type UserRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/userRepresentation\";\nimport {\n AlertVariant,\n Button,\n ButtonVariant,\n Divider,\n PageSection,\n PageSectionVariants,\n} from \"@patternfly/react-core\";\nimport styles from \"@patternfly/react-styles/css/components/Table/table\";\nimport { Table, Tbody, Td, Th, Thead, Tr } from \"@patternfly/react-table\";\nimport {\n Fragment,\n DragEvent as ReactDragEvent,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { HelpItem } from \"@keycloak/keycloak-ui-shared\";\nimport { useAdminClient } from \"../admin-client\";\nimport { useAlerts } from \"../components/alert/Alerts\";\nimport { useConfirmDialog } from \"../components/confirm-dialog/ConfirmDialog\";\nimport { KeycloakSpinner } from \"../components/keycloak-spinner/KeycloakSpinner\";\nimport { ListEmptyState } from \"../components/list-empty-state/ListEmptyState\";\nimport { toUpperCase } from \"../util\";\nimport { useFetch } from \"../utils/useFetch\";\nimport { FederatedUserLink } from \"./FederatedUserLink\";\nimport { CredentialRow } from \"./user-credentials/CredentialRow\";\nimport { InlineLabelEdit } from \"./user-credentials/InlineLabelEdit\";\nimport { ResetCredentialDialog } from \"./user-credentials/ResetCredentialDialog\";\nimport { ResetPasswordDialog } from \"./user-credentials/ResetPasswordDialog\";\n\nimport \"./user-credentials.css\";\n\ntype UserCredentialsProps = {\n user: UserRepresentation;\n setUser: (user: UserRepresentation) => void;\n};\n\ntype ExpandableCredentialRepresentation = {\n key: string;\n value: CredentialRepresentation[];\n isExpanded: boolean;\n};\n\ntype UserLabelEdit = {\n status: boolean;\n rowKey: string;\n};\n\ntype UserCredentialsRowProps = {\n credential: CredentialRepresentation;\n userId: string;\n toggleDelete: (credential: CredentialRepresentation) => void;\n resetPassword: () => void;\n isUserLabelEdit?: UserLabelEdit;\n setIsUserLabelEdit: (isUserLabelEdit: UserLabelEdit) => void;\n refresh: () => void;\n};\n\nconst UserCredentialsRow = ({\n credential,\n userId,\n toggleDelete,\n resetPassword,\n isUserLabelEdit,\n setIsUserLabelEdit,\n refresh,\n}: UserCredentialsRowProps) => (\n toggleDelete(credential)}\n resetPassword={resetPassword}\n >\n {\n setIsUserLabelEdit({\n status: !isUserLabelEdit?.status,\n rowKey: credential.id!,\n });\n if (isUserLabelEdit?.status) {\n refresh();\n }\n }}\n />\n \n);\n\nexport const UserCredentials = ({ user, setUser }: UserCredentialsProps) => {\n const { adminClient } = useAdminClient();\n\n const { t } = useTranslation();\n const { addAlert, addError } = useAlerts();\n const [key, setKey] = useState(0);\n const refresh = () => setKey(key + 1);\n const [isOpen, setIsOpen] = useState(false);\n const [openCredentialReset, setOpenCredentialReset] = useState(false);\n const [userCredentials, setUserCredentials] = useState<\n CredentialRepresentation[]\n >([]);\n const [groupedUserCredentials, setGroupedUserCredentials] = useState<\n ExpandableCredentialRepresentation[]\n >([]);\n const [selectedCredential, setSelectedCredential] =\n useState({});\n const [isResetPassword, setIsResetPassword] = useState(false);\n const [isUserLabelEdit, setIsUserLabelEdit] = useState();\n\n const bodyRef = useRef(null);\n const [state, setState] = useState({\n draggedItemId: \"\",\n draggingToItemIndex: -1,\n dragging: false,\n tempItemOrder: [\"\"],\n });\n\n useFetch(\n () => adminClient.users.getCredentials({ id: user.id! }),\n (credentials) => {\n setUserCredentials(credentials);\n\n const groupedCredentials = credentials.reduce((r, a) => {\n r[a.type!] = r[a.type!] || [];\n r[a.type!].push(a);\n return r;\n }, Object.create(null));\n\n const groupedCredentialsArray = Object.keys(groupedCredentials).map(\n (key) => ({ key, value: groupedCredentials[key] }),\n );\n\n setGroupedUserCredentials(\n groupedCredentialsArray.map((groupedCredential) => ({\n ...groupedCredential,\n isExpanded: false,\n })),\n );\n },\n [key],\n );\n\n const passwordTypeFinder = userCredentials.find(\n (credential) => credential.type === \"password\",\n );\n\n const toggleModal = () => setIsOpen(!isOpen);\n\n const toggleCredentialsResetModal = () => {\n setOpenCredentialReset(!openCredentialReset);\n };\n\n const resetPassword = () => {\n setIsResetPassword(true);\n toggleModal();\n };\n\n const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({\n titleKey: t(\"deleteCredentialsConfirmTitle\"),\n messageKey: t(\"deleteCredentialsConfirm\"),\n continueButtonLabel: t(\"delete\"),\n continueButtonVariant: ButtonVariant.danger,\n onConfirm: async () => {\n try {\n await adminClient.users.deleteCredential({\n id: user.id!,\n credentialId: selectedCredential.id!,\n });\n addAlert(t(\"deleteCredentialsSuccess\"), AlertVariant.success);\n setKey((key) => key + 1);\n } catch (error) {\n addError(\"deleteCredentialsError\", error);\n }\n },\n });\n\n const itemOrder = useMemo(\n () =>\n groupedUserCredentials.flatMap((groupedCredential) => [\n groupedCredential.value.map(({ id }) => id).toString(),\n ...(groupedCredential.isExpanded\n ? groupedCredential.value.map((c) => c.id!)\n : []),\n ]),\n [groupedUserCredentials],\n );\n\n const onDragStart = (evt: ReactDragEvent) => {\n evt.dataTransfer.effectAllowed = \"move\";\n evt.dataTransfer.setData(\"text/plain\", evt.currentTarget.id);\n const draggedItemId = evt.currentTarget.id;\n evt.currentTarget.classList.add(styles.modifiers.ghostRow);\n evt.currentTarget.setAttribute(\"aria-pressed\", \"true\");\n setState({ ...state, draggedItemId, dragging: true });\n };\n\n const moveItem = (items: string[], targetItem: string, toIndex: number) => {\n const fromIndex = items.indexOf(targetItem);\n if (fromIndex === toIndex) {\n return items;\n }\n const result = [...items];\n result.splice(toIndex, 0, result.splice(fromIndex, 1)[0]);\n return result;\n };\n\n const move = (itemOrder: string[]) => {\n if (!bodyRef.current) return;\n const ulNode = bodyRef.current;\n const nodes = Array.from(ulNode.children);\n if (nodes.every(({ id }, i) => id === itemOrder[i])) {\n return;\n }\n ulNode.replaceChildren();\n itemOrder.forEach((itemId) => {\n ulNode.appendChild(nodes.find(({ id }) => id === itemId)!);\n });\n };\n\n const onDragCancel = () => {\n if (!bodyRef.current) return;\n Array.from(bodyRef.current.children).forEach((el) => {\n el.classList.remove(styles.modifiers.ghostRow);\n el.setAttribute(\"aria-pressed\", \"false\");\n });\n setState({\n ...state,\n draggedItemId: \"\",\n draggingToItemIndex: -1,\n dragging: false,\n });\n };\n\n const onDragLeave = (evt: ReactDragEvent) => {\n if (!isValidDrop(evt)) {\n move(itemOrder);\n setState({ ...state, draggingToItemIndex: -1 });\n }\n };\n\n const isValidDrop = (evt: ReactDragEvent) => {\n if (!bodyRef.current) return false;\n const ulRect = bodyRef.current.getBoundingClientRect();\n return (\n evt.clientX > ulRect.x &&\n evt.clientX < ulRect.x + ulRect.width &&\n evt.clientY > ulRect.y &&\n evt.clientY < ulRect.y + ulRect.height\n );\n };\n\n const onDrop = (evt: ReactDragEvent) => {\n if (isValidDrop(evt)) {\n onDragFinish(state.draggedItemId, state.tempItemOrder);\n } else {\n onDragCancel();\n }\n };\n\n const onDragOver = (evt: ReactDragEvent) => {\n evt.preventDefault();\n const td = evt.target as HTMLTableCellElement;\n const curListItem = td.closest(\"tr\");\n if (\n !curListItem ||\n (bodyRef.current && !bodyRef.current.contains(curListItem)) ||\n curListItem.id === state.draggedItemId\n ) {\n return;\n } else {\n const dragId = curListItem.id;\n const draggingToItemIndex = Array.from(\n bodyRef.current?.children || [],\n ).findIndex((item) => item.id === dragId);\n if (draggingToItemIndex === state.draggingToItemIndex) {\n return;\n }\n const tempItemOrder = moveItem(\n itemOrder,\n state.draggedItemId,\n draggingToItemIndex,\n );\n move(tempItemOrder);\n setState({\n ...state,\n draggingToItemIndex,\n tempItemOrder,\n });\n }\n };\n\n const onAddRequiredActions = (requiredActions: string[]) => {\n setUser({\n ...user,\n requiredActions: [...(user.requiredActions ?? []), ...requiredActions],\n });\n };\n\n const onDragEnd = ({ target }: ReactDragEvent) => {\n if (!(target instanceof HTMLTableRowElement)) {\n return;\n }\n target.classList.remove(styles.modifiers.ghostRow);\n target.setAttribute(\"aria-pressed\", \"false\");\n setState({\n ...state,\n draggedItemId: \"\",\n draggingToItemIndex: -1,\n dragging: false,\n });\n };\n\n const onDragFinish = async (dragged: string, newOrder: string[]) => {\n const oldIndex = itemOrder.findIndex((key) => key === dragged);\n const newIndex = newOrder.findIndex((key) => key === dragged);\n const times = newIndex - oldIndex;\n\n const ids = dragged.split(\",\");\n\n try {\n for (const id of ids)\n for (let index = 0; index < Math.abs(times); index++) {\n if (times > 0) {\n await adminClient.users.moveCredentialPositionDown({\n id: user.id!,\n credentialId: id,\n newPreviousCredentialId: itemOrder[newIndex],\n });\n } else {\n await adminClient.users.moveCredentialPositionUp({\n id: user.id!,\n credentialId: id,\n });\n }\n }\n\n refresh();\n addAlert(t(\"updatedCredentialMoveSuccess\"), AlertVariant.success);\n } catch (error) {\n addError(\"updatedCredentialMoveError\", error);\n }\n };\n\n const onToggleDelete = (credential: CredentialRepresentation) => {\n setSelectedCredential(credential);\n toggleDeleteDialog();\n };\n\n const useFederatedCredentials = user.federationLink || user.origin;\n const [credentialTypes, setCredentialTypes] = useState([]);\n\n useFetch(\n () => adminClient.users.getUserStorageCredentialTypes({ id: user.id! }),\n setCredentialTypes,\n [],\n );\n\n if (!credentialTypes) {\n return ;\n }\n\n const hasCredentialTypes = credentialTypes.length > 0;\n const noCredentials = groupedUserCredentials.length === 0;\n const noFederatedCredentials =\n !user.credentials || user.credentials.length === 0;\n const emptyState =\n noCredentials && noFederatedCredentials && !hasCredentialTypes;\n\n return (\n <>\n {isOpen && (\n setIsOpen(false)}\n />\n )}\n {openCredentialReset && (\n setOpenCredentialReset(false)}\n />\n )}\n \n {user.email && !emptyState && (\n setOpenCredentialReset(true)}\n >\n {t(\"credentialResetBtn\")}\n \n )}\n {userCredentials.length !== 0 && passwordTypeFinder === undefined && (\n <>\n {\n setIsOpen(true);\n }}\n >\n {t(\"setPassword\")}\n \n \n >\n )}\n {groupedUserCredentials.length !== 0 && (\n \n \n \n \n \n \n \n \n {t(\"type\")} \n {t(\"userLabel\")} \n {t(\"createdAt\")} \n {t(\"data\")} \n \n \n \n \n \n {groupedUserCredentials.map((groupedCredential, rowIndex) => (\n \n id).toString()}\n draggable={groupedUserCredentials.length > 1}\n onDrop={onDrop}\n onDragEnd={onDragEnd}\n onDragStart={onDragStart}\n >\n id,\n )}`,\n }}\n />\n {groupedCredential.value.length > 1 ? (\n {\n const rows = groupedUserCredentials.map(\n (credential, index) =>\n index === rowIndex\n ? {\n ...credential,\n isExpanded: !credential.isExpanded,\n }\n : credential,\n );\n setGroupedUserCredentials(rows);\n },\n }}\n />\n ) : (\n \n )}\n \n {toUpperCase(groupedCredential.key)}\n \n {groupedCredential.value.length <= 1 &&\n groupedCredential.value.map((credential) => (\n \n ))}\n \n {groupedCredential.isExpanded &&\n groupedCredential.value.map((credential) => (\n \n \n id,\n )}`,\n }}\n />\n \n {toUpperCase(credential.type!)}\n \n \n \n ))}\n \n ))}\n \n
\n \n )}\n {useFederatedCredentials && hasCredentialTypes && (\n \n \n \n \n {t(\"type\")} \n {t(\"providedBy\")} \n \n \n \n \n {credentialTypes.map((credential) => (\n \n \n {credential} \n \n \n \n \n {credential === \"password\" && (\n \n \n {t(\"setPassword\")}\n \n \n )}\n \n ))}\n \n
\n \n )}\n {emptyState && (\n \n )}\n >\n );\n};\n","import type GroupRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/groupRepresentation\";\nimport type UserRepresentation from \"@keycloak/keycloak-admin-client/lib/defs/userRepresentation\";\nimport { useHelp } from \"@keycloak/keycloak-ui-shared\";\nimport {\n AlertVariant,\n Button,\n ButtonVariant,\n Checkbox,\n Popover,\n} from \"@patternfly/react-core\";\nimport { QuestionCircleIcon } from \"@patternfly/react-icons\";\nimport { cellWidth } from \"@patternfly/react-table\";\nimport { intersectionBy, sortBy, uniqBy } from \"lodash-es\";\nimport { useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { useAdminClient } from \"../admin-client\";\nimport { useAlerts } from \"../components/alert/Alerts\";\nimport { useConfirmDialog } from \"../components/confirm-dialog/ConfirmDialog\";\nimport { GroupPath } from \"../components/group/GroupPath\";\nimport { GroupPickerDialog } from \"../components/group/GroupPickerDialog\";\nimport { ListEmptyState } from \"../components/list-empty-state/ListEmptyState\";\nimport { KeycloakDataTable } from \"../components/table-toolbar/KeycloakDataTable\";\nimport { useAccess } from \"../context/access/Access\";\n\ntype UserGroupsProps = {\n user: UserRepresentation;\n};\n\nexport const UserGroups = ({ user }: UserGroupsProps) => {\n const { adminClient } = useAdminClient();\n\n const { t } = useTranslation();\n const { addAlert, addError } = useAlerts();\n const [key, setKey] = useState(0);\n const refresh = () => setKey(key + 1);\n\n const [selectedGroups, setSelectedGroups] = useState(\n [],\n );\n\n const [isDirectMembership, setDirectMembership] = useState(true);\n const [directMembershipList, setDirectMembershipList] = useState<\n GroupRepresentation[]\n >([]);\n const [open, setOpen] = useState(false);\n\n const { enabled } = useHelp();\n\n const { hasAccess } = useAccess();\n const isManager = hasAccess(\"manage-users\");\n\n const alphabetize = (groupsList: GroupRepresentation[]) => {\n return sortBy(groupsList, (group) => group.path?.toUpperCase());\n };\n\n const loader = async (first?: number, max?: number, search?: string) => {\n const params: { [name: string]: string | number } = {\n first: first!,\n max: max!,\n };\n\n const searchParam = search || \"\";\n if (searchParam) {\n params.search = searchParam;\n }\n\n const joinedUserGroups = await adminClient.users.listGroups({\n ...params,\n id: user.id!,\n });\n\n setDirectMembershipList([...joinedUserGroups]);\n\n const indirect: GroupRepresentation[] = [];\n if (!isDirectMembership)\n joinedUserGroups.forEach((g) => {\n const paths = (\n g.path?.substring(1).match(/((~\\/)|[^/])+/g) || []\n ).slice(0, -1);\n\n indirect.push(\n ...paths.map((p) => ({\n name: p,\n path: g.path?.substring(0, g.path.indexOf(p) + p.length),\n })),\n );\n });\n\n return alphabetize(uniqBy([...joinedUserGroups, ...indirect], \"path\"));\n };\n\n const toggleModal = () => {\n setOpen(!open);\n };\n\n const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({\n titleKey: t(\"leaveGroup\", {\n count: selectedGroups.length,\n name: selectedGroups[0]?.name,\n }),\n messageKey: t(\"leaveGroupConfirmDialog\", {\n count: selectedGroups.length,\n groupname: selectedGroups[0]?.name,\n username: user.username,\n }),\n continueButtonLabel: \"leave\",\n continueButtonVariant: ButtonVariant.danger,\n onConfirm: async () => {\n try {\n await Promise.all(\n selectedGroups.map((group) =>\n adminClient.users.delFromGroup({\n id: user.id!,\n groupId: group.id!,\n }),\n ),\n );\n\n setSelectedGroups([]);\n addAlert(t(\"removedGroupMembership\"), AlertVariant.success);\n } catch (error) {\n addError(\"removedGroupMembershipError\", error);\n }\n refresh();\n },\n });\n\n const leave = (group: GroupRepresentation[]) => {\n setSelectedGroups(group);\n toggleDeleteDialog();\n };\n\n const addGroups = async (groups: GroupRepresentation[]): Promise => {\n try {\n await Promise.all(\n groups.map((group) =>\n adminClient.users.addToGroup({\n id: user.id!,\n groupId: group.id!,\n }),\n ),\n );\n\n addAlert(t(\"addedGroupMembership\"), AlertVariant.success);\n } catch (error) {\n addError(\"addedGroupMembershipError\", error);\n }\n refresh();\n };\n\n return (\n <>\n \n {open && (\n setOpen(false)}\n onConfirm={async (groups = []) => {\n await addGroups(groups);\n setOpen(false);\n }}\n />\n )}\n \n isDirectMembership\n ? setSelectedGroups(groups)\n : setSelectedGroups(\n intersectionBy(groups, directMembershipList, \"id\"),\n )\n }\n isRowDisabled={(group) =>\n !isDirectMembership &&\n directMembershipList.every((item) => item.id !== group.id)\n }\n toolbarItem={\n <>\n \n {t(\"joinGroup\")}\n \n {\n setDirectMembership(!isDirectMembership);\n refresh();\n }}\n isChecked={isDirectMembership}\n className=\"direct-membership-check\"\n />\n leave(selectedGroups)}\n data-testid=\"leave-group-button\"\n variant=\"link\"\n isDisabled={selectedGroups.length === 0}\n >\n {t(\"leave\")}\n \n\n {enabled && (\n {t(\"whoWillAppearPopoverTextUsers\")}
}\n >\n