Initial version of config settings for executions (#1015)
* initial version of config settings for executions still needs to render different controls based on the types * fixing the types * Update src/authentication/components/ExecutionConfigModal.tsx Co-authored-by: Jon Koops <jonkoops@gmail.com> * fix use effect dependency fix * added form type * Add ExecutionConfigModalProps type Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
5b23354e35
commit
45095fdc42
6 changed files with 237 additions and 10 deletions
|
@ -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();
|
||||
|
|
222
src/authentication/components/ExecutionConfigModal.tsx
Normal file
222
src/authentication/components/ExecutionConfigModal.tsx
Normal file
|
@ -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<AuthenticatorConfigRepresentation>();
|
||||
const [configDescription, setConfigDescription] =
|
||||
useState<AuthenticatorConfigInfoRepresentation>();
|
||||
|
||||
const { register, errors, setValue, handleSubmit } =
|
||||
useForm<ExecutionConfigModalForm>();
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label={t("common:settings")}
|
||||
onClick={() => setShow(true)}
|
||||
>
|
||||
<CogIcon />
|
||||
</Button>
|
||||
{configDescription && (
|
||||
<Modal
|
||||
variant={ModalVariant.small}
|
||||
isOpen={show}
|
||||
title={t("executionConfig", { name: configDescription.name })}
|
||||
onClose={() => setShow(false)}
|
||||
>
|
||||
<Form id="execution-config-form" onSubmit={handleSubmit(save)}>
|
||||
<FormGroup
|
||||
label={t("alias")}
|
||||
fieldId="alias"
|
||||
helperTextInvalid={t("common:required")}
|
||||
validated={
|
||||
errors.alias ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
isRequired
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="authentication-help:alias"
|
||||
forLabel={t("alias")}
|
||||
forID="alias"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
isReadOnly={!!config}
|
||||
type="text"
|
||||
id="alias"
|
||||
name="alias"
|
||||
data-testid="alias"
|
||||
ref={register({ required: true })}
|
||||
validated={
|
||||
errors.alias
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
{configDescription?.properties?.map((property) => (
|
||||
<FormGroup
|
||||
key={property.name}
|
||||
label={property.label}
|
||||
fieldId={property.name!}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={property.helpText}
|
||||
forLabel={property.name!}
|
||||
forID={property.name!}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
type="text"
|
||||
id={property.name}
|
||||
name={property.name}
|
||||
ref={register}
|
||||
/>
|
||||
</FormGroup>
|
||||
))}
|
||||
<ActionGroup>
|
||||
<Button data-testid="save" variant="primary" type="submit">
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="cancel"
|
||||
variant={ButtonVariant.link}
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
}}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
{config && (
|
||||
<Button
|
||||
className="pf-u-ml-4xl"
|
||||
data-testid="clear"
|
||||
variant={ButtonVariant.link}
|
||||
onClick={async () => {
|
||||
await adminClient.authenticationManagement.delConfig({
|
||||
id: config.id!,
|
||||
});
|
||||
setConfig(undefined);
|
||||
setShow(false);
|
||||
}}
|
||||
>
|
||||
{t("common:clear")} <TrashIcon />
|
||||
</Button>
|
||||
)}
|
||||
</ActionGroup>
|
||||
</Form>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -20,11 +20,10 @@ export const FlowHeader = () => {
|
|||
className="keycloak__authentication__header"
|
||||
dataListCells={[
|
||||
<DataListCell key="step" id="headerName">
|
||||
<>{t("steps")}</>
|
||||
</DataListCell>,
|
||||
<DataListCell key="requirement">
|
||||
<>{t("requirement")}</>
|
||||
{t("steps")}
|
||||
</DataListCell>,
|
||||
<DataListCell key="requirement">{t("requirement")}</DataListCell>,
|
||||
<DataListCell key="config"></DataListCell>,
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</DataListCell>,
|
||||
<DataListCell key={`${execution.id}-config`}>
|
||||
{execution.configurable && (
|
||||
<ExecutionConfigModal execution={execution} />
|
||||
)}
|
||||
</DataListCell>,
|
||||
]}
|
||||
/>
|
||||
</DataListItemRow>
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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}}",
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue