User credentials (#1597)
* user credentials - wip * user credentials - wip * user credentials - wip * user credentials - wip * user credentials - wip * user credentials - wip * user credentials - wip * user credentials - wip * user credentials - wip * added deleting credentials * replaced DataList with Table * added reset password - wip * added reset password * added show data dialog - wip * added show data dialog - wip * added password data dialog * added few translations * added sorting to password data * tidied up * clean up rows code * feedback fixes Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com> Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
999b502d44
commit
25030a790f
5 changed files with 597 additions and 0 deletions
|
@ -119,6 +119,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
attributes: "Attributes",
|
attributes: "Attributes",
|
||||||
|
credentials: "Credentials",
|
||||||
clientId: "Client ID",
|
clientId: "Client ID",
|
||||||
id: "ID",
|
id: "ID",
|
||||||
|
|
||||||
|
@ -153,5 +154,11 @@ export default {
|
||||||
onDragFinish: "Dragging finished {{list}}",
|
onDragFinish: "Dragging finished {{list}}",
|
||||||
|
|
||||||
notFound: "Could not find the resource that you are looking for",
|
notFound: "Could not find the resource that you are looking for",
|
||||||
|
|
||||||
|
password: "Password",
|
||||||
|
passwordConfirmation: "Password confirmation",
|
||||||
|
temporaryPassword: "Temporary",
|
||||||
|
temporaryPasswordHelpText:
|
||||||
|
"If enabled, the user must change the password on next login",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
505
src/user/UserCredentials.tsx
Normal file
505
src/user/UserCredentials.tsx
Normal file
|
@ -0,0 +1,505 @@
|
||||||
|
import React, { FunctionComponent, useMemo, useState } from "react";
|
||||||
|
import {
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownPosition,
|
||||||
|
Form,
|
||||||
|
FormGroup,
|
||||||
|
KebabToggle,
|
||||||
|
Modal,
|
||||||
|
ModalVariant,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
TextVariants,
|
||||||
|
ValidatedOptions,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableComposable,
|
||||||
|
TableHeader,
|
||||||
|
TableVariant,
|
||||||
|
Tbody,
|
||||||
|
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 { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||||
|
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||||
|
import { PasswordInput } from "../components/password-input/PasswordInput";
|
||||||
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
|
import "./user-section.css";
|
||||||
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
|
import type CredentialRepresentation from "@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation";
|
||||||
|
|
||||||
|
type UserCredentialsProps = {
|
||||||
|
user: UserRepresentation;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CredentialsForm = {
|
||||||
|
password: string;
|
||||||
|
passwordConfirmation: string;
|
||||||
|
temporaryPassword: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultValues: CredentialsForm = {
|
||||||
|
password: "",
|
||||||
|
passwordConfirmation: "",
|
||||||
|
temporaryPassword: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
type DisplayDialogProps = {
|
||||||
|
titleKey: string;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DisplayDialog: FunctionComponent<DisplayDialogProps> = ({
|
||||||
|
titleKey,
|
||||||
|
onClose,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation("users");
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
variant={ModalVariant.medium}
|
||||||
|
title={t(titleKey)}
|
||||||
|
isOpen={true}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserCredentials = ({ user }: UserCredentialsProps) => {
|
||||||
|
const { t } = useTranslation("users");
|
||||||
|
const { whoAmI } = useWhoAmI();
|
||||||
|
const { addAlert, addError } = useAlerts();
|
||||||
|
const [key, setKey] = useState(0);
|
||||||
|
const refresh = () => setKey(key + 1);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [openSaveConfirm, setOpenSaveConfirm] = useState(false);
|
||||||
|
const [kebabOpen, setKebabOpen] = useState(false);
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const form = useForm<CredentialsForm>({ defaultValues });
|
||||||
|
const { control, errors, handleSubmit, register } = form;
|
||||||
|
const [credentials, setCredentials] = useState<CredentialsForm>();
|
||||||
|
const [userCredentials, setUserCredentials] = useState<
|
||||||
|
CredentialRepresentation[]
|
||||||
|
>([]);
|
||||||
|
const [selectedCredential, setSelectedCredential] =
|
||||||
|
useState<CredentialRepresentation>({});
|
||||||
|
const [isResetPassword, setIsResetPassword] = useState(false);
|
||||||
|
const [showData, setShowData] = useState(false);
|
||||||
|
|
||||||
|
useFetch(
|
||||||
|
() => adminClient.users.getCredentials({ id: user.id! }),
|
||||||
|
(credentials) => {
|
||||||
|
setUserCredentials(credentials);
|
||||||
|
},
|
||||||
|
[key]
|
||||||
|
);
|
||||||
|
|
||||||
|
const passwordWatcher = useWatch<CredentialsForm["password"]>({
|
||||||
|
control,
|
||||||
|
name: "password",
|
||||||
|
});
|
||||||
|
|
||||||
|
const passwordConfirmationWatcher = useWatch<
|
||||||
|
CredentialsForm["passwordConfirmation"]
|
||||||
|
>({
|
||||||
|
control,
|
||||||
|
name: "passwordConfirmation",
|
||||||
|
});
|
||||||
|
|
||||||
|
const isNotDisabled =
|
||||||
|
passwordWatcher !== "" && passwordConfirmationWatcher !== "";
|
||||||
|
|
||||||
|
const toggleModal = () => {
|
||||||
|
setOpen(!open);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleConfirmSaveModal = () => {
|
||||||
|
setOpenSaveConfirm(!openSaveConfirm);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveUserPassword = async () => {
|
||||||
|
if (!credentials) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordsMatch =
|
||||||
|
credentials.password === credentials.passwordConfirmation;
|
||||||
|
|
||||||
|
if (!passwordsMatch) {
|
||||||
|
addAlert(
|
||||||
|
isResetPassword
|
||||||
|
? t("resetPasswordNotMatchError")
|
||||||
|
: t("savePasswordNotMatchError"),
|
||||||
|
AlertVariant.danger
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await adminClient.users.resetPassword({
|
||||||
|
id: user.id!,
|
||||||
|
credential: {
|
||||||
|
temporary: credentials.temporaryPassword,
|
||||||
|
type: "password",
|
||||||
|
value: credentials.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
addAlert(
|
||||||
|
isResetPassword
|
||||||
|
? t("resetCredentialsSuccess")
|
||||||
|
: t("savePasswordSuccess"),
|
||||||
|
AlertVariant.success
|
||||||
|
);
|
||||||
|
setIsResetPassword(false);
|
||||||
|
setOpenSaveConfirm(false);
|
||||||
|
} catch (error) {
|
||||||
|
addError(
|
||||||
|
isResetPassword ? t("resetPasswordError") : t("savePasswordError"),
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetPassword = () => {
|
||||||
|
setIsResetPassword(true);
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
addError(t("deleteCredentialsError"), error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const rows = useMemo(() => {
|
||||||
|
if (!selectedCredential.credentialData) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentialData = JSON.parse(selectedCredential.credentialData);
|
||||||
|
const locale = whoAmI.getLocale();
|
||||||
|
|
||||||
|
return Object.entries(credentialData)
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b, locale))
|
||||||
|
.map<[string, string]>(([key, value]) => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return [key, value];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [key, JSON.stringify(value)];
|
||||||
|
});
|
||||||
|
}, [selectedCredential.credentialData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{open && (
|
||||||
|
<Modal
|
||||||
|
variant={ModalVariant.small}
|
||||||
|
width={600}
|
||||||
|
title={
|
||||||
|
isResetPassword
|
||||||
|
? `${t("resetPasswordFor")} ${user.username}`
|
||||||
|
: `${t("setPasswordFor")} ${user.username}`
|
||||||
|
}
|
||||||
|
isOpen
|
||||||
|
onClose={() => {
|
||||||
|
setIsResetPassword(false);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
data-testid="okBtn"
|
||||||
|
key={`confirmBtn-${user.id}`}
|
||||||
|
variant="primary"
|
||||||
|
form="userCredentials-form"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setCredentials(form.getValues());
|
||||||
|
toggleConfirmSaveModal();
|
||||||
|
}}
|
||||||
|
isDisabled={!isNotDisabled}
|
||||||
|
>
|
||||||
|
{t("save")}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
data-testid="cancelBtn"
|
||||||
|
key={`cancelBtn-${user.id}`}
|
||||||
|
variant="link"
|
||||||
|
form="userCredentials-form"
|
||||||
|
onClick={() => {
|
||||||
|
setIsResetPassword(false);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Form id="userCredentials-form" isHorizontal>
|
||||||
|
<FormGroup
|
||||||
|
name="password"
|
||||||
|
label={t("password")}
|
||||||
|
fieldId="password"
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
validated={
|
||||||
|
errors.password
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<div className="kc-password">
|
||||||
|
<PasswordInput
|
||||||
|
name="password"
|
||||||
|
aria-label="password"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
name="passwordConfirmation"
|
||||||
|
label={
|
||||||
|
isResetPassword
|
||||||
|
? t("resetPasswordConfirmation")
|
||||||
|
: t("passwordConfirmation")
|
||||||
|
}
|
||||||
|
fieldId="passwordConfirmation"
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
validated={
|
||||||
|
errors.passwordConfirmation
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
|
}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<div className="kc-passwordConfirmation">
|
||||||
|
<PasswordInput
|
||||||
|
name="passwordConfirmation"
|
||||||
|
aria-label="passwordConfirm"
|
||||||
|
ref={register({ required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("common:temporaryPassword")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("common:temporaryPasswordHelpText")}
|
||||||
|
forLabel={t("common:temporaryPassword")}
|
||||||
|
forID="kc-temporaryPasswordSwitch"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="kc-temporaryPassword"
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
<Controller
|
||||||
|
name="temporaryPassword"
|
||||||
|
defaultValue={true}
|
||||||
|
control={control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Switch
|
||||||
|
className={"kc-temporaryPassword"}
|
||||||
|
onChange={(value) => onChange(value)}
|
||||||
|
isChecked={value}
|
||||||
|
label={t("common:on")}
|
||||||
|
labelOff={t("common:off")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
></Controller>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
{openSaveConfirm && (
|
||||||
|
<Modal
|
||||||
|
variant={ModalVariant.small}
|
||||||
|
width={600}
|
||||||
|
title={
|
||||||
|
isResetPassword
|
||||||
|
? t("resetPasswordConfirm")
|
||||||
|
: t("setPasswordConfirm")
|
||||||
|
}
|
||||||
|
isOpen
|
||||||
|
onClose={() => setOpenSaveConfirm(false)}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
data-testid="setPasswordBtn"
|
||||||
|
key={`confirmSaveBtn-${user.id}`}
|
||||||
|
variant="danger"
|
||||||
|
form="userCredentials-form"
|
||||||
|
onClick={() => {
|
||||||
|
handleSubmit(saveUserPassword)();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isResetPassword ? t("resetPassword") : t("savePassword")}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
data-testid="cancelSetPasswordBtn"
|
||||||
|
key={`cancelConfirmBtn-${user.id}`}
|
||||||
|
variant="link"
|
||||||
|
form="userCredentials-form"
|
||||||
|
onClick={() => {
|
||||||
|
setOpenSaveConfirm(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text component={TextVariants.h3}>
|
||||||
|
{isResetPassword
|
||||||
|
? `${t("resetPasswordConfirmText")} ${user.username} ${t(
|
||||||
|
"questionMark"
|
||||||
|
)}`
|
||||||
|
: `${t("setPasswordConfirmText")} ${user.username} ${t(
|
||||||
|
"questionMark"
|
||||||
|
)}`}
|
||||||
|
</Text>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
<DeleteConfirm />
|
||||||
|
{showData && Object.keys(selectedCredential).length !== 0 && (
|
||||||
|
<DisplayDialog
|
||||||
|
titleKey={t("passwordDataTitle")}
|
||||||
|
onClose={() => {
|
||||||
|
setShowData(false);
|
||||||
|
setSelectedCredential({});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
aria-label="password-data"
|
||||||
|
data-testid="password-data-dialog"
|
||||||
|
variant={TableVariant.compact}
|
||||||
|
cells={[t("showPasswordDataName"), t("showPasswordDataValue")]}
|
||||||
|
rows={rows}
|
||||||
|
>
|
||||||
|
<TableHeader />
|
||||||
|
<TableBody />
|
||||||
|
</Table>
|
||||||
|
</DisplayDialog>
|
||||||
|
)}
|
||||||
|
{userCredentials.length !== 0 ? (
|
||||||
|
<TableComposable aria-label="password-data-table" variant={"compact"}>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>
|
||||||
|
<HelpItem
|
||||||
|
helpText={t("userCredentialsHelpText")}
|
||||||
|
forLabel={t("userCredentialsHelpTextLabel")}
|
||||||
|
forID={t(`common:helpLabel`, {
|
||||||
|
label: t("userCredentialsHelpTextLabel"),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Th>
|
||||||
|
<Th>{t("type")}</Th>
|
||||||
|
<Th>{t("userLabel")}</Th>
|
||||||
|
<Th>{t("data")}</Th>
|
||||||
|
<Th />
|
||||||
|
<Th />
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
<Tr key={"key"}>
|
||||||
|
{userCredentials.map((credential) => (
|
||||||
|
<>
|
||||||
|
<Td
|
||||||
|
draggableRow={{
|
||||||
|
id: `draggable-row-${credential.id}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Td key={`${credential}`} dataLabel={`columns-${credential}`}>
|
||||||
|
{credential.type?.charAt(0).toUpperCase()! +
|
||||||
|
credential.type?.slice(1)}
|
||||||
|
</Td>
|
||||||
|
<Td>My Password</Td>
|
||||||
|
<Td>
|
||||||
|
<Button
|
||||||
|
className="kc-showData-btn"
|
||||||
|
variant="link"
|
||||||
|
data-testid="showDataBtn"
|
||||||
|
onClick={() => {
|
||||||
|
setShowData(true);
|
||||||
|
setSelectedCredential(credential);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("showDataBtn")}
|
||||||
|
</Button>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
data-testid="resetPasswordBtn"
|
||||||
|
onClick={resetPassword}
|
||||||
|
>
|
||||||
|
{t("resetPasswordBtn")}
|
||||||
|
</Button>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Dropdown
|
||||||
|
isPlain
|
||||||
|
position={DropdownPosition.right}
|
||||||
|
toggle={
|
||||||
|
<KebabToggle onToggle={(open) => setKebabOpen(open)} />
|
||||||
|
}
|
||||||
|
isOpen={kebabOpen}
|
||||||
|
onSelect={() => setSelectedCredential(credential)}
|
||||||
|
dropdownItems={[
|
||||||
|
<DropdownItem
|
||||||
|
key={`delete-dropdown-item-${credential.id}`}
|
||||||
|
data-testid="deleteDropdownItem"
|
||||||
|
component="button"
|
||||||
|
onClick={() => {
|
||||||
|
toggleDeleteDialog();
|
||||||
|
setKebabOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("deleteBtn")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</Tr>
|
||||||
|
</Tbody>
|
||||||
|
</TableComposable>
|
||||||
|
) : (
|
||||||
|
<ListEmptyState
|
||||||
|
hasIcon={true}
|
||||||
|
message={t("noCredentials")}
|
||||||
|
instructions={t("noCredentialsText")}
|
||||||
|
primaryActionText={t("setPassword")}
|
||||||
|
onPrimaryAction={toggleModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -27,6 +27,7 @@ import { toUser } from "./routes/User";
|
||||||
import { toUsers } from "./routes/Users";
|
import { toUsers } from "./routes/Users";
|
||||||
import { UserRoleMapping } from "./UserRoleMapping";
|
import { UserRoleMapping } from "./UserRoleMapping";
|
||||||
import { UserAttributes } from "./UserAttributes";
|
import { UserAttributes } from "./UserAttributes";
|
||||||
|
import { UserCredentials } from "./UserCredentials";
|
||||||
import { useAccess } from "../context/access/Access";
|
import { useAccess } from "../context/access/Access";
|
||||||
|
|
||||||
const UsersTabs = () => {
|
const UsersTabs = () => {
|
||||||
|
@ -185,6 +186,13 @@ const UsersTabs = () => {
|
||||||
>
|
>
|
||||||
<UserAttributes user={user} />
|
<UserAttributes user={user} />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="credentials"
|
||||||
|
data-testid="credentials"
|
||||||
|
title={<TabTitleText>{t("common:credentials")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<UserCredentials user={user} />
|
||||||
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
eventKey="groups"
|
eventKey="groups"
|
||||||
data-testid="user-groups-tab"
|
data-testid="user-groups-tab"
|
||||||
|
|
|
@ -116,5 +116,55 @@ export default {
|
||||||
unlock: "Unlock",
|
unlock: "Unlock",
|
||||||
unlockUsersSuccess: "Any temporarily locked users are now unlocked",
|
unlockUsersSuccess: "Any temporarily locked users are now unlocked",
|
||||||
unlockUsersError: "Could not unlock all users {{error}}",
|
unlockUsersError: "Could not unlock all users {{error}}",
|
||||||
|
noCredentials: "No credentials",
|
||||||
|
noCredentialsText:
|
||||||
|
"This user does not have any credentials. You can set password for this user.",
|
||||||
|
setPassword: "Set password",
|
||||||
|
setPasswordFor: "Set password for ",
|
||||||
|
save: "Save",
|
||||||
|
cancel: "Cancel",
|
||||||
|
savePasswordSuccess: "The password has been set successfully.",
|
||||||
|
savePasswordError: "Error saving password: {{error}}",
|
||||||
|
savePasswordNotMatchError:
|
||||||
|
"Error saving password: Password and confirmation does not match.",
|
||||||
|
credentialType: "Type",
|
||||||
|
credentialUserLabel: "User Label",
|
||||||
|
credentialData: "Data",
|
||||||
|
credentialsList: "Credentials List",
|
||||||
|
setPasswordConfirm: "Set password?",
|
||||||
|
setPasswordConfirmText:
|
||||||
|
"Are you sure you want to set the password for the user",
|
||||||
|
password: "Password",
|
||||||
|
passwordConfirmation: "Password confirmation",
|
||||||
|
resetPasswordConfirmation: "New password confirmation",
|
||||||
|
questionMark: "?",
|
||||||
|
savePassword: "Save password",
|
||||||
|
deleteCredentialsConfirmTitle: "Delete credentials?",
|
||||||
|
deleteCredentialsConfirm:
|
||||||
|
"Are you sure you want to delete these users credentials?",
|
||||||
|
deleteCredentialsSuccess: "The credentials has been deleted successfully.",
|
||||||
|
deleteCredentialsError: "Error deleting users credentials: {{error}}",
|
||||||
|
deleteBtn: "Delete",
|
||||||
|
resetPasswordFor: "Reset password for ",
|
||||||
|
resetPasswordConfirm: "Reset password?",
|
||||||
|
resetPasswordConfirmText:
|
||||||
|
"Are you sure you want to reset the password for the user",
|
||||||
|
resetPassword: "Reset password",
|
||||||
|
resetCredentialsSuccess: "The password has been reset successfully.",
|
||||||
|
resetCredentialsError: "Error resetting users credentials: {{error}}",
|
||||||
|
resetPasswordNotMatchError:
|
||||||
|
"Error resetting password: Password and confirmation does not match.",
|
||||||
|
resetPasswordError: "Error resetting password: {{error}}",
|
||||||
|
resetPasswordBtn: "Reset password",
|
||||||
|
showPasswordDataName: "Name",
|
||||||
|
showPasswordDataValue: "Value",
|
||||||
|
showDataBtn: "Show data",
|
||||||
|
userCredentialsHelpText:
|
||||||
|
"The top level handlers allow you to shift the priority of the credential for the user, the topmost credential having the highest priority. The handlers within one expandable panel allow you to change the visual order of the credentials, the topmost credential will show at the most left.",
|
||||||
|
userCredentialsHelpTextLabel: "User Credentials Help Text",
|
||||||
|
type: "Type",
|
||||||
|
userLabel: "User label",
|
||||||
|
data: "Data",
|
||||||
|
passwordDataTitle: "Password data",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -113,3 +113,30 @@ article.pf-c-card.pf-m-flat.kc-available-idps > div > div > h1 {
|
||||||
.kc-no-providers-text {
|
.kc-no-providers-text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kc-temporaryPassword {
|
||||||
|
margin: 6px 0 10px 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-password, .kc-passwordConfirmation {
|
||||||
|
width: 355px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-form__group-label {
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-m-error {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: var(--pf-global--spacer--xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-edit-icon {
|
||||||
|
color: var(--pf-global--Color--200);
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-showData-btn {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue