Clients: Authorization -> Evaluate tab (#1861)
This commit is contained in:
parent
109c255d90
commit
dea54e674a
6 changed files with 584 additions and 70 deletions
|
@ -60,6 +60,9 @@ import { AuthorizationResources } from "./authorization/Resources";
|
|||
import { AuthorizationScopes } from "./authorization/Scopes";
|
||||
import { AuthorizationPolicies } from "./authorization/Policies";
|
||||
import { AuthorizationPermissions } from "./authorization/Permissions";
|
||||
import { AuthorizationEvaluate } from "./authorization/AuthorizationEvaluate";
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||
|
||||
type ClientDetailHeaderProps = {
|
||||
onChange: (value: boolean) => void;
|
||||
|
@ -195,6 +198,24 @@ export default function ClientDetails() {
|
|||
});
|
||||
|
||||
const [client, setClient] = useState<ClientRepresentation>();
|
||||
const [clients, setClients] = useState<ClientRepresentation[]>([]);
|
||||
const [clientRoles, setClientRoles] = useState<RoleRepresentation[]>([]);
|
||||
const [users, setUsers] = useState<UserRepresentation[]>([]);
|
||||
|
||||
useFetch(
|
||||
() =>
|
||||
Promise.all([
|
||||
adminClient.clients.find(),
|
||||
adminClient.roles.find(),
|
||||
adminClient.users.find(),
|
||||
]),
|
||||
([clients, roles, users]) => {
|
||||
setClients(clients);
|
||||
setClientRoles(roles);
|
||||
setUsers(users);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const loader = async () => {
|
||||
const roles = await adminClient.clients.listRoles({ id: clientId });
|
||||
|
@ -508,6 +529,20 @@ export default function ClientDetails() {
|
|||
>
|
||||
<AuthorizationPermissions clientId={clientId} />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="Evaluate"
|
||||
eventKey={44}
|
||||
title={<TabTitleText>{t("evaluate")}</TabTitleText>}
|
||||
>
|
||||
<AuthorizationEvaluate
|
||||
clients={clients}
|
||||
clientName={client.clientId}
|
||||
clientRoles={clientRoles}
|
||||
users={users}
|
||||
save={save}
|
||||
reset={() => setupForm(client)}
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Tab>
|
||||
)}
|
||||
|
|
425
src/clients/authorization/AuthorizationEvaluate.tsx
Normal file
425
src/clients/authorization/AuthorizationEvaluate.tsx
Normal file
|
@ -0,0 +1,425 @@
|
|||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectVariant,
|
||||
SelectOption,
|
||||
PageSection,
|
||||
ActionGroup,
|
||||
Button,
|
||||
Switch,
|
||||
ExpandableSection,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { FormPanel } from "../../components/scroll-form/FormPanel";
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||
import type ResourceEvaluation from "@keycloak/keycloak-admin-client/lib/defs/resourceEvaluation";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { AttributeInput } from "../../components/attribute-input/AttributeInput";
|
||||
import { defaultContextAttributes } from "../utils";
|
||||
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
|
||||
import { useParams } from "react-router-dom";
|
||||
import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation";
|
||||
|
||||
export type AttributeType = {
|
||||
key: string;
|
||||
name: string;
|
||||
custom?: boolean;
|
||||
values?: {
|
||||
[key: string]: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
type ClientSettingsProps = {
|
||||
clients: ClientRepresentation[];
|
||||
clientName?: string;
|
||||
save: () => void;
|
||||
reset: () => void;
|
||||
users: UserRepresentation[];
|
||||
clientRoles: RoleRepresentation[];
|
||||
};
|
||||
|
||||
export const AuthorizationEvaluate = ({
|
||||
clients,
|
||||
clientRoles,
|
||||
clientName,
|
||||
users,
|
||||
reset,
|
||||
}: ClientSettingsProps) => {
|
||||
const form = useFormContext<ResourceEvaluation>();
|
||||
const { control } = form;
|
||||
const { t } = useTranslation("clients");
|
||||
const adminClient = useAdminClient();
|
||||
const realm = useRealm();
|
||||
const { clientId } = useParams<{ clientId: string }>();
|
||||
|
||||
const [clientsDropdownOpen, setClientsDropdownOpen] = useState(false);
|
||||
const [scopesDropdownOpen, setScopesDropdownOpen] = useState(false);
|
||||
|
||||
const [userDropdownOpen, setUserDropdownOpen] = useState(false);
|
||||
const [roleDropdownOpen, setRoleDropdownOpen] = useState(false);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [applyToResourceType, setApplyToResourceType] = useState(false);
|
||||
const [resources, setResources] = useState<ResourceRepresentation[]>([]);
|
||||
const [scopes, setScopes] = useState<ScopeRepresentation[]>([]);
|
||||
const [selectedClient, setSelectedClient] = useState<ClientRepresentation>();
|
||||
const [selectedUser, setSelectedUser] = useState<UserRepresentation>();
|
||||
|
||||
useFetch(
|
||||
async () =>
|
||||
Promise.all([
|
||||
adminClient.clients.listResources({
|
||||
id: clientId,
|
||||
}),
|
||||
adminClient.clients.listAllScopes({
|
||||
id: clientId,
|
||||
}),
|
||||
]),
|
||||
([resources, scopes]) => {
|
||||
setResources(resources);
|
||||
setScopes(scopes);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const evaluate = (formValues: ResourceEvaluation) => {
|
||||
const resEval: ResourceEvaluation = {
|
||||
roleIds: formValues.roleIds ?? [],
|
||||
userId: selectedUser?.id!,
|
||||
entitlements: false,
|
||||
context: formValues.context,
|
||||
resources: formValues.resources,
|
||||
clientId: selectedClient?.id!,
|
||||
};
|
||||
return adminClient.clients.evaluateResource(
|
||||
{ id: clientId!, realm: realm.realm },
|
||||
resEval
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageSection>
|
||||
<FormPanel
|
||||
className="kc-identity-information"
|
||||
title={t("clients:identityInformation")}
|
||||
>
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
role="manage-clients"
|
||||
onSubmit={form.handleSubmit(evaluate)}
|
||||
>
|
||||
<FormGroup
|
||||
label={t("client")}
|
||||
isRequired
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:client"
|
||||
fieldLabelId="clients:client"
|
||||
/>
|
||||
}
|
||||
fieldId="client"
|
||||
>
|
||||
<Controller
|
||||
name="client"
|
||||
defaultValue={clientName}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="client"
|
||||
onToggle={setClientsDropdownOpen}
|
||||
onSelect={(_, value) => {
|
||||
setSelectedClient(value as ClientRepresentation);
|
||||
onChange((value as ClientRepresentation).clientId);
|
||||
setClientsDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.typeahead}
|
||||
aria-label={t("client")}
|
||||
isOpen={clientsDropdownOpen}
|
||||
>
|
||||
{clients.map((client) => (
|
||||
<SelectOption
|
||||
selected={client === value}
|
||||
key={client.clientId}
|
||||
value={client}
|
||||
>
|
||||
{client.clientId}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("user")}
|
||||
isRequired
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:userSelect"
|
||||
fieldLabelId="clients:userSelect"
|
||||
/>
|
||||
}
|
||||
fieldId="loginTheme"
|
||||
>
|
||||
<Controller
|
||||
name="userId"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="user"
|
||||
placeholderText={t("selectAUser")}
|
||||
onToggle={setUserDropdownOpen}
|
||||
onSelect={(_, value) => {
|
||||
setSelectedUser(value as UserRepresentation);
|
||||
onChange((value as UserRepresentation).username);
|
||||
setUserDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.typeahead}
|
||||
aria-label={t("user")}
|
||||
isOpen={userDropdownOpen}
|
||||
>
|
||||
{users.map((user) => (
|
||||
<SelectOption
|
||||
selected={user.username === value}
|
||||
key={user.username}
|
||||
value={user}
|
||||
>
|
||||
{user.username}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("roles")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:roles"
|
||||
fieldLabelId="clients:roles"
|
||||
/>
|
||||
}
|
||||
fieldId="realmRole"
|
||||
>
|
||||
<Controller
|
||||
name="rolesIds"
|
||||
placeholderText={t("selectARole")}
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
toggleId="role"
|
||||
onToggle={setRoleDropdownOpen}
|
||||
selections={value}
|
||||
onSelect={(_, v) => {
|
||||
const option = v.toString();
|
||||
if (value.includes(option)) {
|
||||
onChange(value.filter((item: string) => item !== option));
|
||||
} else {
|
||||
onChange([...value, option]);
|
||||
}
|
||||
setRoleDropdownOpen(false);
|
||||
}}
|
||||
onClear={(event) => {
|
||||
event.stopPropagation();
|
||||
onChange([]);
|
||||
}}
|
||||
aria-label={t("realmRole")}
|
||||
isOpen={roleDropdownOpen}
|
||||
>
|
||||
{clientRoles.map((role) => (
|
||||
<SelectOption
|
||||
selected={role.name === value}
|
||||
key={role.name}
|
||||
value={role.name}
|
||||
/>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormAccess>
|
||||
</FormPanel>
|
||||
<FormPanel className="kc-permissions" title={t("permissions")}>
|
||||
<FormAccess isHorizontal role="manage-clients">
|
||||
<FormGroup
|
||||
label={t("applyToResourceType")}
|
||||
fieldId="applyToResourceType"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:applyToResourceType"
|
||||
fieldLabelId="clients:applyToResourceType"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="applyToResource"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="applyToResource-switch"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value === "true"}
|
||||
onChange={(value) => {
|
||||
onChange(value.toString());
|
||||
setApplyToResourceType(value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{!applyToResourceType && (
|
||||
<FormGroup
|
||||
label={t("resourcesAndAuthScopes")}
|
||||
id="resourcesAndAuthScopes"
|
||||
isRequired
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("clients-help:contextualAttributes")}
|
||||
fieldLabelId={`resourcesAndAuthScopes`}
|
||||
/>
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
fieldId={name!}
|
||||
>
|
||||
<AttributeInput
|
||||
selectableValues={resources.map((item) => item.name!)}
|
||||
resources={resources}
|
||||
isKeySelectable
|
||||
name="resources"
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
{applyToResourceType && (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("resourceType")}
|
||||
isRequired
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:resourceType"
|
||||
fieldLabelId="clients:resourceType"
|
||||
/>
|
||||
}
|
||||
fieldId="client"
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
id="alias"
|
||||
name="alias"
|
||||
data-testid="alias"
|
||||
ref={form.register({ required: true })}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("authScopes")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:scopesSelect"
|
||||
fieldLabelId="clients:client"
|
||||
/>
|
||||
}
|
||||
fieldId="authScopes"
|
||||
>
|
||||
<Controller
|
||||
name="authScopes"
|
||||
defaultValue={[]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="authScopes"
|
||||
onToggle={setScopesDropdownOpen}
|
||||
onSelect={(_, v) => {
|
||||
const option = v.toString();
|
||||
if (value.includes(option)) {
|
||||
onChange(
|
||||
value.filter((item: string) => item !== option)
|
||||
);
|
||||
} else {
|
||||
onChange([...value, option]);
|
||||
}
|
||||
setScopesDropdownOpen(false);
|
||||
}}
|
||||
selections={value}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
aria-label={t("authScopes")}
|
||||
isOpen={scopesDropdownOpen}
|
||||
>
|
||||
{scopes.map((scope) => (
|
||||
<SelectOption
|
||||
selected={scope.name === value}
|
||||
key={scope.id}
|
||||
value={scope.name}
|
||||
/>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
<ExpandableSection
|
||||
toggleText={t("contextualInfo")}
|
||||
onToggle={() => setIsExpanded(!isExpanded)}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<FormGroup
|
||||
label={t("contextualAttributes")}
|
||||
id="contextualAttributes"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("clients-help:contextualAttributes")}
|
||||
fieldLabelId={`contextualAttributes`}
|
||||
/>
|
||||
}
|
||||
helperTextInvalid={t("common:required")}
|
||||
fieldId={name!}
|
||||
>
|
||||
<AttributeInput
|
||||
selectableValues={defaultContextAttributes.map(
|
||||
(item) => item.name
|
||||
)}
|
||||
isKeySelectable
|
||||
name="context"
|
||||
/>
|
||||
</FormGroup>
|
||||
</ExpandableSection>
|
||||
<ActionGroup>
|
||||
<Button data-testid="authorization-eval" type="submit">
|
||||
{t("evaluate")}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="authorization-revert"
|
||||
variant="link"
|
||||
onClick={reset}
|
||||
>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="authorization-revert"
|
||||
variant="primary"
|
||||
onClick={reset}
|
||||
isDisabled
|
||||
>
|
||||
{t("lastEvaluation")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</FormPanel>
|
||||
</PageSection>
|
||||
);
|
||||
};
|
|
@ -44,8 +44,23 @@ export default {
|
|||
"Default URL to use when the auth server needs to redirect or link back to the client.",
|
||||
adminURL:
|
||||
"URL to the admin interface of the client. Set this if the client supports the adapter REST API. This REST API allows the auth server to push revocation policies and other administrative tasks. Usually this is set to the base URL of the client.",
|
||||
client:
|
||||
"Select the client making this authorization request. If not provided, authorization requests would be done based on the client you are in.",
|
||||
clientId:
|
||||
"Specifies ID referenced in URI and tokens. For example 'my-client'. For SAML this is also the expected issuer value from authn requests",
|
||||
selectUser:
|
||||
"Select a user whose identity is going to be used to query permissions from the server.",
|
||||
roles: "Select the roles you want to associate with the selected user.",
|
||||
contextualAttributes:
|
||||
"Any attribute provided by a running environment or execution context.",
|
||||
resourceType:
|
||||
"Specifies that this permission must be applied to all resource instances of a given type.",
|
||||
applyToResourceType:
|
||||
"Specifies if this permission should be applied to all resources with a given type. In this case, this permission will be evaluated for all instances of a given resource type.",
|
||||
resources:
|
||||
"Specifies that this permission must be applied to a specific resource instance.",
|
||||
scopesSelect:
|
||||
"Specifies that this permission must be applied to one or more scopes.",
|
||||
clientName:
|
||||
"Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example: ${my_client}",
|
||||
description:
|
||||
|
|
|
@ -36,7 +36,18 @@ export default {
|
|||
clientScopeError: "Could not update the scope mapping {{error}}",
|
||||
searchByName: "Search by name",
|
||||
setup: "Setup",
|
||||
selectAUser: "Select a user",
|
||||
client: "Client",
|
||||
evaluate: "Evaluate",
|
||||
lastEvaluation: "Last Evaluation",
|
||||
resourcesAndAuthScopes: "Resources and Authentication Scopes",
|
||||
authScopes: "Authorization scopes",
|
||||
anyResource: "Any resource",
|
||||
anyScope: "Any scope",
|
||||
selectScope: "Select a scope",
|
||||
applyToResourceType: "Apply to Resource Type",
|
||||
contextualInfo: "Contextual Information",
|
||||
contextualAttributes: "Contextual Attributes",
|
||||
selectOrTypeAKey: "Select or type a key",
|
||||
custom: "Custom Attribute...",
|
||||
kc: {
|
||||
|
@ -128,6 +139,7 @@ export default {
|
|||
"The permissions below will be removed when they are no longer used by other resources:",
|
||||
resourceDeletedSuccess: "The resource successfully deleted",
|
||||
resourceDeletedError: "Could not remove the resource {{error}}",
|
||||
identityInformation: "Identity Information",
|
||||
permissions: "Permissions",
|
||||
searchForPermission: "Search for permission",
|
||||
deleteScope: "Permanently delete authorization scope?",
|
||||
|
|
|
@ -27,5 +27,9 @@
|
|||
}
|
||||
|
||||
.pf-c-select.kc-attribute-value-selectable {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.pf-c-form-control.value-input {
|
||||
width: 350px;
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
// import { Controller, useFieldArray, useFormContext } from "react-hook-form";
|
||||
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
Button,
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
|
@ -18,10 +17,10 @@ import {
|
|||
Tr,
|
||||
} from "@patternfly/react-table";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
|
||||
|
||||
import "../attribute-form/attribute-form.css";
|
||||
import { defaultContextAttributes } from "../../clients/utils";
|
||||
import { camelCase } from "lodash";
|
||||
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
|
||||
|
||||
export type AttributeType = {
|
||||
|
@ -44,10 +43,11 @@ export const AttributeInput = ({
|
|||
name,
|
||||
isKeySelectable,
|
||||
selectableValues,
|
||||
resources,
|
||||
}: AttributeInputProps) => {
|
||||
const { t } = useTranslation("common");
|
||||
const { control, register, watch } = useFormContext();
|
||||
const { fields, append, remove, insert } = useFieldArray({
|
||||
const { control, register, watch, getValues } = useFormContext();
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control: control,
|
||||
name,
|
||||
});
|
||||
|
@ -58,63 +58,90 @@ export const AttributeInput = ({
|
|||
}
|
||||
}, []);
|
||||
|
||||
const [isOpenArray, setIsOpenArray] = useState<boolean[]>([false]);
|
||||
const [isKeyOpenArray, setIsKeyOpenArray] = useState([false]);
|
||||
const watchLastKey = watch(`${name}[${fields.length - 1}].key`, "");
|
||||
const watchLastValue = watch(`${name}[${fields.length - 1}].value`, "");
|
||||
|
||||
const [valueOpen, setValueOpen] = useState(false);
|
||||
const toggleSelect = (rowIndex: number, open: boolean) => {
|
||||
const arr = [...isOpenArray];
|
||||
const [isValueOpenArray, setIsValueOpenArray] = useState([false]);
|
||||
const toggleKeySelect = (rowIndex: number, open: boolean) => {
|
||||
const arr = [...isKeyOpenArray];
|
||||
arr[rowIndex] = open;
|
||||
setIsOpenArray(arr);
|
||||
setIsKeyOpenArray(arr);
|
||||
};
|
||||
|
||||
const toggleValueSelect = (rowIndex: number, open: boolean) => {
|
||||
const arr = [...isValueOpenArray];
|
||||
arr[rowIndex] = open;
|
||||
setIsValueOpenArray(arr);
|
||||
};
|
||||
|
||||
const renderValueInput = (rowIndex: number, attribute: any) => {
|
||||
const attributeValues = defaultContextAttributes.find(
|
||||
(attr) => attr.key === attribute.key
|
||||
)?.values;
|
||||
let attributeValues: { key: string; name: string }[] | undefined = [];
|
||||
|
||||
const scopeValues = resources?.find(
|
||||
(resource) => resource.name === getValues().resources[rowIndex]?.key
|
||||
)?.scopes;
|
||||
|
||||
if (selectableValues) {
|
||||
attributeValues = defaultContextAttributes.find(
|
||||
(attr) => attr.name === getValues().context[rowIndex]?.key
|
||||
)?.values;
|
||||
}
|
||||
|
||||
const getMessageBundleKey = (attributeName: string) =>
|
||||
camelCase(attributeName).replace(/\W/g, "");
|
||||
|
||||
return (
|
||||
<Td>
|
||||
{attributeValues?.length ? (
|
||||
{scopeValues?.length || attributeValues?.length ? (
|
||||
<Controller
|
||||
name={`${name}[${rowIndex}].value`}
|
||||
defaultValue={attribute.value}
|
||||
defaultValue={[]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
id={`${attribute.id}-value`}
|
||||
className="kc-attribute-value-selectable"
|
||||
name={`${name}[${rowIndex}].value`}
|
||||
chipGroupProps={{
|
||||
numChips: 1,
|
||||
expandedText: t("common:hide"),
|
||||
collapsedText: t("common:showRemaining"),
|
||||
}}
|
||||
toggleId={`group-${name}`}
|
||||
onToggle={(open) => setValueOpen(open)}
|
||||
isOpen={valueOpen}
|
||||
variant={SelectVariant.typeahead}
|
||||
onToggle={(open) => toggleValueSelect(rowIndex, open)}
|
||||
isOpen={isValueOpenArray[rowIndex]}
|
||||
variant={
|
||||
resources
|
||||
? SelectVariant.typeaheadMulti
|
||||
: SelectVariant.typeahead
|
||||
}
|
||||
typeAheadAriaLabel={t("clients:selectOrTypeAKey")}
|
||||
placeholderText={t("clients:selectOrTypeAKey")}
|
||||
selections={value}
|
||||
onSelect={(_, selectedValue) => {
|
||||
remove(rowIndex);
|
||||
insert(rowIndex, {
|
||||
key: attribute.key,
|
||||
value: selectedValue,
|
||||
});
|
||||
onChange(selectedValue);
|
||||
|
||||
setValueOpen(false);
|
||||
onSelect={(_, v) => {
|
||||
if (resources) {
|
||||
const option = v.toString();
|
||||
if (value.includes(option)) {
|
||||
onChange(value.filter((item: string) => item !== option));
|
||||
} else {
|
||||
onChange([...value, option]);
|
||||
}
|
||||
} else {
|
||||
onChange(v);
|
||||
}
|
||||
toggleValueSelect(rowIndex, false);
|
||||
}}
|
||||
>
|
||||
{attributeValues.map((attribute) => (
|
||||
<SelectOption key={attribute.key} value={attribute.key}>
|
||||
{t(`${attribute.name}`)}
|
||||
</SelectOption>
|
||||
{(scopeValues || attributeValues)?.map((scope) => (
|
||||
<SelectOption key={scope.name} value={scope.name} />
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<TextInput
|
||||
id={`$clients:${attribute.key}-value`}
|
||||
id={`${getMessageBundleKey(attribute.key)}-value`}
|
||||
className="value-input"
|
||||
name={`${name}[${rowIndex}].value`}
|
||||
ref={register()}
|
||||
|
@ -148,44 +175,40 @@ export const AttributeInput = ({
|
|||
<Tr key={attribute.id} data-testid="attribute-row">
|
||||
<Td>
|
||||
{isKeySelectable ? (
|
||||
<FormGroup fieldId="test">
|
||||
<Controller
|
||||
name={`${name}[${rowIndex}].key`}
|
||||
defaultValue={attribute.key}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="id"
|
||||
id={`${attribute.id}-key`}
|
||||
name={`${name}[${rowIndex}].key`}
|
||||
className="kc-attribute-key-selectable"
|
||||
variant={SelectVariant.typeahead}
|
||||
typeAheadAriaLabel={t("clients:selectOrTypeAKey")}
|
||||
placeholderText={t("clients:selectOrTypeAKey")}
|
||||
onToggle={(open) => toggleSelect(rowIndex, open)}
|
||||
onSelect={(_, selectedValue) => {
|
||||
remove(rowIndex);
|
||||
insert(rowIndex, {
|
||||
key: selectedValue,
|
||||
value: attribute.value,
|
||||
});
|
||||
onChange(selectedValue);
|
||||
<Controller
|
||||
name={`${name}[${rowIndex}].key`}
|
||||
defaultValue={attribute.key}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
id={`${name}[${rowIndex}].key`}
|
||||
className="kc-attribute-key-selectable"
|
||||
name={`${name}[${rowIndex}].key`}
|
||||
toggleId={`group-${name}`}
|
||||
onToggle={(open) => toggleKeySelect(rowIndex, open)}
|
||||
isOpen={isKeyOpenArray[rowIndex]}
|
||||
variant={SelectVariant.typeahead}
|
||||
typeAheadAriaLabel={t("clients:selectOrTypeAKey")}
|
||||
placeholderText={t("clients:selectOrTypeAKey")}
|
||||
selections={value}
|
||||
onSelect={(_, v) => {
|
||||
onChange(v);
|
||||
|
||||
toggleSelect(rowIndex, false);
|
||||
}}
|
||||
selections={value}
|
||||
aria-label="some label"
|
||||
isOpen={isOpenArray[rowIndex]}
|
||||
>
|
||||
{selectableValues?.map((attribute) => (
|
||||
<SelectOption key={attribute} value={attribute}>
|
||||
{t(`clients:${attribute}`)}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
toggleKeySelect(rowIndex, false);
|
||||
}}
|
||||
>
|
||||
{selectableValues?.map((attribute) => (
|
||||
<SelectOption
|
||||
selected={attribute === value}
|
||||
key={attribute}
|
||||
value={attribute}
|
||||
>
|
||||
{attribute}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<TextInput
|
||||
id={`${attribute.id}-key`}
|
||||
|
@ -219,7 +242,7 @@ export const AttributeInput = ({
|
|||
onClick={() => {
|
||||
append({ key: "", value: "" });
|
||||
if (isKeySelectable) {
|
||||
setIsOpenArray([...isOpenArray, false]);
|
||||
setIsKeyOpenArray([...isKeyOpenArray, false]);
|
||||
}
|
||||
}}
|
||||
icon={<PlusCircleIcon />}
|
||||
|
|
Loading…
Reference in a new issue