Changed to use form to store executors (#3242)
This commit is contained in:
parent
fe1b602f45
commit
df6526311a
2 changed files with 119 additions and 184 deletions
|
@ -1,4 +1,7 @@
|
||||||
import { Fragment, useEffect, useMemo, useState } from "react";
|
import { Fragment, useMemo, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useForm, useFieldArray } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
ActionGroup,
|
ActionGroup,
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
|
@ -19,26 +22,28 @@ import {
|
||||||
TextVariants,
|
TextVariants,
|
||||||
ValidatedOptions,
|
ValidatedOptions,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useForm } from "react-hook-form";
|
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
|
||||||
|
import type ClientProfilesRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfilesRepresentation";
|
||||||
|
import type ClientPolicyExecutorRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyExecutorRepresentation";
|
||||||
import { FormAccess } from "../components/form-access/FormAccess";
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { Link, useNavigate } from "react-router-dom-v5-compat";
|
import { Link, useNavigate } from "react-router-dom-v5-compat";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
|
|
||||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
|
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
|
||||||
import { KeycloakTextArea } from "../components/keycloak-text-area/KeycloakTextArea";
|
import { KeycloakTextArea } from "../components/keycloak-text-area/KeycloakTextArea";
|
||||||
import { PlusCircleIcon, TrashIcon } from "@patternfly/react-icons";
|
import { PlusCircleIcon, TrashIcon } from "@patternfly/react-icons";
|
||||||
import "./realm-settings-section.css";
|
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { toAddExecutor } from "./routes/AddExecutor";
|
import { toAddExecutor } from "./routes/AddExecutor";
|
||||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||||
import { ClientProfileParams, toClientProfile } from "./routes/ClientProfile";
|
import { ClientProfileParams, toClientProfile } from "./routes/ClientProfile";
|
||||||
import { toExecutor } from "./routes/Executor";
|
import { toExecutor } from "./routes/Executor";
|
||||||
import { toClientPolicies } from "./routes/ClientPolicies";
|
import { toClientPolicies } from "./routes/ClientPolicies";
|
||||||
|
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||||
|
|
||||||
|
import "./realm-settings-section.css";
|
||||||
|
|
||||||
type ClientProfileForm = Required<ClientProfileRepresentation>;
|
type ClientProfileForm = Required<ClientProfileRepresentation>;
|
||||||
|
|
||||||
|
@ -54,19 +59,25 @@ export default function ClientProfileForm() {
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
|
getValues,
|
||||||
register,
|
register,
|
||||||
formState: { isDirty, errors },
|
formState: { isDirty, errors },
|
||||||
|
control,
|
||||||
} = useForm<ClientProfileForm>({
|
} = useForm<ClientProfileForm>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { fields: profileExecutors, remove } =
|
||||||
|
useFieldArray<ClientPolicyExecutorRepresentation>({
|
||||||
|
name: "executors",
|
||||||
|
control,
|
||||||
|
});
|
||||||
|
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const { adminClient } = useAdminClient();
|
const { adminClient } = useAdminClient();
|
||||||
const [globalProfiles, setGlobalProfiles] = useState<
|
const [profiles, setProfiles] = useState<ClientProfilesRepresentation>();
|
||||||
ClientProfileRepresentation[]
|
const [isGlobalProfile, setIsGlobalProfile] = useState(false);
|
||||||
>([]);
|
|
||||||
const [profiles, setProfiles] = useState<ClientProfileRepresentation[]>([]);
|
|
||||||
const { realm, profileName } = useParams<ClientProfileParams>();
|
const { realm, profileName } = useParams<ClientProfileParams>();
|
||||||
const serverInfo = useServerInfo();
|
const serverInfo = useServerInfo();
|
||||||
const executorTypes = useMemo(
|
const executorTypes = useMemo(
|
||||||
|
@ -82,25 +93,38 @@ export default function ClientProfileForm() {
|
||||||
}>();
|
}>();
|
||||||
const editMode = profileName ? true : false;
|
const editMode = profileName ? true : false;
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
const reload = () => setKey(new Date().getTime());
|
const reload = () => setKey(key + 1);
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
() =>
|
() =>
|
||||||
adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }),
|
adminClient.clientPolicies.listProfiles({ includeGlobalProfiles: true }),
|
||||||
(profiles) => {
|
(profiles) => {
|
||||||
setGlobalProfiles(profiles.globalProfiles ?? []);
|
setProfiles({
|
||||||
setProfiles(profiles.profiles ?? []);
|
globalProfiles: profiles.globalProfiles,
|
||||||
|
profiles: profiles.profiles?.filter((p) => p.name !== profileName),
|
||||||
|
});
|
||||||
|
const globalProfile = profiles.globalProfiles?.find(
|
||||||
|
(p) => p.name === profileName
|
||||||
|
);
|
||||||
|
const profile = profiles.profiles?.find((p) => p.name === profileName);
|
||||||
|
setIsGlobalProfile(globalProfile !== undefined);
|
||||||
|
setValue("name", globalProfile?.name ?? profile?.name);
|
||||||
|
setValue(
|
||||||
|
"description",
|
||||||
|
globalProfile?.description ?? profile?.description
|
||||||
|
);
|
||||||
|
setValue("executors", globalProfile?.executors ?? profile?.executors);
|
||||||
},
|
},
|
||||||
[key]
|
[key]
|
||||||
);
|
);
|
||||||
|
|
||||||
const save = async (form: ClientProfileForm) => {
|
const save = async (form: ClientProfileForm) => {
|
||||||
const updatedProfiles = editMode ? patchProfiles(form) : addProfile(form);
|
const updatedProfiles = form;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await adminClient.clientPolicies.createProfiles({
|
await adminClient.clientPolicies.createProfiles({
|
||||||
profiles: updatedProfiles,
|
...profiles,
|
||||||
globalProfiles: globalProfiles,
|
profiles: [...(profiles?.profiles || []), updatedProfiles],
|
||||||
});
|
});
|
||||||
|
|
||||||
addAlert(
|
addAlert(
|
||||||
|
@ -121,25 +145,6 @@ export default function ClientProfileForm() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const patchProfiles = (data: ClientProfileRepresentation) =>
|
|
||||||
profiles.map((profile) => {
|
|
||||||
if (profile.name !== profileName) {
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...profile,
|
|
||||||
name: data.name,
|
|
||||||
description: data.description,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const addProfile = (data: ClientProfileRepresentation) =>
|
|
||||||
profiles.concat({
|
|
||||||
...data,
|
|
||||||
executors: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
titleKey: executorToDelete?.name!
|
titleKey: executorToDelete?.name!
|
||||||
? t("deleteExecutorProfileConfirmTitle")
|
? t("deleteExecutorProfileConfirmTitle")
|
||||||
|
@ -156,11 +161,11 @@ export default function ClientProfileForm() {
|
||||||
|
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
if (executorToDelete?.name!) {
|
if (executorToDelete?.name!) {
|
||||||
profileExecutors.splice(executorToDelete.idx!, 1);
|
remove(executorToDelete.idx);
|
||||||
try {
|
try {
|
||||||
await adminClient.clientPolicies.createProfiles({
|
await adminClient.clientPolicies.createProfiles({
|
||||||
profiles: profiles,
|
...profiles,
|
||||||
globalProfiles,
|
profiles: [...(profiles!.profiles || []), getValues()],
|
||||||
});
|
});
|
||||||
addAlert(t("deleteExecutorSuccess"), AlertVariant.success);
|
addAlert(t("deleteExecutorSuccess"), AlertVariant.success);
|
||||||
navigate(toClientProfile({ realm, profileName }));
|
navigate(toClientProfile({ realm, profileName }));
|
||||||
|
@ -168,15 +173,8 @@ export default function ClientProfileForm() {
|
||||||
addError(t("deleteExecutorError"), error);
|
addError(t("deleteExecutorError"), error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const updatedProfiles = profiles.filter(
|
|
||||||
(profile) => profile.name !== profileName
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await adminClient.clientPolicies.createProfiles({
|
await adminClient.clientPolicies.createProfiles(profiles);
|
||||||
profiles: updatedProfiles,
|
|
||||||
globalProfiles,
|
|
||||||
});
|
|
||||||
addAlert(t("deleteClientSuccess"), AlertVariant.success);
|
addAlert(t("deleteClientSuccess"), AlertVariant.success);
|
||||||
navigate(toClientPolicies({ realm, tab: "profiles" }));
|
navigate(toClientPolicies({ realm, tab: "profiles" }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -186,17 +184,9 @@ export default function ClientProfileForm() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = profiles.find((profile) => profile.name === profileName);
|
if (!profiles) {
|
||||||
const profileExecutors = profile?.executors || [];
|
return <KeycloakSpinner />;
|
||||||
const globalProfile = globalProfiles.find(
|
}
|
||||||
(globalProfile) => globalProfile.name === profileName
|
|
||||||
);
|
|
||||||
const globalProfileExecutors = globalProfile?.executors || [];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValue("name", globalProfile?.name ?? profile?.name);
|
|
||||||
setValue("description", globalProfile?.description ?? profile?.description);
|
|
||||||
}, [profiles]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -206,12 +196,12 @@ export default function ClientProfileForm() {
|
||||||
badges={[
|
badges={[
|
||||||
{
|
{
|
||||||
id: "global-client-profile-badge",
|
id: "global-client-profile-badge",
|
||||||
text: globalProfile ? t("global") : "",
|
text: isGlobalProfile ? t("global") : "",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
divider
|
divider
|
||||||
dropdownItems={
|
dropdownItems={
|
||||||
editMode && !globalProfile
|
editMode && !isGlobalProfile
|
||||||
? [
|
? [
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="delete"
|
key="delete"
|
||||||
|
@ -244,7 +234,7 @@ export default function ClientProfileForm() {
|
||||||
id="name"
|
id="name"
|
||||||
aria-label={t("name")}
|
aria-label={t("name")}
|
||||||
data-testid="client-profile-name"
|
data-testid="client-profile-name"
|
||||||
isReadOnly={!!globalProfile}
|
isReadOnly={isGlobalProfile}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup label={t("common:description")} fieldId="kc-description">
|
<FormGroup label={t("common:description")} fieldId="kc-description">
|
||||||
|
@ -255,11 +245,11 @@ export default function ClientProfileForm() {
|
||||||
id="description"
|
id="description"
|
||||||
aria-label={t("description")}
|
aria-label={t("description")}
|
||||||
data-testid="client-profile-description"
|
data-testid="client-profile-description"
|
||||||
isReadOnly={!!globalProfile}
|
isReadOnly={isGlobalProfile}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
{!globalProfile && (
|
{!isGlobalProfile && (
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => handleSubmit(save)()}
|
onClick={() => handleSubmit(save)()}
|
||||||
|
@ -269,7 +259,7 @@ export default function ClientProfileForm() {
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{editMode && !globalProfile && (
|
{editMode && !isGlobalProfile && (
|
||||||
<Button
|
<Button
|
||||||
id={"reloadProfile"}
|
id={"reloadProfile"}
|
||||||
variant="link"
|
variant="link"
|
||||||
|
@ -280,7 +270,7 @@ export default function ClientProfileForm() {
|
||||||
{t("realm-settings:reload")}
|
{t("realm-settings:reload")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!editMode && !globalProfile && (
|
{!editMode && !isGlobalProfile && (
|
||||||
<Button
|
<Button
|
||||||
id={"cancelCreateProfile"}
|
id={"cancelCreateProfile"}
|
||||||
variant="link"
|
variant="link"
|
||||||
|
@ -308,7 +298,7 @@ export default function ClientProfileForm() {
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
</FlexItem>
|
</FlexItem>
|
||||||
{profile && (
|
{!isGlobalProfile && (
|
||||||
<FlexItem align={{ default: "alignRight" }}>
|
<FlexItem align={{ default: "alignRight" }}>
|
||||||
<Button
|
<Button
|
||||||
id="addExecutor"
|
id="addExecutor"
|
||||||
|
@ -332,6 +322,7 @@ export default function ClientProfileForm() {
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
{profileExecutors.length > 0 && (
|
{profileExecutors.length > 0 && (
|
||||||
|
<>
|
||||||
<DataList aria-label={t("executors")} isCompact>
|
<DataList aria-label={t("executors")} isCompact>
|
||||||
{profileExecutors.map((executor, idx) => (
|
{profileExecutors.map((executor, idx) => (
|
||||||
<DataListItem
|
<DataListItem
|
||||||
|
@ -379,6 +370,7 @@ export default function ClientProfileForm() {
|
||||||
helpText={type.helpText}
|
helpText={type.helpText}
|
||||||
fieldLabelId="realm-settings:executorTypeTextHelpText"
|
fieldLabelId="realm-settings:executorTypeTextHelpText"
|
||||||
/>
|
/>
|
||||||
|
{!isGlobalProfile && (
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
isInline
|
isInline
|
||||||
|
@ -396,7 +388,8 @@ export default function ClientProfileForm() {
|
||||||
name: type.id,
|
name: type.id,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
></Button>
|
/>
|
||||||
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
|
@ -406,64 +399,7 @@ export default function ClientProfileForm() {
|
||||||
</DataListItem>
|
</DataListItem>
|
||||||
))}
|
))}
|
||||||
</DataList>
|
</DataList>
|
||||||
)}
|
{isGlobalProfile && (
|
||||||
{globalProfileExecutors.length > 0 && (
|
|
||||||
<>
|
|
||||||
<DataList aria-label={t("executors")} isCompact>
|
|
||||||
{globalProfileExecutors.map((executor) => (
|
|
||||||
<DataListItem
|
|
||||||
aria-labelledby={"global-executors-list-item"}
|
|
||||||
key={executor.executor}
|
|
||||||
id={executor.executor}
|
|
||||||
>
|
|
||||||
<DataListItemRow data-testid="global-executors-list-row">
|
|
||||||
<DataListItemCells
|
|
||||||
dataListCells={[
|
|
||||||
<DataListCell
|
|
||||||
key="executor"
|
|
||||||
data-testid="global-executor-type"
|
|
||||||
>
|
|
||||||
{Object.keys(executor.configuration!).length !==
|
|
||||||
0 ? (
|
|
||||||
<Button
|
|
||||||
component={(props) => (
|
|
||||||
<Link
|
|
||||||
{...props}
|
|
||||||
to={toExecutor({
|
|
||||||
realm,
|
|
||||||
profileName,
|
|
||||||
executorName: executor.executor!,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
variant="link"
|
|
||||||
data-testid="editExecutor"
|
|
||||||
>
|
|
||||||
{executor.executor}
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<span className="kc-unclickable-executor">
|
|
||||||
{executor.executor}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{executorTypes
|
|
||||||
?.filter(
|
|
||||||
(type) => type.id === executor.executor
|
|
||||||
)
|
|
||||||
.map((type) => (
|
|
||||||
<HelpItem
|
|
||||||
key={type.id}
|
|
||||||
helpText={type.helpText}
|
|
||||||
fieldLabelId="realm-settings:executorTypeTextHelpText"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</DataListCell>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</DataListItemRow>
|
|
||||||
</DataListItem>
|
|
||||||
))}
|
|
||||||
</DataList>
|
|
||||||
<Button
|
<Button
|
||||||
id="backToClientPolicies"
|
id="backToClientPolicies"
|
||||||
component={(props) => (
|
component={(props) => (
|
||||||
|
@ -478,10 +414,10 @@ export default function ClientProfileForm() {
|
||||||
>
|
>
|
||||||
{t("realm-settings:back")}
|
{t("realm-settings:back")}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{profileExecutors.length === 0 &&
|
{profileExecutors.length === 0 && (
|
||||||
globalProfileExecutors.length === 0 && (
|
|
||||||
<>
|
<>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Text
|
<Text
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { DynamicComponents } from "../components/dynamic/DynamicComponents";
|
||||||
import type { ExecutorParams } from "./routes/Executor";
|
import type { ExecutorParams } from "./routes/Executor";
|
||||||
|
|
||||||
type ExecutorForm = {
|
type ExecutorForm = {
|
||||||
config: object;
|
config?: object;
|
||||||
executor: string;
|
executor: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -89,16 +89,15 @@ export default function ExecutorForm() {
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileExecutor = profile.executors!.find(
|
|
||||||
(executor) => executor.executor === executorName
|
|
||||||
);
|
|
||||||
|
|
||||||
const executors = (profile.executors ?? []).concat({
|
const executors = (profile.executors ?? []).concat({
|
||||||
executor: formValues.executor,
|
executor: formValues.executor,
|
||||||
configuration: formValues.config,
|
configuration: formValues.config || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
|
const profileExecutor = profile.executors!.find(
|
||||||
|
(executor) => executor.executor === executorName
|
||||||
|
);
|
||||||
profileExecutor!.configuration = {
|
profileExecutor!.configuration = {
|
||||||
...profileExecutor!.configuration,
|
...profileExecutor!.configuration,
|
||||||
...formValues.config,
|
...formValues.config,
|
||||||
|
|
Loading…
Reference in a new issue