2022-02-09 14:39:10 +00:00
|
|
|
import React, { Fragment, useState } from "react";
|
2021-11-24 15:37:30 +00:00
|
|
|
import {
|
|
|
|
AlertVariant,
|
|
|
|
Button,
|
|
|
|
ButtonVariant,
|
2021-12-06 09:52:05 +00:00
|
|
|
Divider,
|
2021-11-24 15:37:30 +00:00
|
|
|
} from "@patternfly/react-core";
|
|
|
|
import {
|
|
|
|
TableComposable,
|
2022-02-09 14:39:10 +00:00
|
|
|
Tbody,
|
2021-11-24 15:37:30 +00:00
|
|
|
Td,
|
|
|
|
Th,
|
|
|
|
Thead,
|
|
|
|
Tr,
|
|
|
|
} from "@patternfly/react-table";
|
|
|
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { useAlerts } from "../components/alert/Alerts";
|
|
|
|
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
|
|
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
|
|
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
|
|
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
|
|
|
import type CredentialRepresentation from "@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation";
|
2022-02-09 14:39:10 +00:00
|
|
|
import { ResetPasswordDialog } from "./user-credentials/ResetPasswordDialog";
|
|
|
|
import { ResetCredentialDialog } from "./user-credentials/ResetCredentialDialog";
|
|
|
|
import { InlineLabelEdit } from "./user-credentials/InlineLabelEdit";
|
|
|
|
|
|
|
|
import "./user-credentials.css";
|
|
|
|
import { CredentialRow } from "./user-credentials/CredentialRow";
|
|
|
|
import { toUpperCase } from "../util";
|
2021-11-24 15:37:30 +00:00
|
|
|
|
|
|
|
type UserCredentialsProps = {
|
|
|
|
user: UserRepresentation;
|
|
|
|
};
|
|
|
|
|
2022-01-10 10:31:50 +00:00
|
|
|
type ExpandableCredentialRepresentation = {
|
|
|
|
key: string;
|
|
|
|
value: CredentialRepresentation[];
|
|
|
|
isExpanded: boolean;
|
|
|
|
};
|
|
|
|
|
2021-11-24 15:37:30 +00:00
|
|
|
export const UserCredentials = ({ user }: UserCredentialsProps) => {
|
|
|
|
const { t } = useTranslation("users");
|
|
|
|
const { addAlert, addError } = useAlerts();
|
|
|
|
const [key, setKey] = useState(0);
|
|
|
|
const refresh = () => setKey(key + 1);
|
2022-02-09 14:39:10 +00:00
|
|
|
const [isOpen, setIsOpen] = useState(false);
|
2021-12-21 06:22:44 +00:00
|
|
|
const [openCredentialReset, setOpenCredentialReset] = useState(false);
|
2021-11-24 15:37:30 +00:00
|
|
|
const adminClient = useAdminClient();
|
|
|
|
const [userCredentials, setUserCredentials] = useState<
|
|
|
|
CredentialRepresentation[]
|
|
|
|
>([]);
|
2022-01-10 10:31:50 +00:00
|
|
|
const [groupedUserCredentials, setGroupedUserCredentials] = useState<
|
|
|
|
ExpandableCredentialRepresentation[]
|
|
|
|
>([]);
|
2021-11-24 15:37:30 +00:00
|
|
|
const [selectedCredential, setSelectedCredential] =
|
|
|
|
useState<CredentialRepresentation>({});
|
|
|
|
const [isResetPassword, setIsResetPassword] = useState(false);
|
2021-12-06 09:52:05 +00:00
|
|
|
const [isUserLabelEdit, setIsUserLabelEdit] = useState<{
|
|
|
|
status: boolean;
|
|
|
|
rowKey: string;
|
|
|
|
}>();
|
2021-11-24 15:37:30 +00:00
|
|
|
|
|
|
|
useFetch(
|
|
|
|
() => adminClient.users.getCredentials({ id: user.id! }),
|
|
|
|
(credentials) => {
|
|
|
|
setUserCredentials(credentials);
|
2022-01-10 10:31:50 +00:00
|
|
|
|
|
|
|
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,
|
|
|
|
}))
|
|
|
|
);
|
2021-11-24 15:37:30 +00:00
|
|
|
},
|
|
|
|
[key]
|
|
|
|
);
|
|
|
|
|
2021-12-06 09:52:05 +00:00
|
|
|
const passwordTypeFinder = userCredentials.find(
|
|
|
|
(credential) => credential.type === "password"
|
|
|
|
);
|
|
|
|
|
2022-02-09 14:39:10 +00:00
|
|
|
const toggleModal = () => setIsOpen(!isOpen);
|
2021-11-24 15:37:30 +00:00
|
|
|
|
2021-12-21 06:22:44 +00:00
|
|
|
const toggleCredentialsResetModal = () => {
|
|
|
|
setOpenCredentialReset(!openCredentialReset);
|
|
|
|
};
|
|
|
|
|
2021-11-24 15:37:30 +00:00
|
|
|
const resetPassword = () => {
|
|
|
|
setIsResetPassword(true);
|
2022-02-09 14:39:10 +00:00
|
|
|
toggleModal();
|
2021-11-24 15:37:30 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
|
|
|
titleKey: t("deleteCredentialsConfirmTitle"),
|
|
|
|
messageKey: t("deleteCredentialsConfirm"),
|
|
|
|
continueButtonLabel: t("common: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) {
|
2021-12-06 09:52:05 +00:00
|
|
|
addError("users:deleteCredentialsError", error);
|
2021-11-24 15:37:30 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2022-02-09 14:39:10 +00:00
|
|
|
const Row = ({ credential }: { credential: CredentialRepresentation }) => (
|
|
|
|
<CredentialRow
|
|
|
|
key={credential.id}
|
|
|
|
credential={credential}
|
|
|
|
toggleDelete={() => {
|
|
|
|
setSelectedCredential(credential);
|
|
|
|
toggleDeleteDialog();
|
|
|
|
}}
|
|
|
|
resetPassword={resetPassword}
|
|
|
|
>
|
|
|
|
<InlineLabelEdit
|
|
|
|
credential={credential}
|
|
|
|
userId={user.id!}
|
|
|
|
isEditable={
|
|
|
|
(isUserLabelEdit?.status &&
|
|
|
|
isUserLabelEdit.rowKey === credential.id) ||
|
|
|
|
false
|
2021-11-24 15:37:30 +00:00
|
|
|
}
|
2022-02-09 14:39:10 +00:00
|
|
|
toggle={() => {
|
|
|
|
setIsUserLabelEdit({
|
|
|
|
status: !isUserLabelEdit?.status,
|
|
|
|
rowKey: credential.id!,
|
|
|
|
});
|
|
|
|
if (isUserLabelEdit?.status) {
|
|
|
|
refresh();
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</CredentialRow>
|
|
|
|
);
|
2021-11-24 15:37:30 +00:00
|
|
|
return (
|
|
|
|
<>
|
2022-02-09 14:39:10 +00:00
|
|
|
{isOpen && (
|
|
|
|
<ResetPasswordDialog
|
|
|
|
user={user}
|
|
|
|
isResetPassword={isResetPassword}
|
|
|
|
refresh={refresh}
|
|
|
|
onClose={() => setIsOpen(false)}
|
|
|
|
/>
|
2021-11-24 15:37:30 +00:00
|
|
|
)}
|
2021-12-21 06:22:44 +00:00
|
|
|
{openCredentialReset && (
|
2022-02-09 14:39:10 +00:00
|
|
|
<ResetCredentialDialog
|
|
|
|
userId={user.id!}
|
|
|
|
onClose={() => setOpenCredentialReset(false)}
|
|
|
|
/>
|
2021-12-21 06:22:44 +00:00
|
|
|
)}
|
2021-11-24 15:37:30 +00:00
|
|
|
<DeleteConfirm />
|
2021-12-06 09:52:05 +00:00
|
|
|
{userCredentials.length !== 0 && passwordTypeFinder === undefined && (
|
|
|
|
<>
|
|
|
|
<Button
|
|
|
|
key={`confirmSaveBtn-table-${user.id}`}
|
2022-01-10 10:31:50 +00:00
|
|
|
className="kc-setPasswordBtn-tbl"
|
2021-12-06 09:52:05 +00:00
|
|
|
data-testid="setPasswordBtn-table"
|
|
|
|
variant="primary"
|
|
|
|
form="userCredentials-form"
|
|
|
|
onClick={() => {
|
2022-02-09 14:39:10 +00:00
|
|
|
setIsOpen(true);
|
2021-12-06 09:52:05 +00:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t("savePassword")}
|
|
|
|
</Button>
|
|
|
|
<Divider />
|
|
|
|
</>
|
|
|
|
)}
|
2022-01-10 10:31:50 +00:00
|
|
|
{groupedUserCredentials.length !== 0 ? (
|
2021-12-21 06:22:44 +00:00
|
|
|
<>
|
|
|
|
{user.email && (
|
|
|
|
<Button
|
|
|
|
className="resetCredentialBtn-header"
|
|
|
|
variant="primary"
|
|
|
|
data-testid="credentialResetBtn"
|
|
|
|
onClick={() => setOpenCredentialReset(true)}
|
|
|
|
>
|
|
|
|
{t("credentialResetBtn")}
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
<TableComposable aria-label="password-data-table" variant={"compact"}>
|
|
|
|
<Thead>
|
|
|
|
<Tr>
|
|
|
|
<Th>
|
|
|
|
<HelpItem
|
|
|
|
helpText="users:userCredentialsHelpText"
|
|
|
|
fieldLabelId="users:userCredentialsHelpTextLabel"
|
2021-11-24 15:37:30 +00:00
|
|
|
/>
|
2021-12-21 06:22:44 +00:00
|
|
|
</Th>
|
|
|
|
<Th>{t("type")}</Th>
|
|
|
|
<Th>{t("userLabel")}</Th>
|
|
|
|
<Th>{t("data")}</Th>
|
|
|
|
<Th />
|
|
|
|
<Th />
|
|
|
|
</Tr>
|
|
|
|
</Thead>
|
2022-02-09 14:39:10 +00:00
|
|
|
<Tbody>
|
|
|
|
{groupedUserCredentials.map((groupedCredential, rowIndex) => (
|
|
|
|
<Fragment key={`table-${groupedCredential.key}`}>
|
|
|
|
<Tr>
|
|
|
|
{groupedCredential.value.length > 1 ? (
|
|
|
|
<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);
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<Td />
|
|
|
|
)}
|
2021-12-21 06:22:44 +00:00
|
|
|
<Td
|
2022-02-09 14:39:10 +00:00
|
|
|
key={`table-item-${groupedCredential.key}`}
|
|
|
|
dataLabel={`columns-${groupedCredential.key}`}
|
|
|
|
className="kc-notExpandableRow-credentialType"
|
|
|
|
>
|
|
|
|
{toUpperCase(groupedCredential.key)}
|
|
|
|
</Td>
|
|
|
|
{groupedCredential.value.length <= 1 &&
|
|
|
|
groupedCredential.value.map((credential) => (
|
|
|
|
<Row
|
|
|
|
key={`subrow-${credential.id}`}
|
|
|
|
credential={credential}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</Tr>
|
|
|
|
{groupedCredential.isExpanded &&
|
2022-01-10 10:31:50 +00:00
|
|
|
groupedCredential.value.map((credential) => (
|
2022-02-09 14:39:10 +00:00
|
|
|
<Tr key={`child-key-${credential.id}`}>
|
|
|
|
<Td />
|
|
|
|
<Td
|
|
|
|
dataLabel={`child-columns-${credential.id}`}
|
|
|
|
className="kc-expandableRow-credentialType"
|
|
|
|
>
|
|
|
|
{toUpperCase(credential.type!)}
|
2022-01-10 10:31:50 +00:00
|
|
|
</Td>
|
2022-02-09 14:39:10 +00:00
|
|
|
<Row credential={credential} />
|
|
|
|
</Tr>
|
2022-01-10 10:31:50 +00:00
|
|
|
))}
|
2022-02-09 14:39:10 +00:00
|
|
|
</Fragment>
|
|
|
|
))}
|
|
|
|
</Tbody>
|
2021-12-21 06:22:44 +00:00
|
|
|
</TableComposable>
|
|
|
|
</>
|
2021-11-24 15:37:30 +00:00
|
|
|
) : (
|
|
|
|
<ListEmptyState
|
|
|
|
hasIcon={true}
|
|
|
|
message={t("noCredentials")}
|
|
|
|
instructions={t("noCredentialsText")}
|
|
|
|
primaryActionText={t("setPassword")}
|
|
|
|
onPrimaryAction={toggleModal}
|
2021-12-21 06:22:44 +00:00
|
|
|
secondaryActions={
|
|
|
|
user.email
|
|
|
|
? [
|
|
|
|
{
|
|
|
|
text: t("credentialResetBtn"),
|
|
|
|
onClick: toggleCredentialsResetModal,
|
|
|
|
type: ButtonVariant.link,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
: undefined
|
|
|
|
}
|
2021-11-24 15:37:30 +00:00
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|