Added default role tab to realm settings (#1497)

* added user registration tab to realm settings

* added keycloak spinner

* fixed routing from all role tabs

* Update src/realm-settings/RealmSettingsTabs.tsx

Co-authored-by: Jenny <32821331+jenny-s51@users.noreply.github.com>

* changed tab name to "associated-roles"

* removed useless key attribute

* removed unnessary variable

* fixed pagination

* rebased

* fixed default role type

* fixed create realm role

* fixed helpItem forID

* fixed test

Co-authored-by: Jenny <32821331+jenny-s51@users.noreply.github.com>
This commit is contained in:
Erik Jan de Wit 2021-11-16 12:10:10 +01:00 committed by GitHub
parent ec5acf65ae
commit ede8db53a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 286 additions and 189 deletions

View file

@ -18,7 +18,7 @@ export default class AssociatedRolesPage {
cy.findByTestId(this.addAssociatedRolesModalButton).contains("Add").click();
cy.url().should("include", "/AssociatedRoles");
cy.url().should("include", "/associated-roles");
cy.findByTestId(this.compositeRoleBadge).should(
"contain.text",

View file

@ -1,5 +1,5 @@
import React, { FunctionComponent, Suspense } from "react";
import { Page, Spinner } from "@patternfly/react-core";
import { Page } from "@patternfly/react-core";
import { HashRouter as Router, Route, Switch } from "react-router-dom";
import { ErrorBoundary } from "react-error-boundary";
@ -13,6 +13,7 @@ import { AlertProvider } from "./components/alert/Alerts";
import { AccessContextProvider, useAccess } from "./context/access/Access";
import { routes, RouteDef } from "./route-config";
import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs";
import { KeycloakSpinner } from "./components/keycloak-spinner/KeycloakSpinner";
import { ForbiddenSection } from "./ForbiddenSection";
import { SubGroups } from "./groups/SubGroupsContext";
import { RealmsProvider } from "./context/RealmsContext";
@ -65,7 +66,7 @@ const SecuredRoute = ({ route }: SecuredRouteProps) => {
if (accessAllowed)
return (
<Suspense fallback={<Spinner />}>
<Suspense fallback={<KeycloakSpinner />}>
<route.component />
</Suspense>
);

View file

@ -1,12 +1,13 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { AlertVariant, Spinner, Switch } from "@patternfly/react-core";
import { AlertVariant, Switch } from "@patternfly/react-core";
import type RequiredActionProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
import type RequiredActionProviderSimpleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderSimpleRepresentation";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { DraggableTable } from "./components/DraggableTable";
import { useAlerts } from "../components/alert/Alerts";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
type DataType = RequiredActionProviderRepresentation &
RequiredActionProviderSimpleRepresentation;
@ -110,11 +111,7 @@ export const RequiredActions = () => {
};
if (!actions) {
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}
return (

View file

@ -6,7 +6,6 @@ import {
ButtonVariant,
DropdownItem,
PageSection,
Spinner,
Tab,
TabTitleText,
} from "@patternfly/react-core";
@ -15,6 +14,7 @@ import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
import { KeycloakTabs } from "../../components/keycloak-tabs/KeycloakTabs";
import { useAlerts } from "../../components/alert/Alerts";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
import { convertFormValuesToObject } from "../../util";
import { MapperList } from "../details/MapperList";
import { ScopeForm } from "../details/ScopeForm";
@ -236,11 +236,7 @@ export default function ClientScopeForm() {
};
if (id && !clientScope) {
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}
return (

View file

@ -6,7 +6,6 @@ import {
DropdownItem,
Label,
PageSection,
Spinner,
Tab,
Tabs,
TabTitleText,
@ -35,6 +34,7 @@ import {
ViewHeader,
ViewHeaderBadge,
} from "../components/view-header/ViewHeader";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { RolesList } from "../realm-roles/RolesList";
@ -329,11 +329,7 @@ export default function ClientDetails() {
};
if (!client) {
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}
return (

View file

@ -1,7 +1,7 @@
import React, { DependencyList, useState } from "react";
import { Spinner } from "@patternfly/react-core";
import { useFetch } from "../../context/auth/AdminClient";
import { KeycloakSpinner } from "../keycloak-spinner/KeycloakSpinner";
type DataLoaderProps<T> = {
loader: () => Promise<T>;
@ -24,9 +24,5 @@ export function DataLoader<T>(props: DataLoaderProps<T>) {
}
return props.children;
}
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}

View file

@ -0,0 +1,8 @@
import React from "react";
import { Spinner } from "@patternfly/react-core";
export const KeycloakSpinner = () => (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);

View file

@ -23,7 +23,7 @@ import { FilterIcon } from "@patternfly/react-icons";
import { Row, ServiceRole } from "./RoleMapping";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
export type MappingType = "service-account" | "client-scope" | "user-fed";
export type MappingType = "service-account" | "client-scope" | "role";
type AddRoleMappingModalProps = {
id: string;
@ -89,8 +89,8 @@ export const AddRoleMappingModal = ({
}
);
break;
case "user-fed":
roles = await adminClient.roles.find();
case "role":
roles = await adminClient.clients.listRoles({ id: client.id! });
break;
}
return {
@ -102,9 +102,7 @@ export const AddRoleMappingModal = ({
)
.flat()
.filter((row) => row.roles.length !== 0)
.map((row) => {
return { ...row.client, numberOfRoles: row.roles.length };
});
.map((row) => ({ ...row.client, numberOfRoles: row.roles.length }));
},
(clients) => {
setClients(clients);
@ -146,7 +144,7 @@ export const AddRoleMappingModal = ({
id,
});
break;
case "user-fed":
case "role":
availableRoles = await adminClient.roles.find();
break;
}
@ -183,7 +181,7 @@ export const AddRoleMappingModal = ({
{ id, client: client.id! }
);
break;
case "user-fed":
case "role":
clientAvailableRoles = await adminClient.clients.listRoles({
id: client.id!,
});

View file

@ -18,12 +18,12 @@ import {
TableProps,
TableVariant,
} from "@patternfly/react-table";
import { Spinner } from "@patternfly/react-core";
import _ from "lodash";
import { PaginatingTableToolbar } from "./PaginatingTableToolbar";
import { useFetch } from "../../context/auth/AdminClient";
import { ListEmptyState } from "../list-empty-state/ListEmptyState";
import { KeycloakSpinner } from "../keycloak-spinner/KeycloakSpinner";
import { useFetch } from "../../context/auth/AdminClient";
import type { SVGIconProps } from "@patternfly/react-icons/dist/js/createIcon";
type TitleCell = { title: JSX.Element };
@ -324,11 +324,7 @@ export function KeycloakDataTable<T>({
return action;
});
const Loading = () => (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
const Loading = () => <KeycloakSpinner />;
const _onSelect = (isSelected: boolean, rowIndex: number) => {
const data = filteredData || rows;

View file

@ -264,6 +264,7 @@ export default function AddMapper() {
/>
{rolesModalOpen && (
<AssociatedRolesModal
id={id}
onConfirm={(role) => setSelectedRole(role)}
omitComposites
isRadio

View file

@ -11,7 +11,6 @@ import {
DropdownItem,
Form,
PageSection,
Spinner,
Tab,
TabTitleText,
ToolbarItem,
@ -21,6 +20,7 @@ import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client
import { FormAccess } from "../../components/form-access/FormAccess";
import { ScrollForm } from "../../components/scroll-form/ScrollForm";
import { ViewHeader } from "../../components/view-header/ViewHeader";
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
import { GeneralSettings } from "./GeneralSettings";
import { AdvancedSettings } from "./AdvancedSettings";
@ -199,7 +199,7 @@ export default function DetailSettings() {
});
if (!provider) {
return <Spinner />;
return <KeycloakSpinner />;
}
const sections = [t("generalSettings"), t("advancedSettings")];

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { useHistory, useParams, useRouteMatch } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { omit, sortBy } from "lodash";
import {
AlertVariant,
Button,
Dropdown,
DropdownItem,
@ -9,31 +9,23 @@ import {
Label,
Modal,
ModalVariant,
Spinner,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { CaretDownIcon, FilterIcon } from "@patternfly/react-icons";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { CaretDownIcon, FilterIcon } from "@patternfly/react-icons";
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";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
type Role = RoleRepresentation & {
clientId?: string;
};
export type AssociatedRolesModalProps = {
id: string;
toggleDialog: () => void;
onConfirm?: (newReps: RoleRepresentation[]) => void;
onConfirm: (newReps: RoleRepresentation[]) => void;
omitComposites?: boolean;
isRadio?: boolean;
isMapperId?: boolean;
@ -42,6 +34,7 @@ export type AssociatedRolesModalProps = {
type FilterType = "roles" | "clients";
export const AssociatedRolesModal = ({
id,
toggleDialog,
onConfirm,
omitComposites,
@ -51,8 +44,6 @@ export const AssociatedRolesModal = ({
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[]>();
@ -61,13 +52,6 @@ export const AssociatedRolesModal = ({
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());
};
@ -117,29 +101,6 @@ export const AssociatedRolesModal = ({
return alphabetize(clientRoles.flat());
};
const addComposites = async (composites: RoleRepresentation[]) => {
const compositeArray = composites;
const to = clientRoleRouteMatch
? toClientRole({ ...clientRoleRouteMatch.params, tab: "AssociateRoles" })
: toRealmRole({
realm,
id,
tab: "AssociatedRoles",
});
try {
await adminClient.roles.createComposite(
{ roleId: id, realm },
compositeArray
);
history.push(to);
addAlert(t("addAssociatedRolesSuccess"), AlertVariant.success);
} catch (error) {
addError("roles:addAssociatedRolesError", error);
}
};
useEffect(() => {
refresh();
}, [filterType]);
@ -175,11 +136,7 @@ export const AssociatedRolesModal = ({
};
if (!compositeRoles) {
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}
return (
@ -197,11 +154,7 @@ export const AssociatedRolesModal = ({
isDisabled={!selectedRows.length}
onClick={() => {
toggleDialog();
if (onConfirm) {
onConfirm(selectedRows);
} else {
addComposites(selectedRows);
}
onConfirm(selectedRows);
}}
>
{t("common:add")}

View file

@ -10,6 +10,17 @@ import {
PageSection,
ToolbarItem,
} from "@patternfly/react-core";
import {
ClientRoleParams,
ClientRoleRoute,
toClientRole,
} from "./routes/ClientRole";
import {
RealmSettingsParams,
RealmSettingsRoute,
} from "../realm-settings/routes/RealmSettings";
import { RealmRoleParams, RealmRoleTab, toRealmRole } from "./routes/RealmRole";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
@ -36,9 +47,11 @@ export const AssociatedRolesTab = ({
refresh: refreshParent,
}: AssociatedRolesTabProps) => {
const { t } = useTranslation("roles");
const history = useHistory();
const { addAlert, addError } = useAlerts();
const { url } = useRouteMatch();
const { id, realm } = useParams<RealmRoleParams>();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());
@ -48,7 +61,13 @@ export const AssociatedRolesTab = ({
const [open, setOpen] = useState(false);
const adminClient = useAdminClient();
const { id } = useParams<{ id: string }>();
const clientRoleRouteMatch = useRouteMatch<ClientRoleParams>(
ClientRoleRoute.path
);
const realmSettingsMatch = useRouteMatch<RealmSettingsParams>(
RealmSettingsRoute.path
);
const subRoles = async (result: Role[], roles: Role[]): Promise<Role[]> => {
const promises = roles.map(async (r) => {
@ -97,6 +116,19 @@ export const AssociatedRolesTab = ({
return compositeRoles;
};
const toRolesTab = (tab: RealmRoleTab | undefined = "associated-roles") => {
const to = clientRoleRouteMatch
? toClientRole({ ...clientRoleRouteMatch.params, tab })
: !realmSettingsMatch
? toRealmRole({
realm,
id,
tab,
})
: undefined;
if (to) history.push(to);
};
const AliasRenderer = ({ id, name, clientRole, containerId }: Role) => {
return (
<>
@ -112,14 +144,12 @@ export const AssociatedRolesTab = ({
const toggleModal = () => {
setOpen(!open);
refresh();
};
const reload = () => {
if (selectedRows.length >= count) {
refreshParent();
const loc = url.replace(/\/AssociatedRoles/g, "/details");
history.push(loc);
toRolesTab("details");
} else {
refresh();
}
@ -135,7 +165,10 @@ export const AssociatedRolesTab = ({
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.roles.delCompositeRoles({ id }, selectedRows);
await adminClient.roles.delCompositeRoles(
{ id: parentRole.id! },
selectedRows
);
reload();
setSelectedRows([]);
@ -156,7 +189,10 @@ export const AssociatedRolesTab = ({
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.roles.delCompositeRoles({ id }, selectedRows);
await adminClient.roles.delCompositeRoles(
{ id: parentRole.id! },
selectedRows
);
addAlert(t("associatedRolesRemoved"), AlertVariant.success);
reload();
} catch (error) {
@ -165,19 +201,40 @@ export const AssociatedRolesTab = ({
},
});
const goToCreate = () => history.push(`${url}/add-role`);
const addComposites = async (composites: RoleRepresentation[]) => {
const compositeArray = composites;
try {
await adminClient.roles.createComposite(
{ roleId: parentRole.id!, realm },
compositeArray
);
toRolesTab();
refresh();
addAlert(t("addAssociatedRolesSuccess"), AlertVariant.success);
} catch (error) {
addError("roles:addAssociatedRolesError", error);
}
};
return (
<PageSection variant="light" padding={{ default: "noPadding" }}>
<DeleteConfirm />
<DeleteAssociatedRolesConfirm />
{open && <AssociatedRolesModal toggleDialog={toggleModal} />}
{open && (
<AssociatedRolesModal
id={parentRole.id!}
toggleDialog={toggleModal}
onConfirm={addComposites}
/>
)}
<KeycloakDataTable
key={key}
loader={loader}
ariaLabelKey="roles:roleList"
searchPlaceholderKey="roles:searchFor"
canSelectAll
isPaginated
isPaginated={isInheritedHidden}
onSelect={(rows) => {
setSelectedRows([
...rows.map((r) => {
@ -253,10 +310,10 @@ export const AssociatedRolesTab = ({
emptyState={
<ListEmptyState
hasIcon={true}
message={t("noRoles")}
instructions={t("noRolesInstructions")}
primaryActionText={t("createRole")}
onPrimaryAction={goToCreate}
message={t("noRolesAssociated")}
instructions={t("noRolesAssociatedInstructions")}
primaryActionText={t("addRole")}
onPrimaryAction={() => setOpen(true)}
/>
}
/>

View file

@ -5,7 +5,6 @@ import {
ButtonVariant,
DropdownItem,
PageSection,
Spinner,
Tab,
TabTitleText,
} from "@patternfly/react-core";
@ -22,6 +21,7 @@ import {
arrayToAttributes,
AttributeForm,
} from "../components/attribute-form/AttributeForm";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { RealmRoleForm } from "./RealmRoleForm";
@ -31,6 +31,12 @@ import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { AssociatedRolesTab } from "./AssociatedRolesTab";
import { UsersInRoleTab } from "./UsersInRoleTab";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { toRealmRole } from "./routes/RealmRole";
import {
ClientRoleParams,
ClientRoleRoute,
toClientRole,
} from "./routes/ClientRole";
export default function RealmRoleTabs() {
const { t } = useTranslation("roles");
@ -199,7 +205,7 @@ export default function RealmRoleTabs() {
});
const dropdownItems =
url.includes("AssociatedRoles") && !realm?.defaultRole
url.includes("associated-roles") && !realm?.defaultRole
? [
<DropdownItem
key="delete-all-associated"
@ -218,7 +224,7 @@ export default function RealmRoleTabs() {
{t("deleteRole")}
</DropdownItem>,
]
: id && realm?.defaultRole && url.includes("AssociatedRoles")
: id && realm?.defaultRole && url.includes("associated-roles")
? [
<DropdownItem
key="delete-all-associated"
@ -278,21 +284,67 @@ export default function RealmRoleTabs() {
const toggleModal = () => {
setOpen(!open);
refresh();
};
const isDefaultRole = (name: string) =>
(realm?.defaultRole! as unknown as RoleRepresentation).name === name;
const clientRoleRouteMatch = useRouteMatch<ClientRoleParams>(
ClientRoleRoute.path
);
if (!realm || !role) {
return <Spinner />;
const toAssociatedRoles = () => {
const to = clientRoleRouteMatch
? toClientRole({
...clientRoleRouteMatch.params,
tab: "associated-roles",
})
: toRealmRole({
realm: realm?.realm!,
id,
tab: "associated-roles",
});
history.push(to);
};
const addComposites = async (composites: RoleRepresentation[]) => {
try {
await adminClient.roles.createComposite(
{ roleId: role?.id!, realm: realm!.realm },
composites
);
refresh();
toAssociatedRoles();
addAlert(t("addAssociatedRolesSuccess"), AlertVariant.success);
} catch (error) {
addError("roles:addAssociatedRolesError", error);
}
};
const isDefaultRole = (name: string) => realm?.defaultRole!.name === name;
if (!realm) {
return <KeycloakSpinner />;
}
if (!role) {
return (
<RealmRoleForm
reset={() => reset(role)}
form={form}
save={save}
editMode={false}
/>
);
}
return (
<>
<DeleteConfirm />
<DeleteAllAssociatedRolesConfirm />
{open && <AssociatedRolesModal toggleDialog={toggleModal} />}
{open && (
<AssociatedRolesModal
id={id}
toggleDialog={toggleModal}
onConfirm={addComposites}
/>
)}
<ViewHeader
titleKey={role.name || t("createRole")}
badges={[
@ -323,7 +375,7 @@ export default function RealmRoleTabs() {
</Tab>
{role.composite && (
<Tab
eventKey="AssociatedRoles"
eventKey="associated-roles"
title={<TabTitleText>{t("associatedRolesText")}</TabTitleText>}
>
<AssociatedRolesTab parentRole={role} refresh={refresh} />
@ -353,14 +405,6 @@ export default function RealmRoleTabs() {
)}
</KeycloakTabs>
)}
{!id && (
<RealmRoleForm
reset={() => reset(role)}
form={form}
save={save}
editMode={false}
/>
)}
</PageSection>
</>
);

View file

@ -6,6 +6,7 @@ import { AlertVariant, Button, ButtonVariant } from "@patternfly/react-core";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
@ -16,6 +17,7 @@ import { HelpItem } from "../components/help-enabler/HelpItem";
import { ClientParams, ClientRoute } from "../clients/routes/Client";
import { toClientRole } from "./routes/ClientRole";
import { toRealmRole } from "./routes/RealmRole";
import { toRealmSettings } from "../realm-settings/routes/RealmSettings";
import "./RealmRolesSection.css";
@ -41,11 +43,7 @@ const RoleLink: FunctionComponent<RoleLinkProps> = ({ children, role }) => {
? toClientRole({ ...clientRouteMatch.params, id: role.id!, tab: "details" })
: toRealmRole({ realm, id: role.id!, tab: "details" });
return (
<Link key={role.id} to={to}>
{children}
</Link>
);
return <Link to={to}>{children}</Link>;
};
export const RolesList = ({
@ -72,21 +70,26 @@ export const RolesList = ({
[]
);
const RoleDetailLink = (role: RoleRepresentation) => (
<>
const RoleDetailLink = (role: RoleRepresentation) =>
role.name !== realm?.defaultRole?.name ? (
<RoleLink role={role}>{role.name}</RoleLink>
{role.name?.includes("default-role") ? (
) : (
<>
<Link
to={toRealmSettings({ realm: realmName, tab: "userRegistration" })}
>
{role.name}{" "}
</Link>
<HelpItem
helpText={t("defaultRole")}
forLabel={t("defaultRole")}
forID="kc-defaultRole"
forID={t("common:helpLabel", {
label: t("defaultRole"),
})}
id="default-role-help-icon"
/>
) : (
""
)}
</>
);
</>
);
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "roles:roleDeleteConfirm",
@ -116,6 +119,10 @@ export const RolesList = ({
const goToCreate = () => history.push(`${url}/add-role`);
if (!realm) {
return <KeycloakSpinner />;
}
return (
<>
<DeleteConfirm />
@ -131,10 +138,7 @@ export const RolesList = ({
title: t("common:delete"),
onRowClick: (role) => {
setSelectedRole(role);
if (
role.name ===
(realm!.defaultRole! as unknown as RoleRepresentation).name
) {
if (role.name === realm!.defaultRole!.name) {
addAlert(t("defaultRoleDeleteError"), AlertVariant.danger);
} else toggleDeleteDialog();
},

View file

@ -79,5 +79,8 @@ export default {
lastName: "Last name",
firstName: "First name",
clearAllFilters: "Clear all filters",
noRolesAssociated: "No roles where associated",
noRolesAssociatedInstructions:
"To add roles to the this role press that 'Add role' button",
},
};

View file

@ -7,7 +7,7 @@ export type ClientRoleTab =
| "details"
| "attributes"
| "users-in-role"
| "AssociateRoles";
| "associated-roles";
export type ClientRoleParams = {
realm: string;

View file

@ -5,7 +5,7 @@ import type { RouteDef } from "../../route-config";
export type RealmRoleTab =
| "details"
| "AssociatedRoles"
| "associated-roles"
| "attributes"
| "users-in-role";

View file

@ -1,16 +1,11 @@
import React, { useState } from "react";
import {
Button,
Label,
Modal,
ModalVariant,
Spinner,
} from "@patternfly/react-core";
import { Button, Label, Modal, ModalVariant } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useFetch, useAdminClient } from "../context/auth/AdminClient";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
type ClientProfile = ClientProfileRepresentation & {
@ -57,11 +52,7 @@ export const AddClientProfileModal = (props: AddClientProfileModalProps) => {
const loader = async () => tableProfiles ?? [];
if (!tableProfiles) {
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}
const AliasRenderer = ({ name }: ClientProfile) => (

View file

@ -8,7 +8,6 @@ import {
FlexItem,
PageSection,
Radio,
Spinner,
Title,
ToolbarItem,
} from "@patternfly/react-core";
@ -28,6 +27,8 @@ import "./RealmSettingsSection.css";
import { useRealm } from "../context/realm-context/RealmContext";
import { toAddClientPolicy } from "./routes/AddClientPolicy";
import { toEditClientPolicy } from "./routes/EditClientPolicy";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
export const PoliciesTab = () => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
@ -112,11 +113,7 @@ export const PoliciesTab = () => {
});
if (!policies) {
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}
return (
<>

View file

@ -8,7 +8,6 @@ import {
FormGroup,
Label,
PageSection,
Spinner,
ToolbarItem,
} from "@patternfly/react-core";
import { Divider, Flex, FlexItem, Radio, Title } from "@patternfly/react-core";
@ -25,6 +24,7 @@ import { Link } from "react-router-dom";
import { toAddClientProfile } from "./routes/AddClientProfile";
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
import { toClientProfile } from "./routes/ClientProfile";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import "./RealmSettingsSection.css";
@ -120,11 +120,7 @@ export default function ProfilesTab() {
);
if (!tableProfiles) {
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}
const save = async () => {

View file

@ -1,9 +1,11 @@
import { Breadcrumb, BreadcrumbItem, Spinner } from "@patternfly/react-core";
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core";
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { KEY_PROVIDER_TYPE } from "../util";
@ -153,11 +155,7 @@ export default function RealmSettingsSection() {
);
if (!realm || !realmComponents) {
return (
<div className="pf-u-text-align-center">
<Spinner />
</div>
);
return <KeycloakSpinner />;
}
return (
<RealmSettingsTabs

View file

@ -48,6 +48,7 @@ import { PartialExportDialog } from "./PartialExport";
import { toRealmSettings } from "./routes/RealmSettings";
import { LocalizationTab } from "./LocalizationTab";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { UserRegistration } from "./UserRegistration";
import { DEFAULT_LOCALE } from "../i18n";
import { toDashboard } from "../dashboard/routes/Dashboard";
import environment from "../environment";
@ -448,6 +449,14 @@ export const RealmSettingsTabs = ({
>
<UserProfileTab />
</Tab>
<Tab
eventKey="userRegistration"
title={<TabTitleText>{t("userRegistration")}</TabTitleText>}
data-testid="rs-userRegistration-tab"
aria-label={t("userRegistrationTab")}
>
<UserRegistration />
</Tab>
</KeycloakTabs>
</FormProvider>
</PageSection>

View file

@ -0,0 +1,55 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Tab, Tabs, TabTitleText } from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { AssociatedRolesTab } from "../realm-roles/AssociatedRolesTab";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
export const UserRegistration = () => {
const { t } = useTranslation("realm-settings");
const [realm, setRealm] = useState<RealmRepresentation>();
const [activeTab, setActiveTab] = useState(10);
const [key, setKey] = useState(0);
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
useFetch(
() => adminClient.realms.findOne({ realm: realmName }),
setRealm,
[]
);
if (!realm) {
return <KeycloakSpinner />;
}
return (
<Tabs
key={key}
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(key as number)}
>
<Tab
id="roles"
eventKey={10}
title={<TabTitleText>{t("defaultRoles")}</TabTitleText>}
>
<AssociatedRolesTab
parentRole={{ ...realm.defaultRole, attributes: [] }}
refresh={() => setKey(key + 1)}
/>
</Tab>
<Tab
id="groups"
eventKey={20}
title={<TabTitleText>{t("defaultGroups")}</TabTitleText>}
>
<h1>Work in progress</h1>
</Tab>
</Tabs>
);
};

View file

@ -14,7 +14,8 @@ export type RealmSettingsTab =
| "sessions"
| "tokens"
| "clientPolicies"
| "userProfile";
| "userProfile"
| "userRegistration";
export type RealmSettingsParams = {
realm: string;

View file

@ -50,7 +50,7 @@ export const LdapMapperHardcodedLdapRole = ({
{showAssign && (
<AddRoleMappingModal
id=""
type="user-fed"
type="role"
onAssign={selectRoles}
isRadio={true}
onClose={() => setShowAssign(false)}