Use React Router v6 for the routable tabs of user details (#4114)
This commit is contained in:
parent
b5e9eb4620
commit
5032770ddb
7 changed files with 367 additions and 315 deletions
|
@ -11,8 +11,7 @@ import {
|
|||
JSXElementConstructor,
|
||||
ReactElement,
|
||||
} from "react";
|
||||
import type { Path } from "react-router-dom-v5-compat";
|
||||
import { useLocation } from "react-router-dom-v5-compat";
|
||||
import { Path, useHref, useLocation } from "react-router-dom-v5-compat";
|
||||
|
||||
// TODO: Remove the custom 'children' props and type once the following issue has been resolved:
|
||||
// https://github.com/patternfly/patternfly-react/issues/6766
|
||||
|
@ -76,3 +75,8 @@ 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),
|
||||
});
|
||||
|
|
60
apps/admin-ui/src/user/CreateUser.tsx
Normal file
60
apps/admin-ui/src/user/CreateUser.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import { AlertVariant, PageSection } from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom-v5-compat";
|
||||
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { UserProfileProvider } from "../realm-settings/user-profile/UserProfileContext";
|
||||
import { toUser } from "./routes/User";
|
||||
import { UserForm } from "./UserForm";
|
||||
|
||||
import "./user-section.css";
|
||||
|
||||
export default function CreateUser() {
|
||||
const { t } = useTranslation("users");
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const navigate = useNavigate();
|
||||
const { adminClient } = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
|
||||
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
|
||||
|
||||
const save = async (formUser: UserRepresentation) => {
|
||||
try {
|
||||
const createdUser = await adminClient.users.create({
|
||||
...formUser,
|
||||
username: formUser.username?.trim(),
|
||||
groups: addedGroups.map((group) => group.path!),
|
||||
});
|
||||
|
||||
addAlert(t("userCreated"), AlertVariant.success);
|
||||
navigate(toUser({ id: createdUser.id, realm, tab: "settings" }));
|
||||
} catch (error) {
|
||||
addError("users:userCreateError", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewHeader
|
||||
titleKey={t("createUser")}
|
||||
className="kc-username-view-header"
|
||||
/>
|
||||
<PageSection variant="light" className="pf-u-p-0">
|
||||
<UserProfileProvider>
|
||||
<FormProvider {...userForm}>
|
||||
<PageSection variant="light">
|
||||
<UserForm onGroupsUpdate={setAddedGroups} save={save} />
|
||||
</PageSection>
|
||||
</FormProvider>
|
||||
</UserProfileProvider>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
}
|
278
apps/admin-ui/src/user/EditUser.tsx
Normal file
278
apps/admin-ui/src/user/EditUser.tsx
Normal file
|
@ -0,0 +1,278 @@
|
|||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import {
|
||||
AlertVariant,
|
||||
ButtonVariant,
|
||||
DropdownItem,
|
||||
PageSection,
|
||||
Tab,
|
||||
TabTitleText,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom-v5-compat";
|
||||
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import {
|
||||
RoutableTabs,
|
||||
useRoutableTab,
|
||||
} from "../components/routable-tabs/RoutableTabs";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { UserProfileProvider } from "../realm-settings/user-profile/UserProfileContext";
|
||||
import { useParams } from "../utils/useParams";
|
||||
import { toUser, UserParams, UserTab } from "./routes/User";
|
||||
import { toUsers } from "./routes/Users";
|
||||
import { UserAttributes } from "./UserAttributes";
|
||||
import { UserConsents } from "./UserConsents";
|
||||
import { UserCredentials } from "./UserCredentials";
|
||||
import { BruteForced, UserForm } from "./UserForm";
|
||||
import { UserGroups } from "./UserGroups";
|
||||
import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
|
||||
import { UserRoleMapping } from "./UserRoleMapping";
|
||||
import { UserSessions } from "./UserSessions";
|
||||
|
||||
import "./user-section.css";
|
||||
|
||||
export default function EditUser() {
|
||||
const { adminClient } = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const { id } = useParams<UserParams>();
|
||||
const { t } = useTranslation("users");
|
||||
const [user, setUser] = useState<UserRepresentation>();
|
||||
const [bruteForced, setBruteForced] = useState<BruteForced>();
|
||||
const [refreshCount, setRefreshCount] = useState(0);
|
||||
const refresh = () => setRefreshCount((count) => count + 1);
|
||||
|
||||
useFetch(
|
||||
async () => {
|
||||
const [user, currentRealm, attackDetection] = await Promise.all([
|
||||
adminClient.users.findOne({ id: id! }),
|
||||
adminClient.realms.findOne({ realm }),
|
||||
adminClient.attackDetection.findOne({ id: id! }),
|
||||
]);
|
||||
|
||||
if (!user || !currentRealm || !attackDetection) {
|
||||
throw new Error(t("common:notFound"));
|
||||
}
|
||||
|
||||
const isBruteForceProtected = currentRealm.bruteForceProtected;
|
||||
const isLocked = isBruteForceProtected && attackDetection.disabled;
|
||||
|
||||
return { user, bruteForced: { isBruteForceProtected, isLocked } };
|
||||
},
|
||||
({ user, bruteForced }) => {
|
||||
setUser(user);
|
||||
setBruteForced(bruteForced);
|
||||
},
|
||||
[refreshCount]
|
||||
);
|
||||
|
||||
if (!user || !bruteForced) {
|
||||
return <KeycloakSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<EditUserForm user={user} bruteForced={bruteForced} refresh={refresh} />
|
||||
);
|
||||
}
|
||||
|
||||
type EditUserFormProps = {
|
||||
user: UserRepresentation;
|
||||
bruteForced: BruteForced;
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const { realm } = useRealm();
|
||||
const { adminClient } = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const navigate = useNavigate();
|
||||
const { hasAccess } = useAccess();
|
||||
const userForm = useForm<UserRepresentation>({
|
||||
mode: "onChange",
|
||||
defaultValues: user,
|
||||
});
|
||||
|
||||
const toTab = (tab: UserTab) =>
|
||||
toUser({
|
||||
realm,
|
||||
id: user.id!,
|
||||
tab,
|
||||
});
|
||||
|
||||
const useTab = (tab: UserTab) => useRoutableTab(toTab(tab));
|
||||
|
||||
const settingsTab = useTab("settings");
|
||||
const attributesTab = useTab("attributes");
|
||||
const credentialsTab = useTab("credentials");
|
||||
const roleMappingTab = useTab("role-mapping");
|
||||
const groupsTab = useTab("groups");
|
||||
const consentsTab = useTab("consents");
|
||||
const identityProviderLinksTab = useTab("identity-provider-links");
|
||||
const sessionsTab = useTab("sessions");
|
||||
|
||||
const save = async (formUser: UserRepresentation) => {
|
||||
try {
|
||||
await adminClient.users.update(
|
||||
{ id: user.id! },
|
||||
{
|
||||
...formUser,
|
||||
username: formUser.username?.trim(),
|
||||
attributes: { ...user.attributes, ...formUser.attributes },
|
||||
}
|
||||
);
|
||||
addAlert(t("userSaved"), AlertVariant.success);
|
||||
refresh();
|
||||
} catch (error) {
|
||||
addError("users:userCreateError", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "users:deleteConfirm",
|
||||
messageKey: "users:deleteConfirmCurrentUser",
|
||||
continueButtonLabel: "common:delete",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await adminClient.users.del({ id: user.id! });
|
||||
addAlert(t("userDeletedSuccess"), AlertVariant.success);
|
||||
navigate(toUsers({ realm }));
|
||||
} catch (error) {
|
||||
addError("users:userDeletedError", error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [toggleImpersonateDialog, ImpersonateConfirm] = useConfirmDialog({
|
||||
titleKey: "users:impersonateConfirm",
|
||||
messageKey: "users:impersonateConfirmDialog",
|
||||
continueButtonLabel: "users:impersonate",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const data = await adminClient.users.impersonation(
|
||||
{ id: user.id! },
|
||||
{ user: user.id!, realm }
|
||||
);
|
||||
if (data.sameRealm) {
|
||||
window.location = data.redirect;
|
||||
} else {
|
||||
window.open(data.redirect, "_blank");
|
||||
}
|
||||
} catch (error) {
|
||||
addError("users:impersonateError", error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ImpersonateConfirm />
|
||||
<DeleteConfirm />
|
||||
<ViewHeader
|
||||
titleKey={user.username!}
|
||||
className="kc-username-view-header"
|
||||
divider={false}
|
||||
dropdownItems={[
|
||||
<DropdownItem
|
||||
key="impersonate"
|
||||
isDisabled={!user.access?.impersonate}
|
||||
onClick={() => toggleImpersonateDialog()}
|
||||
>
|
||||
{t("impersonate")}
|
||||
</DropdownItem>,
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
isDisabled={!user.access?.manage}
|
||||
onClick={() => toggleDeleteDialog()}
|
||||
>
|
||||
{t("common:delete")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
|
||||
<PageSection variant="light" className="pf-u-p-0">
|
||||
<UserProfileProvider>
|
||||
<FormProvider {...userForm}>
|
||||
<RoutableTabs
|
||||
isBox
|
||||
mountOnEnter
|
||||
defaultLocation={toTab("settings")}
|
||||
>
|
||||
<Tab
|
||||
data-testid="user-details-tab"
|
||||
title={<TabTitleText>{t("common:details")}</TabTitleText>}
|
||||
{...settingsTab}
|
||||
>
|
||||
<PageSection variant="light">
|
||||
<UserForm save={save} user={user} bruteForce={bruteForced} />
|
||||
</PageSection>
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="attributes"
|
||||
title={<TabTitleText>{t("common:attributes")}</TabTitleText>}
|
||||
{...attributesTab}
|
||||
>
|
||||
<UserAttributes user={user} />
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="credentials"
|
||||
isHidden={!user.access?.manage}
|
||||
title={<TabTitleText>{t("common:credentials")}</TabTitleText>}
|
||||
{...credentialsTab}
|
||||
>
|
||||
<UserCredentials user={user} />
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="role-mapping-tab"
|
||||
isHidden={!user.access?.mapRoles}
|
||||
title={<TabTitleText>{t("roleMapping")}</TabTitleText>}
|
||||
{...roleMappingTab}
|
||||
>
|
||||
<UserRoleMapping id={user.id!} name={user.username!} />
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="user-groups-tab"
|
||||
title={<TabTitleText>{t("common:groups")}</TabTitleText>}
|
||||
{...groupsTab}
|
||||
>
|
||||
<UserGroups user={user} />
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="user-consents-tab"
|
||||
title={<TabTitleText>{t("consents")}</TabTitleText>}
|
||||
{...consentsTab}
|
||||
>
|
||||
<UserConsents />
|
||||
</Tab>
|
||||
{hasAccess("view-identity-providers") && (
|
||||
<Tab
|
||||
data-testid="identity-provider-links-tab"
|
||||
title={
|
||||
<TabTitleText>{t("identityProviderLinks")}</TabTitleText>
|
||||
}
|
||||
{...identityProviderLinksTab}
|
||||
>
|
||||
<UserIdentityProviderLinks userId={user.id!} />
|
||||
</Tab>
|
||||
)}
|
||||
<Tab
|
||||
data-testid="user-sessions-tab"
|
||||
title={<TabTitleText>{t("sessions")}</TabTitleText>}
|
||||
{...sessionsTab}
|
||||
>
|
||||
<UserSessions />
|
||||
</Tab>
|
||||
</RoutableTabs>
|
||||
</FormProvider>
|
||||
</UserProfileProvider>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -42,7 +42,7 @@ export type UserFormProps = {
|
|||
user?: UserRepresentation;
|
||||
bruteForce?: BruteForced;
|
||||
save: (user: UserRepresentation) => void;
|
||||
onGroupsUpdate: (groups: GroupRepresentation[]) => void;
|
||||
onGroupsUpdate?: (groups: GroupRepresentation[]) => void;
|
||||
};
|
||||
|
||||
export const UserForm = ({
|
||||
|
@ -119,12 +119,12 @@ export const UserForm = ({
|
|||
|
||||
const deleteItem = (id: string) => {
|
||||
setSelectedGroups(selectedGroups.filter((item) => item.name !== id));
|
||||
onGroupsUpdate(selectedGroups);
|
||||
onGroupsUpdate?.(selectedGroups);
|
||||
};
|
||||
|
||||
const addChips = async (groups: GroupRepresentation[]): Promise<void> => {
|
||||
setSelectedGroups([...selectedGroups!, ...groups]);
|
||||
onGroupsUpdate([...selectedGroups!, ...groups]);
|
||||
onGroupsUpdate?.([...selectedGroups!, ...groups]);
|
||||
};
|
||||
|
||||
const addGroups = async (groups: GroupRepresentation[]): Promise<void> => {
|
||||
|
@ -171,6 +171,22 @@ export const UserForm = ({
|
|||
filterGroups={selectedGroups}
|
||||
/>
|
||||
)}
|
||||
<FormGroup label={t("common:enabled")} fieldId="kc-user-enabled">
|
||||
<Controller
|
||||
name="enabled"
|
||||
defaultValue={true}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-user-enabled"
|
||||
onChange={(value) => onChange(value)}
|
||||
isChecked={value}
|
||||
label={t("common:yes")}
|
||||
labelOff={t("common:no")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
{user?.id && (
|
||||
<>
|
||||
<FormGroup label={t("common:id")} fieldId="kc-id" isRequired>
|
||||
|
@ -321,8 +337,8 @@ export const UserForm = ({
|
|||
isDisabled={false}
|
||||
onChange={(value) => onChange(value)}
|
||||
isChecked={value}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
label={t("common:yes")}
|
||||
labelOff={t("common:no")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -1,306 +0,0 @@
|
|||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import {
|
||||
AlertVariant,
|
||||
ButtonVariant,
|
||||
DropdownItem,
|
||||
PageSection,
|
||||
Tab,
|
||||
TabTitleText,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom-v5-compat";
|
||||
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import {
|
||||
RoutableTabs,
|
||||
routableTab,
|
||||
} from "../components/routable-tabs/RoutableTabs";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { UserProfileProvider } from "../realm-settings/user-profile/UserProfileContext";
|
||||
import { useParams } from "../utils/useParams";
|
||||
import { toUser, UserParams, UserTab } from "./routes/User";
|
||||
import { toUsers } from "./routes/Users";
|
||||
import { UserAttributes } from "./UserAttributes";
|
||||
import { UserConsents } from "./UserConsents";
|
||||
import { UserCredentials } from "./UserCredentials";
|
||||
import { BruteForced, UserForm } from "./UserForm";
|
||||
import { UserGroups } from "./UserGroups";
|
||||
import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
|
||||
import { UserRoleMapping } from "./UserRoleMapping";
|
||||
import { UserSessions } from "./UserSessions";
|
||||
|
||||
import "./user-section.css";
|
||||
|
||||
const UsersTabs = () => {
|
||||
const { t } = useTranslation("users");
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const navigate = useNavigate();
|
||||
const { realm } = useRealm();
|
||||
const { hasAccess } = useAccess();
|
||||
const history = useHistory();
|
||||
|
||||
const { adminClient } = useAdminClient();
|
||||
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
|
||||
const { id } = useParams<UserParams>();
|
||||
const [user, setUser] = useState<UserRepresentation>();
|
||||
const [bruteForced, setBruteForced] = useState<BruteForced>();
|
||||
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
|
||||
const [refreshCount, setRefreshCount] = useState(0);
|
||||
const refresh = () => setRefreshCount((count) => count + 1);
|
||||
|
||||
useFetch(
|
||||
async () => {
|
||||
if (id) {
|
||||
const user = await adminClient.users.findOne({ id });
|
||||
if (!user) {
|
||||
throw new Error(t("common:notFound"));
|
||||
}
|
||||
|
||||
const isBruteForceProtected = (await adminClient.realms.findOne({
|
||||
realm,
|
||||
}))!.bruteForceProtected;
|
||||
const bruteForce = await adminClient.attackDetection.findOne({
|
||||
id: user.id!,
|
||||
});
|
||||
const isLocked: boolean =
|
||||
isBruteForceProtected && bruteForce && bruteForce.disabled;
|
||||
return { user, bruteForced: { isBruteForceProtected, isLocked } };
|
||||
}
|
||||
return { user: undefined };
|
||||
},
|
||||
({ user, bruteForced }) => {
|
||||
setUser(user);
|
||||
setBruteForced(bruteForced);
|
||||
user && setupForm(user);
|
||||
},
|
||||
[user?.username, refreshCount]
|
||||
);
|
||||
|
||||
const setupForm = (user: UserRepresentation) => {
|
||||
userForm.reset(user);
|
||||
};
|
||||
|
||||
const updateGroups = (groups: GroupRepresentation[]) => {
|
||||
setAddedGroups(groups);
|
||||
};
|
||||
|
||||
const save = async (formUser: UserRepresentation) => {
|
||||
formUser.username = formUser.username?.trim();
|
||||
|
||||
try {
|
||||
if (id) {
|
||||
await adminClient.users.update(
|
||||
{ id },
|
||||
{
|
||||
...formUser,
|
||||
attributes: { ...user?.attributes, ...formUser.attributes },
|
||||
}
|
||||
);
|
||||
addAlert(t("userSaved"), AlertVariant.success);
|
||||
refresh();
|
||||
} else {
|
||||
const createdUser = await adminClient.users.create({
|
||||
...formUser,
|
||||
groups: addedGroups.map((group) => group.path!),
|
||||
});
|
||||
|
||||
addAlert(t("userCreated"), AlertVariant.success);
|
||||
navigate(toUser({ id: createdUser.id, realm, tab: "settings" }));
|
||||
}
|
||||
} catch (error) {
|
||||
addError("users:userCreateError", error);
|
||||
}
|
||||
};
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: "users:deleteConfirm",
|
||||
messageKey: "users:deleteConfirmCurrentUser",
|
||||
continueButtonLabel: "common:delete",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await adminClient.users.del({ id });
|
||||
addAlert(t("userDeletedSuccess"), AlertVariant.success);
|
||||
navigate(toUsers({ realm }));
|
||||
} catch (error) {
|
||||
addError("users:userDeletedError", error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [toggleImpersonateDialog, ImpersonateConfirm] = useConfirmDialog({
|
||||
titleKey: "users:impersonateConfirm",
|
||||
messageKey: "users:impersonateConfirmDialog",
|
||||
continueButtonLabel: "users:impersonate",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const data = await adminClient.users.impersonation(
|
||||
{ id },
|
||||
{ user: id, realm }
|
||||
);
|
||||
if (data.sameRealm) {
|
||||
window.location = data.redirect;
|
||||
} else {
|
||||
window.open(data.redirect, "_blank");
|
||||
}
|
||||
} catch (error) {
|
||||
addError("users:impersonateError", error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (id && !user) {
|
||||
return <KeycloakSpinner />;
|
||||
}
|
||||
|
||||
const toTab = (tab: UserTab) =>
|
||||
toUser({
|
||||
realm,
|
||||
id,
|
||||
tab,
|
||||
});
|
||||
|
||||
const routableUserTab = (tab: UserTab) =>
|
||||
routableTab({ history, to: toTab(tab) });
|
||||
|
||||
return (
|
||||
<>
|
||||
<ImpersonateConfirm />
|
||||
<DeleteConfirm />
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={userForm.control}
|
||||
defaultValue={true}
|
||||
render={({ onChange, value }) => (
|
||||
<ViewHeader
|
||||
titleKey={user?.id ? user.username! : t("createUser")}
|
||||
className="kc-username-view-header"
|
||||
divider={!id}
|
||||
dropdownItems={[
|
||||
<DropdownItem
|
||||
key="impersonate"
|
||||
isDisabled={!user?.access?.impersonate}
|
||||
onClick={() => toggleImpersonateDialog()}
|
||||
>
|
||||
{t("impersonate")}
|
||||
</DropdownItem>,
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
isDisabled={!user?.access?.manage}
|
||||
onClick={() => toggleDeleteDialog()}
|
||||
>
|
||||
{t("common:delete")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
isEnabled={value}
|
||||
onToggle={(value) => {
|
||||
onChange(value);
|
||||
save(userForm.getValues());
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<PageSection variant="light" className="pf-u-p-0">
|
||||
<UserProfileProvider>
|
||||
<FormProvider {...userForm}>
|
||||
{id && user && (
|
||||
<RoutableTabs
|
||||
isBox
|
||||
mountOnEnter
|
||||
defaultLocation={toTab("settings")}
|
||||
>
|
||||
<Tab
|
||||
data-testid="user-details-tab"
|
||||
title={<TabTitleText>{t("common:details")}</TabTitleText>}
|
||||
{...routableUserTab("settings")}
|
||||
>
|
||||
<PageSection variant="light">
|
||||
{bruteForced && (
|
||||
<UserForm
|
||||
onGroupsUpdate={updateGroups}
|
||||
save={save}
|
||||
user={user}
|
||||
bruteForce={bruteForced}
|
||||
/>
|
||||
)}
|
||||
</PageSection>
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="attributes"
|
||||
title={<TabTitleText>{t("common:attributes")}</TabTitleText>}
|
||||
{...routableUserTab("attributes")}
|
||||
>
|
||||
<UserAttributes user={user} />
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="credentials"
|
||||
isHidden={!user.access?.manage}
|
||||
title={<TabTitleText>{t("common:credentials")}</TabTitleText>}
|
||||
{...routableUserTab("credentials")}
|
||||
>
|
||||
<UserCredentials user={user} />
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="role-mapping-tab"
|
||||
isHidden={!user.access?.mapRoles}
|
||||
title={<TabTitleText>{t("roleMapping")}</TabTitleText>}
|
||||
{...routableUserTab("role-mapping")}
|
||||
>
|
||||
<UserRoleMapping id={id} name={user.username!} />
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="user-groups-tab"
|
||||
title={<TabTitleText>{t("common:groups")}</TabTitleText>}
|
||||
{...routableUserTab("groups")}
|
||||
>
|
||||
<UserGroups user={user} />
|
||||
</Tab>
|
||||
<Tab
|
||||
data-testid="user-consents-tab"
|
||||
title={<TabTitleText>{t("consents")}</TabTitleText>}
|
||||
{...routableUserTab("consents")}
|
||||
>
|
||||
<UserConsents />
|
||||
</Tab>
|
||||
{hasAccess("view-identity-providers") && (
|
||||
<Tab
|
||||
data-testid="identity-provider-links-tab"
|
||||
title={
|
||||
<TabTitleText>{t("identityProviderLinks")}</TabTitleText>
|
||||
}
|
||||
{...routableUserTab("identity-provider-links")}
|
||||
>
|
||||
<UserIdentityProviderLinks userId={id} />
|
||||
</Tab>
|
||||
)}
|
||||
<Tab
|
||||
data-testid="user-sessions-tab"
|
||||
title={<TabTitleText>{t("sessions")}</TabTitleText>}
|
||||
{...routableUserTab("sessions")}
|
||||
>
|
||||
<UserSessions />
|
||||
</Tab>
|
||||
</RoutableTabs>
|
||||
)}
|
||||
{!id && (
|
||||
<PageSection variant="light">
|
||||
<UserForm onGroupsUpdate={updateGroups} save={save} />
|
||||
</PageSection>
|
||||
)}
|
||||
</FormProvider>
|
||||
</UserProfileProvider>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersTabs;
|
|
@ -8,7 +8,7 @@ export type AddUserParams = { realm: string };
|
|||
|
||||
export const AddUserRoute: RouteDef = {
|
||||
path: "/:realm/users/add-user",
|
||||
component: lazy(() => import("../UsersTabs")),
|
||||
component: lazy(() => import("../CreateUser")),
|
||||
breadcrumb: (t) => t("users:createUser"),
|
||||
access: ["query-users", "query-groups"],
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ export type UserParams = {
|
|||
|
||||
export const UserRoute: RouteDef = {
|
||||
path: "/:realm/users/:id/:tab",
|
||||
component: lazy(() => import("../UsersTabs")),
|
||||
component: lazy(() => import("../EditUser")),
|
||||
breadcrumb: (t) => t("users:userDetails"),
|
||||
access: "query-users",
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue