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:
parent
ec5acf65ae
commit
ede8db53a0
26 changed files with 286 additions and 189 deletions
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 />;
|
||||
}
|
||||
|
|
8
src/components/keycloak-spinner/KeycloakSpinner.tsx
Normal file
8
src/components/keycloak-spinner/KeycloakSpinner.tsx
Normal 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>
|
||||
);
|
|
@ -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!,
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -264,6 +264,7 @@ export default function AddMapper() {
|
|||
/>
|
||||
{rolesModalOpen && (
|
||||
<AssociatedRolesModal
|
||||
id={id}
|
||||
onConfirm={(role) => setSelectedRole(role)}
|
||||
omitComposites
|
||||
isRadio
|
||||
|
|
|
@ -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")];
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ export type ClientRoleTab =
|
|||
| "details"
|
||||
| "attributes"
|
||||
| "users-in-role"
|
||||
| "AssociateRoles";
|
||||
| "associated-roles";
|
||||
|
||||
export type ClientRoleParams = {
|
||||
realm: string;
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { RouteDef } from "../../route-config";
|
|||
|
||||
export type RealmRoleTab =
|
||||
| "details"
|
||||
| "AssociatedRoles"
|
||||
| "associated-roles"
|
||||
| "attributes"
|
||||
| "users-in-role";
|
||||
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
55
src/realm-settings/UserRegistration.tsx
Normal file
55
src/realm-settings/UserRegistration.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -14,7 +14,8 @@ export type RealmSettingsTab =
|
|||
| "sessions"
|
||||
| "tokens"
|
||||
| "clientPolicies"
|
||||
| "userProfile";
|
||||
| "userProfile"
|
||||
| "userRegistration";
|
||||
|
||||
export type RealmSettingsParams = {
|
||||
realm: string;
|
||||
|
|
|
@ -50,7 +50,7 @@ export const LdapMapperHardcodedLdapRole = ({
|
|||
{showAssign && (
|
||||
<AddRoleMappingModal
|
||||
id=""
|
||||
type="user-fed"
|
||||
type="role"
|
||||
onAssign={selectRoles}
|
||||
isRadio={true}
|
||||
onClose={() => setShowAssign(false)}
|
||||
|
|
Loading…
Reference in a new issue