diff --git a/src/realm-roles/RealmRoleDetails.tsx b/src/realm-roles/RealmRoleDetails.tsx
deleted file mode 100644
index 58a5cdb9b1..0000000000
--- a/src/realm-roles/RealmRoleDetails.tsx
+++ /dev/null
@@ -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 (
- <>
-
-
-
- setActiveTab(key as number)}
- isBox
- >
- {t("details")}}>
-
-
- {name ? (
-
- ) : undefined}
-
-
- (
-
- )}
- />
-
-
-
-
-
-
-
- {t("attributes")}}>
-
-
-
-
- >
- );
-};
diff --git a/src/realm-roles/RealmRoleForm.tsx b/src/realm-roles/RealmRoleForm.tsx
index c38d70d5b8..19495c2d47 100644
--- a/src/realm-roles/RealmRoleForm.tsx
+++ b/src/realm-roles/RealmRoleForm.tsx
@@ -1,48 +1,31 @@
-import React, { useEffect, useState } from "react";
-import { useHistory, useParams } from "react-router-dom";
+import React from "react";
import {
ActionGroup,
- AlertVariant,
Button,
- ButtonVariant,
- DropdownItem,
FormGroup,
- PageSection,
- Tab,
- Tabs,
- TabTitleText,
TextArea,
TextInput,
ValidatedOptions,
} from "@patternfly/react-core";
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 { FormAccess } from "../components/form-access/FormAccess";
-import { useAlerts } from "../components/alert/Alerts";
-import { ViewHeader } from "../components/view-header/ViewHeader";
-
-import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
-import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
-import { RoleAttributes } from "./RoleAttributes";
-import { useRealm } from "../context/realm-context/RealmContext";
-
-type RoleFormType = {
- form?: UseFormMethods;
- save?: SubmitHandler;
- editMode?: boolean;
+export type RealmRoleFormProps = {
+ form: UseFormMethods;
+ save: SubmitHandler;
+ editMode: boolean;
};
-export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
+export const RealmRoleForm = ({ form, save, editMode }: RealmRoleFormProps) => {
const { t } = useTranslation("roles");
- const history = useHistory();
- const { realm } = useRealm();
+
return (
@@ -50,11 +33,11 @@ export const RoleForm = ({ form, save, editMode }: RoleFormType) => {
label={t("roleName")}
fieldId="kc-name"
isRequired
- validated={form!.errors.name ? "error" : "default"}
+ validated={form.errors.name ? "error" : "default"}
helperTextInvalid={t("common:required")}
>
{
label={t("description")}
fieldId="kc-description"
validated={
- form!.errors.description
+ form.errors.description
? ValidatedOptions.error
: ValidatedOptions.default
}
- helperTextInvalid={form!.errors.description?.message}
+ helperTextInvalid={form.errors.description?.message}
>
);
-};
-
-export const RealmRolesForm = () => {
- const { t } = useTranslation("roles");
- const form = useForm();
- 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 (
- <>
-
- toggleDeleteDialog()}
- >
- {t("deleteRole")}
- ,
- ]
- : undefined
- }
- />
-
-
- {id && (
- setActiveTab(key as number)}
- isBox
- >
- {t("details")}}
- >
-
-
- {t("attributes")}}
- >
-
-
-
- )}
- {!id && }
-
- >
- );
-};
+};
\ No newline at end of file
diff --git a/src/realm-roles/RealmRoleTabs.tsx b/src/realm-roles/RealmRoleTabs.tsx
index 3a2a2eda00..c1fbb129b7 100644
--- a/src/realm-roles/RealmRoleTabs.tsx
+++ b/src/realm-roles/RealmRoleTabs.tsx
@@ -1,31 +1,52 @@
import React, { useEffect, useState } from "react";
import { useHistory, useParams } from "react-router-dom";
import {
- ActionGroup,
AlertVariant,
- Button,
- FormGroup,
+ ButtonVariant,
+ DropdownItem,
+ 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 { useForm } from "react-hook-form";
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 { RoleAttributes } from "./RoleAttributes";
-import "./RealmRolesSection.css";
+import { KeyValueType, RoleAttributes } from "./RoleAttributes";
+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";
-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 { errors, control, setValue } = useForm();
+ const form = useForm({ mode: "onChange" });
const history = useHistory();
const [name, setName] = useState("");
const adminClient = useAdminClient();
@@ -37,116 +58,114 @@ export const RolesTabs = () => {
const { addAlert } = useAlerts();
useEffect(() => {
- return asyncStateFetch(
- () => adminClient.roles.findOneById({ id }),
- (fetchedRole) => {
+ (async () => {
+ if (id) {
+ const fetchedRole = await adminClient.roles.findOneById({ id });
setName(fetchedRole.name!);
setupForm(fetchedRole);
+ } else {
+ setName(t("createRole"));
}
- );
+ })();
}, []);
const setupForm = (role: RoleRepresentation) => {
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) => {
try {
- await adminClient.roles.updateById({ id }, role);
- setupForm(role as RoleRepresentation);
- addAlert(t("roleSaveSuccess"), AlertVariant.success);
+ 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);
+ } 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("roleSaveError")} '${error}'`, AlertVariant.danger);
+ addAlert(
+ t((id ? "roleSave" : "roleCreate") + "Error", {
+ error: error.response.data?.errorMessage || error,
+ }),
+ AlertVariant.danger
+ );
}
};
- const form = useForm();
+ 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 (
<>
- setActiveTab(key as number)}
- isBox
- >
- {t("details")}}>
-
+ toggleDeleteDialog()}
+ >
+ {t("deleteRole")}
+ ,
+ ]
+ : undefined
+ }
+ />
+
+ {id && (
+ setActiveTab(key as number)}
+ isBox
>
- {t("details")}}
>
- {name ? (
-
- ) : undefined}
-
-
- (
-
- )}
- />
-
-
-
-
-
-
-
- {t("attributes")}}
- >
-
-
-
-
+ {t("attributes")}}
>
- {t("common:reload")}
-
-
-
-
+
+
+
+ )}
+ {!id && }
+
>
);
-};
+};
\ No newline at end of file
diff --git a/src/realm-roles/RealmRolesSection.css b/src/realm-roles/RealmRolesSection.css
index 5863d2b4ec..4c9446b5a9 100644
--- a/src/realm-roles/RealmRolesSection.css
+++ b/src/realm-roles/RealmRolesSection.css
@@ -9,9 +9,16 @@
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 {
/* subtract the padding at the bottom of the table from the action group margin */
--pf-c-form__group--m-action--MarginTop: calc(
var(--pf-global--spacer--2xl) - var(--pf-global--spacer--sm)
);
+
}
diff --git a/src/realm-roles/__tests__/mock-roles.json b/src/realm-roles/__tests__/mock-roles.json
index 556e4c6a1d..120467bfe2 100644
--- a/src/realm-roles/__tests__/mock-roles.json
+++ b/src/realm-roles/__tests__/mock-roles.json
@@ -2,7 +2,11 @@
{
"name":"Admin",
"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",
diff --git a/src/realm-roles/messages.json b/src/realm-roles/messages.json
index 1fa3289b74..d182b5c422 100644
--- a/src/realm-roles/messages.json
+++ b/src/realm-roles/messages.json
@@ -1,10 +1,8 @@
{
"roles": {
"attributes": "Attributes",
-<<<<<<< HEAD
"addAttributeText": "Add an attribute",
-=======
->>>>>>> add role attributes, WIP
+ "deleteAttributeText": "Delete an attribute",
"title": "Realm roles",
"createRole": "Create role",
"importRole": "Import role",
diff --git a/src/route-config.ts b/src/route-config.ts
index f0b23db6e3..c7a67c0b17 100644
--- a/src/route-config.ts
+++ b/src/route-config.ts
@@ -11,7 +11,6 @@ import { EventsSection } from "./events/EventsSection";
import { GroupsSection } from "./groups/GroupsSection";
import { IdentityProvidersSection } from "./identity-providers/IdentityProvidersSection";
import { PageNotFoundSection } from "./PageNotFoundSection";
-import { RealmRolesForm } from "./realm-roles/RealmRoleForm";
import { RealmRolesSection } from "./realm-roles/RealmRolesSection";
import { RealmSettingsSection } from "./realm-settings/RealmSettingsSection";
import { NewRealmForm } from "./realm/add/NewRealmForm";
@@ -24,6 +23,7 @@ import { UserFederationKerberosSettings } from "./user-federation/UserFederation
import { UserFederationLdapSettings } from "./user-federation/UserFederationLdapSettings";
import { RoleMappingForm } from "./client-scopes/add/RoleMappingForm";
import { BreadcrumbsRoute } from "use-react-router-breadcrumbs";
+import { RealmRoleTabs } from "./realm-roles/RealmRoleTabs";
export type RouteDef = BreadcrumbsRoute & {
component: () => JSX.Element;
@@ -107,13 +107,13 @@ export const routes: RoutesFn = (t) => [
},
{
path: "/:realm/roles/add-role",
- component: RealmRolesForm,
+ component: RealmRoleTabs,
breadcrumb: t("roles:createRole"),
access: "manage-realm",
},
{
path: "/:realm/roles/:id",
- component: RealmRolesForm,
+ component: RealmRoleTabs,
breadcrumb: t("roles:roleDetails"),
access: "view-realm",
},
diff --git a/src/stories/RoleAttributes.stories.tsx b/src/stories/RoleAttributes.stories.tsx
index d8bb648ec0..39fb19961b 100644
--- a/src/stories/RoleAttributes.stories.tsx
+++ b/src/stories/RoleAttributes.stories.tsx
@@ -3,19 +3,19 @@ import { Meta } from "@storybook/react";
import { MockAdminClient } from "./MockAdminClient";
import { MemoryRouter, Route } from "react-router-dom";
import rolesMock from "../realm-roles/__tests__/mock-roles.json";
-import { RolesTabs } from "../realm-roles/RealmRoleTabs";
+import { RealmRoleTabs } from "../realm-roles/RealmRoleTabs";
export default {
title: "Roles tabs",
- component: RolesTabs,
+ component: RealmRoleTabs,
} as Meta;
-export const RoleTabsExample = () => {
+export const RolesTabsExample = () => {
return (
rolesMock[0] } }}>
-
+
diff --git a/src/stories/RoleDetails.stories.tsx b/src/stories/RoleDetails.stories.tsx
index d4548eb948..542752009c 100644
--- a/src/stories/RoleDetails.stories.tsx
+++ b/src/stories/RoleDetails.stories.tsx
@@ -3,18 +3,18 @@ import { Page } from "@patternfly/react-core";
import { Meta } from "@storybook/react";
import { MockAdminClient } from "./MockAdminClient";
-import { RealmRolesForm } from "../realm-roles/RealmRoleForm";
+import { RealmRoleTabs } from "../realm-roles/RealmRoleTabs";
export default {
title: "New role form",
- component: RealmRolesForm,
+ component: RealmRoleTabs,
} as Meta;
export const View = () => {
return (
-
+
);
diff --git a/yarn.lock b/yarn.lock
index a583bc3f0c..90024153bf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -19351,9 +19351,9 @@ use-latest@^1.0.0:
dependencies:
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"
- 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==
use-sidecar@^1.0.1: