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 { useTranslation } from "react-i18next";
import React from "react"; import React, { useEffect } from "react";
import { ScrollForm } from "../components/scroll-form/ScrollForm";
import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced"; import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced";
import { LdapSettingsKerberosIntegration } from "./ldap/LdapSettingsKerberosIntegration"; import { LdapSettingsKerberosIntegration } from "./ldap/LdapSettingsKerberosIntegration";
import { LdapSettingsCache } from "./ldap/LdapSettingsCache"; import { LdapSettingsCache } from "./ldap/LdapSettingsCache";
@ -9,9 +15,55 @@ import { LdapSettingsSynchronization } from "./ldap/LdapSettingsSynchronization"
import { LdapSettingsGeneral } from "./ldap/LdapSettingsGeneral"; import { LdapSettingsGeneral } from "./ldap/LdapSettingsGeneral";
import { LdapSettingsConnection } from "./ldap/LdapSettingsConnection"; import { LdapSettingsConnection } from "./ldap/LdapSettingsConnection";
import { LdapSettingsSearching } from "./ldap/LdapSettingsSearching"; 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 = () => { export const UserFederationLdapSettings = () => {
const { t } = useTranslation("user-federation"); 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 ( return (
<> <>
@ -27,27 +79,27 @@ export const UserFederationLdapSettings = () => {
t("advancedSettings"), t("advancedSettings"),
]} ]}
> >
{/* General settings */} <LdapSettingsGeneral form={form} />
<LdapSettingsGeneral /> <LdapSettingsConnection form={form} />
<LdapSettingsSearching form={form} />
{/* Connection settings */} <LdapSettingsSynchronization form={form} />
<LdapSettingsConnection /> <LdapSettingsKerberosIntegration form={form} />
<LdapSettingsCache form={form} />
{/* Searching and updating settings */} <LdapSettingsAdvanced form={form} />
<LdapSettingsSearching />
{/* Synchronization settings */}
<LdapSettingsSynchronization />
{/* Kerberos integration */}
<LdapSettingsKerberosIntegration />
{/* Cache settings */}
<LdapSettingsCache />
{/* Advanced settings */}
<LdapSettingsAdvanced />
</ScrollForm> </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> </PageSection>
</> </>
); );

View file

