Use routable tabs for realm role details (#3963)
This commit is contained in:
parent
fe4c3dc151
commit
5ba147b384
7 changed files with 42 additions and 113 deletions
|
@ -11,7 +11,7 @@ export default class ClientRolesTab extends CommonPage {
|
|||
private createRoleEmptyStateBtn = "no-roles-for-this-client-empty-action";
|
||||
private hideInheritedRolesChkBox = "#hideInheritedRoles";
|
||||
private rolesTab = "rolesTab";
|
||||
private associatedRolesTab = ".kc-associated-roles-tab > button";
|
||||
private associatedRolesTab = "associatedRolesTab";
|
||||
|
||||
goToDetailsTab() {
|
||||
this.tabUtils().clickTab(ClientRolesTabItems.Details);
|
||||
|
@ -34,7 +34,7 @@ export default class ClientRolesTab extends CommonPage {
|
|||
}
|
||||
|
||||
goToAssociatedRolesTab() {
|
||||
cy.get(this.associatedRolesTab).click();
|
||||
cy.findByTestId(this.associatedRolesTab).click();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ class CreateRealmRolePage {
|
|||
}
|
||||
|
||||
goToAttributesTab() {
|
||||
cy.get(".kc-attributes-tab > button").click();
|
||||
cy.findByTestId("attributesTab").click();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,8 +194,6 @@
|
|||
"emptyMappers": "No mappers",
|
||||
"emptyMappersInstructions": "If you want to add mappers, please click the button below to add some predefined mappers or to configure a new mapper.",
|
||||
"emptyPrimaryAction": "Add predefined mapper",
|
||||
"leaveDirtyTitle": "Leave without saving?",
|
||||
"leaveDirtyConfirm": "Do you want to leave this page without saving? Any unsaved changes will be lost.",
|
||||
"leave": "Leave",
|
||||
"reorder": "Reorder",
|
||||
"onDragStart": "Dragging started for item {{item}}",
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
import { Children, isValidElement, useState } from "react";
|
||||
import { useRouteMatch } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom-v5-compat";
|
||||
import { TabProps, Tabs, TabsProps } from "@patternfly/react-core";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useConfirmDialog } from "../confirm-dialog/ConfirmDialog";
|
||||
|
||||
type KeycloakTabsProps = Omit<TabsProps, "ref" | "activeKey" | "onSelect"> & {
|
||||
paramName?: string;
|
||||
};
|
||||
|
||||
const createUrl = (
|
||||
path: string,
|
||||
params: { [index: string]: string }
|
||||
): string => {
|
||||
let url = path;
|
||||
for (const key in params) {
|
||||
const value = params[key];
|
||||
if (url.includes(key)) {
|
||||
url = url.replace(new RegExp(`:${key}\\??`), value || "");
|
||||
}
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export const KeycloakTabs = ({
|
||||
paramName = "tab",
|
||||
children,
|
||||
...rest
|
||||
}: KeycloakTabsProps) => {
|
||||
const match = useRouteMatch();
|
||||
const params = match.params as { [index: string]: string };
|
||||
const navigate = useNavigate();
|
||||
const form = useFormContext() as
|
||||
| ReturnType<typeof useFormContext>
|
||||
| undefined;
|
||||
const [key, setKey] = useState("");
|
||||
|
||||
const firstTab = Children.toArray(children)[0];
|
||||
const tab =
|
||||
params[paramName] ||
|
||||
(isValidElement<TabProps>(firstTab) && firstTab.props.eventKey) ||
|
||||
"";
|
||||
|
||||
const pathIndex = match.path.indexOf(paramName) + paramName.length;
|
||||
const path = match.path.substr(0, pathIndex);
|
||||
|
||||
const [toggleChangeTabDialog, ChangeTabConfirm] = useConfirmDialog({
|
||||
titleKey: "common:leaveDirtyTitle",
|
||||
messageKey: "common:leaveDirtyConfirm",
|
||||
continueButtonLabel: "common:leave",
|
||||
onConfirm: () => {
|
||||
form?.reset();
|
||||
navigate(createUrl(path, { ...params, [paramName]: key as string }));
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChangeTabConfirm />
|
||||
<Tabs
|
||||
inset={{
|
||||
default: "insetNone",
|
||||
md: "insetSm",
|
||||
xl: "inset2xl",
|
||||
"2xl": "insetLg",
|
||||
}}
|
||||
activeKey={tab}
|
||||
onSelect={(_, key) => {
|
||||
if (form?.formState.isDirty) {
|
||||
setKey(key as string);
|
||||
toggleChangeTabDialog();
|
||||
} else {
|
||||
navigate(
|
||||
createUrl(path, { ...params, [paramName]: key as string })
|
||||
);
|
||||
}
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -4,7 +4,6 @@ import {
|
|||
TabsComponent,
|
||||
TabsProps,
|
||||
} from "@patternfly/react-core";
|
||||
import type { History } from "history";
|
||||
import {
|
||||
Children,
|
||||
isValidElement,
|
||||
|
@ -66,16 +65,6 @@ export const RoutableTabs = ({
|
|||
);
|
||||
};
|
||||
|
||||
type RoutableTabParams = {
|
||||
to: Partial<Path>;
|
||||
history: History<unknown>;
|
||||
};
|
||||
|
||||
export const routableTab = ({ to, history }: RoutableTabParams) => ({
|
||||
eventKey: to.pathname ?? "",
|
||||
href: history.createHref(to),
|
||||
});
|
||||
|
||||
export const useRoutableTab = (to: Partial<Path>) => ({
|
||||
eventKey: to.pathname ?? "",
|
||||
href: useHref(to),
|
||||
|
|
|
@ -19,6 +19,7 @@ import { toClient } from "../clients/routes/Client";
|
|||
import {
|
||||
ClientRoleParams,
|
||||
ClientRoleRoute,
|
||||
ClientRoleTab,
|
||||
toClientRole,
|
||||
} from "../clients/routes/ClientRole";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
|
@ -32,17 +33,20 @@ import {
|
|||
keyValueToArray,
|
||||
} from "../components/key-value-form/key-value-convert";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||
import { PermissionsTab } from "../components/permission-tab/PermissionTab";
|
||||
import { RoleForm } from "../components/role-form/RoleForm";
|
||||
import { AddRoleMappingModal } from "../components/role-mapping/AddRoleMappingModal";
|
||||
import { RoleMapping } from "../components/role-mapping/RoleMapping";
|
||||
import {
|
||||
RoutableTabs,
|
||||
useRoutableTab,
|
||||
} from "../components/routable-tabs/RoutableTabs";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { useParams } from "../utils/useParams";
|
||||
import { RealmRoleRoute, toRealmRole } from "./routes/RealmRole";
|
||||
import { RealmRoleRoute, RealmRoleTab, toRealmRole } from "./routes/RealmRole";
|
||||
import { toRealmRoles } from "./routes/RealmRoles";
|
||||
import { UsersInRoleTab } from "./UsersInRoleTab";
|
||||
|
||||
|
@ -57,7 +61,7 @@ export default function RealmRoleTabs() {
|
|||
const { adminClient } = useAdminClient();
|
||||
const [role, setRole] = useState<AttributeForm>();
|
||||
|
||||
const { id, clientId } = useParams<{ id: string; clientId: string }>();
|
||||
const { id, clientId } = useParams<ClientRoleParams>();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const { realm: realmName } = useRealm();
|
||||
|
@ -292,6 +296,27 @@ export default function RealmRoleTabs() {
|
|||
navigate(to);
|
||||
};
|
||||
|
||||
const toTab = (tab: RealmRoleTab | ClientRoleTab) =>
|
||||
clientRoleRouteMatch
|
||||
? toClientRole({
|
||||
...clientRoleRouteMatch.params,
|
||||
tab: tab as ClientRoleTab,
|
||||
})
|
||||
: toRealmRole({
|
||||
realm: realmName,
|
||||
id,
|
||||
tab,
|
||||
});
|
||||
|
||||
const useTab = (tab: RealmRoleTab | ClientRoleTab) =>
|
||||
useRoutableTab(toTab(tab));
|
||||
|
||||
const detailsTab = useTab("details");
|
||||
const associatedRolesTab = useTab("associated-roles");
|
||||
const attributesTab = useTab("attributes");
|
||||
const usersInRoleTab = useTab("users-in-role");
|
||||
const permissionsTab = useTab("permissions");
|
||||
|
||||
const addComposites = async (composites: RoleRepresentation[]) => {
|
||||
try {
|
||||
await adminClient.roles.createComposite(
|
||||
|
@ -339,10 +364,10 @@ export default function RealmRoleTabs() {
|
|||
divider={false}
|
||||
/>
|
||||
<PageSection variant="light" className="pf-u-p-0">
|
||||
<KeycloakTabs isBox mountOnEnter>
|
||||
<RoutableTabs isBox mountOnEnter defaultLocation={toTab("details")}>
|
||||
<Tab
|
||||
eventKey="details"
|
||||
title={<TabTitleText>{t("common:details")}</TabTitleText>}
|
||||
{...detailsTab}
|
||||
>
|
||||
<RoleForm
|
||||
form={form}
|
||||
|
@ -358,9 +383,9 @@ export default function RealmRoleTabs() {
|
|||
</Tab>
|
||||
{role.composite && (
|
||||
<Tab
|
||||
eventKey="associated-roles"
|
||||
className="kc-associated-roles-tab"
|
||||
data-testid="associatedRolesTab"
|
||||
title={<TabTitleText>{t("associatedRolesText")}</TabTitleText>}
|
||||
{...associatedRolesTab}
|
||||
>
|
||||
<RoleMapping
|
||||
name={role.name!}
|
||||
|
@ -373,9 +398,10 @@ export default function RealmRoleTabs() {
|
|||
)}
|
||||
{!isDefaultRole(role.name!) && (
|
||||
<Tab
|
||||
eventKey="attributes"
|
||||
data-testid="attributesTab"
|
||||
className="kc-attributes-tab"
|
||||
title={<TabTitleText>{t("common:attributes")}</TabTitleText>}
|
||||
{...attributesTab}
|
||||
>
|
||||
<AttributesForm
|
||||
form={form}
|
||||
|
@ -386,8 +412,8 @@ export default function RealmRoleTabs() {
|
|||
)}
|
||||
{!isDefaultRole(role.name!) && (
|
||||
<Tab
|
||||
eventKey="users-in-role"
|
||||
title={<TabTitleText>{t("usersInRole")}</TabTitleText>}
|
||||
{...usersInRoleTab}
|
||||
>
|
||||
<UsersInRoleTab data-cy="users-in-role-tab" />
|
||||
</Tab>
|
||||
|
@ -396,13 +422,13 @@ export default function RealmRoleTabs() {
|
|||
"ADMIN_FINE_GRAINED_AUTHZ"
|
||||
) && (
|
||||
<Tab
|
||||
eventKey="permissions"
|
||||
title={<TabTitleText>{t("common:permissions")}</TabTitleText>}
|
||||
{...permissionsTab}
|
||||
>
|
||||
<PermissionsTab id={role.id} type="roles" />
|
||||
</Tab>
|
||||
)}
|
||||
</KeycloakTabs>
|
||||
</RoutableTabs>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,8 @@ export type RealmRoleTab =
|
|||
| "details"
|
||||
| "associated-roles"
|
||||
| "attributes"
|
||||
| "users-in-role";
|
||||
| "users-in-role"
|
||||
| "permissions";
|
||||
|
||||
export type RealmRoleParams = {
|
||||
realm: string;
|
||||
|
|
Loading…
Reference in a new issue