diff --git a/src/authentication/FlowDetails.tsx b/src/authentication/FlowDetails.tsx index 6d3488363d..becd040e87 100644 --- a/src/authentication/FlowDetails.tsx +++ b/src/authentication/FlowDetails.tsx @@ -26,11 +26,6 @@ import { ExecutionList, IndexChange, LevelChange } from "./execution-model"; import { FlowDiagram } from "./components/FlowDiagram"; import { useAlerts } from "../components/alert/Alerts"; -export type ExpandableExecution = AuthenticationExecutionInfoRepresentation & { - executionList: ExpandableExecution[]; - isCollapsed: boolean; -}; - export const FlowDetails = () => { const { t } = useTranslation("authentication"); const adminClient = useAdminClient(); diff --git a/src/authentication/components/ExecutionConfigModal.tsx b/src/authentication/components/ExecutionConfigModal.tsx new file mode 100644 index 0000000000..a326425485 --- /dev/null +++ b/src/authentication/components/ExecutionConfigModal.tsx @@ -0,0 +1,222 @@ +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useForm } from "react-hook-form"; +import { + ActionGroup, + AlertVariant, + Button, + ButtonVariant, + Form, + FormGroup, + Modal, + ModalVariant, + TextInput, + ValidatedOptions, +} from "@patternfly/react-core"; +import { CogIcon, TrashIcon } from "@patternfly/react-icons"; + +import type AuthenticatorConfigRepresentation from "keycloak-admin/lib/defs/authenticatorConfigRepresentation"; +import type AuthenticatorConfigInfoRepresentation from "keycloak-admin/lib/defs/authenticatorConfigInfoRepresentation"; +import type { ConfigPropertyRepresentation } from "keycloak-admin/lib/defs/authenticatorConfigInfoRepresentation"; +import type { ExpandableExecution } from "../execution-model"; +import { useAdminClient, useFetch } from "../../context/auth/AdminClient"; +import { useAlerts } from "../../components/alert/Alerts"; +import { HelpItem } from "../../components/help-enabler/HelpItem"; + +type ExecutionConfigModalForm = { + [index: string]: string; +}; + +type ExecutionConfigModalProps = { + execution: ExpandableExecution; +}; + +export const ExecutionConfigModal = ({ + execution, +}: ExecutionConfigModalProps) => { + const { t } = useTranslation("authentication"); + const adminClient = useAdminClient(); + const { addAlert, addError } = useAlerts(); + + const [show, setShow] = useState(false); + const [config, setConfig] = useState(); + const [configDescription, setConfigDescription] = + useState(); + + const { register, errors, setValue, handleSubmit } = + useForm(); + + const setupForm = ( + configDescription: AuthenticatorConfigInfoRepresentation, + config?: AuthenticatorConfigRepresentation + ) => { + configDescription.properties!.map( + (property: ConfigPropertyRepresentation) => { + setValue( + property.name!, + config?.config?.[property.name!] || property.defaultValue || "" + ); + } + ); + if (config) { + setValue("alias", config.alias); + setValue("id", config.id); + } + }; + + useFetch( + async () => { + const configDescription = + await adminClient.authenticationManagement.getConfigDescription({ + providerId: execution.providerId!, + }); + let config: AuthenticatorConfigRepresentation | undefined; + if (execution.authenticationConfig) { + config = await adminClient.authenticationManagement.getConfig({ + id: execution.authenticationConfig, + }); + } + return { configDescription, config }; + }, + ({ configDescription, config }) => { + setConfigDescription(configDescription); + setConfig(config); + }, + [] + ); + + useEffect(() => { + if (configDescription) setupForm(configDescription, config); + }, [show]); + + const save = async (changedConfig: ExecutionConfigModalForm) => { + try { + if (config) { + const newConfig = { + ...config, + config: changedConfig, + }; + await adminClient.authenticationManagement.updateConfig(newConfig); + setConfig(newConfig); + } else { + const newConfig = { + id: execution.id!, + alias: changedConfig.alias, + config: changedConfig, + }; + const { id } = await adminClient.authenticationManagement.createConfig( + newConfig + ); + setConfig({ ...newConfig, id }); + } + addAlert(t("configSaveSuccess"), AlertVariant.success); + setShow(false); + } catch (error) { + addError("authentication:configSaveError", error); + } + }; + + return ( + <> + + {configDescription && ( + setShow(false)} + > +
+ + } + > + + + {configDescription?.properties?.map((property) => ( + + } + > + + + ))} + + + + {config && ( + + )} + +
+
+ )} + + ); +}; diff --git a/src/authentication/components/FlowHeader.tsx b/src/authentication/components/FlowHeader.tsx index 553f3e42cb..ac3710347d 100644 --- a/src/authentication/components/FlowHeader.tsx +++ b/src/authentication/components/FlowHeader.tsx @@ -20,11 +20,10 @@ export const FlowHeader = () => { className="keycloak__authentication__header" dataListCells={[ - <>{t("steps")} - , - - <>{t("requirement")} + {t("steps")} , + {t("requirement")}, + , ]} /> diff --git a/src/authentication/components/FlowRow.tsx b/src/authentication/components/FlowRow.tsx index d5fbd64716..f288d09c5b 100644 --- a/src/authentication/components/FlowRow.tsx +++ b/src/authentication/components/FlowRow.tsx @@ -13,11 +13,12 @@ import { } from "@patternfly/react-core"; import type AuthenticationExecutionInfoRepresentation from "keycloak-admin/lib/defs/authenticationExecutionInfoRepresentation"; -import type { ExpandableExecution } from "../FlowDetails"; +import type { ExpandableExecution } from "../execution-model"; import { FlowTitle } from "./FlowTitle"; import { FlowRequirementDropdown } from "./FlowRequirementDropdown"; import "./flow-row.css"; +import { ExecutionConfigModal } from "./ExecutionConfigModal"; type FlowRowProps = { execution: ExpandableExecution; @@ -81,6 +82,11 @@ export const FlowRow = ({ onChange={onRowChange} /> , + + {execution.configurable && ( + + )} + , ]} /> diff --git a/src/authentication/help.ts b/src/authentication/help.ts index 00bafb7302..6c1d79a1bb 100644 --- a/src/authentication/help.ts +++ b/src/authentication/help.ts @@ -9,5 +9,6 @@ export default { "Execution can have a wide range of actions, from sending a reset email to validating an OTP", addSubFlow: "Sub-Flows can be either generic or form. The form type is used to construct a sub-flow that generates a single flow for the user. Sub-flows are a special type of execution that evaluate as successful depending on how the executions they contain evaluate.", + alias: "Name of the configuration", }, }; diff --git a/src/authentication/messages.ts b/src/authentication/messages.ts index d6426e83b4..560fdb429b 100644 --- a/src/authentication/messages.ts +++ b/src/authentication/messages.ts @@ -49,5 +49,9 @@ export default { DISABLED: "Disabled", CONDITIONAL: "Conditional", }, + executionConfig: "{{name}} config", + alias: "Alias", + configSaveSuccess: "Successfully saved the execution config", + configSaveError: "Could not save the execution config: {{error}}", }, };