Add Save/Cancel buttons and logic to Kerberos settings (#307)

* initial save cancel logic

* cleanup work after merge

* move form logic up to parent

* save working

* update messages

* no longer need to pass in save from children

* pass form into wizard to fix build errors

* fix lint fmt issue

* add simple field validation and error prevention

* localize validation strings

* fix default value issues

* localize default value
This commit is contained in:
mfrances17 2021-01-22 15:15:32 -05:00 committed by GitHub
parent f2e26b0049
commit 0dc3f08917
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 238 additions and 134 deletions

View file

@ -1,16 +1,87 @@
import { PageSection } from "@patternfly/react-core";
import React from "react";
import {
ActionGroup,
AlertVariant,
Button,
Form,
PageSection,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React, { useEffect } from "react";
import { KerberosSettingsRequired } from "./kerberos/KerberosSettingsRequired";
import { KerberosSettingsCache } from "./kerberos/KerberosSettingsCache";
import { useHistory } from "react-router-dom";
import { useRealm } from "../context/realm-context/RealmContext";
import { useParams } from "react-router-dom";
import { convertToFormValues } from "../util";
import { useAlerts } from "../components/alert/Alerts";
import { useAdminClient } from "../context/auth/AdminClient";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { useForm } from "react-hook-form";
export const UserFederationKerberosSettings = () => {
const { t } = useTranslation("user-federation");
const form = useForm<ComponentRepresentation>({ mode: "onChange" });
const history = useHistory();
const adminClient = useAdminClient();
const { realm } = useRealm();
const { id } = useParams<{ id: string }>();
const { addAlert } = useAlerts();
useEffect(() => {
(async () => {
const fetchedComponent = await adminClient.components.findOne({ id });
if (fetchedComponent) {
setupForm(fetchedComponent);
}
})();
}, []);
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
form.setValue(
"config.allowPasswordAuthentication",
component.config?.allowPasswordAuthentication
);
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", form.setValue);
}
form.setValue(entry[0], entry[1]);
});
};
const save = async (component: ComponentRepresentation) => {
try {
await adminClient.components.update({ id }, component);
setupForm(component as ComponentRepresentation);
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addAlert(`${t("saveError")} '${error}'`, AlertVariant.danger);
}
};
return (
<>
<PageSection variant="light">
<KerberosSettingsRequired showSectionHeading />
<KerberosSettingsRequired form={form} showSectionHeading />
</PageSection>
<PageSection variant="light" isFilled>
<KerberosSettingsCache showSectionHeading />
<KerberosSettingsCache form={form} showSectionHeading />
<Form onSubmit={form.handleSubmit(save)}>
<ActionGroup>
<Button variant="primary" type="submit">
{t("common:save")}
</Button>
<Button
variant="link"
onClick={() => history.push(`/${realm}/user-federation`)}
>
{t("common:cancel")}
</Button>
</ActionGroup>
</Form>
</PageSection>
</>
);

View file

