Realm settings(client policies): add/edit client-updater-source-groups condition (#1577)
This commit is contained in:
parent
17d9872c04
commit
d08b55ae9a
9 changed files with 211 additions and 14 deletions
|
@ -298,6 +298,7 @@ export const AddScopeDialog = ({
|
|||
{
|
||||
name: "name",
|
||||
},
|
||||
{ name: "protocol", displayKey: "Protocol" },
|
||||
{
|
||||
name: "protocol",
|
||||
displayKey: "clients:protocol",
|
||||
|
|
|
@ -13,7 +13,7 @@ export const DynamicComponents = ({ properties }: DynamicComponentProps) => (
|
|||
<>
|
||||
{properties.map((property) => {
|
||||
const componentType = property.type!;
|
||||
if (isValidComponentType(componentType)) {
|
||||
if (isValidComponentType(componentType) && property.name !== "scopes") {
|
||||
const Component = COMPONENTS[componentType];
|
||||
return <Component key={property.name} {...property} />;
|
||||
} else {
|
||||
|
|
172
src/components/dynamic/MultivaluedChipsComponent.tsx
Normal file
172
src/components/dynamic/MultivaluedChipsComponent.tsx
Normal file
|
@ -0,0 +1,172 @@
|
|||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipGroup,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../help-enabler/HelpItem";
|
||||
import type { ComponentProps } from "./components";
|
||||
import { AddScopeDialog } from "../../clients/scopes/AddScopeDialog";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
|
||||
import { useParams } from "react-router";
|
||||
import type { EditClientPolicyConditionParams } from "../../realm-settings/routes/EditCondition";
|
||||
import { GroupPickerDialog } from "../group/GroupPickerDialog";
|
||||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
|
||||
export const MultivaluedChipsComponent = ({
|
||||
defaultValue,
|
||||
name,
|
||||
}: ComponentProps) => {
|
||||
const { t } = useTranslation("dynamic");
|
||||
const { control } = useFormContext();
|
||||
const { conditionName } = useParams<EditClientPolicyConditionParams>();
|
||||
const adminClient = useAdminClient();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [clientScopes, setClientScopes] = useState<ClientScopeRepresentation[]>(
|
||||
[]
|
||||
);
|
||||
const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
useFetch(
|
||||
() => adminClient.clientScopes.find(),
|
||||
(clientScopes) => {
|
||||
setClientScopes(clientScopes);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const toggleModal = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label={
|
||||
name === "scopes"
|
||||
? t("realm-settings:clientScopesCondition")
|
||||
: t("common:groups")
|
||||
}
|
||||
id={name === "scopes" ? "expected-scopes" : "expected-groups"}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={
|
||||
name === "scopes"
|
||||
? t("realm-settings-help:clientScopesConditionTooltip")
|
||||
: t("realm-settings-help:clientUpdaterSourceGroupsTooltip")
|
||||
}
|
||||
forLabel={
|
||||
name === "scopes"
|
||||
? t("clientScopes")
|
||||
: t("clientUpdaterSourceGroups")
|
||||
}
|
||||
forID={name!}
|
||||
/>
|
||||
}
|
||||
fieldId={name!}
|
||||
>
|
||||
<Controller
|
||||
name={`config.${name}`}
|
||||
control={control}
|
||||
defaultValue={[defaultValue]}
|
||||
rules={{ required: true }}
|
||||
render={({ onChange, value }) => {
|
||||
return (
|
||||
<>
|
||||
{open && name === "scopes" && (
|
||||
<AddScopeDialog
|
||||
clientScopes={clientScopes.filter(
|
||||
(scope) => !value.includes(scope.name!)
|
||||
)}
|
||||
isClientScopesConditionType
|
||||
open={open}
|
||||
toggleDialog={() => setOpen(!open)}
|
||||
onAdd={(scopes) => {
|
||||
onChange([
|
||||
...value,
|
||||
...scopes
|
||||
.map((scope) => scope.scope)
|
||||
.map((item) => item.name!),
|
||||
]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{open && name === "groups" && (
|
||||
<GroupPickerDialog
|
||||
type="selectMany"
|
||||
text={{
|
||||
title: "users:selectGroups",
|
||||
ok: "users:join",
|
||||
}}
|
||||
onConfirm={(groups) => {
|
||||
onChange([...value, ...groups.map((group) => group.name)]);
|
||||
setSelectedGroups([...selectedGroups!, ...groups]);
|
||||
setOpen(false);
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
filterGroups={value}
|
||||
/>
|
||||
)}
|
||||
{value.length === 0 && !conditionName && (
|
||||
<TextInput
|
||||
type="text"
|
||||
id="kc-scopes"
|
||||
value={value}
|
||||
data-testid="client-scope-input"
|
||||
name="config.client-scopes"
|
||||
isDisabled
|
||||
/>
|
||||
)}
|
||||
<ChipGroup
|
||||
className="kc-client-scopes-chip-group"
|
||||
isClosable
|
||||
onClick={() => {
|
||||
onChange([]);
|
||||
if (name === "groups") {
|
||||
setSelectedGroups([]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{value.map((currentChip: string) => (
|
||||
<Chip
|
||||
key={currentChip}
|
||||
onClick={() => {
|
||||
onChange(
|
||||
value.filter((item: string) => item !== currentChip)
|
||||
);
|
||||
if (name === "groups") {
|
||||
setSelectedGroups(
|
||||
value.filter((item: string) => item !== currentChip)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{currentChip}
|
||||
</Chip>
|
||||
))}
|
||||
</ChipGroup>
|
||||
<Button
|
||||
data-testid="select-scope-button"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
toggleModal();
|
||||
}}
|
||||
>
|
||||
{t("common:select")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
|
@ -25,7 +25,7 @@ import { GroupPath } from "./GroupPath";
|
|||
export type GroupPickerDialogProps = {
|
||||
id?: string;
|
||||
type: "selectOne" | "selectMany";
|
||||
filterGroups?: GroupRepresentation[];
|
||||
filterGroups?: string[];
|
||||
text: { title: string; ok: string };
|
||||
onConfirm: (groups: GroupRepresentation[]) => void;
|
||||
onClose: () => void;
|
||||
|
@ -100,9 +100,10 @@ export const GroupPickerDialog = ({
|
|||
);
|
||||
|
||||
const isRowDisabled = (row?: GroupRepresentation) => {
|
||||
return !![...joinedGroups, ...(filterGroups || [])].find(
|
||||
(group) => group.id === row?.id
|
||||
);
|
||||
return [
|
||||
...joinedGroups.map((item) => item.name),
|
||||
...(filterGroups || []),
|
||||
].some((group) => group === row?.name);
|
||||
};
|
||||
|
||||
const hasSubgroups = (group: GroupRepresentation) => {
|
||||
|
@ -129,7 +130,7 @@ export const GroupPickerDialog = ({
|
|||
<Modal
|
||||
variant={ModalVariant.small}
|
||||
title={t(text.title, {
|
||||
group1: filterGroups?.[0]?.name,
|
||||
group1: filterGroups?.[0],
|
||||
group2: currentGroup() ? currentGroup().name : t("root"),
|
||||
})}
|
||||
isOpen
|
||||
|
@ -230,7 +231,7 @@ export const GroupPickerDialog = ({
|
|||
>
|
||||
{type === "selectMany" && (
|
||||
<DataListCheck
|
||||
className="join-group-modal-check"
|
||||
className="kc-join-group-modal-check"
|
||||
data-testid={`${group.name}-check`}
|
||||
checked={group.checked}
|
||||
isDisabled={isRowDisabled(group)}
|
||||
|
|
|
@ -203,7 +203,7 @@ export const GroupTable = () => {
|
|||
{move && (
|
||||
<GroupPickerDialog
|
||||
type="selectOne"
|
||||
filterGroups={[move]}
|
||||
filterGroups={[move.name!]}
|
||||
text={{
|
||||
title: "groups:moveToGroup",
|
||||
ok: "groups:moveHere",
|
||||
|
|
|
@ -38,7 +38,7 @@ import {
|
|||
COMPONENTS,
|
||||
isValidComponentType,
|
||||
} from "../components/dynamic/components";
|
||||
import { MultivaluedScopesComponent } from "../components/dynamic/MultivaluedScopesComponent";
|
||||
import { MultivaluedChipsComponent } from "../components/dynamic/MultivaluedChipsComponent";
|
||||
import { MultivaluedRoleComponent } from "../components/dynamic/MultivaluedRoleComponent";
|
||||
export type ItemType = { value: string };
|
||||
|
||||
|
@ -92,7 +92,8 @@ export default function NewClientPolicyCondition() {
|
|||
const property = properties.find((p) => p.name === key);
|
||||
if (
|
||||
property?.type === "MultivaluedString" &&
|
||||
property.name !== "scopes"
|
||||
property.name !== "scopes" &&
|
||||
property.name !== "groups"
|
||||
) {
|
||||
form.setValue(formKey, convertToMultiline(value));
|
||||
} else if (property?.name === "client-scopes") {
|
||||
|
@ -135,7 +136,9 @@ export default function NewClientPolicyCondition() {
|
|||
|
||||
const writeConfig = () => {
|
||||
return conditionProperties.reduce((r: any, p) => {
|
||||
p.type === "MultivaluedString" && p.name !== "scopes"
|
||||
p.type === "MultivaluedString" &&
|
||||
p.name !== "scopes" &&
|
||||
p.name !== "groups"
|
||||
? (r[p.name!] = toValue(configValues[p.name!]))
|
||||
: (r[p.name!] = configValues[p.name!]);
|
||||
return r;
|
||||
|
@ -300,11 +303,22 @@ export default function NewClientPolicyCondition() {
|
|||
conditionName === "client-scopes")
|
||||
) {
|
||||
return (
|
||||
<MultivaluedScopesComponent
|
||||
<MultivaluedChipsComponent
|
||||
defaultValue="offline_access"
|
||||
{...property}
|
||||
/>
|
||||
);
|
||||
} else if (
|
||||
property.name === "groups" &&
|
||||
(conditionType === "client-updater-source-groups" ||
|
||||
conditionName === "client-updater-source-groups")
|
||||
) {
|
||||
return (
|
||||
<MultivaluedChipsComponent
|
||||
defaultValue="topgroup"
|
||||
{...property}
|
||||
/>
|
||||
);
|
||||
} else if (isValidComponentType(componentType)) {
|
||||
const Component = COMPONENTS[componentType];
|
||||
return <Component key={property.name} {...property} />;
|
||||
|
|
|
@ -265,3 +265,7 @@ input#kc-scopes {
|
|||
min-width: 585px;
|
||||
padding-left: none;
|
||||
}
|
||||
|
||||
.kc-join-group-modal-check {
|
||||
margin-right: var(--pf-global--spacer--sm);
|
||||
}
|
||||
|
|
|
@ -145,13 +145,13 @@ export const UserForm = ({
|
|||
setOpen(false);
|
||||
}}
|
||||
onClose={() => setOpen(false)}
|
||||
filterGroups={selectedGroups}
|
||||
filterGroups={selectedGroups.map((group) => group.name!)}
|
||||
/>
|
||||
)}
|
||||
{user?.id ? (
|
||||
<>
|
||||
<FormGroup label={t("common:id")} fieldId="kc-id" isRequired>
|
||||
<TextInput id={user?.id} value={user?.id} type="text" isReadOnly />
|
||||
<TextInput id={user.id} value={user.id} type="text" isReadOnly />
|
||||
</FormGroup>
|
||||
<FormGroup label={t("createdAt")} fieldId="kc-created-at" isRequired>
|
||||
<TextInput
|
||||
|
|
|
@ -15,6 +15,11 @@ kc-consents-chip-group .pf-c-chip-group__list {
|
|||
button#kc-join-groups-button {
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
.kc-join-group-modal-check {
|
||||
margin-right: var(--pf-global--spacer--sm);
|
||||
}
|
||||
|
||||
.join-group-dialog-row-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue