WIP role attributes
fix storybook demos add attributes tab to realm roles section use TableComposable fix formatting css updates fix up styling of role attributes table fix check-types erros logic from call with Jeff clean up, format, make eslint proud delete roledetails call with Erik add delete function and css changes fix storybook demos and format make key input disabled once new attribute is created minus icon gray address PR feedback from Sarah set add and save buttons to disabled when new input field is empty fix save/add fix onChange formState rebase with resolved conflicts fix formatting remove !important Update RealmRolesSection.css Update src/realm-roles/RoleAttributes.tsx Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com> address PR feedback from Sarah and Erik fix paths and use error msg from response remove log stmt fix lint remove minLength
This commit is contained in:
parent
5d7d2b5636
commit
8ef7bd7ddb
10 changed files with 163 additions and 413 deletions
|
@ -1,140 +0,0 @@
|
||||||
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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,48 +1,31 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
AlertVariant,
|
|
||||||
Button,
|
Button,
|
||||||
ButtonVariant,
|
|
||||||
DropdownItem,
|
|
||||||
FormGroup,
|
FormGroup,
|
||||||
PageSection,
|
|
||||||
Tab,
|
|
||||||
Tabs,
|
|
||||||
TabTitleText,
|
|
||||||
TextArea,
|
TextArea,
|
||||||
TextInput,
|
TextInput,
|
||||||
ValidatedOptions,
|
ValidatedOptions,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { SubmitHandler, useForm, UseFormMethods } from "react-hook-form";
|
import { SubmitHandler, UseFormMethods } from "react-hook-form";
|
||||||
|
|
||||||
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
||||||
import { FormAccess } from "../components/form-access/FormAccess";
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
|
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
export type RealmRoleFormProps = {
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
form: UseFormMethods;
|
||||||
|
save: SubmitHandler<RoleRepresentation>;
|
||||||
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
|
editMode: boolean;
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
|
||||||
import { RoleAttributes } from "./RoleAttributes";
|
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
|
||||||
|
|
||||||
type RoleFormType = {
|
|
||||||
form?: UseFormMethods;
|
|
||||||
save?: SubmitHandler<RoleRepresentation>;
|
|
||||||
editMode?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
|
export const RealmRoleForm = ({ form, save, editMode }: RealmRoleFormProps) => {
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
const history = useHistory();
|
|
||||||
const { realm } = useRealm();
|
|
||||||
return (
|
return (
|
||||||
<FormAccess
|
<FormAccess
|
||||||
isHorizontal
|
isHorizontal
|
||||||
onSubmit={form!.handleSubmit(save!)}
|
onSubmit={form.handleSubmit(save)}
|
||||||
role="manage-realm"
|
role="manage-realm"
|
||||||
className="pf-u-mt-lg"
|
className="pf-u-mt-lg"
|
||||||
>
|
>
|
||||||
|
@ -50,11 +33,11 @@ export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
|
||||||
label={t("roleName")}
|
label={t("roleName")}
|
||||||
fieldId="kc-name"
|
fieldId="kc-name"
|
||||||
isRequired
|
isRequired
|
||||||
validated={form!.errors.name ? "error" : "default"}
|
validated={form.errors.name ? "error" : "default"}
|
||||||
helperTextInvalid={t("common:required")}
|
helperTextInvalid={t("common:required")}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={form!.register({ required: true })}
|
ref={form.register({ required: !editMode })}
|
||||||
type="text"
|
type="text"
|
||||||
id="kc-name"
|
id="kc-name"
|
||||||
name="name"
|
name="name"
|
||||||
|
@ -65,15 +48,15 @@ export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
|
||||||
label={t("description")}
|
label={t("description")}
|
||||||
fieldId="kc-description"
|
fieldId="kc-description"
|
||||||
validated={
|
validated={
|
||||||
form!.errors.description
|
form.errors.description
|
||||||
? ValidatedOptions.error
|
? ValidatedOptions.error
|
||||||
: ValidatedOptions.default
|
: ValidatedOptions.default
|
||||||
}
|
}
|
||||||
helperTextInvalid={form!.errors.description?.message}
|
helperTextInvalid={form.errors.description?.message}
|
||||||
>
|
>
|
||||||
<TextArea
|
<TextArea
|
||||||
name="description"
|
name="description"
|
||||||
ref={form!.register({
|
ref={form.register({
|
||||||
maxLength: {
|
maxLength: {
|
||||||
value: 255,
|
value: 255,
|
||||||
message: t("common:maxLength", { length: 255 }),
|
message: t("common:maxLength", { length: 255 }),
|
||||||
|
@ -81,7 +64,7 @@ export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
|
||||||
})}
|
})}
|
||||||
type="text"
|
type="text"
|
||||||
validated={
|
validated={
|
||||||
form!.errors.description
|
form.errors.description
|
||||||
? ValidatedOptions.error
|
? ValidatedOptions.error
|
||||||
: ValidatedOptions.default
|
: ValidatedOptions.default
|
||||||
}
|
}
|
||||||
|
@ -92,131 +75,10 @@ export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
|
||||||
<Button variant="primary" type="submit">
|
<Button variant="primary" type="submit">
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="link" onClick={() => history.push(`/${realm}/roles`)}>
|
<Button variant="link">
|
||||||
{editMode ? t("common:reload") : t("common:cancel")}
|
{editMode ? t("common:reload") : t("common:cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RealmRolesForm = () => {
|
|
||||||
const { t } = useTranslation("roles");
|
|
||||||
const form = useForm<RoleRepresentation>();
|
|
||||||
const adminClient = useAdminClient();
|
|
||||||
const { addAlert } = useAlerts();
|
|
||||||
const history = useHistory();
|
|
||||||
const { realm } = useRealm();
|
|
||||||
|
|
||||||
const { id } = useParams<{ id: string }>();
|
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return asyncStateFetch(
|
|
||||||
async () => {
|
|
||||||
if (id) {
|
|
||||||
const role = await adminClient.roles.findOneById({ id });
|
|
||||||
return { role, name: role.name };
|
|
||||||
} else {
|
|
||||||
return { name: t("createRole") };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
({ role, name }) => {
|
|
||||||
setName(name!);
|
|
||||||
if (role) {
|
|
||||||
setupForm(role);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setupForm = (role: RoleRepresentation) => {
|
|
||||||
Object.entries(role).map((entry) => {
|
|
||||||
form.setValue(entry[0], entry[1]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const save = async (role: RoleRepresentation) => {
|
|
||||||
try {
|
|
||||||
if (id) {
|
|
||||||
await adminClient.roles.updateById({ id }, role);
|
|
||||||
} else {
|
|
||||||
await adminClient.roles.create(role);
|
|
||||||
const createdRole = await adminClient.roles.findOneByName({
|
|
||||||
name: role.name!,
|
|
||||||
});
|
|
||||||
history.push(`/${realm}/roles/${createdRole.id}`);
|
|
||||||
}
|
|
||||||
addAlert(t(id ? "roleSaveSuccess" : "roleCreated"), AlertVariant.success);
|
|
||||||
} catch (error) {
|
|
||||||
addAlert(
|
|
||||||
t((id ? "roleSave" : "roleCreate") + "Error", { error }),
|
|
||||||
AlertVariant.danger
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
|
||||||
titleKey: "roles:roleDeleteConfirm",
|
|
||||||
messageKey: t("roles:roleDeleteConfirmDialog", { name }),
|
|
||||||
continueButtonLabel: "common:delete",
|
|
||||||
continueButtonVariant: ButtonVariant.danger,
|
|
||||||
onConfirm: async () => {
|
|
||||||
try {
|
|
||||||
await adminClient.roles.delById({ id });
|
|
||||||
addAlert(t("roleDeletedSuccess"), AlertVariant.success);
|
|
||||||
history.push(`/${realm}/roles`);
|
|
||||||
} catch (error) {
|
|
||||||
addAlert(`${t("roleDeleteError")} ${error}`, AlertVariant.danger);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DeleteConfirm />
|
|
||||||
<ViewHeader
|
|
||||||
titleKey={name}
|
|
||||||
subKey={id ? "" : "roles:roleCreateExplain"}
|
|
||||||
dropdownItems={
|
|
||||||
id
|
|
||||||
? [
|
|
||||||
<DropdownItem
|
|
||||||
key="action"
|
|
||||||
component="button"
|
|
||||||
onClick={() => toggleDeleteDialog()}
|
|
||||||
>
|
|
||||||
{t("deleteRole")}
|
|
||||||
</DropdownItem>,
|
|
||||||
]
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PageSection variant="light">
|
|
||||||
{id && (
|
|
||||||
<Tabs
|
|
||||||
activeKey={activeTab}
|
|
||||||
onSelect={(_, key) => setActiveTab(key as number)}
|
|
||||||
isBox
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
eventKey={0}
|
|
||||||
title={<TabTitleText>{t("details")}</TabTitleText>}
|
|
||||||
>
|
|
||||||
<RoleForm form={form} save={save} editMode={true} />
|
|
||||||
</Tab>
|
|
||||||
<Tab
|
|
||||||
eventKey={1}
|
|
||||||
title={<TabTitleText>{t("attributes")}</TabTitleText>}
|
|
||||||
>
|
|
||||||
<RoleAttributes />
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
)}
|
|
||||||
{!id && <RoleForm form={form} save={save} editMode={false} />}
|
|
||||||
</PageSection>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,31 +1,52 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
Button,
|
ButtonVariant,
|
||||||
FormGroup,
|
DropdownItem,
|
||||||
|
PageSection,
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
TabTitleText,
|
TabTitleText,
|
||||||
TextArea,
|
|
||||||
TextInput,
|
|
||||||
ValidatedOptions,
|
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { FormAccess } from "../components/form-access/FormAccess";
|
|
||||||
|
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
||||||
import { RoleAttributes } from "./RoleAttributes";
|
import { KeyValueType, RoleAttributes } from "./RoleAttributes";
|
||||||
import "./RealmRolesSection.css";
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
|
import { RealmRoleForm } from "./RealmRoleForm";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
|
||||||
export const RolesTabs = () => {
|
const arrayToAttributes = (attributeArray: KeyValueType[]) => {
|
||||||
|
const initValue: { [index: string]: string[] } = {};
|
||||||
|
return attributeArray.reduce((acc, attribute) => {
|
||||||
|
acc[attribute.key] = [attribute.value];
|
||||||
|
return acc;
|
||||||
|
}, initValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const attributesToArray = (attributes: { [key: string]: string }): any => {
|
||||||
|
if (!attributes || Object.keys(attributes).length == 0) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return Object.keys(attributes).map((key) => ({
|
||||||
|
key: key,
|
||||||
|
value: attributes[key],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RealmRoleTabs = () => {
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
const { errors, control, setValue } = useForm<RoleRepresentation>();
|
const form = useForm<RoleRepresentation>({ mode: "onChange" });
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
|
@ -37,116 +58,114 @@ export const RolesTabs = () => {
|
||||||
const { addAlert } = useAlerts();
|
const { addAlert } = useAlerts();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return asyncStateFetch(
|
(async () => {
|
||||||
() => adminClient.roles.findOneById({ id }),
|
if (id) {
|
||||||
(fetchedRole) => {
|
const fetchedRole = await adminClient.roles.findOneById({ id });
|
||||||
setName(fetchedRole.name!);
|
setName(fetchedRole.name!);
|
||||||
setupForm(fetchedRole);
|
setupForm(fetchedRole);
|
||||||
|
} else {
|
||||||
|
setName(t("createRole"));
|
||||||
}
|
}
|
||||||
);
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setupForm = (role: RoleRepresentation) => {
|
const setupForm = (role: RoleRepresentation) => {
|
||||||
Object.entries(role).map((entry) => {
|
Object.entries(role).map((entry) => {
|
||||||
setValue(entry[0], entry[1]);
|
if (entry[0] === "attributes") {
|
||||||
|
form.setValue(entry[0], attributesToArray(entry[1]));
|
||||||
|
} else {
|
||||||
|
form.setValue(entry[0], entry[1]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = async (role: RoleRepresentation) => {
|
const save = async (role: RoleRepresentation) => {
|
||||||
try {
|
try {
|
||||||
|
if (id) {
|
||||||
|
if (role.attributes) {
|
||||||
|
// react-hook-form will use `KeyValueType[]` here we convert it back into an indexed property of string[]
|
||||||
|
role.attributes = arrayToAttributes(
|
||||||
|
(role.attributes as unknown) as KeyValueType[]
|
||||||
|
);
|
||||||
|
}
|
||||||
await adminClient.roles.updateById({ id }, role);
|
await adminClient.roles.updateById({ id }, role);
|
||||||
setupForm(role as RoleRepresentation);
|
} else {
|
||||||
addAlert(t("roleSaveSuccess"), AlertVariant.success);
|
await adminClient.roles.create(role);
|
||||||
|
const createdRole = await adminClient.roles.findOneByName({
|
||||||
|
name: role.name!,
|
||||||
|
});
|
||||||
|
history.push(`/${realm}/roles/${createdRole.id}`);
|
||||||
|
}
|
||||||
|
addAlert(t(id ? "roleSaveSuccess" : "roleCreated"), AlertVariant.success);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addAlert(`${t("roleSaveError")} '${error}'`, AlertVariant.danger);
|
addAlert(
|
||||||
|
t((id ? "roleSave" : "roleCreate") + "Error", {
|
||||||
|
error: error.response.data?.errorMessage || error,
|
||||||
|
}),
|
||||||
|
AlertVariant.danger
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm<RoleRepresentation>();
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
|
titleKey: "roles:roleDeleteConfirm",
|
||||||
|
messageKey: t("roles:roleDeleteConfirmDialog", { name }),
|
||||||
|
continueButtonLabel: "common:delete",
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await adminClient.roles.delById({ id });
|
||||||
|
addAlert(t("roleDeletedSuccess"), AlertVariant.success);
|
||||||
|
history.replace(`/${realm}/roles`);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(`${t("roleDeleteError")} ${error}`, AlertVariant.danger);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<DeleteConfirm />
|
||||||
|
<ViewHeader
|
||||||
|
titleKey={name}
|
||||||
|
subKey={id ? "" : "roles:roleCreateExplain"}
|
||||||
|
dropdownItems={
|
||||||
|
id
|
||||||
|
? [
|
||||||
|
<DropdownItem
|
||||||
|
key="action"
|
||||||
|
component="button"
|
||||||
|
onClick={() => toggleDeleteDialog()}
|
||||||
|
>
|
||||||
|
{t("deleteRole")}
|
||||||
|
</DropdownItem>,
|
||||||
|
]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<PageSection variant="light">
|
||||||
|
{id && (
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeTab}
|
activeKey={activeTab}
|
||||||
onSelect={(_, key) => setActiveTab(key as number)}
|
onSelect={(_, key) => setActiveTab(key as number)}
|
||||||
isBox
|
isBox
|
||||||
>
|
>
|
||||||
<Tab eventKey={0} title={<TabTitleText>{t("details")}</TabTitleText>}>
|
<Tab
|
||||||
<FormAccess
|
eventKey={0}
|
||||||
isHorizontal
|
title={<TabTitleText>{t("details")}</TabTitleText>}
|
||||||
onSubmit={form.handleSubmit(save)}
|
|
||||||
role="manage-realm"
|
|
||||||
className="pf-u-mt-lg"
|
|
||||||
>
|
>
|
||||||
<FormGroup
|
<RealmRoleForm form={form} save={save} editMode={true} />
|
||||||
label={t("roleName")}
|
|
||||||
fieldId="kc-name"
|
|
||||||
isRequired
|
|
||||||
validated={errors.name ? "error" : "default"}
|
|
||||||
helperTextInvalid={t("common:required")}
|
|
||||||
>
|
|
||||||
{name ? (
|
|
||||||
<TextInput
|
|
||||||
ref={form.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(`/${realm}/roles`)}
|
|
||||||
>
|
|
||||||
{t("common:reload")}
|
|
||||||
</Button>
|
|
||||||
</ActionGroup>
|
|
||||||
</FormAccess>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
eventKey={1}
|
eventKey={1}
|
||||||
title={<TabTitleText>{t("attributes")}</TabTitleText>}
|
title={<TabTitleText>{t("attributes")}</TabTitleText>}
|
||||||
>
|
>
|
||||||
<RoleAttributes />
|
<RoleAttributes form={form} save={save} />
|
||||||
<ActionGroup className="kc-role-attributes__action-group">
|
|
||||||
<Button variant="primary" type="submit">
|
|
||||||
{t("common:save")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
onClick={() => history.push(`/${realm}/roles`)}
|
|
||||||
>
|
|
||||||
{t("common:reload")}
|
|
||||||
</Button>
|
|
||||||
</ActionGroup>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
)}
|
||||||
|
{!id && <RealmRoleForm form={form} save={save} editMode={false} />}
|
||||||
|
</PageSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -9,9 +9,16 @@
|
||||||
margin-left: calc(var(--pf-global--spacer--md) * -1);
|
margin-left: calc(var(--pf-global--spacer--md) * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pf-c-button.kc-role-attributes__minus-icon {
|
||||||
|
/* shift the button left to adjust for table cell padding */
|
||||||
|
margin-left: calc(var(--pf-global--spacer--md) * -1);
|
||||||
|
color: var(--pf-c-button--m-plain--Color);
|
||||||
|
}
|
||||||
|
|
||||||
.kc-role-attributes__action-group {
|
.kc-role-attributes__action-group {
|
||||||
/* subtract the padding at the bottom of the table from the action group margin */
|
/* subtract the padding at the bottom of the table from the action group margin */
|
||||||
--pf-c-form__group--m-action--MarginTop: calc(
|
--pf-c-form__group--m-action--MarginTop: calc(
|
||||||
var(--pf-global--spacer--2xl) - var(--pf-global--spacer--sm)
|
var(--pf-global--spacer--2xl) - var(--pf-global--spacer--sm)
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
{
|
{
|
||||||
"name":"Admin",
|
"name":"Admin",
|
||||||
"composite":true,
|
"composite":true,
|
||||||
"description": "Lorem ipsum dolor sit amet"
|
"description": "Lorem ipsum dolor sit amet",
|
||||||
|
"attributes": {
|
||||||
|
"key input 1": "value input 1",
|
||||||
|
"key input 2": "value input 2"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name":"Author",
|
"name":"Author",
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
{
|
{
|
||||||
"roles": {
|
"roles": {
|
||||||
"attributes": "Attributes",
|
"attributes": "Attributes",
|
||||||
<<<<<<< HEAD
|
|
||||||
"addAttributeText": "Add an attribute",
|
"addAttributeText": "Add an attribute",
|
||||||
=======
|
"deleteAttributeText": "Delete an attribute",
|
||||||
>>>>>>> add role attributes, WIP
|
|
||||||
"title": "Realm roles",
|
"title": "Realm roles",
|
||||||
"createRole": "Create role",
|
"createRole": "Create role",
|
||||||
"importRole": "Import role",
|
"importRole": "Import role",
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { EventsSection } from "./events/EventsSection";
|
||||||
import { GroupsSection } from "./groups/GroupsSection";
|
import { GroupsSection } from "./groups/GroupsSection";
|
||||||
import { IdentityProvidersSection } from "./identity-providers/IdentityProvidersSection";
|
import { IdentityProvidersSection } from "./identity-providers/IdentityProvidersSection";
|
||||||
import { PageNotFoundSection } from "./PageNotFoundSection";
|
import { PageNotFoundSection } from "./PageNotFoundSection";
|
||||||
import { RealmRolesForm } from "./realm-roles/RealmRoleForm";
|
|
||||||
import { RealmRolesSection } from "./realm-roles/RealmRolesSection";
|
import { RealmRolesSection } from "./realm-roles/RealmRolesSection";
|
||||||
import { RealmSettingsSection } from "./realm-settings/RealmSettingsSection";
|
import { RealmSettingsSection } from "./realm-settings/RealmSettingsSection";
|
||||||
import { NewRealmForm } from "./realm/add/NewRealmForm";
|
import { NewRealmForm } from "./realm/add/NewRealmForm";
|
||||||
|
@ -24,6 +23,7 @@ import { UserFederationKerberosSettings } from "./user-federation/UserFederation
|
||||||
import { UserFederationLdapSettings } from "./user-federation/UserFederationLdapSettings";
|
import { UserFederationLdapSettings } from "./user-federation/UserFederationLdapSettings";
|
||||||
import { RoleMappingForm } from "./client-scopes/add/RoleMappingForm";
|
import { RoleMappingForm } from "./client-scopes/add/RoleMappingForm";
|
||||||
import { BreadcrumbsRoute } from "use-react-router-breadcrumbs";
|
import { BreadcrumbsRoute } from "use-react-router-breadcrumbs";
|
||||||
|
import { RealmRoleTabs } from "./realm-roles/RealmRoleTabs";
|
||||||
|
|
||||||
export type RouteDef = BreadcrumbsRoute & {
|
export type RouteDef = BreadcrumbsRoute & {
|
||||||
component: () => JSX.Element;
|
component: () => JSX.Element;
|
||||||
|
@ -107,13 +107,13 @@ export const routes: RoutesFn = (t) => [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/:realm/roles/add-role",
|
path: "/:realm/roles/add-role",
|
||||||
component: RealmRolesForm,
|
component: RealmRoleTabs,
|
||||||
breadcrumb: t("roles:createRole"),
|
breadcrumb: t("roles:createRole"),
|
||||||
access: "manage-realm",
|
access: "manage-realm",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/:realm/roles/:id",
|
path: "/:realm/roles/:id",
|
||||||
component: RealmRolesForm,
|
component: RealmRoleTabs,
|
||||||
breadcrumb: t("roles:roleDetails"),
|
breadcrumb: t("roles:roleDetails"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,19 +3,19 @@ import { Meta } from "@storybook/react";
|
||||||
import { MockAdminClient } from "./MockAdminClient";
|
import { MockAdminClient } from "./MockAdminClient";
|
||||||
import { MemoryRouter, Route } from "react-router-dom";
|
import { MemoryRouter, Route } from "react-router-dom";
|
||||||
import rolesMock from "../realm-roles/__tests__/mock-roles.json";
|
import rolesMock from "../realm-roles/__tests__/mock-roles.json";
|
||||||
import { RolesTabs } from "../realm-roles/RealmRoleTabs";
|
import { RealmRoleTabs } from "../realm-roles/RealmRoleTabs";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Roles tabs",
|
title: "Roles tabs",
|
||||||
component: RolesTabs,
|
component: RealmRoleTabs,
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
export const RoleTabsExample = () => {
|
export const RolesTabsExample = () => {
|
||||||
return (
|
return (
|
||||||
<MockAdminClient mock={{ roles: { findOneById: () => rolesMock[0] } }}>
|
<MockAdminClient mock={{ roles: { findOneById: () => rolesMock[0] } }}>
|
||||||
<MemoryRouter initialEntries={["/roles/1"]}>
|
<MemoryRouter initialEntries={["/roles/1"]}>
|
||||||
<Route path="/roles/:id">
|
<Route path="/roles/:id">
|
||||||
<RolesTabs />
|
<RealmRoleTabs />
|
||||||
</Route>
|
</Route>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</MockAdminClient>
|
</MockAdminClient>
|
||||||
|
|
|
@ -3,18 +3,18 @@ import { Page } from "@patternfly/react-core";
|
||||||
import { Meta } from "@storybook/react";
|
import { Meta } from "@storybook/react";
|
||||||
|
|
||||||
import { MockAdminClient } from "./MockAdminClient";
|
import { MockAdminClient } from "./MockAdminClient";
|
||||||
import { RealmRolesForm } from "../realm-roles/RealmRoleForm";
|
import { RealmRoleTabs } from "../realm-roles/RealmRoleTabs";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "New role form",
|
title: "New role form",
|
||||||
component: RealmRolesForm,
|
component: RealmRoleTabs,
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
export const View = () => {
|
export const View = () => {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<MockAdminClient>
|
<MockAdminClient>
|
||||||
<RealmRolesForm />
|
<RealmRoleTabs />
|
||||||
</MockAdminClient>
|
</MockAdminClient>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
|
@ -19351,9 +19351,9 @@ use-latest@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
use-isomorphic-layout-effect "^1.0.0"
|
use-isomorphic-layout-effect "^1.0.0"
|
||||||
|
|
||||||
use-react-router-breadcrumbs@^1.0.4:
|
use-react-router-breadcrumbs@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/use-react-router-breadcrumbs/-/use-react-router-breadcrumbs-1.0.5.tgz#3b39a2c2a6ab72544c2fc8984f6825d0f1122877"
|
resolved "https://registry.npmjs.org/use-react-router-breadcrumbs/-/use-react-router-breadcrumbs-1.0.5.tgz#3b39a2c2a6ab72544c2fc8984f6825d0f1122877"
|
||||||
integrity sha512-NDMgWr5MdksqnATRvp84RtZ0ABfuztlsgR4VWlsBV0D3TVV6xhbmkhTdV3cWnyRIZqNlMXZhwJhyRHoC6fbAsQ==
|
integrity sha512-NDMgWr5MdksqnATRvp84RtZ0ABfuztlsgR4VWlsBV0D3TVV6xhbmkhTdV3cWnyRIZqNlMXZhwJhyRHoC6fbAsQ==
|
||||||
|
|
||||||
use-sidecar@^1.0.1:
|
use-sidecar@^1.0.1:
|
||||||
|
|
Loading…
Reference in a new issue