Add Save/Cancel buttons and logic to LDAP settings (#311)

* add save cancel and move all form logic to parent

* rm obsolete convert days function

* save working all fields

* fix save on two fields and rm console msgs

* add basic validation to empty fields

* fix default value issues in cache

* fix two fields default values

* dropdown text and help fixes

* add empty option for edit mode
This commit is contained in:
mfrances17 2021-01-25 20:41:14 -05:00 committed by GitHub
parent 71a41694d9
commit 5633101f42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 566 additions and 590 deletions

View file

@ -1,7 +1,13 @@
import { PageSection } from "@patternfly/react-core";
import {
ActionGroup,
AlertVariant,
Button,
Form,
PageSection,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React from "react";
import { ScrollForm } from "../components/scroll-form/ScrollForm";
import React, { useEffect } from "react";
import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced";
import { LdapSettingsKerberosIntegration } from "./ldap/LdapSettingsKerberosIntegration";
import { LdapSettingsCache } from "./ldap/LdapSettingsCache";
@ -9,9 +15,55 @@ import { LdapSettingsSynchronization } from "./ldap/LdapSettingsSynchronization"
import { LdapSettingsGeneral } from "./ldap/LdapSettingsGeneral";
import { LdapSettingsConnection } from "./ldap/LdapSettingsConnection";
import { LdapSettingsSearching } from "./ldap/LdapSettingsSearching";
import { ScrollForm } from "../components/scroll-form/ScrollForm";
import { useHistory, useParams } from "react-router-dom";
import { useRealm } from "../context/realm-context/RealmContext";
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 UserFederationLdapSettings = () => {
const { t } = useTranslation("user-federation");
const form = useForm<ComponentRepresentation>();
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) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", form.setValue);
} else {
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 (
<>
@ -27,27 +79,27 @@ export const UserFederationLdapSettings = () => {
t("advancedSettings"),
]}
>
{/* General settings */}
<LdapSettingsGeneral />
{/* Connection settings */}
<LdapSettingsConnection />
{/* Searching and updating settings */}
<LdapSettingsSearching />
{/* Synchronization settings */}
<LdapSettingsSynchronization />
{/* Kerberos integration */}
<LdapSettingsKerberosIntegration />
{/* Cache settings */}
<LdapSettingsCache />
{/* Advanced settings */}
<LdapSettingsAdvanced />
<LdapSettingsGeneral form={form} />
<LdapSettingsConnection form={form} />
<LdapSettingsSearching form={form} />
<LdapSettingsSynchronization form={form} />
<LdapSettingsKerberosIntegration form={form} />
<LdapSettingsCache form={form} />
<LdapSettingsAdvanced form={form} />
</ScrollForm>
<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

@ -13,8 +13,12 @@ import { LdapSettingsKerberosIntegration } from "./ldap/LdapSettingsKerberosInte
import { LdapSettingsCache } from "./ldap/LdapSettingsCache";
import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced";
import { useTranslation } from "react-i18next";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { useForm } from "react-hook-form";
export const UserFederationLdapWizard = () => {
const form = useForm<ComponentRepresentation>();
const { t } = useTranslation("user-federation");
const steps = [
@ -22,21 +26,33 @@ export const UserFederationLdapWizard = () => {
name: t("requiredSettings"),
id: "ldapRequiredSettingsStep",
component: (
<LdapSettingsGeneral showSectionHeading showSectionDescription />
<LdapSettingsGeneral
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("connectionAndAuthenticationSettings"),
id: "ldapConnectionSettingsStep",
component: (
<LdapSettingsConnection showSectionHeading showSectionDescription />
<LdapSettingsConnection
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("ldapSearchingAndUpdatingSettings"),
id: "ldapSearchingSettingsStep",
component: (
<LdapSettingsSearching showSectionHeading showSectionDescription />
<LdapSettingsSearching
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
@ -44,6 +60,7 @@ export const UserFederationLdapWizard = () => {
id: "ldapSynchronizationSettingsStep",
component: (
<LdapSettingsSynchronization
form={form}
showSectionHeading
showSectionDescription
/>
@ -54,6 +71,7 @@ export const UserFederationLdapWizard = () => {
id: "ldapKerberosIntegrationSettingsStep",
component: (
<LdapSettingsKerberosIntegration
form={form}
showSectionHeading
showSectionDescription
/>
@ -63,14 +81,22 @@ export const UserFederationLdapWizard = () => {
name: t("cacheSettings"),
id: "ldapCacheSettingsStep",
component: (
<LdapSettingsCache showSectionHeading showSectionDescription />
<LdapSettingsCache
form={form}
showSectionHeading
showSectionDescription
/>
),
},
{
name: t("advancedSettings"),
id: "ldapAdvancedSettingsStep",
component: (
<LdapSettingsAdvanced showSectionHeading showSectionDescription />
<LdapSettingsAdvanced
form={form}
showSectionHeading
showSectionDescription
/>
),
},
];

View file

@ -9,7 +9,7 @@
"ldapConnectionAndAuthorizationSettingsDescription": "This section contains options related to the configuration of the connection to the LDAP server. It also contains options related to authentication of the LDAP connection to the LDAP server.",
"consoleDisplayConnectionUrlHelp": "Connection URL to your LDAP server",
"enableStarttlsHelp": "Encrypts the connection to LDAP using STARTTLS, which will disable connection pooling",
"enableStartTlsHelp": "Encrypts the connection to LDAP using STARTTLS, which will disable connection pooling",
"useTruststoreSpiHelp": "Specifies whether LDAP connection will use the Truststore SPI with the truststore configured in standalone.xml/domain.sml. 'Always' means that it will always use it. 'Never' means that it will not use it. 'Only for ldaps' means that it will use it if your connection URL use ldaps. Note that even if standalone.xml/domain.xml is not configured, the default java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used.",
"connectionPoolingHelp": "Determines if Keycloak should use connection pooling for accessing LDAP server.",
"connectionTimeoutHelp": "LDAP connection timeout in milliseconds",
@ -30,10 +30,12 @@
"paginationHelp": "Whether the LDAP server supports pagination",
"ldapSynchronizationSettingsDescription": "This section contains options related to synchronization of users from LDAP to the Keycloak database.",
"importUsersHelp": "Import users",
"importUsersHelp": "If true, LDAP users will be imported into the Keycloak DB and synced by the configured sync policies.",
"batchSizeHelp": "Count of LDAP users to be imported from LDAP to Keycloak within a single transaction",
"periodicFullSyncHelp": "Whether periodic full synchronization of LDAP users to Keycloak should be enabled or not",
"fullSyncPeriodHelp": "Period for full synchronization in seconds",
"periodicChangedUsersSyncHelp": "Whether periodic synchronization of changed or newly created LDAP users to Keycloak should be enabled or not",
"changedUsersSyncHelp": "Period for synchronization of changed or newly created LDAP users in seconds",
"ldapKerberosSettingsDescription": "This section contains options useful for the Kerberos integration. This is used only when the LDAP server is used together with Kerberos/SPNEGO for user authentication.",
"allowKerberosAuthenticationHelp": "Enable/disable HTTP authentication of users with SPNEGO/Kerberos tokens. The data about authenticated users will be provisioned from this LDAP server.",

View file

@ -1,49 +1,24 @@
import { FormGroup, Switch } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React, { useEffect } from "react";
import React from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { UseFormMethods, Controller } 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 { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsAdvancedProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const LdapSettingsAdvanced = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: LdapSettingsAdvancedProps) => {
const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t;
const adminClient = useAdminClient();
const { control, setValue } = useForm<ComponentRepresentation>();
const { id } = useParams<{ id: string }>();
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
} else {
setValue(entry[0], entry[1]);
}
});
};
useEffect(() => {
return asyncStateFetch(
() => adminClient.components.findOne({ id }),
(fetchedComponent) => setupForm(fetchedComponent)
);
}, []);
return (
<>
@ -70,14 +45,14 @@ export const LdapSettingsAdvanced = ({
>
<Controller
name="config.usePasswordModifyExtendedOp"
defaultValue={false}
control={control}
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-enable-ldapv3-password"}
isChecked={value[0] === "true"}
isDisabled={false}
onChange={onChange}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
@ -99,14 +74,14 @@ export const LdapSettingsAdvanced = ({
>
<Controller
name="config.validatePasswordPolicy"
defaultValue={false}
control={control}
defaultValue={"false"}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-validate-password-policy"}
isChecked={value[0] === "true"}
isDisabled={false}
onChange={onChange}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
@ -128,14 +103,14 @@ export const LdapSettingsAdvanced = ({
>
<Controller
name="config.trustEmail"
defaultValue={false}
control={control}
defaultValue={"false"}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-trust-email"}
isChecked={value[0] === "true"}
isDisabled={false}
onChange={onChange}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>

View file

@ -3,84 +3,30 @@ import {
Select,
SelectOption,
SelectVariant,
Text,
TextContent,
TextInput,
Title,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { UseFormMethods, useWatch, Controller } from "react-hook-form";
import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom";
import _ from "lodash";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsCacheProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const LdapSettingsCache = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: LdapSettingsCacheProps) => {
const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t;
const adminClient = useAdminClient();
const { control, setValue, register } = useForm<ComponentRepresentation>();
const { id } = useParams<{ id: string }>();
const convertToDays = (num: string) => {
switch (num) {
case "1":
return t("common:Sunday");
case "2":
return t("common:Monday");
case "3":
return t("common:Tuesday");
case "4":
return t("common:Wednesday");
case "5":
return t("common:Thursday");
case "6":
return t("common:Friday");
case "7":
return t("common:Saturday");
default:
return t("common:selectOne");
}
};
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
if (entry[1].evictionDay) {
setValue(
"config.evictionDay",
convertToDays(entry[1].evictionDay[0])
);
}
} else {
setValue(entry[0], entry[1]);
}
});
};
useEffect(() => {
(async () => {
const fetchedComponent = await adminClient.components.findOne({ id });
if (fetchedComponent) {
setupForm(fetchedComponent);
}
})();
}, []);
const [isCachePolicyDropdownOpen, setIsCachePolicyDropdownOpen] = useState(
false
);
@ -89,6 +35,11 @@ export const LdapSettingsCache = ({
false
);
const cachePolicyType = useWatch({
control: form.control,
name: "config.cachePolicy",
});
const [
isEvictionMinuteDropdownOpen,
setIsEvictionMinuteDropdownOpen,
@ -98,18 +49,16 @@ export const LdapSettingsCache = ({
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 (
@ -121,8 +70,6 @@ export const LdapSettingsCache = ({
showDescription={showSectionDescription}
/>
)}
{/* Cache settings */}
<FormAccess role="manage-realm" isHorizontal>
<FormGroup
label={t("cachePolicy")}
@ -137,8 +84,8 @@ export const LdapSettingsCache = ({
>
<Controller
name="config.cachePolicy"
defaultValue=""
control={control}
defaultValue={["DEFAULT"]}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-cache-policy"
@ -154,18 +101,16 @@ export const LdapSettingsCache = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption key={0} value="Choose..." 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>
</FormGroup>
{/* TODO: Field shows only if cache policy is EVICT_WEEKLY */}
{_.isEqual(cachePolicyType, ["EVICT_WEEKLY"]) ? (
<FormGroup
label={t("evictionDay")}
labelIcon={
@ -175,12 +120,13 @@ export const LdapSettingsCache = ({
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"
@ -196,25 +142,37 @@ export const LdapSettingsCache = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption
key={0}
value={t("common:selectOne")}
isPlaceholder
/>
<SelectOption key={1} value={t("common:Sunday")} />
<SelectOption key={2} value={t("common:Monday")} />
<SelectOption key={3} value={t("common:Tuesday")} />
<SelectOption key={4} value={t("common:Wednesday")} />
<SelectOption key={5} value={t("common:Thursday")} />
<SelectOption key={6} value={t("common:Friday")} />
<SelectOption key={7} value={t("common:Saturday")} />
<SelectOption key={0} value={["1"]} isPlaceholder>
{t("common:Sunday")}
</SelectOption>
<SelectOption key={1} value={["2"]}>
{t("common:Monday")}
</SelectOption>
<SelectOption key={2} value={["3"]}>
{t("common:Tuesday")}
</SelectOption>
<SelectOption key={3} value={["4"]}>
{t("common:Wednesday")}
</SelectOption>
<SelectOption key={4} value={["5"]}>
{t("common:Thursday")}
</SelectOption>
<SelectOption key={5} value={["6"]}>
{t("common:Friday")}
</SelectOption>
<SelectOption key={6} value={["7"]}>
{t("common:Saturday")}
</SelectOption>
</Select>
)}
></Controller>
</FormGroup>
{/* TODO: Field shows only if cache policy is EVICT_WEEKLY or EVICT_DAILY */}
{/* TODO: Investigate whether this should be a number field instead of a dropdown/text field */}
) : (
<></>
)}
{_.isEqual(cachePolicyType, ["EVICT_DAILY"]) ||
_.isEqual(cachePolicyType, ["EVICT_WEEKLY"]) ? (
<>
<FormGroup
label={t("evictionHour")}
labelIcon={
@ -224,12 +182,13 @@ export const LdapSettingsCache = ({
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"
@ -249,9 +208,6 @@ export const LdapSettingsCache = ({
)}
></Controller>
</FormGroup>
{/* TODO: Field shows only if cache policy is EVICT_WEEKLY or EVICT_DAILY */}
{/* TODO: Investigate whether this should be a number field instead of a dropdown/text field */}
<FormGroup
label={t("evictionMinute")}
labelIcon={
@ -261,17 +217,20 @@ export const LdapSettingsCache = ({
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"
onToggle={() =>
setIsEvictionMinuteDropdownOpen(!isEvictionMinuteDropdownOpen)
setIsEvictionMinuteDropdownOpen(
!isEvictionMinuteDropdownOpen
)
}
isOpen={isEvictionMinuteDropdownOpen}
onSelect={(_, value) => {
@ -286,8 +245,11 @@ export const LdapSettingsCache = ({
)}
></Controller>
</FormGroup>
{/* TODO: Field shows only if cache policy is MAX_LIFESPAN */}
</>
) : (
<></>
)}
{_.isEqual(cachePolicyType, ["MAX_LIFESPAN"]) ? (
<FormGroup
label={t("maxLifespan")}
labelIcon={
@ -303,10 +265,13 @@ export const LdapSettingsCache = ({
isRequired
type="text"
id="kc-max-lifespan"
name="config.maxLifespan"
ref={register}
name="config.maxLifespan[0]"
ref={form.register}
/>
</FormGroup>
) : (
<></>
)}
</FormAccess>
</>
);

View file

@ -9,46 +9,26 @@ import {
TextInput,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { Controller, useForm } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { Controller, UseFormMethods } from "react-hook-form";
import { EyeIcon } from "@patternfly/react-icons";
import { FormAccess } from "../../components/form-access/FormAccess";
import {
useAdminClient,
asyncStateFetch,
} from "../../context/auth/AdminClient";
import { useParams } from "react-router-dom";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsConnectionProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const LdapSettingsConnection = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: LdapSettingsConnectionProps) => {
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 convertTruststoreSpiValues = (truststoreValue: string) => {
switch (truststoreValue) {
case "always":
return `${t("always")}`;
case "never":
return `${t("never")}`;
case "ldapsOnly":
default:
return `${t("onlyLdaps")}`;
}
};
const [
isTruststoreSpiDropdownOpen,
@ -57,29 +37,6 @@ export const LdapSettingsConnection = ({
const [isBindTypeDropdownOpen, setIsBindTypeDropdownOpen] = useState(false);
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
if (entry[1].useTruststoreSpi) {
setValue(
"config.useTruststoreSpi",
convertTruststoreSpiValues(entry[1].useTruststoreSpi[0])
);
}
} else {
setValue(entry[0], entry[1]);
}
});
};
useEffect(() => {
return asyncStateFetch(
() => adminClient.components.findOne({ id }),
(fetchedComponent) => setupForm(fetchedComponent)
);
}, []);
return (
<>
{showSectionHeading && (
@ -91,7 +48,6 @@ export const LdapSettingsConnection = ({
showDescription={showSectionDescription}
/>
)}
<FormAccess role="manage-realm" isHorizontal>
<FormGroup
label={t("connectionURL")}
@ -109,9 +65,21 @@ export const LdapSettingsConnection = ({
isRequired
type="text"
id="kc-console-connection-url"
name="config.connectionUrl"
ref={register}
name="config.connectionUrl[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateConnectionUrl")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.connectionUrl &&
form.errors.config.connectionUrl[0] && (
<div className="error">
{form.errors.config.connectionUrl[0].message}
</div>
)}
</FormGroup>
<FormGroup
label={t("enableStartTls")}
@ -127,14 +95,14 @@ export const LdapSettingsConnection = ({
>
<Controller
name="config.startTls"
defaultValue={false}
control={control}
defaultValue={["false"]}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-enable-start-tls"}
isChecked={value[0] === "true"}
isDisabled={false}
onChange={onChange}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>
@ -154,9 +122,9 @@ export const LdapSettingsConnection = ({
fieldId="kc-use-truststore-spi"
>
<Controller
name="config.useTruststoreSpi"
name="config.useTruststoreSpi[0]"
defaultValue=""
control={control}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-use-truststore-spi"
@ -171,9 +139,15 @@ export const LdapSettingsConnection = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption key={0} value={t("always")} />
<SelectOption key={1} value={t("onlyLdaps")} />
<SelectOption key={2} value={t("never")} />
<SelectOption key={0} value="always">
{t("always")}
</SelectOption>
<SelectOption key={1} value="ldapsOnly">
{t("onlyLdaps")}
</SelectOption>
<SelectOption key={2} value="never">
{t("never")}
</SelectOption>
</Select>
)}
></Controller>
@ -193,12 +167,12 @@ export const LdapSettingsConnection = ({
<Controller
name="config.connectionPooling"
defaultValue={false}
control={control}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-connection-pooling"}
isDisabled={false}
onChange={onChange}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
@ -220,8 +194,8 @@ export const LdapSettingsConnection = ({
<TextInput
type="text"
id="kc-console-connection-timeout"
name="config.connectionTimeout"
ref={register}
name="config.connectionTimeout[0]"
ref={form.register}
/>
</FormGroup>
<FormGroup
@ -237,9 +211,9 @@ export const LdapSettingsConnection = ({
isRequired
>
<Controller
name="config.authType"
name="config.authType[0]"
defaultValue=""
control={control}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-bind-type"
@ -275,8 +249,8 @@ export const LdapSettingsConnection = ({
<TextInput
type="text"
id="kc-console-bind-dn"
name="config.bindDn"
ref={register}
name="config.bindDn[0]"
ref={form.register}
/>
</FormGroup>
<FormGroup
@ -291,14 +265,18 @@ export const LdapSettingsConnection = ({
fieldId="kc-console-bind-credentials"
isRequired
>
{/* TODO: MF The input group below throws a 'React does not recognize the `isDisabled` prop on a DOM element' error */}
<InputGroup>
<TextInput // TODO: Make password field switch to type=text with button
isRequired
type="password"
id="kc-console-bind-credentials"
name="config.bindCredential"
ref={register}
name="config.bindCredential[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateBindCredentials")}`,
},
})}
/>
<Button
variant="control"
@ -307,6 +285,13 @@ export const LdapSettingsConnection = ({
<EyeIcon />
</Button>
</InputGroup>
{form.errors.config &&
form.errors.config.bindCredential &&
form.errors.config.bindCredential[0] && (
<div className="error">
{form.errors.config.bindCredential[0].message}
</div>
)}
</FormGroup>
<FormGroup fieldId="kc-test-button">
{" "}

View file

@ -3,78 +3,31 @@ import {
Select,
SelectOption,
SelectVariant,
Text,
TextContent,
TextInput,
Title,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { UseFormMethods, Controller } 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 { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsGeneralProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const LdapSettingsGeneral = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: LdapSettingsGeneralProps) => {
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 [isVendorDropdownOpen, setIsVendorDropdownOpen] = useState(false);
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
if (entry[1].vendor) {
setValue("config.vendor", convertVendorNames(entry[1].vendor[0]));
}
} else {
setValue(entry[0], entry[1]);
}
});
};
useEffect(() => {
return asyncStateFetch(
() => adminClient.components.findOne({ id }),
(fetchedComponent) => setupForm(fetchedComponent)
);
}, []);
const convertVendorNames = (vendorName: string) => {
switch (vendorName) {
case "ad":
return "Active Directory";
case "rhds":
return "Red Hat Directory Server";
case "tivoli":
return "Tivoli";
case "edirectory":
return "Novell eDirectory";
case "other":
return "Other";
default:
return t("common:choose");
}
};
return (
<>
{showSectionHeading && (
@ -84,7 +37,6 @@ export const LdapSettingsGeneral = ({
showDescription={showSectionDescription}
/>
)}
<FormAccess role="manage-realm" isHorizontal>
<FormGroup
label={t("consoleDisplayName")}
@ -98,13 +50,50 @@ export const LdapSettingsGeneral = ({
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
isRequired
type="text"
id="kc-console-display-name"
name="name"
ref={register}
ref={form.register({
required: {
value: true,
message: `${t("validateName")}`,
},
})}
/>
<TextInput
hidden
type="text"
id="kc-console-provider-id"
name="providerId"
ref={form.register}
/>
<TextInput
hidden
type="text"
id="kc-console-provider-type"
name="providerType"
ref={form.register}
/>
<TextInput
hidden
type="text"
id="kc-console-parentId"
name="parentId"
ref={form.register}
/>
{form.errors.name && (
<div className="error">{form.errors.name.message}</div>
)}
</FormGroup>
<FormGroup
label={t("vendor")}
@ -119,9 +108,9 @@ export const LdapSettingsGeneral = ({
isRequired
>
<Controller
name="config.vendor"
name="config.vendor[0]"
defaultValue=""
control={control}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-vendor"
@ -135,16 +124,21 @@ export const LdapSettingsGeneral = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption
key={0}
value={t("common:choose")}
isPlaceholder
/>
<SelectOption key={1} value="Active Directory" />
<SelectOption key={2} value="Red Hat Directory Server" />
<SelectOption key={3} value="Tivoli" />
<SelectOption key={4} value="Novell eDirectory" />
<SelectOption key={5} value="Other" />
<SelectOption key={0} value="ad" isPlaceholder>
Active Directory
</SelectOption>
<SelectOption key={1} value="rhds">
Red Hat Directory Server
</SelectOption>
<SelectOption key={2} value="tivoli">
Tivoli
</SelectOption>
<SelectOption key={3} value="edirectory">
Novell eDirectory
</SelectOption>
<SelectOption key={4} value="other">
Other
</SelectOption>
</Select>
)}
></Controller>

View file

@ -1,51 +1,25 @@
import { FormGroup, Switch } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React, { useEffect } from "react";
import React from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { UseFormMethods, Controller } 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 { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsKerberosIntegrationProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const LdapSettingsKerberosIntegration = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: LdapSettingsKerberosIntegrationProps) => {
const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t;
const adminClient = useAdminClient();
const { control, setValue } = useForm<ComponentRepresentation>();
const { id } = useParams<{ id: string }>();
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
} else {
setValue(entry[0], entry[1]);
}
});
};
useEffect(() => {
return asyncStateFetch(
() => adminClient.components.findOne({ id }),
(fetchedComponent) => setupForm(fetchedComponent)
);
}, []);
return (
<>
{showSectionHeading && (
@ -72,12 +46,12 @@ export const LdapSettingsKerberosIntegration = ({
<Controller
name="config.allowKerberosAuthentication"
defaultValue={false}
control={control}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-allow-kerberos-authentication"}
isDisabled={false}
onChange={onChange}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
@ -100,12 +74,12 @@ export const LdapSettingsKerberosIntegration = ({
<Controller
name="config.useKerberosForPasswordAuthentication"
defaultValue={false}
control={control}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-use-kerberos-password-authentication"}
isDisabled={false}
onChange={onChange}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}

View file

@ -7,70 +7,30 @@ import {
TextInput,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { UseFormMethods, Controller } 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 { convertToFormValues } from "../../util";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsSearchingProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const LdapSettingsSearching = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: LdapSettingsSearchingProps) => {
const { t } = useTranslation("user-federation");
const adminClient = useAdminClient();
const helpText = useTranslation("user-federation-help").t;
const [isEditModeDropdownOpen, setIsEditModeDropdownOpen] = useState(false);
const { id } = useParams<{ id: string }>();
const [isSearchScopeDropdownOpen, setIsSearchScopeDropdownOpen] = useState(
false
);
const { register, control, setValue } = useForm<ComponentRepresentation>();
const convertSearchScopes = (scope: string) => {
switch (scope) {
case "1":
default:
return `${t("oneLevel")}`;
case "2":
return `${t("subtree")}`;
}
};
const setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
if (entry[1].searchScope) {
setValue(
"config.searchScope",
convertSearchScopes(entry[1].searchScope[0])
);
}
} else {
setValue(entry[0], entry[1]);
}
});
};
useEffect(() => {
return asyncStateFetch(
() => adminClient.components.findOne({ id }),
(fetchedComponent) => setupForm(fetchedComponent)
);
}, []);
const [isEditModeDropdownOpen, setIsEditModeDropdownOpen] = useState(false);
return (
<>
@ -95,9 +55,9 @@ export const LdapSettingsSearching = ({
fieldId="kc-edit-mode"
>
<Controller
name="config.editMode"
name="config.editMode[0]"
defaultValue=""
control={control}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-edit-mode"
@ -113,12 +73,8 @@ export const LdapSettingsSearching = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption
key={0}
value={t("common:choose")}
isPlaceholder
/>
<SelectOption key={1} value="RACT_ONLY" />
<SelectOption key={0} value="" isPlaceholder />
<SelectOption key={1} value="READ_ONLY" />
<SelectOption key={2} value="WRITABLE" />
<SelectOption key={3} value="UNSYNCED" />
</Select>
@ -141,9 +97,21 @@ export const LdapSettingsSearching = ({
isRequired
type="text"
id="kc-console-users-dn"
name="config.usersDn"
ref={register}
name="config.usersDn[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateUsersDn")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.usersDn &&
form.errors.config.usersDn[0] && (
<div className="error">
{form.errors.config.usersDn[0].message}
</div>
)}
</FormGroup>
<FormGroup
label={t("usernameLdapAttribute")}
@ -161,9 +129,21 @@ export const LdapSettingsSearching = ({
isRequired
type="text"
id="kc-username-ldap-attribute"
name="config.usernameLDAPAttribute"
ref={register}
name="config.usernameLDAPAttribute[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateUsernameLDAPAttribute")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.usernameLDAPAttribute &&
form.errors.config.usernameLDAPAttribute[0] && (
<div className="error">
{form.errors.config.usernameLDAPAttribute[0].message}
</div>
)}
</FormGroup>
<FormGroup
label={t("rdnLdapAttribute")}
@ -181,9 +161,21 @@ export const LdapSettingsSearching = ({
isRequired
type="text"
id="kc-rdn-ldap-attribute"
name="config.rdnLDAPAttribute"
ref={register}
name="config.rdnLDAPAttribute[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateRdnLdapAttribute")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.rdnLDAPAttribute &&
form.errors.config.rdnLDAPAttribute[0] && (
<div className="error">
{form.errors.config.rdnLDAPAttribute[0].message}
</div>
)}
</FormGroup>
<FormGroup
label={t("uuidLdapAttribute")}
@ -201,9 +193,21 @@ export const LdapSettingsSearching = ({
isRequired
type="text"
id="kc-uuid-ldap-attribute"
name="config.uuidLDAPAttribute"
ref={register}
name="config.uuidLDAPAttribute[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateUuidLDAPAttribute")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.uuidLDAPAttribute &&
form.errors.config.uuidLDAPAttribute[0] && (
<div className="error">
{form.errors.config.uuidLDAPAttribute[0].message}
</div>
)}
</FormGroup>
<FormGroup
label={t("userObjectClasses")}
@ -221,9 +225,21 @@ export const LdapSettingsSearching = ({
isRequired
type="text"
id="kc-user-object-classes"
name="config.userObjectClasses"
ref={register}
name="config.userObjectClasses[0]"
ref={form.register({
required: {
value: true,
message: `${t("validateUserObjectClasses")}`,
},
})}
/>
{form.errors.config &&
form.errors.config.userObjectClasses &&
form.errors.config.userObjectClasses[0] && (
<div className="error">
{form.errors.config.userObjectClasses[0].message}
</div>
)}
</FormGroup>
<FormGroup
label={t("userLdapFilter")}
@ -239,10 +255,11 @@ export const LdapSettingsSearching = ({
<TextInput
type="text"
id="kc-user-ldap-filter"
name="config.customUserSearchFilter"
ref={register}
name="config.customUserSearchFilter[0]"
ref={form.register}
/>
</FormGroup>
<FormGroup
label={t("searchScope")}
labelIcon={
@ -255,9 +272,9 @@ export const LdapSettingsSearching = ({
fieldId="kc-search-scope"
>
<Controller
name="config.searchScope"
name="config.searchScope[0]"
defaultValue=""
control={control}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-search-scope"
@ -273,13 +290,12 @@ export const LdapSettingsSearching = ({
selections={value}
variant={SelectVariant.single}
>
<SelectOption
key={0}
value={t("common:choose")}
isPlaceholder
/>
<SelectOption key={1} value={t("oneLevel")} />
<SelectOption key={2} value={t("subtree")} />
<SelectOption key={0} value="1" isPlaceholder>
{t("oneLevel")}
</SelectOption>
<SelectOption key={1} value="2">
{t("subtree")}
</SelectOption>
</Select>
)}
></Controller>
@ -298,8 +314,8 @@ export const LdapSettingsSearching = ({
<TextInput
type="text"
id="kc-read-timeout"
name="config.readTimeout"
ref={register}
name="config.readTimeout[0]"
ref={form.register}
/>
</FormGroup>
<FormGroup
@ -317,13 +333,13 @@ export const LdapSettingsSearching = ({
<Controller
name="config.pagination"
defaultValue={false}
control={control}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-console-pagination"}
isChecked={value[0] === "true"}
isDisabled={false}
onChange={onChange}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")}
labelOff={t("common:off")}
/>

View file

@ -1,49 +1,24 @@
import { FormGroup, Switch, TextInput } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import React, { useEffect } from "react";
import React from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { UseFormMethods, Controller } 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 { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsSynchronizationProps = {
form: UseFormMethods;
showSectionHeading?: boolean;
showSectionDescription?: boolean;
};
export const LdapSettingsSynchronization = ({
form,
showSectionHeading = false,
showSectionDescription = false,
}: LdapSettingsSynchronizationProps) => {
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 setupForm = (component: ComponentRepresentation) => {
Object.entries(component).map((entry) => {
if (entry[0] === "config") {
convertToFormValues(entry[1], "config", setValue);
} else {
setValue(entry[0], entry[1]);
}
});
};
useEffect(() => {
return asyncStateFetch(
() => adminClient.components.findOne({ id }),
(fetchedComponent) => setupForm(fetchedComponent)
);
}, []);
return (
<>
@ -54,7 +29,6 @@ export const LdapSettingsSynchronization = ({
showDescription={showSectionDescription}
/>
)}
<FormAccess role="manage-realm" isHorizontal>
<FormGroup
hasNoPaddingTop
@ -71,16 +45,16 @@ export const LdapSettingsSynchronization = ({
<Controller
name="config.importEnabled"
defaultValue={false}
control={control}
control={form.control}
render={({ onChange, value }) => (
<Switch
id={"kc-import-users"}
name="importEnabled"
label={t("common:on")}
labelOff={t("common:off")}
onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
isDisabled={false}
onChange={onChange}
/>
)}
></Controller>
@ -99,46 +73,50 @@ export const LdapSettingsSynchronization = ({
<TextInput
type="text"
id="kc-batch-size"
name="config.batchSizeForSync"
ref={register}
name="config.batchSizeForSync[0]"
ref={form.register}
/>
</FormGroup>
{/* Enter -1 to switch off, otherwise enter value */}
<FormGroup
hasNoPaddingTop
label={t("periodicFullSync")}
label={t("fullSyncPeriod")}
labelIcon={
<HelpItem
helpText={helpText("periodicFullSyncHelp")}
forLabel={t("periodicFullSync")}
forID="kc-periodic-full-sync"
helpText={helpText("fullSyncPeriodHelp")}
forLabel={t("fullSyncPeriod")}
forID="kc-full-sync-period"
/>
}
fieldId="kc-periodic-full-sync"
fieldId="kc-full-sync-period"
>
<TextInput
type="text"
id="kc-batch-size"
name="config.fullSyncPeriod"
ref={register}
id="kc-full-sync-period"
name="config.fullSyncPeriod[0]"
ref={form.register}
/>
</FormGroup>
{/* Enter -1 to switch off, otherwise enter value */}
<FormGroup
label={t("periodicChangedUsersSync")}
label={t("changedUsersSyncPeriod")}
labelIcon={
<HelpItem
helpText={helpText("periodicChangedUsersSyncHelp")}
forLabel={t("periodicChangedUsersSync")}
forID="kc-periodic-changed-users-sync"
helpText={helpText("changedUsersSyncHelp")}
forLabel={t("changedUsersSyncPeriod")}
forID="kc-changed-users-sync-period"
/>
}
fieldId="kc-periodic-changed-users-sync"
fieldId="kc-changed-users-sync-period"
hasNoPaddingTop
>
<TextInput
type="text"
id="kc-batch-size"
name="config.changedSyncPeriod"
ref={register}
id="kc-changed-users-sync-period"
name="config.changedSyncPeriod[0]"
ref={form.register}
/>
</FormGroup>
</FormAccess>

View file

@ -46,7 +46,9 @@
"importUsers": "Import users",
"batchSize": "Batch size",
"periodicFullSync": "Periodic full sync",
"fullSyncPeriod": "Full sync period",
"periodicChangedUsersSync": "Periodic changed users sync",
"changedUsersSyncPeriod": "Changed users sync period",
"kerberosIntegration": "Kerberos integration",
"allowKerberosAuthentication": "Allow Kerberos authentication",
@ -92,6 +94,13 @@
"validateRealm":"You must enter a realm",
"validateServerPrincipal":"You must enter a server principal",
"validateKeyTab": "You must enter a key tab",
"validateConnectionUrl": "You must enter a connection URL",
"validateBindCredentials": "You must enter bind credentials",
"validateUuidLDAPAttribute": "You must enter a UUID LDAP attribute",
"validateUserObjectClasses": "You must enter one or more user object classes",
"validateUsersDn": "You must enter users DN",
"validateUsernameLDAPAttribute": "You must enter a username LDAP attribute",
"validateRdnLdapAttribute": "You must enter an RDN LDAP attribute",
"id": "ID",
"mapperType": "Mapper type",