da2fa32a69
* WIP modal * modal WIP add modal place modal in separate file format wip implementation getCompositeRoles with Jeff add associated roles tab WIP addComposites function WIP fix post call additional roles fetch big rebase WIP refresh resolve conflicts with Erik latest -> fixes role creation cypress tests, bump react-hook-form to remove console warnings delete add refresh with Jeff, update cypress tests, select additional roles tab on add make dropdownId optional format add additionalRolesModal to associated roles tab add toolbar items add toolbaritems to associated role tab, matches mock rebase add descriptions to alert add badge fix badge logic fix URL when associate roles are deleted, format update cypress test format add associated roles refresh, PR feedback from Erik add associated roles refresh, PR feedback from Erik lint * add inherited roles with Jeff * hide inherited roles * clean up * rebase * clean up modal file * remove filter dropdown * remove log stmts * fix types error with Erik * format after rebase * fix lint * fix cypress test * PR feedback from Erik * PR feedback from Erik * remove comment * remove client hook * remove unused declaration
352 lines
11 KiB
TypeScript
352 lines
11 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import { useHistory, useParams, useRouteMatch } from "react-router-dom";
|
|
import {
|
|
AlertVariant,
|
|
ButtonVariant,
|
|
DropdownItem,
|
|
PageSection,
|
|
Tab,
|
|
TabTitleText,
|
|
} from "@patternfly/react-core";
|
|
import { useTranslation } from "react-i18next";
|
|
import { useFieldArray, useForm } from "react-hook-form";
|
|
|
|
import { useAlerts } from "../components/alert/Alerts";
|
|
import { useAdminClient } from "../context/auth/AdminClient";
|
|
import RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
|
|
import Composites from "keycloak-admin/lib/defs/roleRepresentation";
|
|
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";
|
|
import { AssociatedRolesModal } from "./AssociatedRolesModal";
|
|
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
|
import { AssociatedRolesTab } from "./AssociatedRolesTab";
|
|
|
|
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[];
|
|
}): KeyValueType[] => {
|
|
if (!attributes || Object.keys(attributes).length == 0) {
|
|
return [];
|
|
}
|
|
return Object.keys(attributes).map((key) => ({
|
|
key: key,
|
|
value: attributes[key][0],
|
|
}));
|
|
};
|
|
|
|
export type RoleFormType = Omit<RoleRepresentation, "attributes"> & {
|
|
attributes: KeyValueType[];
|
|
};
|
|
|
|
export const RealmRoleTabs = () => {
|
|
const { t } = useTranslation("roles");
|
|
const form = useForm<RoleFormType>({ mode: "onChange" });
|
|
const history = useHistory();
|
|
|
|
const adminClient = useAdminClient();
|
|
const [role, setRole] = useState<RoleFormType>();
|
|
|
|
const { id, clientId } = useParams<{ id: string; clientId: string }>();
|
|
const { url } = useRouteMatch();
|
|
|
|
const { realm } = useRealm();
|
|
|
|
const [key, setKey] = useState("");
|
|
|
|
const refresh = () => {
|
|
setKey(`${new Date().getTime()}`);
|
|
};
|
|
|
|
const [additionalRoles, setAdditionalRoles] = useState<RoleRepresentation[]>(
|
|
[]
|
|
);
|
|
const { addAlert } = useAlerts();
|
|
|
|
const [open, setOpen] = useState(false);
|
|
const convert = (role: RoleRepresentation) => {
|
|
const { attributes, ...rest } = role;
|
|
return {
|
|
attributes: attributesToArray(attributes),
|
|
...rest,
|
|
};
|
|
};
|
|
|
|
useEffect(() => {
|
|
const update = async () => {
|
|
if (id) {
|
|
const fetchedRole = await adminClient.roles.findOneById({ id });
|
|
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,
|
|
name: "attributes",
|
|
});
|
|
|
|
useEffect(() => append({ key: "", value: "" }), [append, role]);
|
|
|
|
const save = async (role: RoleFormType) => {
|
|
try {
|
|
const { attributes, ...rest } = role;
|
|
const roleRepresentation: RoleRepresentation = rest;
|
|
if (id) {
|
|
if (attributes) {
|
|
roleRepresentation.attributes = arrayToAttributes(attributes);
|
|
}
|
|
if (!clientId) {
|
|
await adminClient.roles.updateById({ id }, roleRepresentation);
|
|
} else {
|
|
await adminClient.clients.updateRole(
|
|
{ id: clientId, roleName: role.name! },
|
|
roleRepresentation
|
|
);
|
|
}
|
|
|
|
await adminClient.roles.createComposite(
|
|
{ roleId: id, realm },
|
|
additionalRoles
|
|
);
|
|
|
|
setRole(role);
|
|
} else {
|
|
let createdRole;
|
|
if (!clientId) {
|
|
await adminClient.roles.create(roleRepresentation);
|
|
createdRole = await adminClient.roles.findOneByName({
|
|
name: role.name!,
|
|
});
|
|
} else {
|
|
await adminClient.clients.createRole({
|
|
id: clientId,
|
|
name: role.name,
|
|
});
|
|
if (role.description) {
|
|
await adminClient.clients.updateRole(
|
|
{ id: clientId, roleName: role.name! },
|
|
roleRepresentation
|
|
);
|
|
}
|
|
createdRole = await adminClient.clients.findRole({
|
|
id: clientId,
|
|
roleName: role.name!,
|
|
});
|
|
}
|
|
setRole(convert(createdRole));
|
|
history.push(
|
|
url.substr(0, url.lastIndexOf("/") + 1) + createdRole.id + "/details"
|
|
);
|
|
}
|
|
addAlert(t(id ? "roleSaveSuccess" : "roleCreated"), AlertVariant.success);
|
|
} catch (error) {
|
|
addAlert(
|
|
t((id ? "roleSave" : "roleCreate") + "Error", {
|
|
error: error.response.data?.errorMessage || error,
|
|
}),
|
|
AlertVariant.danger
|
|
);
|
|
}
|
|
};
|
|
|
|
const addComposites = async (composites: Composites[]): Promise<void> => {
|
|
const compositeArray = composites;
|
|
setAdditionalRoles([...additionalRoles, ...compositeArray]);
|
|
|
|
try {
|
|
await adminClient.roles.createComposite(
|
|
{ roleId: id, realm: realm },
|
|
compositeArray
|
|
);
|
|
history.push(url.substr(0, url.lastIndexOf("/") + 1) + "AssociatedRoles");
|
|
refresh();
|
|
addAlert(t("addAssociatedRolesSuccess"), AlertVariant.success);
|
|
} catch (error) {
|
|
addAlert(t("addAssociatedRolesError", { error }), AlertVariant.danger);
|
|
}
|
|
};
|
|
|
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
|
titleKey: "roles:roleDeleteConfirm",
|
|
messageKey: t("roles:roleDeleteConfirmDialog", {
|
|
name: role?.name || t("createRole"),
|
|
}),
|
|
continueButtonLabel: "common:delete",
|
|
continueButtonVariant: ButtonVariant.danger,
|
|
onConfirm: async () => {
|
|
try {
|
|
if (!clientId) {
|
|
await adminClient.roles.delById({ id });
|
|
} else {
|
|
await adminClient.clients.delRole({
|
|
id: clientId,
|
|
roleName: role!.name as string,
|
|
});
|
|
}
|
|
addAlert(t("roleDeletedSuccess"), AlertVariant.success);
|
|
const loc = url.replace(/\/attributes/g, "");
|
|
history.replace(`${loc.substr(0, loc.lastIndexOf("/"))}`);
|
|
} catch (error) {
|
|
addAlert(`${t("roleDeleteError")} ${error}`, AlertVariant.danger);
|
|
}
|
|
},
|
|
});
|
|
|
|
const [
|
|
toggleDeleteAllAssociatedRolesDialog,
|
|
DeleteAllAssociatedRolesConfirm,
|
|
] = useConfirmDialog({
|
|
titleKey: t("roles:removeAllAssociatedRoles") + "?",
|
|
messageKey: t("roles:removeAllAssociatedRolesConfirmDialog", {
|
|
name: role?.name || t("createRole"),
|
|
}),
|
|
continueButtonLabel: "common:delete",
|
|
continueButtonVariant: ButtonVariant.danger,
|
|
onConfirm: async () => {
|
|
try {
|
|
await adminClient.roles.delCompositeRoles({ id }, additionalRoles);
|
|
addAlert(
|
|
t("compositeRoleOff"),
|
|
AlertVariant.success,
|
|
t("compositesRemovedAlertDescription")
|
|
);
|
|
const loc = url.replace(/\/AssociatedRoles/g, "/details");
|
|
history.push(loc);
|
|
refresh();
|
|
} catch (error) {
|
|
addAlert(`${t("roleDeleteError")} ${error}`, AlertVariant.danger);
|
|
}
|
|
},
|
|
});
|
|
|
|
const toggleModal = () => setOpen(!open);
|
|
|
|
return (
|
|
<>
|
|
<DeleteConfirm />
|
|
<DeleteAllAssociatedRolesConfirm />
|
|
<AssociatedRolesModal
|
|
onConfirm={addComposites}
|
|
existingCompositeRoles={additionalRoles}
|
|
open={open}
|
|
toggleDialog={() => setOpen(!open)}
|
|
/>
|
|
<ViewHeader
|
|
titleKey={role?.name || t("createRole")}
|
|
badge={additionalRoles.length > 0 ? t("composite") : ""}
|
|
badgeId="composite-role-badge"
|
|
badgeIsRead={true}
|
|
subKey={id ? "" : "roles:roleCreateExplain"}
|
|
actionsDropdownId="roles-actions-dropdown"
|
|
dropdownItems={
|
|
url.includes("AssociatedRoles")
|
|
? [
|
|
<DropdownItem
|
|
key="delete-all-associated"
|
|
component="button"
|
|
onClick={() => toggleDeleteAllAssociatedRolesDialog()}
|
|
>
|
|
{t("roles:removeAllAssociatedRoles")}
|
|
</DropdownItem>,
|
|
<DropdownItem
|
|
key="delete-role"
|
|
component="button"
|
|
onClick={() => toggleDeleteDialog()}
|
|
>
|
|
{t("deleteRole")}
|
|
</DropdownItem>,
|
|
]
|
|
: id
|
|
? [
|
|
<DropdownItem
|
|
key="delete-role"
|
|
component="button"
|
|
onClick={() => toggleDeleteDialog()}
|
|
>
|
|
{t("deleteRole")}
|
|
</DropdownItem>,
|
|
<DropdownItem
|
|
key="toggle-modal"
|
|
id="add-roles"
|
|
component="button"
|
|
onClick={() => toggleModal()}
|
|
>
|
|
{t("addAssociatedRolesText")}
|
|
</DropdownItem>,
|
|
]
|
|
: undefined
|
|
}
|
|
/>
|
|
<PageSection variant="light">
|
|
{id && (
|
|
<KeycloakTabs isBox>
|
|
<Tab
|
|
eventKey="details"
|
|
title={<TabTitleText>{t("details")}</TabTitleText>}
|
|
>
|
|
<RealmRoleForm
|
|
reset={() => form.reset(role)}
|
|
form={form}
|
|
save={save}
|
|
editMode={true}
|
|
/>
|
|
</Tab>
|
|
{additionalRoles.length > 0 ? (
|
|
<Tab
|
|
eventKey="AssociatedRoles"
|
|
title={<TabTitleText>{t("associatedRolesText")}</TabTitleText>}
|
|
>
|
|
<AssociatedRolesTab
|
|
additionalRoles={additionalRoles}
|
|
addComposites={addComposites}
|
|
parentRole={role!}
|
|
onRemove={() => refresh()}
|
|
// client={client!}
|
|
/>
|
|
</Tab>
|
|
) : null}
|
|
<Tab
|
|
eventKey="attributes"
|
|
title={<TabTitleText>{t("attributes")}</TabTitleText>}
|
|
>
|
|
<RoleAttributes
|
|
form={form}
|
|
save={save}
|
|
array={{ fields, append, remove }}
|
|
reset={() => form.reset(role)}
|
|
/>
|
|
</Tab>
|
|
</KeycloakTabs>
|
|
)}
|
|
{!id && (
|
|
<RealmRoleForm
|
|
reset={() => form.reset()}
|
|
form={form}
|
|
save={save}
|
|
editMode={false}
|
|
/>
|
|
)}
|
|
</PageSection>
|
|
</>
|
|
);
|
|
};
|