@ -3,21 +3,32 @@ import { useTranslation } from "react-i18next";
import React from "react";
import { KerberosSettingsRequired } from "./kerberos/KerberosSettingsRequired";
import { KerberosSettingsCache } from "./kerberos/KerberosSettingsCache";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { useForm } from "react-hook-form";
export const UserFederationKerberosWizard = () => {
const { t } = useTranslation("user-federation");
const form = useForm<ComponentRepresentation>({ mode: "onChange" });
const steps = [
{
name: t("requiredSettings"),
component: (
<KerberosSettingsRequired showSectionHeading showSectionDescription />
<KerberosSettingsRequired
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("cacheSettings"),
component: (
<KerberosSettingsCache showSectionHeading showSectionDescription />
<KerberosSettingsCache
form={form}
showSectionHeading
showSectionDescription
/>
),
nextButtonText: t("common:finish"), // TODO: needs to disable until cache policy is valid
},

View file

@ -7,58 +7,31 @@ import {
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import React, { useEffect, useState } from "react";
import { convertToFormValues } from "../../util";
import { useForm, Controller, useWatch } from "react-hook-form";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import React, { useState } from "react";
import { UseFormMethods, Controller, useWatch } from "react-hook-form";
import { FormAccess } from "../../components/form-access/FormAccess";
import {
useAdminClient,
asyncStateFetch,
} from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom";
import _ from "lodash";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type KerberosSettingsCacheProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const KerberosSettingsCache = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: KerberosSettingsCacheProps) => {
const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t;
const adminClient = useAdminClient();
const { register, control, setValue } = useForm<ComponentRepresentation>();
const { id } = useParams<{ id: string }>();
const cachePolicyType = useWatch({
control: control,
control: form.control,
name: "config.cachePolicy",
});
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
setValue("config.cachePolicy", component.config?.cachePolicy);
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
} else {
setValue(entry[0], entry[1]);
}
});
};
useEffect(() => {
return asyncStateFetch(
() => adminClient.components.findOne({ id }),
(component) => setupForm(component)
);
}, []);
const [isCachePolicyDropdownOpen, setIsCachePolicyDropdownOpen] = useState(
false
);
@ -73,18 +46,16 @@ export const KerberosSettingsCache = ({
false
);
const hourOptions = [
<SelectOption key={0} value={t("common:selectOne")} isPlaceholder />,
];
for (let index = 1; index <= 24; index++) {
hourOptions.push(<SelectOption key={index + 1} value={[`${index}`]} />);
const hourOptions = [<SelectOption key={0} value={[`${1}`]} isPlaceholder />];
for (let index = 2; index <= 24; index++) {
hourOptions.push(<SelectOption key={index - 1} value={[`${index}`]} />);
}
const minuteOptions = [
<SelectOption key={0} value={t("common:selectOne")} isPlaceholder />,
<SelectOption key={0} value={[`${1}`]} isPlaceholder />,
];
for (let index = 1; index <= 60; index++) {
minuteOptions.push(<SelectOption key={index + 1} value={[`${index}`]} />);
for (let index = 2; index <= 60; index++) {
minuteOptions.push(<SelectOption key={index - 1} value={[`${index}`]} />);
}
return (
@ -112,8 +83,8 @@ export const KerberosSettingsCache = ({
>
<Controller
name="config.cachePolicy"
defaultValue=""
control={control}
defaultValue={["DEFAULT"]}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-cache-policy"
@ -129,16 +100,11 @@ export const KerberosSettingsCache = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption
key={0}
value={t("common:selectOne")}
isPlaceholder
/>
<SelectOption key={1} value={["DEFAULT"]} />
<SelectOption key={2} value={["EVICT_DAILY"]} />
<SelectOption key={3} value={["EVICT_WEEKLY"]} />
<SelectOption key={4} value={["MAX_LIFESPAN"]} />
<SelectOption key={5} value={["NO_CACHE"]} />
<SelectOption key={0} value={["DEFAULT"]} isPlaceholder />
<SelectOption key={1} value={["EVICT_DAILY"]} />
<SelectOption key={2} value={["EVICT_WEEKLY"]} />
<SelectOption key={3} value={["MAX_LIFESPAN"]} />
<SelectOption key={4} value={["NO_CACHE"]} />
</Select>
)}
></Controller>
@ -154,12 +120,13 @@ export const KerberosSettingsCache = ({
forID="kc-eviction-day"
/>
}
isRequired
fieldId="kc-eviction-day"
>
<Controller
name="config.evictionDay"
defaultValue=""
control={control}
defaultValue={[t("common:Sunday")]}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-day"
@ -175,30 +142,25 @@ export const KerberosSettingsCache = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption
key={0}
value={t("common:selectOne")}
isPlaceholder
/>
<SelectOption key={1} value={["1"]}>
<SelectOption key={0} value={["1"]} isPlaceholder>
{t("common:Sunday")}
</SelectOption>
<SelectOption key={2} value={["2"]}>
<SelectOption key={1} value={["2"]}>
{t("common:Monday")}
</SelectOption>
<SelectOption key={3} value={["3"]}>
<SelectOption key={2} value={["3"]}>
{t("common:Tuesday")}
</SelectOption>
<SelectOption key={4} value={["4"]}>
<SelectOption key={3} value={["4"]}>
{t("common:Wednesday")}
</SelectOption>
<SelectOption key={5} value={["5"]}>
<SelectOption key={4} value={["5"]}>
{t("common:Thursday")}
</SelectOption>
<SelectOption key={6} value={["6"]}>
<SelectOption key={5} value={["6"]}>
{t("common:Friday")}
</SelectOption>
<SelectOption key={7} value={["7"]}>
<SelectOption key={6} value={["7"]}>
{t("common:Saturday")}
</SelectOption>
</Select>
@ -221,12 +183,13 @@ export const KerberosSettingsCache = ({
forID="kc-eviction-hour"
/>
}
isRequired
fieldId="kc-eviction-hour"
>
<Controller
name="config.evictionHour"
defaultValue=""
control={control}
defaultValue={["1"]}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-hour"
@ -256,12 +219,13 @@ export const KerberosSettingsCache = ({
forID="kc-eviction-minute"
/>
}
isRequired
fieldId="kc-eviction-minute"
>
<Controller
name="config.evictionMinute"
defaultValue=""
control={control}
defaultValue={["1"]}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-minute"
@ -297,14 +261,15 @@ export const KerberosSettingsCache = ({
forID="kc-max-lifespan"
/>
}
isRequired
fieldId="kc-max-lifespan"
>
<TextInput
isRequired
type="text"
id="kc-max-lifespan"
name="config.maxLifespan"
ref={register}
name="config.maxLifespan[0]"
ref={form.register}
/>
</FormGroup>
) : (

View file

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import {
FormGroup,
Select,
@ -6,62 +6,37 @@ import {
SelectVariant,
Switch,
TextInput,
Title,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller, useWatch } from "react-hook-form";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { UseFormMethods, Controller, useWatch } from "react-hook-form";
import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom";
import { convertToFormValues } from "../../util";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import _ from "lodash";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type KerberosSettingsRequiredProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const KerberosSettingsRequired = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: KerberosSettingsRequiredProps) => {
const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t;
const adminClient = useAdminClient();
const [isEditModeDropdownOpen, setIsEditModeDropdownOpen] = useState(false);
const { register, control, setValue } = useForm<ComponentRepresentation>();
const { id } = useParams<{ id: string }>();
const allowPassAuth = useWatch({
control: control,
control: form.control,
name: "config.allowPasswordAuthentication",
});
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
setValue(
"config.allowPasswordAuthentication",
component.config?.allowPasswordAuthentication
);
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
}
setValue(entry[0], entry[1]);
});
};
useEffect(() => {
(async () => {
const fetchedComponent = await adminClient.components.findOne({ id });
if (fetchedComponent) {
setupForm(fetchedComponent);
}
})();
}, []);
return (
<>
{showSectionHeading && (
@ -86,13 +61,51 @@ export const KerberosSettingsRequired = ({
fieldId="kc-console-display-name"
isRequired
>
{/* These hidden fields are required so data object written back matches data retrieved */}
<TextInput
hidden
type="text"
id="kc-console-id"
name="id"
ref={form.register}
/>
<TextInput
hidden
type="text"
id="kc-console-providerId"
name="providerId"
ref={form.register}
/>
<TextInput
hidden
type="text"
id="kc-console-providerType"
name="providerType"
ref={form.register}
/>
<TextInput
hidden
type="text"
id="kc-console-parentId"
name="parentId"
ref={form.register}
/>
<TextInput
isRequired
type="text"
id="kc-console-display-name"
id="kc-console-name"
name="name"
ref={register}
ref={form.register({
required: {
value: true,
message: `${t("validateName")}`,
},
})}
/>
{form.errors.name && (
<div className="error">{form.errors.name.message}</div>
)}
</FormGroup>
<FormGroup
@ -111,9 +124,21 @@ export const KerberosSettingsRequired = ({
isRequired
type="text"
id="kc-kerberos-realm"
name="config.kerberosRealm"
ref={register}
name="config.kerberosRealm[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateRealm")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.kerberosRealm &&
form.errors.config.kerberosRealm[0] && (
<div className="error">
{form.errors.config.kerberosRealm[0].message}
</div>
)}
</FormGroup>
<FormGroup
@ -132,9 +157,21 @@ export const KerberosSettingsRequired = ({
isRequired
type="text"
id="kc-server-principal"
name="config.serverPrincipal"
ref={register}
name="config.serverPrincipal[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateServerPrincipal")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.serverPrincipal &&
form.errors.config.serverPrincipal[0] && (
<div className="error">
{form.errors.config.serverPrincipal[0].message}
</div>
)}
</FormGroup>
<FormGroup
@ -153,9 +190,21 @@ export const KerberosSettingsRequired = ({
isRequired
type="text"
id="kc-key-tab"
name="config.keyTab"
ref={register}
name="config.keyTab[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateKeyTab")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.keyTab &&
form.errors.config.keyTab[0] && (
<div className="error">
{form.errors.config.keyTab[0].message}
</div>
)}
</FormGroup>
<FormGroup
@ -174,7 +223,7 @@ export const KerberosSettingsRequired = ({
<Controller
name="config.debug"
defaultValue={false}
control={control}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-debug"}
@ -203,7 +252,7 @@ export const KerberosSettingsRequired = ({
<Controller
name="config.allowPasswordAuthentication"
defaultValue={false}
control={control}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-allow-password-authentication"}
@ -227,13 +276,15 @@ export const KerberosSettingsRequired = ({
forID="kc-edit-mode"
/>
}
isRequired
fieldId="kc-edit-mode"
>
{" "}
<Controller
name="config.editMode"
defaultValue={t("common:selectOne")}
control={control}
name="config.editMode[0]"
defaultValue="READ_ONLY"
control={form.control}
rules={{ required: true }}
render={({ onChange, value }) => (
<Select
toggleId="kc-edit-mode"
@ -249,13 +300,8 @@ export const KerberosSettingsRequired = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption
key={0}
value={t("common:selectOne")}
isPlaceholder
/>
<SelectOption key={1} value="READ_ONLY" />
<SelectOption key={2} value="UNSYNCED" />
<SelectOption key={0} value="READ_ONLY" isPlaceholder />
<SelectOption key={1} value="UNSYNCED" />
</Select>
)}
></Controller>
@ -279,7 +325,7 @@ export const KerberosSettingsRequired = ({
<Controller
name="config.updateProfileFirstLogin"
defaultValue={false}
control={control}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-update-first-login"}

View file

@ -78,6 +78,9 @@
"oneLevel": "One Level",
"subtree": "Subtree",
"saveSuccess": "User federation successfully saved",
"saveError": "User federation could not be saved: {error}",
"learnMore": "Learn more",
"addNewProvider": "Add new provider",
"userFedDeletedSuccess": "The user federation provider has been deleted.",
@ -85,8 +88,12 @@
"userFedDeleteConfirmTitle": "Delete user federation provider?",
"userFedDeleteConfirm": "If you delete this user federation provider, all associated data will be removed.",
"id": "ID",
"validateName": "You must enter a name",
"validateRealm":"You must enter a realm",
"validateServerPrincipal":"You must enter a server principal",
"validateKeyTab": "You must enter a key tab",
"id": "ID",
"mapperType": "Mapper type",
"mapperTypeMsadUserAccountControlManager": "msad-user-account-control-mapper",
"mapperTypeMsadLdsUserAccountControlMapper": "msad-user-account-control-mapper",

View file

@ -5,3 +5,7 @@
.keycloak-admin--user-federation__gallery-item {
display: contents;
}
.error {
color: red;
}