add role attributes, WIP

This commit is contained in:
jenny-s51 2020-12-02 09:48:06 -05:00
parent 84ad3853a8
commit 5d7d2b5636
3 changed files with 258 additions and 75 deletions

View file

@ -0,0 +1,140 @@
import React, { useEffect, useState } from "react";
import { useHistory, useParams } from "react-router-dom";
import {
ActionGroup,
AlertVariant,
Button,
FormGroup,
PageSection,
Tab,
Tabs,
TabTitleText,
TextArea,
TextInput,
ValidatedOptions,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { Controller, useForm } from "react-hook-form";
import { FormAccess } from "../components/form-access/FormAccess";
import { useAlerts } from "../components/alert/Alerts";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient } from "../context/auth/AdminClient";
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import { RoleAttributes } from "./RoleAttributes";
export const RolesForm = () => {
const { t } = useTranslation("roles");
const { register, handleSubmit, errors, control, setValue } = useForm<
RoleRepresentation
>();
const history = useHistory();
const [name, setName] = useState("");
const [activeTab, setActiveTab] = useState(0);
const adminClient = useAdminClient();
const form = useForm();
const { id } = useParams<{ id: string }>();
const { addAlert } = useAlerts();
useEffect(() => {
(async () => {
const fetchedRole = await adminClient.roles.findOneById({ id });
setName(fetchedRole.name!);
setupForm(fetchedRole);
})();
}, []);
const setupForm = (role: RoleRepresentation) => {
Object.entries(role).map((entry) => {
setValue(entry[0], entry[1]);
});
};
const save = async (role: RoleRepresentation) => {
try {
await adminClient.roles.updateById({ id }, role);
setupForm(role as RoleRepresentation);
addAlert(t("roleSaveSuccess"), AlertVariant.success);
} catch (error) {
addAlert(`${t("roleSaveError")} '${error}'`, AlertVariant.danger);
}
};
return (
<>
<ViewHeader titleKey={name} subKey="" />
<PageSection variant="light">
<Tabs
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(key as number)}
isBox
>
<Tab eventKey={0} title={<TabTitleText>{t("details")}</TabTitleText>}>
<FormAccess
isHorizontal
onSubmit={handleSubmit(save)}
role="manage-realm"
className="pf-u-mt-lg"
>
<FormGroup
label={t("roleName")}
fieldId="kc-name"
isRequired
validated={errors.name ? "error" : "default"}
helperTextInvalid={t("common:required")}
>
{name ? (
<TextInput
ref={register({ required: true })}
type="text"
id="kc-name"
name="name"
isReadOnly
/>
) : undefined}
</FormGroup>
<FormGroup label={t("description")} fieldId="kc-description">
<Controller
name="description"
defaultValue=""
control={control}
rules={{ maxLength: 255 }}
render={({ onChange, value }) => (
<TextArea
type="text"
validated={
errors.description
? ValidatedOptions.error
: ValidatedOptions.default
}
id="kc-role-description"
value={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" type="submit">
{t("common:save")}
</Button>
<Button variant="link" onClick={() => history.push("/roles/")}>
{t("common:reload")}
</Button>
</ActionGroup>
</FormAccess>
</Tab>
<Tab eventKey={1} title={<TabTitleText>{t("attributes")}</TabTitleText>}>
<RoleAttributes form={form} />
</Tab>
</Tabs>
</PageSection>
</>
);
};

View file

@ -1,13 +1,8 @@
/* eslint-disable react/jsx-key */ import React from "react";
/* eslint-disable react/display-name */ import { ActionGroup, Button, TextInput } from "@patternfly/react-core";
import React, { useEffect, useState } from "react"; import { SubmitHandler, useFieldArray, UseFormMethods } from "react-hook-form";
import { useParams } from "react-router-dom";
import { Button, ButtonVariant, TextInput } from "@patternfly/react-core";
import { useForm } from "react-hook-form";
import "./RealmRolesSection.css"; import "./RealmRolesSection.css";
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation"; import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import { useTranslation } from "react-i18next";
import { import {
TableComposable, TableComposable,
@ -17,82 +12,127 @@ import {
Thead, Thead,
Tr, Tr,
} from "@patternfly/react-table"; } from "@patternfly/react-table";
import { PlusCircleIcon } from "@patternfly/react-icons"; import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
import { useTranslation } from "react-i18next";
import { useHistory, useRouteMatch } from "react-router-dom";
import { FormAccess } from "../components/form-access/FormAccess";
export const RoleAttributes = () => { export type KeyValueType = { key: string; value: string };
type RoleAttributesProps = {
form: UseFormMethods;
save: SubmitHandler<RoleRepresentation>;
};
export const RoleAttributes = ({ form, save }: RoleAttributesProps) => {
const { t } = useTranslation("roles"); const { t } = useTranslation("roles");
const { setValue } = useForm<RoleRepresentation>(); const history = useHistory();
const [, setName] = useState(""); const { url } = useRouteMatch();
const adminClient = useAdminClient(); const { fields, append, remove } = useFieldArray({
control: form.control,
const { id } = useParams<{ id: string }>(); name: "attributes",
});
const columns = ["Key", "Value"]; const columns = ["Key", "Value"];
const rows = [
[
<TextInput />,
<TextInput />,
<Button
id="kc-plus-icon"
variant={ButtonVariant.link}
tabIndex={-1}
className="kc-role-attributes__plus-icon"
aria-label={t("roles:addAttributeText")}
>
<PlusCircleIcon />
</Button>,
],
];
useEffect(() => { const onAdd = () => {
return asyncStateFetch( append({ key: "", value: "" });
() => adminClient.roles.findOneById({ id }),
(fetchedRole) => {
setName(fetchedRole.name!);
setupForm(fetchedRole);
}
);
}, []);
const setupForm = (role: RoleRepresentation) => {
Object.entries(role).map((entry) => {
setValue(entry[0], entry[1]);
});
}; };
return ( return (
<TableComposable <>
className="kc-role-attributes__table" <FormAccess role="anyone" onSubmit={form.handleSubmit(save)}>
aria-label="Role attribute keys and values" <TableComposable
variant="compact" className="kc-role-attributes__table"
borders={false} aria-label="Role attribute keys and values"
> variant="compact"
<Thead> borders={false}
<Tr> >
<Th id="kc-key-label" width={40}> <Thead>
{columns[0]} <Tr>
</Th> <Th id="key" width={40}>
<Th id="kc-value-label" width={40}> {columns[0]}
{columns[1]} </Th>
</Th> <Th id="value" width={40}>
</Tr> {columns[1]}
</Thead> </Th>
<Tbody> </Tr>
{rows.map((row, rowIndex) => ( </Thead>
<Tr key={rowIndex}> <Tbody>
{row.map((cell, cellIndex) => ( {fields.map((attribute, rowIndex) => (
<Td <Tr key={attribute.id}>
key={`${rowIndex}_${cellIndex}`} <Td
id={`text-input-${rowIndex}-${cellIndex}`} key={`${attribute.id}-key`}
dataLabel={columns[cellIndex]} id={`text-input-${rowIndex}-key`}
> dataLabel={columns[0]}
{cell} >
</Td> <TextInput
name={`attributes[${rowIndex}].key`}
ref={form.register({ required: true })}
aria-label="key-input"
defaultValue={attribute.key}
/>
</Td>
<Td
key={`${attribute}-value`}
id={`text-input-${rowIndex}-value`}
dataLabel={columns[1]}
>
<TextInput
name={`attributes[${rowIndex}].value`}
ref={form.register({
required: true,
})}
aria-label="value-input"
defaultValue={attribute.value}
/>
</Td>
{rowIndex !== fields.length - 1 && fields.length - 1 !== 0 && (
<Td
key="minus-button"
id={`kc-minus-button-${rowIndex}`}
dataLabel={columns[2]}
>
<Button
id={`minus-button-${rowIndex}`}
aria-label={`remove ${attribute.key} with value ${attribute.value} `}
variant="link"
className="kc-role-attributes__minus-icon"
onClick={() => remove(rowIndex)}
>
<MinusCircleIcon />
</Button>
</Td>
)}
{rowIndex === fields.length - 1 && (
<Td key="add-button" id="add-button" dataLabel={columns[2]}>
<Button
aria-label={t("roles:addAttributeText")}
id="plus-icon"
variant="link"
className="kc-role-attributes__plus-icon"
onClick={onAdd}
icon={<PlusCircleIcon />}
isDisabled={!form.formState.isValid}
/>
</Td>
)}
</Tr>
))} ))}
</Tr> </Tbody>
))} </TableComposable>
</Tbody> <ActionGroup className="kc-role-attributes__action-group">
</TableComposable> <Button
variant="primary"
type="submit"
isDisabled={!form.formState.isValid}
>
{t("common:save")}
</Button>
<Button variant="link">{t("common:reload")}</Button>
</ActionGroup>
</FormAccess>
</>
); );
}; };

View file

@ -1,7 +1,10 @@
{ {
"roles": { "roles": {
"attributes": "Attributes", "attributes": "Attributes",
<<<<<<< HEAD
"addAttributeText": "Add an attribute", "addAttributeText": "Add an attribute",
=======
>>>>>>> add role attributes, WIP
"title": "Realm roles", "title": "Realm roles",
"createRole": "Create role", "createRole": "Create role",
"importRole": "Import role", "importRole": "Import role",