@ -13,8 +13,12 @@ import { LdapSettingsKerberosIntegration } from "./ldap/LdapSettingsKerberosInte
import { LdapSettingsCache } from "./ldap/LdapSettingsCache"; import { LdapSettingsCache } from "./ldap/LdapSettingsCache";
import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced"; import { LdapSettingsAdvanced } from "./ldap/LdapSettingsAdvanced";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { useForm } from "react-hook-form";
export const UserFederationLdapWizard = () => { export const UserFederationLdapWizard = () => {
const form = useForm<ComponentRepresentation>();
const { t } = useTranslation("user-federation"); const { t } = useTranslation("user-federation");
const steps = [ const steps = [
@ -22,21 +26,33 @@ export const UserFederationLdapWizard = () => {
name: t("requiredSettings"), name: t("requiredSettings"),
id: "ldapRequiredSettingsStep", id: "ldapRequiredSettingsStep",
component: ( component: (
<LdapSettingsGeneral showSectionHeading showSectionDescription /> <LdapSettingsGeneral
form={form}
showSectionHeading
showSectionDescription
/>
), ),
}, },
{ {
name: t("connectionAndAuthenticationSettings"), name: t("connectionAndAuthenticationSettings"),
id: "ldapConnectionSettingsStep", id: "ldapConnectionSettingsStep",
component: ( component: (
<LdapSettingsConnection showSectionHeading showSectionDescription /> <LdapSettingsConnection
form={form}
showSectionHeading
showSectionDescription
/>
), ),
}, },
{ {
name: t("ldapSearchingAndUpdatingSettings"), name: t("ldapSearchingAndUpdatingSettings"),
id: "ldapSearchingSettingsStep", id: "ldapSearchingSettingsStep",
component: ( component: (
<LdapSettingsSearching showSectionHeading showSectionDescription /> <LdapSettingsSearching
form={form}
showSectionHeading
showSectionDescription
/>
), ),
}, },
{ {
@ -44,6 +60,7 @@ export const UserFederationLdapWizard = () => {
id: "ldapSynchronizationSettingsStep", id: "ldapSynchronizationSettingsStep",
component: ( component: (
<LdapSettingsSynchronization <LdapSettingsSynchronization
form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
@ -54,6 +71,7 @@ export const UserFederationLdapWizard = () => {
id: "ldapKerberosIntegrationSettingsStep", id: "ldapKerberosIntegrationSettingsStep",
component: ( component: (
<LdapSettingsKerberosIntegration <LdapSettingsKerberosIntegration
form={form}
showSectionHeading showSectionHeading
showSectionDescription showSectionDescription
/> />
@ -63,14 +81,22 @@ export const UserFederationLdapWizard = () => {
name: t("cacheSettings"), name: t("cacheSettings"),
id: "ldapCacheSettingsStep", id: "ldapCacheSettingsStep",
component: ( component: (
<LdapSettingsCache showSectionHeading showSectionDescription /> <LdapSettingsCache
form={form}
showSectionHeading
showSectionDescription
/>
), ),
}, },
{ {
name: t("advancedSettings"), name: t("advancedSettings"),
id: "ldapAdvancedSettingsStep", id: "ldapAdvancedSettingsStep",
component: ( 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.", "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", "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.", "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.", "connectionPoolingHelp": "Determines if Keycloak should use connection pooling for accessing LDAP server.",
"connectionTimeoutHelp": "LDAP connection timeout in milliseconds", "connectionTimeoutHelp": "LDAP connection timeout in milliseconds",
@ -30,10 +30,12 @@
"paginationHelp": "Whether the LDAP server supports pagination", "paginationHelp": "Whether the LDAP server supports pagination",
"ldapSynchronizationSettingsDescription": "This section contains options related to synchronization of users from LDAP to the Keycloak database.", "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", "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", "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", "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.", "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.", "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 { FormGroup, Switch } from "@patternfly/react-core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import React, { useEffect } from "react"; import React from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form"; import { UseFormMethods, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; 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"; import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsAdvancedProps = { export type LdapSettingsAdvancedProps = {
form: UseFormMethods;
showSectionHeading?: boolean; showSectionHeading?: boolean;
showSectionDescription?: boolean; showSectionDescription?: boolean;
}; };
export const LdapSettingsAdvanced = ({ export const LdapSettingsAdvanced = ({
form,
showSectionHeading = false, showSectionHeading = false,
showSectionDescription = false, showSectionDescription = false,
}: LdapSettingsAdvancedProps) => { }: LdapSettingsAdvancedProps) => {
const { t } = useTranslation("user-federation"); const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t; 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 ( return (
<> <>
@ -70,14 +45,14 @@ export const LdapSettingsAdvanced = ({
> >
<Controller <Controller
name="config.usePasswordModifyExtendedOp" name="config.usePasswordModifyExtendedOp"
defaultValue={false} defaultValue={["false"]}
control={control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
id={"kc-enable-ldapv3-password"} id={"kc-enable-ldapv3-password"}
isChecked={value[0] === "true"}
isDisabled={false} isDisabled={false}
onChange={onChange} onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
/> />
@ -99,14 +74,14 @@ export const LdapSettingsAdvanced = ({
> >
<Controller <Controller
name="config.validatePasswordPolicy" name="config.validatePasswordPolicy"
defaultValue={false} defaultValue={"false"}
control={control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
id={"kc-validate-password-policy"} id={"kc-validate-password-policy"}
isChecked={value[0] === "true"}
isDisabled={false} isDisabled={false}
onChange={onChange} onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
/> />
@ -128,14 +103,14 @@ export const LdapSettingsAdvanced = ({
> >
<Controller <Controller
name="config.trustEmail" name="config.trustEmail"
defaultValue={false} defaultValue={"false"}
control={control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
id={"kc-trust-email"} id={"kc-trust-email"}
isChecked={value[0] === "true"}
isDisabled={false} isDisabled={false}
onChange={onChange} onChange={(value) => onChange([`${value}`])}
isChecked={value[0] === "true"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
/> />

View file

@ -3,84 +3,30 @@ import {
Select, Select,
SelectOption, SelectOption,
SelectVariant, SelectVariant,
Text,
TextContent,
TextInput, TextInput,
Title,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form"; import { UseFormMethods, useWatch, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { useAdminClient } from "../../context/auth/AdminClient"; import _ from "lodash";
import { useParams } from "react-router-dom";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader"; import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsCacheProps = { export type LdapSettingsCacheProps = {
form: UseFormMethods;
showSectionHeading?: boolean; showSectionHeading?: boolean;
showSectionDescription?: boolean; showSectionDescription?: boolean;
}; };
export const LdapSettingsCache = ({ export const LdapSettingsCache = ({
form,
showSectionHeading = false, showSectionHeading = false,
showSectionDescription = false, showSectionDescription = false,
}: LdapSettingsCacheProps) => { }: LdapSettingsCacheProps) => {
const { t } = useTranslation("user-federation"); const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t; 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( const [isCachePolicyDropdownOpen, setIsCachePolicyDropdownOpen] = useState(
false false
); );
@ -89,6 +35,11 @@ export const LdapSettingsCache = ({
false false
); );
const cachePolicyType = useWatch({
control: form.control,
name: "config.cachePolicy",
});
const [ const [
isEvictionMinuteDropdownOpen, isEvictionMinuteDropdownOpen,
setIsEvictionMinuteDropdownOpen, setIsEvictionMinuteDropdownOpen,
@ -98,18 +49,16 @@ export const LdapSettingsCache = ({
false false
); );
const hourOptions = [ const hourOptions = [<SelectOption key={0} value={[`${1}`]} isPlaceholder />];
<SelectOption key={0} value={t("common:selectOne")} isPlaceholder />, for (let index = 2; index <= 24; index++) {
]; hourOptions.push(<SelectOption key={index - 1} value={[`${index}`]} />);
for (let index = 1; index <= 24; index++) {
hourOptions.push(<SelectOption key={index + 1} value={index} />);
} }
const minuteOptions = [ const minuteOptions = [
<SelectOption key={0} value={t("common:selectOne")} isPlaceholder />, <SelectOption key={0} value={[`${1}`]} isPlaceholder />,
]; ];
for (let index = 1; index <= 60; index++) { for (let index = 2; index <= 60; index++) {
minuteOptions.push(<SelectOption key={index + 1} value={index} />); minuteOptions.push(<SelectOption key={index - 1} value={[`${index}`]} />);
} }
return ( return (
@ -121,8 +70,6 @@ export const LdapSettingsCache = ({
showDescription={showSectionDescription} showDescription={showSectionDescription}
/> />
)} )}
{/* Cache settings */}
<FormAccess role="manage-realm" isHorizontal> <FormAccess role="manage-realm" isHorizontal>
<FormGroup <FormGroup
label={t("cachePolicy")} label={t("cachePolicy")}
@ -137,8 +84,8 @@ export const LdapSettingsCache = ({
> >
<Controller <Controller
name="config.cachePolicy" name="config.cachePolicy"
defaultValue="" defaultValue={["DEFAULT"]}
control={control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="kc-cache-policy" toggleId="kc-cache-policy"
@ -154,159 +101,177 @@ export const LdapSettingsCache = ({
selections={value} selections={value}
variant={SelectVariant.single} variant={SelectVariant.single}
> >
<SelectOption key={0} value="Choose..." isPlaceholder /> <SelectOption key={0} value={["DEFAULT"]} isPlaceholder />
<SelectOption key={1} value="DEFAULT" /> <SelectOption key={1} value={["EVICT_DAILY"]} />
<SelectOption key={2} value="EVICT_DAILY" /> <SelectOption key={2} value={["EVICT_WEEKLY"]} />
<SelectOption key={3} value="EVICT_WEEKLY" /> <SelectOption key={3} value={["MAX_LIFESPAN"]} />
<SelectOption key={4} value="MAX_LIFESPAN" /> <SelectOption key={4} value={["NO_CACHE"]} />
<SelectOption key={5} value="NO_CACHE" />
</Select> </Select>
)} )}
></Controller> ></Controller>
</FormGroup> </FormGroup>
{_.isEqual(cachePolicyType, ["EVICT_WEEKLY"]) ? (
{/* TODO: Field shows only if cache policy is EVICT_WEEKLY */} <FormGroup
<FormGroup label={t("evictionDay")}
label={t("evictionDay")} labelIcon={
labelIcon={ <HelpItem
<HelpItem helpText={helpText("evictionDayHelp")}
helpText={helpText("evictionDayHelp")} forLabel={t("evictionDay")}
forLabel={t("evictionDay")} forID="kc-eviction-day"
forID="kc-eviction-day" />
/> }
}
fieldId="kc-eviction-day"
>
<Controller
name="config.evictionDay"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-day"
required
onToggle={() =>
setIsEvictionDayDropdownOpen(!isEvictionDayDropdownOpen)
}
isOpen={isEvictionDayDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsEvictionDayDropdownOpen(false);
}}
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")} />
</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 */}
<FormGroup
label={t("evictionHour")}
labelIcon={
<HelpItem
helpText={helpText("evictionHourHelp")}
forLabel={t("evictionHour")}
forID="kc-eviction-hour"
/>
}
fieldId="kc-eviction-hour"
>
<Controller
name="config.evictionHour"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-hour"
onToggle={() =>
setIsEvictionHourDropdownOpen(!isEvictionHourDropdownOpen)
}
isOpen={isEvictionHourDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsEvictionHourDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
{hourOptions}
</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 */}
<FormGroup
label={t("evictionMinute")}
labelIcon={
<HelpItem
helpText={helpText("evictionMinuteHelp")}
forLabel={t("evictionMinute")}
forID="kc-eviction-minute"
/>
}
fieldId="kc-eviction-minute"
>
<Controller
name="config.evictionMinute"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-minute"
onToggle={() =>
setIsEvictionMinuteDropdownOpen(!isEvictionMinuteDropdownOpen)
}
isOpen={isEvictionMinuteDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsEvictionMinuteDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
{minuteOptions}
</Select>
)}
></Controller>
</FormGroup>
{/* TODO: Field shows only if cache policy is MAX_LIFESPAN */}
<FormGroup
label={t("maxLifespan")}
labelIcon={
<HelpItem
helpText={helpText("maxLifespanHelp")}
forLabel={t("maxLifespan")}
forID="kc-max-lifespan"
/>
}
fieldId="kc-max-lifespan"
>
<TextInput
isRequired isRequired
type="text" fieldId="kc-eviction-day"
id="kc-max-lifespan" >
name="config.maxLifespan" <Controller
ref={register} name="config.evictionDay"
/> defaultValue={[t("common:Sunday")]}
</FormGroup> control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-day"
required
onToggle={() =>
setIsEvictionDayDropdownOpen(!isEvictionDayDropdownOpen)
}
isOpen={isEvictionDayDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsEvictionDayDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
<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>
) : (
<></>
)}
{_.isEqual(cachePolicyType, ["EVICT_DAILY"]) ||
_.isEqual(cachePolicyType, ["EVICT_WEEKLY"]) ? (
<>
<FormGroup
label={t("evictionHour")}
labelIcon={
<HelpItem
helpText={helpText("evictionHourHelp")}
forLabel={t("evictionHour")}
forID="kc-eviction-hour"
/>
}
isRequired
fieldId="kc-eviction-hour"
>
<Controller
name="config.evictionHour"
defaultValue={["1"]}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-hour"
onToggle={() =>
setIsEvictionHourDropdownOpen(!isEvictionHourDropdownOpen)
}
isOpen={isEvictionHourDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsEvictionHourDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
{hourOptions}
</Select>
)}
></Controller>
</FormGroup>
<FormGroup
label={t("evictionMinute")}
labelIcon={
<HelpItem
helpText={helpText("evictionMinuteHelp")}
forLabel={t("evictionMinute")}
forID="kc-eviction-minute"
/>
}
isRequired
fieldId="kc-eviction-minute"
>
<Controller
name="config.evictionMinute"
defaultValue={["1"]}
control={form.control}
render={({ onChange, value }) => (
<Select
toggleId="kc-eviction-minute"
onToggle={() =>
setIsEvictionMinuteDropdownOpen(
!isEvictionMinuteDropdownOpen
)
}
isOpen={isEvictionMinuteDropdownOpen}
onSelect={(_, value) => {
onChange(value as string);
setIsEvictionMinuteDropdownOpen(false);
}}
selections={value}
variant={SelectVariant.single}
>
{minuteOptions}
</Select>
)}
></Controller>
</FormGroup>
</>
) : (
<></>
)}
{_.isEqual(cachePolicyType, ["MAX_LIFESPAN"]) ? (
<FormGroup
label={t("maxLifespan")}
labelIcon={
<HelpItem
helpText={helpText("maxLifespanHelp")}
forLabel={t("maxLifespan")}
forID="kc-max-lifespan"
/>
}
fieldId="kc-max-lifespan"
>
<TextInput
isRequired
type="text"
id="kc-max-lifespan"
name="config.maxLifespan[0]"
ref={form.register}
/>
</FormGroup>
) : (
<></>
)}
</FormAccess> </FormAccess>
</> </>
); );

View file

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

View file

@ -3,78 +3,31 @@ import {
Select, Select,
SelectOption, SelectOption,
SelectVariant, SelectVariant,
Text,
TextContent,
TextInput, TextInput,
Title,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { useForm, Controller } from "react-hook-form"; import { UseFormMethods, Controller } from "react-hook-form";
import { convertToFormValues } from "../../util";
import ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; 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"; import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
export type LdapSettingsGeneralProps = { export type LdapSettingsGeneralProps = {
form: UseFormMethods;
showSectionHeading?: boolean; showSectionHeading?: boolean;
showSectionDescription?: boolean; showSectionDescription?: boolean;
}; };
export const LdapSettingsGeneral = ({ export const LdapSettingsGeneral = ({
form,
showSectionHeading = false, showSectionHeading = false,
showSectionDescription = false, showSectionDescription = false,
}: LdapSettingsGeneralProps) => { }: LdapSettingsGeneralProps) => {
const { t } = useTranslation("user-federation"); const { t } = useTranslation("user-federation");
const helpText = useTranslation("user-federation-help").t; 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 [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 ( return (
<> <>
{showSectionHeading && ( {showSectionHeading && (
@ -84,7 +37,6 @@ export const LdapSettingsGeneral = ({
showDescription={showSectionDescription} showDescription={showSectionDescription}
/> />
)} )}
<FormAccess role="manage-realm" isHorizontal> <FormAccess role="manage-realm" isHorizontal>
<FormGroup <FormGroup
label={t("consoleDisplayName")} label={t("consoleDisplayName")}
@ -98,13 +50,50 @@ export const LdapSettingsGeneral = ({
fieldId="kc-console-display-name" fieldId="kc-console-display-name"
isRequired 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 <TextInput
isRequired isRequired
type="text" type="text"
id="kc-console-display-name" id="kc-console-display-name"
name="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>
<FormGroup <FormGroup
label={t("vendor")} label={t("vendor")}
@ -119,9 +108,9 @@ export const LdapSettingsGeneral = ({
isRequired isRequired
> >
<Controller <Controller
name="config.vendor" name="config.vendor[0]"
defaultValue="" defaultValue=""
control={control} control={form.control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="kc-vendor" toggleId="kc-vendor"
@ -135,16 +124,21 @@ export const LdapSettingsGeneral = ({
selections={value} selections={value}
variant={SelectVariant.single} variant={SelectVariant.single}
> >
<SelectOption <SelectOption key={0} value="ad" isPlaceholder>
key={0} Active Directory
value={t("common:choose")} </SelectOption>
isPlaceholder <SelectOption key={1} value="rhds">
/> Red Hat Directory Server
<SelectOption key={1} value="Active Directory" /> </SelectOption>
<SelectOption key={2} value="Red Hat Directory Server" /> <SelectOption key={2} value="tivoli">
<SelectOption key={3} value="Tivoli" /> Tivoli
<SelectOption key={4} value="Novell eDirectory" /> </SelectOption>
<SelectOption key={5} value="Other" /> <SelectOption key={3} value="edirectory">
Novell eDirectory
</SelectOption>
<SelectOption key={4} value="other">
Other
</SelectOption>
</Select> </Select>
)} )}
></Controller> ></Controller>

View file

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

View file

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

View file

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

View file

@ -46,7 +46,9 @@
"importUsers": "Import users", "importUsers": "Import users",
"batchSize": "Batch size", "batchSize": "Batch size",
"periodicFullSync": "Periodic full sync", "periodicFullSync": "Periodic full sync",
"fullSyncPeriod": "Full sync period",
"periodicChangedUsersSync": "Periodic changed users sync", "periodicChangedUsersSync": "Periodic changed users sync",
"changedUsersSyncPeriod": "Changed users sync period",
"kerberosIntegration": "Kerberos integration", "kerberosIntegration": "Kerberos integration",
"allowKerberosAuthentication": "Allow Kerberos authentication", "allowKerberosAuthentication": "Allow Kerberos authentication",
@ -92,6 +94,13 @@
"validateRealm":"You must enter a realm", "validateRealm":"You must enter a realm",
"validateServerPrincipal":"You must enter a server principal", "validateServerPrincipal":"You must enter a server principal",
"validateKeyTab": "You must enter a key tab", "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", "id": "ID",
"mapperType": "Mapper type", "mapperType": "Mapper type",