refactor + use new composite role api (#1399)
* refactor + use new composite role api * fixed types * Update src/realm-roles/AssociatedRolesModal.tsx Co-authored-by: Jon Koops <jonkoops@gmail.com> * Update src/realm-roles/AssociatedRolesModal.tsx Co-authored-by: Jon Koops <jonkoops@gmail.com> * Update src/realm-roles/RealmRoleTabs.tsx Co-authored-by: Jon Koops <jonkoops@gmail.com> * code review comments * removed duplicate type * route * Update src/realm-roles/AssociatedRolesModal.tsx Co-authored-by: Jon Koops <jonkoops@gmail.com> * Update src/realm-roles/routes/RealmRole.ts Co-authored-by: Jon Koops <jonkoops@gmail.com> * Update src/realm-roles/AssociatedRolesModal.tsx Co-authored-by: Jon Koops <jonkoops@gmail.com> * fix unused import * fixed test * fixed merge errors Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
e22b27b471
commit
9c48bb4d90
17 changed files with 347 additions and 409 deletions
|
@ -12,12 +12,8 @@ export default class AssociatedRolesPage {
|
|||
addAssociatedRealmRole() {
|
||||
cy.findByTestId(this.actionDropdown).last().click();
|
||||
|
||||
const load = "/auth/admin/realms/master/clients";
|
||||
cy.intercept(load).as("load");
|
||||
|
||||
cy.findByTestId(this.addRolesDropdownItem).click();
|
||||
|
||||
cy.wait(["@load"]);
|
||||
cy.get(this.checkbox).eq(2).check();
|
||||
|
||||
cy.findByTestId(this.addAssociatedRolesModalButton).contains("Add").click();
|
||||
|
@ -29,8 +25,6 @@ export default class AssociatedRolesPage {
|
|||
"Composite"
|
||||
);
|
||||
|
||||
cy.wait(["@load"]);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,13 +60,12 @@ const SecuredRoute = ({ route }: SecuredRouteProps) => {
|
|||
? hasAccess(...route.access)
|
||||
: hasAccess(route.access);
|
||||
|
||||
if (accessAllowed) {
|
||||
if (accessAllowed)
|
||||
return (
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<route.component />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return <ForbiddenSection />;
|
||||
};
|
||||
|
|
|
@ -12,14 +12,15 @@ import {
|
|||
} from "@patternfly/react-table";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
|
||||
import type { RoleRepresentation } from "../../model/role-model";
|
||||
import { FormAccess } from "../form-access/FormAccess";
|
||||
|
||||
import "./attribute-form.css";
|
||||
|
||||
export type KeyValueType = { key: string; value: string };
|
||||
|
||||
export type AttributeForm = {
|
||||
attributes: KeyValueType[];
|
||||
export type AttributeForm = Omit<RoleRepresentation, "attributes"> & {
|
||||
attributes?: KeyValueType[];
|
||||
};
|
||||
|
||||
export type AttributesFormProps = {
|
||||
|
|
|
@ -47,7 +47,7 @@ export const GroupAttributes = () => {
|
|||
const save = async (attributeForm: AttributeForm) => {
|
||||
try {
|
||||
const group = currentGroup();
|
||||
const attributes = arrayToAttributes(attributeForm.attributes);
|
||||
const attributes = arrayToAttributes(attributeForm.attributes!);
|
||||
await adminClient.groups.update({ id: id! }, { ...group, attributes });
|
||||
|
||||
setSubGroups([
|
||||
|
|
|
@ -21,8 +21,8 @@ import { useRealm } from "../../context/realm-context/RealmContext";
|
|||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import {
|
||||
AttributeForm,
|
||||
AttributesForm,
|
||||
KeyValueType,
|
||||
} from "../../components/attribute-form/AttributeForm";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
|
@ -39,9 +39,7 @@ import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
|
|||
import { groupBy } from "lodash";
|
||||
|
||||
export type IdPMapperRepresentationWithAttributes =
|
||||
IdentityProviderMapperRepresentation & {
|
||||
attributes: KeyValueType[];
|
||||
};
|
||||
IdentityProviderMapperRepresentation & AttributeForm;
|
||||
|
||||
type Role = RoleRepresentation & {
|
||||
clientId?: string;
|
||||
|
@ -86,7 +84,6 @@ export default function AddMapper() {
|
|||
|
||||
const [currentMapper, setCurrentMapper] =
|
||||
useState<IdentityProviderMapperRepresentation>();
|
||||
const [roles, setRoles] = useState<RoleRepresentation[]>([]);
|
||||
|
||||
const [rolesModalOpen, setRolesModalOpen] = useState(false);
|
||||
|
||||
|
@ -144,19 +141,14 @@ export default function AddMapper() {
|
|||
Promise.all([
|
||||
id ? adminClient.identityProviders.findOneMapper({ alias, id }) : null,
|
||||
adminClient.identityProviders.findMapperTypes({ alias }),
|
||||
!id ? adminClient.roles.find() : null,
|
||||
]),
|
||||
([mapper, mapperTypes, roles]) => {
|
||||
([mapper, mapperTypes]) => {
|
||||
if (mapper) {
|
||||
setCurrentMapper(mapper);
|
||||
setupForm(mapper);
|
||||
}
|
||||
|
||||
setMapperTypes(mapperTypes);
|
||||
|
||||
if (roles) {
|
||||
setRoles(roles);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
@ -257,17 +249,15 @@ export default function AddMapper() {
|
|||
}
|
||||
divider
|
||||
/>
|
||||
<AssociatedRolesModal
|
||||
onConfirm={(role: Role[]) => {
|
||||
setSelectedRole(role);
|
||||
}}
|
||||
allRoles={roles}
|
||||
open={rolesModalOpen}
|
||||
omitComposites
|
||||
isRadio
|
||||
isMapperId
|
||||
toggleDialog={toggleModal}
|
||||
/>
|
||||
{rolesModalOpen && (
|
||||
<AssociatedRolesModal
|
||||
onConfirm={(role) => setSelectedRole(role)}
|
||||
omitComposites
|
||||
isRadio
|
||||
isMapperId
|
||||
toggleDialog={toggleModal}
|
||||
/>
|
||||
)}
|
||||
<FormAccess
|
||||
role="manage-identity-providers"
|
||||
isHorizontal
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useHistory, useParams, useRouteMatch } from "react-router-dom";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
|
@ -8,6 +9,7 @@ import {
|
|||
Label,
|
||||
Modal,
|
||||
ModalVariant,
|
||||
Spinner,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFetch, useAdminClient } from "../context/auth/AdminClient";
|
||||
|
@ -15,76 +17,74 @@ import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/ro
|
|||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
|
||||
import { CaretDownIcon, FilterIcon } from "@patternfly/react-icons";
|
||||
import _ from "lodash";
|
||||
import type { RealmRoleParams } from "./routes/RealmRole";
|
||||
import { omit, sortBy } from "lodash";
|
||||
import { RealmRoleParams, toRealmRole } from "./routes/RealmRole";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import {
|
||||
ClientRoleParams,
|
||||
ClientRoleRoute,
|
||||
toClientRole,
|
||||
} from "./routes/ClientRole";
|
||||
|
||||
type Role = RoleRepresentation & {
|
||||
clientId?: string;
|
||||
};
|
||||
|
||||
export type AssociatedRolesModalProps = {
|
||||
open: boolean;
|
||||
toggleDialog: () => void;
|
||||
onConfirm: (newReps: RoleRepresentation[]) => void;
|
||||
existingCompositeRoles?: RoleRepresentation[];
|
||||
allRoles?: RoleRepresentation[];
|
||||
onConfirm?: (newReps: RoleRepresentation[]) => void;
|
||||
omitComposites?: boolean;
|
||||
isRadio?: boolean;
|
||||
isMapperId?: boolean;
|
||||
};
|
||||
|
||||
export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
||||
type FilterType = "roles" | "clients";
|
||||
|
||||
export const AssociatedRolesModal = ({
|
||||
toggleDialog,
|
||||
onConfirm,
|
||||
omitComposites,
|
||||
isRadio,
|
||||
isMapperId,
|
||||
}: AssociatedRolesModalProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
const [name, setName] = useState("");
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const { realm } = useRealm();
|
||||
const [selectedRows, setSelectedRows] = useState<RoleRepresentation[]>([]);
|
||||
const [compositeRoles, setCompositeRoles] = useState<RoleRepresentation[]>();
|
||||
|
||||
const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false);
|
||||
const [filterType, setFilterType] = useState("roles");
|
||||
const [filterType, setFilterType] = useState<FilterType>("roles");
|
||||
const [key, setKey] = useState(0);
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
||||
const { id } = useParams<RealmRoleParams>();
|
||||
const clientRoleRouteMatch = useRouteMatch<ClientRoleParams>(
|
||||
ClientRoleRoute.path
|
||||
);
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const alphabetize = (rolesList: RoleRepresentation[]) => {
|
||||
return _.sortBy(rolesList, (role) => role.name?.toUpperCase());
|
||||
return sortBy(rolesList, (role) => role.name?.toUpperCase());
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const roles = await adminClient.roles.find();
|
||||
const loader = async (first?: number, max?: number, search?: string) => {
|
||||
const params: { [name: string]: string | number } = {
|
||||
first: first!,
|
||||
max: max!,
|
||||
};
|
||||
|
||||
if (!props.omitComposites) {
|
||||
const existingAdditionalRoles = await adminClient.roles.getCompositeRoles(
|
||||
{
|
||||
id,
|
||||
}
|
||||
);
|
||||
const allRoles = [...roles, ...existingAdditionalRoles];
|
||||
const searchParam = search || "";
|
||||
|
||||
const filterDupes: Role[] = allRoles.filter(
|
||||
(thing, index, self) =>
|
||||
index === self.findIndex((t) => t.name === thing.name)
|
||||
);
|
||||
|
||||
const clients = await adminClient.clients.find();
|
||||
filterDupes
|
||||
.filter((role) => role.clientRole)
|
||||
.map(
|
||||
(role) =>
|
||||
(role.clientId = clients.find(
|
||||
(client) => client.id === role.containerId
|
||||
)!.clientId!)
|
||||
);
|
||||
|
||||
return alphabetize(filterDupes).filter((role: RoleRepresentation) => {
|
||||
return (
|
||||
props.existingCompositeRoles?.find(
|
||||
(existing: RoleRepresentation) => existing.name === role.name
|
||||
) === undefined && role.name !== name
|
||||
);
|
||||
});
|
||||
if (searchParam) {
|
||||
params.search = searchParam;
|
||||
}
|
||||
return alphabetize(roles);
|
||||
|
||||
return adminClient.roles.find(params);
|
||||
};
|
||||
|
||||
const AliasRenderer = ({ id, name, clientId }: Role) => {
|
||||
|
@ -100,45 +100,44 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
/* this is still pretty expensive querying all client and then all roles */
|
||||
const clientRolesLoader = async () => {
|
||||
const clients = await adminClient.clients.find();
|
||||
const clientRoles = await Promise.all(
|
||||
clients.map(async (client) => {
|
||||
const roles = await adminClient.clients.listRoles({ id: client.id! });
|
||||
|
||||
const clientIdArray = clients.map((client) => client.id);
|
||||
return roles.map<Role>((role) => ({
|
||||
...role,
|
||||
clientId: client.clientId,
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
let rolesList: Role[] = [];
|
||||
for (const id of clientIdArray) {
|
||||
const clientRolesList = await adminClient.clients.listRoles({
|
||||
id: id as string,
|
||||
});
|
||||
rolesList = [...rolesList, ...clientRolesList];
|
||||
}
|
||||
return alphabetize(clientRoles.flat());
|
||||
};
|
||||
|
||||
rolesList
|
||||
.filter((role) => role.clientRole)
|
||||
.map(
|
||||
(role) =>
|
||||
(role.clientId = clients.find(
|
||||
(client) => client.id === role.containerId
|
||||
)!.clientId!)
|
||||
);
|
||||
const addComposites = async (composites: RoleRepresentation[]) => {
|
||||
const compositeArray = composites;
|
||||
|
||||
if (!props.omitComposites) {
|
||||
const existingAdditionalRoles = await adminClient.roles.getCompositeRoles(
|
||||
{
|
||||
const to = clientRoleRouteMatch
|
||||
? toClientRole({ ...clientRoleRouteMatch.params, tab: "AssociateRoles" })
|
||||
: toRealmRole({
|
||||
realm,
|
||||
id,
|
||||
}
|
||||
tab: "AssociatedRoles",
|
||||
});
|
||||
|
||||
try {
|
||||
await adminClient.roles.createComposite(
|
||||
{ roleId: id, realm },
|
||||
compositeArray
|
||||
);
|
||||
|
||||
return alphabetize(rolesList).filter((role: RoleRepresentation) => {
|
||||
return (
|
||||
existingAdditionalRoles.find(
|
||||
(existing: RoleRepresentation) => existing.name === role.name
|
||||
) === undefined && role.name !== name
|
||||
);
|
||||
});
|
||||
history.push(to);
|
||||
addAlert(t("addAssociatedRolesSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addError("roles:addAssociatedRolesError", error);
|
||||
}
|
||||
|
||||
return alphabetize(rolesList);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -146,11 +145,18 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
}, [filterType]);
|
||||
|
||||
useFetch(
|
||||
() =>
|
||||
!props.isMapperId
|
||||
? adminClient.roles.findOneById({ id })
|
||||
: Promise.resolve(null),
|
||||
(role) => setName(role ? role.name! : t("createRole")),
|
||||
async () => {
|
||||
const [role, compositeRoles] = await Promise.all([
|
||||
!isMapperId ? adminClient.roles.findOneById({ id }) : undefined,
|
||||
!omitComposites ? adminClient.roles.getCompositeRoles({ id }) : [],
|
||||
]);
|
||||
|
||||
return { role, compositeRoles };
|
||||
},
|
||||
({ role, compositeRoles }) => {
|
||||
setName(role ? role.name! : t("createRole"));
|
||||
setCompositeRoles(compositeRoles);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
|
@ -168,12 +174,20 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
setIsFilterDropdownOpen(!isFilterDropdownOpen);
|
||||
};
|
||||
|
||||
if (!compositeRoles) {
|
||||
return (
|
||||
<div className="pf-u-text-align-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
data-testid="addAssociatedRole"
|
||||
title={t("roles:associatedRolesModalTitle", { name })}
|
||||
isOpen={props.open}
|
||||
onClose={props.toggleDialog}
|
||||
isOpen
|
||||
onClose={toggleDialog}
|
||||
variant={ModalVariant.large}
|
||||
actions={[
|
||||
<Button
|
||||
|
@ -182,8 +196,12 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
variant="primary"
|
||||
isDisabled={!selectedRows.length}
|
||||
onClick={() => {
|
||||
props.toggleDialog();
|
||||
props.onConfirm(selectedRows);
|
||||
toggleDialog();
|
||||
if (onConfirm) {
|
||||
onConfirm(selectedRows);
|
||||
} else {
|
||||
addComposites(selectedRows);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("common:add")}
|
||||
|
@ -192,7 +210,7 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
key="cancel"
|
||||
variant="link"
|
||||
onClick={() => {
|
||||
props.toggleDialog();
|
||||
toggleDialog();
|
||||
}}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
|
@ -204,7 +222,9 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
loader={filterType === "roles" ? loader : clientRolesLoader}
|
||||
ariaLabelKey="roles:roleList"
|
||||
searchPlaceholderKey="roles:searchFor"
|
||||
isRadio={props.isRadio}
|
||||
isRadio={isRadio}
|
||||
isPaginated={filterType === "roles"}
|
||||
isRowDisabled={(r) => compositeRoles.some((o) => o.name === r.name)}
|
||||
searchTypeComponent={
|
||||
<Dropdown
|
||||
onSelect={() => onFilterDropdownSelect(filterType)}
|
||||
|
@ -234,7 +254,7 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
}
|
||||
canSelectAll
|
||||
onSelect={(rows) => {
|
||||
setSelectedRows([...rows]);
|
||||
setSelectedRows(rows.map((r) => omit(r, "clientId")));
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useHistory, useParams, useRouteMatch } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
|
@ -18,27 +18,22 @@ import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
|||
import { emptyFormatter } from "../util";
|
||||
import { AssociatedRolesModal } from "./AssociatedRolesModal";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import type { RoleFormType } from "./RealmRoleTabs";
|
||||
import type { AttributeForm } from "../components/attribute-form/AttributeForm";
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import _ from "lodash";
|
||||
|
||||
type AssociatedRolesTabProps = {
|
||||
additionalRoles: Role[];
|
||||
addComposites: (newReps: RoleRepresentation[]) => void;
|
||||
parentRole: RoleFormType;
|
||||
onRemove: (newReps: RoleRepresentation[]) => void;
|
||||
parentRole: AttributeForm;
|
||||
client?: ClientRepresentation;
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
type Role = RoleRepresentation & {
|
||||
clientId?: string;
|
||||
inherited?: string;
|
||||
};
|
||||
|
||||
export const AssociatedRolesTab = ({
|
||||
additionalRoles,
|
||||
addComposites,
|
||||
parentRole,
|
||||
onRemove,
|
||||
refresh: refreshParent,
|
||||
}: AssociatedRolesTabProps) => {
|
||||
const { t } = useTranslation("roles");
|
||||
const history = useHistory();
|
||||
|
@ -49,100 +44,65 @@ export const AssociatedRolesTab = ({
|
|||
|
||||
const [selectedRows, setSelectedRows] = useState<RoleRepresentation[]>([]);
|
||||
const [isInheritedHidden, setIsInheritedHidden] = useState(false);
|
||||
const [allRoles, setAllRoles] = useState<RoleRepresentation[]>([]);
|
||||
|
||||
const [count, setCount] = useState(0);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const inheritanceMap = React.useRef<{ [key: string]: string }>({});
|
||||
|
||||
const getSubRoles = async (role: Role, allRoles: Role[]): Promise<Role[]> => {
|
||||
// Fetch all composite roles
|
||||
const allCompositeRoles = await adminClient.roles.getCompositeRoles({
|
||||
id: role.id!,
|
||||
const subRoles = async (result: Role[], roles: Role[]): Promise<Role[]> => {
|
||||
const promises = roles.map(async (r) => {
|
||||
if (result.find((o) => o.id === r.id)) return result;
|
||||
result.push(r);
|
||||
if (r.composite) {
|
||||
const subList = (await adminClient.roles.getCompositeRoles({
|
||||
id: r.id!,
|
||||
})) as Role[];
|
||||
subList.map((o) => (o.inherited = r.name));
|
||||
result.concat(await subRoles(result, subList));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
// Need to ensure we don't get into an infinite loop, do not add any role that is already there or the starting role
|
||||
const newRoles: Promise<RoleRepresentation[]> = allCompositeRoles.reduce(
|
||||
async (acc: Promise<RoleRepresentation[]>, newRole) => {
|
||||
const resolvedRoles = await acc;
|
||||
if (!allRoles.find((ar) => ar.id === newRole.id)) {
|
||||
inheritanceMap.current[newRole.id!] = role.name!;
|
||||
resolvedRoles.push(newRole);
|
||||
const subRoles = await getSubRoles(newRole, [
|
||||
...allRoles,
|
||||
...resolvedRoles,
|
||||
]);
|
||||
resolvedRoles.push(...subRoles);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
Promise.resolve([] as RoleRepresentation[])
|
||||
);
|
||||
|
||||
return newRoles;
|
||||
await Promise.all(promises);
|
||||
return [...result];
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const alphabetize = (rolesList: Role[]) => {
|
||||
return _.sortBy(rolesList, (role) => role.name?.toUpperCase());
|
||||
};
|
||||
const clients = await adminClient.clients.find();
|
||||
const loader = async (first?: number, max?: number, search?: string) => {
|
||||
const compositeRoles = await adminClient.roles.getCompositeRoles({
|
||||
id: parentRole.id!,
|
||||
first: first,
|
||||
max: max!,
|
||||
search: search,
|
||||
});
|
||||
setCount(compositeRoles.length);
|
||||
|
||||
if (isInheritedHidden) {
|
||||
setAllRoles(additionalRoles);
|
||||
return alphabetize(
|
||||
additionalRoles.filter(
|
||||
(role) =>
|
||||
role.containerId === "master" && !inheritanceMap.current[role.id!]
|
||||
)
|
||||
);
|
||||
if (!isInheritedHidden) {
|
||||
const children = await subRoles([], compositeRoles);
|
||||
compositeRoles.splice(0, compositeRoles.length);
|
||||
compositeRoles.push(...children);
|
||||
}
|
||||
|
||||
const fetchedRoles: Promise<Role[]> = additionalRoles.reduce(
|
||||
async (acc: Promise<Role[]>, role) => {
|
||||
const resolvedRoles = await acc;
|
||||
resolvedRoles.push(role);
|
||||
const subRoles = await getSubRoles(role, resolvedRoles);
|
||||
resolvedRoles.push(...subRoles);
|
||||
return acc;
|
||||
},
|
||||
Promise.resolve([] as Role[])
|
||||
await Promise.all(
|
||||
compositeRoles.map(async (role) => {
|
||||
if (role.clientRole) {
|
||||
role.containerId = (
|
||||
await adminClient.clients.findOne({
|
||||
id: role.containerId!,
|
||||
})
|
||||
)?.clientId;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return fetchedRoles.then((results: Role[]) => {
|
||||
const filterDupes = results.filter(
|
||||
(thing, index, self) =>
|
||||
index === self.findIndex((t) => t.name === thing.name)
|
||||
);
|
||||
filterDupes
|
||||
.filter((role) => role.clientRole)
|
||||
.map(
|
||||
(role) =>
|
||||
(role.clientId = clients.find(
|
||||
(client) => client.id === role.containerId
|
||||
)!.clientId!)
|
||||
);
|
||||
|
||||
return alphabetize(additionalRoles);
|
||||
});
|
||||
return compositeRoles;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [additionalRoles, isInheritedHidden]);
|
||||
|
||||
const InheritedRoleName = (role: RoleRepresentation) =>
|
||||
inheritanceMap.current[role.id!];
|
||||
|
||||
const AliasRenderer = ({ id, name, clientId }: Role) => {
|
||||
const AliasRenderer = ({ id, name, clientRole, containerId }: Role) => {
|
||||
return (
|
||||
<>
|
||||
{clientId && (
|
||||
{clientRole && (
|
||||
<Label color="blue" key={`label-${id}`}>
|
||||
{clientId}
|
||||
{containerId}
|
||||
</Label>
|
||||
)}{" "}
|
||||
{name}
|
||||
|
@ -150,7 +110,20 @@ export const AssociatedRolesTab = ({
|
|||
);
|
||||
};
|
||||
|
||||
const toggleModal = () => setOpen(!open);
|
||||
const toggleModal = () => {
|
||||
setOpen(!open);
|
||||
refresh();
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
if (selectedRows.length >= count) {
|
||||
refreshParent();
|
||||
const loc = url.replace(/\/AssociatedRoles/g, "/details");
|
||||
history.push(loc);
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "roles:roleRemoveAssociatedRoleConfirm",
|
||||
|
@ -163,7 +136,7 @@ export const AssociatedRolesTab = ({
|
|||
onConfirm: async () => {
|
||||
try {
|
||||
await adminClient.roles.delCompositeRoles({ id }, selectedRows);
|
||||
onRemove(selectedRows);
|
||||
reload();
|
||||
setSelectedRows([]);
|
||||
|
||||
addAlert(t("associatedRolesRemoved"), AlertVariant.success);
|
||||
|
@ -177,21 +150,15 @@ export const AssociatedRolesTab = ({
|
|||
useConfirmDialog({
|
||||
titleKey: t("roles:removeAssociatedRoles") + "?",
|
||||
messageKey: t("roles:removeAllAssociatedRolesConfirmDialog", {
|
||||
name: parentRole?.name || t("createRole"),
|
||||
name: parentRole.name || t("createRole"),
|
||||
}),
|
||||
continueButtonLabel: "common:remove",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
if (selectedRows.length >= allRoles.length) {
|
||||
onRemove(selectedRows);
|
||||
const loc = url.replace(/\/AssociatedRoles/g, "/details");
|
||||
history.push(loc);
|
||||
}
|
||||
onRemove(selectedRows);
|
||||
await adminClient.roles.delCompositeRoles({ id }, selectedRows);
|
||||
addAlert(t("associatedRolesRemoved"), AlertVariant.success);
|
||||
refresh();
|
||||
reload();
|
||||
} catch (error) {
|
||||
addError("roles:roleDeleteError", error);
|
||||
}
|
||||
|
@ -203,20 +170,21 @@ export const AssociatedRolesTab = ({
|
|||
<PageSection variant="light" padding={{ default: "noPadding" }}>
|
||||
<DeleteConfirm />
|
||||
<DeleteAssociatedRolesConfirm />
|
||||
<AssociatedRolesModal
|
||||
onConfirm={addComposites}
|
||||
existingCompositeRoles={additionalRoles}
|
||||
open={open}
|
||||
toggleDialog={() => setOpen(!open)}
|
||||
/>
|
||||
{open && <AssociatedRolesModal toggleDialog={toggleModal} />}
|
||||
<KeycloakDataTable
|
||||
key={key}
|
||||
loader={loader}
|
||||
ariaLabelKey="roles:roleList"
|
||||
searchPlaceholderKey="roles:searchFor"
|
||||
canSelectAll
|
||||
isPaginated
|
||||
onSelect={(rows) => {
|
||||
setSelectedRows([...rows]);
|
||||
setSelectedRows([
|
||||
...rows.map((r) => {
|
||||
delete r.inherited;
|
||||
return r;
|
||||
}),
|
||||
]);
|
||||
}}
|
||||
toolbarItem={
|
||||
<>
|
||||
|
@ -225,7 +193,10 @@ export const AssociatedRolesTab = ({
|
|||
label="Hide inherited roles"
|
||||
key="associated-roles-check"
|
||||
id="kc-hide-inherited-roles-checkbox"
|
||||
onChange={() => setIsInheritedHidden(!isInheritedHidden)}
|
||||
onChange={() => {
|
||||
setIsInheritedHidden(!isInheritedHidden);
|
||||
refresh();
|
||||
}}
|
||||
isChecked={isInheritedHidden}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
|
@ -269,9 +240,8 @@ export const AssociatedRolesTab = ({
|
|||
cellFormatters: [emptyFormatter()],
|
||||
},
|
||||
{
|
||||
name: "containerId",
|
||||
name: "inherited",
|
||||
displayKey: "roles:inheritedFrom",
|
||||
cellRenderer: InheritedRoleName,
|
||||
cellFormatters: [emptyFormatter()],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -3,17 +3,18 @@ import {
|
|||
ActionGroup,
|
||||
Button,
|
||||
FormGroup,
|
||||
PageSection,
|
||||
TextArea,
|
||||
TextInput,
|
||||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { UseFormMethods } from "react-hook-form";
|
||||
import type { RoleFormType } from "./RealmRoleTabs";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import type { AttributeForm } from "../components/attribute-form/AttributeForm";
|
||||
|
||||
export type RealmRoleFormProps = {
|
||||
form: UseFormMethods<RoleFormType>;
|
||||
form: UseFormMethods<AttributeForm>;
|
||||
save: () => void;
|
||||
editMode: boolean;
|
||||
reset: () => void;
|
||||
|
@ -28,66 +29,70 @@ export const RealmRoleForm = ({
|
|||
const { t } = useTranslation("roles");
|
||||
|
||||
return (
|
||||
<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")}
|
||||
<PageSection variant="light">
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
onSubmit={handleSubmit(save)}
|
||||
role="manage-realm"
|
||||
className="pf-u-mt-lg"
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: !editMode })}
|
||||
type="text"
|
||||
id="kc-name"
|
||||
name="name"
|
||||
isReadOnly={editMode}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("common:description")}
|
||||
fieldId="kc-description"
|
||||
validated={
|
||||
errors.description ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
helperTextInvalid={errors.description?.message}
|
||||
>
|
||||
<TextArea
|
||||
name="description"
|
||||
aria-label="description"
|
||||
isDisabled={getValues().name?.includes("default-roles")}
|
||||
ref={register({
|
||||
maxLength: {
|
||||
value: 255,
|
||||
message: t("common:maxLength", { length: 255 }),
|
||||
},
|
||||
})}
|
||||
type="text"
|
||||
<FormGroup
|
||||
label={t("roleName")}
|
||||
fieldId="kc-name"
|
||||
isRequired
|
||||
validated={errors.name ? "error" : "default"}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: !editMode })}
|
||||
type="text"
|
||||
id="kc-name"
|
||||
name="name"
|
||||
isReadOnly={editMode}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("common:description")}
|
||||
fieldId="kc-description"
|
||||
validated={
|
||||
errors.description
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
id="kc-role-description"
|
||||
/>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={save}
|
||||
data-testid="realm-roles-save-button"
|
||||
helperTextInvalid={errors.description?.message}
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button onClick={() => reset()} variant="link">
|
||||
{editMode ? t("common:revert") : t("common:cancel")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
<TextArea
|
||||
name="description"
|
||||
aria-label="description"
|
||||
isDisabled={getValues().name?.includes("default-roles")}
|
||||
ref={register({
|
||||
maxLength: {
|
||||
value: 255,
|
||||
message: t("common:maxLength", { length: 255 }),
|
||||
},
|
||||
})}
|
||||
type="text"
|
||||
validated={
|
||||
errors.description
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
id="kc-role-description"
|
||||
/>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={save}
|
||||
data-testid="realm-roles-save-button"
|
||||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button onClick={() => reset()} variant="link">
|
||||
{editMode ? t("common:revert") : t("common:cancel")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</PageSection>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useHistory, useParams, useRouteMatch } from "react-router-dom";
|
||||
import {
|
||||
AlertVariant,
|
||||
|
@ -10,16 +10,16 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import { omit } from "lodash";
|
||||
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||
import type Composites from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||
import {
|
||||
KeyValueType,
|
||||
AttributesForm,
|
||||
attributesToArray,
|
||||
arrayToAttributes,
|
||||
AttributeForm,
|
||||
} from "../components/attribute-form/AttributeForm";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
|
@ -31,10 +31,6 @@ import { AssociatedRolesTab } from "./AssociatedRolesTab";
|
|||
import { UsersInRoleTab } from "./UsersInRoleTab";
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
|
||||
export type RoleFormType = Omit<RoleRepresentation, "attributes"> & {
|
||||
attributes: KeyValueType[];
|
||||
};
|
||||
|
||||
type myRealmRepresentation = RealmRepresentation & {
|
||||
defaultRole?: {
|
||||
id: string;
|
||||
|
@ -44,11 +40,14 @@ type myRealmRepresentation = RealmRepresentation & {
|
|||
|
||||
export default function RealmRoleTabs() {
|
||||
const { t } = useTranslation("roles");
|
||||
const form = useForm<RoleFormType>({ mode: "onChange" });
|
||||
const form = useForm<AttributeForm>({
|
||||
mode: "onChange",
|
||||
});
|
||||
const { control, setValue, getValues, trigger, reset } = form;
|
||||
const history = useHistory();
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const [role, setRole] = useState<RoleFormType>();
|
||||
const [role, setRole] = useState<AttributeForm>();
|
||||
|
||||
const { id, clientId } = useParams<{ id: string; clientId: string }>();
|
||||
|
||||
|
@ -62,10 +61,6 @@ export default function RealmRoleTabs() {
|
|||
setKey(`${new Date().getTime()}`);
|
||||
};
|
||||
|
||||
const [additionalRoles, setAdditionalRoles] = useState<RoleRepresentation[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
@ -80,104 +75,93 @@ export default function RealmRoleTabs() {
|
|||
const [realm, setRealm] = useState<myRealmRepresentation>();
|
||||
|
||||
useFetch(
|
||||
() => adminClient.realms.findOne({ realm: realmName }),
|
||||
(realm) => {
|
||||
if (!realm) {
|
||||
async () => {
|
||||
const realm = await adminClient.realms.findOne({ realm: realmName });
|
||||
if (!id) {
|
||||
return { realm };
|
||||
}
|
||||
const role = await adminClient.roles.findOneById({ id });
|
||||
return { realm, role };
|
||||
},
|
||||
({ realm, role }) => {
|
||||
if (!realm || (!role && id)) {
|
||||
throw new Error(t("common:notFound"));
|
||||
}
|
||||
|
||||
if (role) {
|
||||
const convertedRole = convert(role);
|
||||
setRole(convertedRole);
|
||||
Object.entries(convertedRole).map((entry) => {
|
||||
setValue(entry[0], entry[1]);
|
||||
});
|
||||
}
|
||||
|
||||
setRealm(realm);
|
||||
},
|
||||
|
||||
[]
|
||||
[key]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const update = async () => {
|
||||
if (id) {
|
||||
const fetchedRole = await adminClient.roles.findOneById({ id });
|
||||
if (!fetchedRole) {
|
||||
throw new Error(t("common:notFound"));
|
||||
}
|
||||
|
||||
const allAdditionalRoles = await adminClient.roles.getCompositeRoles({
|
||||
id,
|
||||
});
|
||||
setAdditionalRoles(allAdditionalRoles);
|
||||
|
||||
const convertedRole = convert(fetchedRole);
|
||||
Object.entries(convertedRole).map((entry) => {
|
||||
form.setValue(entry[0], entry[1]);
|
||||
});
|
||||
setRole(convertedRole);
|
||||
}
|
||||
};
|
||||
setTimeout(update, 100);
|
||||
}, [key]);
|
||||
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control: form.control,
|
||||
control,
|
||||
name: "attributes",
|
||||
});
|
||||
|
||||
const save = async () => {
|
||||
try {
|
||||
const role = form.getValues();
|
||||
const values = getValues();
|
||||
if (
|
||||
role.attributes &&
|
||||
role.attributes[role.attributes.length - 1].key === ""
|
||||
values.attributes &&
|
||||
values.attributes[values.attributes.length - 1]?.key === ""
|
||||
) {
|
||||
form.setValue(
|
||||
setValue(
|
||||
"attributes",
|
||||
role.attributes.slice(0, role.attributes.length - 1)
|
||||
values.attributes.slice(0, values.attributes.length - 1)
|
||||
);
|
||||
}
|
||||
if (!(await form.trigger())) {
|
||||
if (!(await trigger())) {
|
||||
return;
|
||||
}
|
||||
const { attributes, ...rest } = role;
|
||||
const roleRepresentation: RoleRepresentation = rest;
|
||||
const { attributes, ...rest } = values;
|
||||
let roleRepresentation: RoleRepresentation = rest;
|
||||
if (id) {
|
||||
if (attributes) {
|
||||
roleRepresentation.attributes = arrayToAttributes(attributes);
|
||||
}
|
||||
roleRepresentation = {
|
||||
...omit(role!, "attributes"),
|
||||
...roleRepresentation,
|
||||
};
|
||||
if (!clientId) {
|
||||
await adminClient.roles.updateById({ id }, roleRepresentation);
|
||||
} else {
|
||||
await adminClient.clients.updateRole(
|
||||
{ id: clientId, roleName: role.name! },
|
||||
{ id: clientId, roleName: values.name! },
|
||||
roleRepresentation
|
||||
);
|
||||
}
|
||||
|
||||
await adminClient.roles.createComposite(
|
||||
{ roleId: id, realm: realmName },
|
||||
additionalRoles
|
||||
);
|
||||
|
||||
setRole(role);
|
||||
form.reset(role);
|
||||
setRole(convert(roleRepresentation));
|
||||
} else {
|
||||
let createdRole;
|
||||
if (!clientId) {
|
||||
await adminClient.roles.create(roleRepresentation);
|
||||
createdRole = await adminClient.roles.findOneByName({
|
||||
name: role.name!,
|
||||
name: values.name!,
|
||||
});
|
||||
} else {
|
||||
await adminClient.clients.createRole({
|
||||
id: clientId,
|
||||
name: role.name,
|
||||
name: values.name,
|
||||
});
|
||||
if (role.description) {
|
||||
if (values.description) {
|
||||
await adminClient.clients.updateRole(
|
||||
{ id: clientId, roleName: role.name! },
|
||||
{ id: clientId, roleName: values.name! },
|
||||
roleRepresentation
|
||||
);
|
||||
}
|
||||
createdRole = await adminClient.clients.findRole({
|
||||
id: clientId,
|
||||
roleName: role.name!,
|
||||
roleName: values.name!,
|
||||
});
|
||||
}
|
||||
if (!createdRole) {
|
||||
|
@ -195,23 +179,6 @@ export default function RealmRoleTabs() {
|
|||
}
|
||||
};
|
||||
|
||||
const addComposites = async (composites: Composites[]): Promise<void> => {
|
||||
const compositeArray = composites;
|
||||
setAdditionalRoles([...additionalRoles, ...compositeArray]);
|
||||
|
||||
try {
|
||||
await adminClient.roles.createComposite(
|
||||
{ roleId: id, realm: realmName },
|
||||
compositeArray
|
||||
);
|
||||
history.push(url.substr(0, url.lastIndexOf("/") + 1) + "AssociatedRoles");
|
||||
refresh();
|
||||
addAlert(t("addAssociatedRolesSuccess"), AlertVariant.success);
|
||||
} catch (error) {
|
||||
addError("roles:addAssociatedRolesError", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "roles:roleDeleteConfirm",
|
||||
messageKey: t("roles:roleDeleteConfirmDialog", {
|
||||
|
@ -297,6 +264,9 @@ export default function RealmRoleTabs() {
|
|||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const additionalRoles = await adminClient.roles.getCompositeRoles({
|
||||
id: role!.id!,
|
||||
});
|
||||
await adminClient.roles.delCompositeRoles({ id }, additionalRoles);
|
||||
addAlert(
|
||||
t("compositeRoleOff"),
|
||||
|
@ -312,24 +282,22 @@ export default function RealmRoleTabs() {
|
|||
},
|
||||
});
|
||||
|
||||
const toggleModal = () => setOpen(!open);
|
||||
const toggleModal = () => {
|
||||
setOpen(!open);
|
||||
refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteConfirm />
|
||||
<DeleteAllAssociatedRolesConfirm />
|
||||
<AssociatedRolesModal
|
||||
onConfirm={addComposites}
|
||||
existingCompositeRoles={additionalRoles}
|
||||
open={open}
|
||||
toggleDialog={() => setOpen(!open)}
|
||||
/>
|
||||
{open && <AssociatedRolesModal toggleDialog={toggleModal} />}
|
||||
<ViewHeader
|
||||
titleKey={role?.name || t("createRole")}
|
||||
badges={[
|
||||
{
|
||||
id: "composite-role-badge",
|
||||
text: additionalRoles.length > 0 ? t("composite") : "",
|
||||
text: role?.composite ? t("composite") : "",
|
||||
readonly: true,
|
||||
},
|
||||
]}
|
||||
|
@ -340,38 +308,27 @@ export default function RealmRoleTabs() {
|
|||
/>
|
||||
<PageSection variant="light" className="pf-u-p-0">
|
||||
{id && (
|
||||
<KeycloakTabs isBox>
|
||||
<KeycloakTabs isBox mountOnEnter>
|
||||
<Tab
|
||||
eventKey="details"
|
||||
title={<TabTitleText>{t("common:details")}</TabTitleText>}
|
||||
>
|
||||
<PageSection variant="light">
|
||||
<RealmRoleForm
|
||||
reset={() => form.reset(role)}
|
||||
form={form}
|
||||
save={save}
|
||||
editMode={true}
|
||||
/>
|
||||
</PageSection>
|
||||
<RealmRoleForm
|
||||
reset={() => reset(role)}
|
||||
form={form}
|
||||
save={save}
|
||||
editMode={true}
|
||||
/>
|
||||
</Tab>
|
||||
{additionalRoles.length > 0 && (
|
||||
{role?.composite && (
|
||||
<Tab
|
||||
eventKey="AssociatedRoles"
|
||||
title={<TabTitleText>{t("associatedRolesText")}</TabTitleText>}
|
||||
>
|
||||
<PageSection variant="light">
|
||||
{role && (
|
||||
<AssociatedRolesTab
|
||||
additionalRoles={additionalRoles}
|
||||
addComposites={addComposites}
|
||||
parentRole={role}
|
||||
onRemove={() => refresh()}
|
||||
/>
|
||||
)}
|
||||
</PageSection>
|
||||
<AssociatedRolesTab parentRole={role} refresh={refresh} />
|
||||
</Tab>
|
||||
)}
|
||||
{form.getValues().name !== realm?.defaultRole?.name && (
|
||||
{role?.name !== realm?.defaultRole?.name && (
|
||||
<Tab
|
||||
eventKey="attributes"
|
||||
className="kc-attributes-tab"
|
||||
|
@ -381,11 +338,11 @@ export default function RealmRoleTabs() {
|
|||
form={form}
|
||||
save={save}
|
||||
array={{ fields, append, remove }}
|
||||
reset={() => form.reset(role)}
|
||||
reset={() => reset(role)}
|
||||
/>
|
||||
</Tab>
|
||||
)}
|
||||
{form.getValues().name !== realm?.defaultRole?.name && (
|
||||
{role?.name !== realm?.defaultRole?.name && (
|
||||
<Tab
|
||||
eventKey="users-in-role"
|
||||
title={<TabTitleText>{t("usersInRole")}</TabTitleText>}
|
||||
|
@ -396,14 +353,12 @@ export default function RealmRoleTabs() {
|
|||
</KeycloakTabs>
|
||||
)}
|
||||
{!id && (
|
||||
<PageSection variant="light">
|
||||
<RealmRoleForm
|
||||
reset={() => form.reset()}
|
||||
form={form}
|
||||
save={save}
|
||||
editMode={false}
|
||||
/>
|
||||
</PageSection>
|
||||
<RealmRoleForm
|
||||
reset={() => reset(role)}
|
||||
form={form}
|
||||
save={save}
|
||||
editMode={false}
|
||||
/>
|
||||
)}
|
||||
</PageSection>
|
||||
</>
|
||||
|
|
|
@ -12,15 +12,15 @@ import {
|
|||
} from "@patternfly/react-table";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
|
||||
import type { AttributeForm } from "../components/attribute-form/AttributeForm";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import type { RoleFormType } from "./RealmRoleTabs";
|
||||
|
||||
import "./RealmRolesSection.css";
|
||||
|
||||
export type KeyValueType = { key: string; value: string };
|
||||
|
||||
type RoleAttributesProps = {
|
||||
form: UseFormMethods<RoleFormType>;
|
||||
form: UseFormMethods<AttributeForm>;
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
array: {
|
||||
|
|
|
@ -46,7 +46,7 @@ const RoleLink: FunctionComponent<RoleLinkProps> = ({ children, role }) => {
|
|||
const clientRouteMatch = useRouteMatch<ClientParams>(ClientRoute.path);
|
||||
const to = clientRouteMatch
|
||||
? toClientRole({ ...clientRouteMatch.params, id: role.id!, tab: "details" })
|
||||
: toRealmRole({ realm, id: role.id! });
|
||||
: toRealmRole({ realm, id: role.id!, tab: "details" });
|
||||
|
||||
return (
|
||||
<Link key={role.id} to={to}>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { lazy } from "react";
|
||||
import type { LocationDescriptorObject } from "history";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { lazy } from "react";
|
||||
import type { LocationDescriptorObject } from "history";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { lazy } from "react";
|
||||
import type { LocationDescriptorObject } from "history";
|
||||
import { generatePath } from "react-router-dom";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
|
||||
export type ClientRoleTab = "details" | "attributes" | "users-in-role";
|
||||
export type ClientRoleTab =
|
||||
| "details"
|
||||
| "attributes"
|
||||
| "users-in-role"
|
||||
| "AssociateRoles";
|
||||
|
||||
export type ClientRoleParams = {
|
||||
realm: string;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { LocationDescriptorObject } from "history";
|
||||
import { lazy } from "react";
|
||||
import { generatePath } from "react-router";
|
||||
import type { LocationDescriptorObject } from "history";
|
||||
import type { RouteDef } from "../../route-config";
|
||||
|
||||
export type RealmRoleTab =
|
||||
|
|
|
@ -19,7 +19,7 @@ import userRoutes from "./user/routes";
|
|||
|
||||
export type RouteDef = {
|
||||
path: string;
|
||||
component: ComponentType;
|
||||
component: ComponentType | React.LazyExoticComponent<() => JSX.Element>;
|
||||
breadcrumb?: (t: TFunction) => string | ComponentType<any>;
|
||||
access: AccessType | AccessType[];
|
||||
matchOptions?: MatchOptions;
|
||||
|
|
|
@ -44,7 +44,7 @@ export const UserAttributes = ({ user }: UserAttributesProps) => {
|
|||
|
||||
const save = async (attributeForm: AttributeForm) => {
|
||||
try {
|
||||
const attributes = arrayToAttributes(attributeForm.attributes);
|
||||
const attributes = arrayToAttributes(attributeForm.attributes!);
|
||||
await adminClient.users.update({ id: user.id! }, { ...user, attributes });
|
||||
|
||||
form.setValue("attributes", convertAttributes(attributes));
|
||||
|
|
Loading…
Reference in a new issue