added acr loa map field to realm settings (#2736)

* added acr loa map field to realm settings

fixes: #2668

* changed to use one form per tab, instead of shared

* fixed test
This commit is contained in:
Erik Jan de Wit 2022-06-15 14:57:39 +02:00 committed by GitHub
parent f775fd6329
commit afbbdaaf52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 620 additions and 617 deletions

View file

@ -237,7 +237,6 @@ describe("Realm settings events tab tests", () => {
sidebarPage.goToRealmSettings(); sidebarPage.goToRealmSettings();
cy.findByTestId("rs-localization-tab").click(); cy.findByTestId("rs-localization-tab").click();
cy.findByTestId("internationalization-disabled").click({ force: true }); cy.findByTestId("internationalization-disabled").click({ force: true });
cy.get(realmSettingsPage.supportedLocalesTypeahead) cy.get(realmSettingsPage.supportedLocalesTypeahead)
@ -245,10 +244,14 @@ describe("Realm settings events tab tests", () => {
.get(".pf-c-select__menu-item") .get(".pf-c-select__menu-item")
.contains("Dansk") .contains("Dansk")
.click(); .click();
cy.get("#kc-l-supported-locales").click(); cy.get("#kc-l-supported-locales").click();
cy.intercept("GET", `/admin/realms/${realmName}/localization/en*`).as(
"load"
);
cy.findByTestId("localization-tab-save").click(); cy.findByTestId("localization-tab-save").click();
cy.wait("@load");
addBundle(); addBundle();

View file

@ -99,25 +99,25 @@
"noKeysDescription": "You haven't created any active keys", "noKeysDescription": "You haven't created any active keys",
"certificate": "Certificate", "certificate": "Certificate",
"loginScreenCustomization": "Login screen customization", "loginScreenCustomization": "Login screen customization",
"userRegistration": "User registration", "registrationAllowed": "User registration",
"userRegistrationHelpText": "Enable/disable the registration page. A link for registration will show on login page too.", "userRegistrationHelpText": "Enable/disable the registration page. A link for registration will show on login page too.",
"forgotPassword": "Forgot password", "resetPasswordAllowed": "Forgot password",
"forgotPasswordHelpText": "Show a link on login page for user to click when they have forgotten their credentials.", "forgotPasswordHelpText": "Show a link on login page for user to click when they have forgotten their credentials.",
"rememberMe": "Remember me", "rememberMe": "Remember me",
"rememberMeHelpText": "Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.", "rememberMeHelpText": "Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.",
"emailSettings": "Email settings", "emailSettings": "Email settings",
"emailAsUsername": "Email as username", "registrationEmailAsUsername": "Email as username",
"emailAsUsernameHelpText": "Allow users to set email as username.", "emailAsUsernameHelpText": "Allow users to set email as username.",
"loginWithEmail": "Login with email", "loginWithEmailAllowed": "Login with email",
"loginWithEmailHelpText": "Allow users to log in with their email address.", "loginWithEmailHelpText": "Allow users to log in with their email address.",
"duplicateEmails": "Duplicate emails", "duplicateEmailsAllowed": "Duplicate emails",
"duplicateEmailsHelpText": "Allow multiple users to have the same email address. Changing this setting will also clear the user's cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses.", "duplicateEmailsHelpText": "Allow multiple users to have the same email address. Changing this setting will also clear the user's cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses.",
"provideEmailTitle": "Provide your email address", "provideEmailTitle": "Provide your email address",
"provideEmail": "To test connection, you should provide your email address first.", "provideEmail": "To test connection, you should provide your email address first.",
"verifyEmail": "Verify email", "verifyEmail": "Verify email",
"verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.", "verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.",
"userInfoSettings": "User info settings", "userInfoSettings": "User info settings",
"editUsername": "Edit username", "editUsernameAllowed": "Edit username",
"enableSwitchSuccess": "{{switch}} changed successfully", "enableSwitchSuccess": "{{switch}} changed successfully",
"enableSwitchError": "Could not enable / disable due to {{error}}", "enableSwitchError": "Could not enable / disable due to {{error}}",
"testConnection": "Test connection", "testConnection": "Test connection",

View file

@ -1,7 +1,7 @@
@import "@patternfly/patternfly/patternfly.min.css"; @import "@patternfly/patternfly/patternfly.min.css";
@import "@patternfly/patternfly/patternfly-addons.css"; @import "@patternfly/patternfly/patternfly-addons.css";
.keycloak__pageheader_brand { img.keycloak__pageheader_brand {
height: 35px; height: 35px;
} }

View file

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form"; import { Controller, FormProvider, useForm } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
Button, Button,
@ -16,7 +16,7 @@ import {
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { getBaseUrl } from "../util"; import { convertToFormValues, getBaseUrl } from "../util";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled"; import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
@ -24,25 +24,28 @@ import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem"; import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormattedLink } from "../components/external-link/FormattedLink"; import { FormattedLink } from "../components/external-link/FormattedLink";
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput"; import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
import { KeyValueInput } from "../components/key-value-form/KeyValueInput";
type RealmSettingsGeneralTabProps = { type RealmSettingsGeneralTabProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void; save: (realm: RealmRepresentation) => void;
reset: () => void;
}; };
export const RealmSettingsGeneralTab = ({ export const RealmSettingsGeneralTab = ({
realm,
save, save,
reset,
}: RealmSettingsGeneralTabProps) => { }: RealmSettingsGeneralTabProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const form = useForm<RealmRepresentation>({ shouldUnregister: false });
const { const {
register, register,
control, control,
handleSubmit, handleSubmit,
setValue,
formState: { isDirty }, formState: { isDirty },
} = useFormContext(); } = form;
const isFeatureEnabled = useIsFeatureEnabled(); const isFeatureEnabled = useIsFeatureEnabled();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -50,6 +53,19 @@ export const RealmSettingsGeneralTab = ({
const requireSslTypes = ["all", "external", "none"]; const requireSslTypes = ["all", "external", "none"];
const setupForm = () => {
convertToFormValues(realm, setValue);
if (realm.attributes?.["acr.loa.map"]) {
const result = Object.entries(
JSON.parse(realm.attributes["acr.loa.map"])
).flatMap(([key, value]) => ({ key, value }));
result.concat({ key: "", value: "" });
setValue("attributes.acr.loa.map", result);
}
};
useEffect(setupForm, []);
return ( return (
<PageSection variant="light"> <PageSection variant="light">
<FormAccess <FormAccess
@ -143,6 +159,20 @@ export const RealmSettingsGeneralTab = ({
)} )}
/> />
</FormGroup> </FormGroup>
<FormGroup
label={t("clients:acrToLoAMapping")}
fieldId="acrToLoAMapping"
labelIcon={
<HelpItem
helpText="clients-help:acrToLoAMapping"
fieldLabelId="clients:acrToLoAMapping"
/>
}
>
<FormProvider {...form}>
<KeyValueInput name="attributes.acr.loa.map" />
</FormProvider>
</FormGroup>
<FormGroup <FormGroup
hasNoPaddingTop hasNoPaddingTop
label={t("userManagedAccess")} label={t("userManagedAccess")}
@ -237,7 +267,7 @@ export const RealmSettingsGeneralTab = ({
<Button <Button
data-testid="general-tab-revert" data-testid="general-tab-revert"
variant="link" variant="link"
onClick={reset} onClick={setupForm}
> >
{t("common:revert")} {t("common:revert")}
</Button> </Button>

View file

@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { cloneDeep, isEqual, uniqWith } from "lodash-es"; import { cloneDeep, isEqual, uniqWith } from "lodash-es";
import { Controller, useForm, useFormContext, useWatch } from "react-hook-form"; import { Controller, useForm, useWatch } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
AlertVariant, AlertVariant,
@ -49,10 +49,10 @@ import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTa
import { SearchIcon } from "@patternfly/react-icons"; import { SearchIcon } from "@patternfly/react-icons";
import { useWhoAmI } from "../context/whoami/WhoAmI"; import { useWhoAmI } from "../context/whoami/WhoAmI";
import type { KeyValueType } from "../components/key-value-form/key-value-convert"; import type { KeyValueType } from "../components/key-value-form/key-value-convert";
import { convertToFormValues } from "../util";
type LocalizationTabProps = { type LocalizationTabProps = {
save: (realm: RealmRepresentation) => void; save: (realm: RealmRepresentation) => void;
reset: () => void;
refresh: () => void; refresh: () => void;
realm: RealmRepresentation; realm: RealmRepresentation;
}; };
@ -67,11 +67,7 @@ export type BundleForm = {
messageBundle: KeyValueType; messageBundle: KeyValueType;
}; };
export const LocalizationTab = ({ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
save,
reset,
realm,
}: LocalizationTabProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] = const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] =
@ -82,7 +78,9 @@ export const LocalizationTab = ({
const [filterDropdownOpen, setFilterDropdownOpen] = useState(false); const [filterDropdownOpen, setFilterDropdownOpen] = useState(false);
const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE); const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE);
const { getValues, control, handleSubmit, formState } = useFormContext(); const { setValue, getValues, control, handleSubmit, formState } = useForm({
shouldUnregister: false,
});
const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false); const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false);
const [messageBundles, setMessageBundles] = useState<[string, string][]>([]); const [messageBundles, setMessageBundles] = useState<[string, string][]>([]);
const [tableRows, setTableRows] = useState<IRow[]>([]); const [tableRows, setTableRows] = useState<IRow[]>([]);
@ -93,6 +91,15 @@ export const LocalizationTab = ({
const { realm: currentRealm } = useRealm(); const { realm: currentRealm } = useRealm();
const { whoAmI } = useWhoAmI(); const { whoAmI } = useWhoAmI();
const setupForm = () => {
convertToFormValues(realm, setValue);
if (realm.supportedLocales?.length === 0) {
setValue("supportedLocales", [DEFAULT_LOCALE]);
}
};
useEffect(setupForm, []);
const watchSupportedLocales = useWatch<string[]>({ const watchSupportedLocales = useWatch<string[]>({
control, control,
name: "supportedLocales", name: "supportedLocales",
@ -475,7 +482,7 @@ export const LocalizationTab = ({
> >
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button variant="link" onClick={reset}> <Button variant="link" onClick={setupForm}>
{t("common:revert")} {t("common:revert")}
</Button> </Button>
</ActionGroup> </ActionGroup>

View file

@ -5,7 +5,6 @@ import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem"; import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel"; import { FormPanel } from "../components/scroll-form/FormPanel";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { Controller, useForm } from "react-hook-form";
import { useAdminClient } from "../context/auth/AdminClient"; import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
@ -15,26 +14,31 @@ type RealmSettingsLoginTabProps = {
refresh: () => void; refresh: () => void;
}; };
type SwitchType = { [K in keyof RealmRepresentation]: boolean };
export const RealmSettingsLoginTab = ({ export const RealmSettingsLoginTab = ({
realm, realm,
refresh, refresh,
}: RealmSettingsLoginTabProps) => { }: RealmSettingsLoginTabProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const form = useForm<RealmRepresentation>({ mode: "onChange" });
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const updateSwitchValue = async (name: string) => { const updateSwitchValue = async (switches: SwitchType | SwitchType[]) => {
const switchValues = form.getValues(); const name = Array.isArray(switches)
? Object.keys(switches[0])[0]
: Object.keys(switches)[0];
try { try {
await adminClient.realms.update( await adminClient.realms.update(
{ {
realm: realmName, realm: realmName,
}, },
switchValues Array.isArray(switches)
? switches.reduce((realm, s) => Object.assign(realm, s), realm)
: Object.assign(realm, switches)
); );
addAlert(t("enableSwitchSuccess", { switch: t(name) })); addAlert(t("enableSwitchSuccess", { switch: t(name) }));
refresh(); refresh();
@ -51,67 +55,51 @@ export const RealmSettingsLoginTab = ({
> >
<FormAccess isHorizontal role="manage-realm"> <FormAccess isHorizontal role="manage-realm">
<FormGroup <FormGroup
label={t("userRegistration")} label={t("registrationAllowed")}
fieldId="kc-user-reg" fieldId="kc-user-reg"
labelIcon={ labelIcon={
<HelpItem <HelpItem
helpText={t("userRegistrationHelpText")} helpText={t("userRegistrationHelpText")}
fieldLabelId="realm-settings:userRegistration" fieldLabelId="realm-settings:registrationAllowed"
/> />
} }
hasNoPaddingTop hasNoPaddingTop
> >
<Controller
name="registrationAllowed"
defaultValue={realm.registrationAllowed}
control={form.control}
render={({ onChange, value }) => (
<Switch <Switch
id="kc-user-reg-switch" id="kc-user-reg-switch"
data-testid="user-reg-switch" data-testid="user-reg-switch"
value={value ? "on" : "off"} value={realm.registrationAllowed ? "on" : "off"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value} isChecked={realm.registrationAllowed}
onChange={(value) => { onChange={(value) => {
onChange(value); updateSwitchValue({ registrationAllowed: value });
updateSwitchValue("userRegistration");
}} }}
/> />
)}
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("forgotPassword")} label={t("resetPasswordAllowed")}
fieldId="kc-forgot-pw" fieldId="kc-forgot-pw"
labelIcon={ labelIcon={
<HelpItem <HelpItem
helpText="realm-settings:forgotPasswordHelpText" helpText="realm-settings:forgotPasswordHelpText"
fieldLabelId="realm-settings:forgotPassword" fieldLabelId="realm-settings:resetPasswordAllowed"
/> />
} }
hasNoPaddingTop hasNoPaddingTop
> >
<Controller
name="resetPasswordAllowed"
defaultValue={realm.resetPasswordAllowed}
control={form.control}
render={({ onChange, value }) => (
<Switch <Switch
id="kc-forgot-pw-switch" id="kc-forgot-pw-switch"
data-testid="forgot-pw-switch" data-testid="forgot-pw-switch"
name="resetPasswordAllowed" name="resetPasswordAllowed"
value={value ? "on" : "off"} value={realm.resetPasswordAllowed ? "on" : "off"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value} isChecked={realm.resetPasswordAllowed}
onChange={(value) => { onChange={(value) => {
onChange(value); updateSwitchValue({ resetPasswordAllowed: value });
updateSwitchValue("forgotPassword");
}} }}
/> />
)}
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("rememberMe")} label={t("rememberMe")}
@ -124,129 +112,110 @@ export const RealmSettingsLoginTab = ({
} }
hasNoPaddingTop hasNoPaddingTop
> >
<Controller
name="rememberMe"
defaultValue={realm.rememberMe}
control={form.control}
render={({ onChange, value }) => (
<Switch <Switch
id="kc-remember-me-switch" id="kc-remember-me-switch"
data-testid="remember-me-switch" data-testid="remember-me-switch"
value={value ? "on" : "off"} value={realm.rememberMe ? "on" : "off"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value} isChecked={realm.rememberMe}
onChange={(value) => { onChange={(value) => {
onChange(value); updateSwitchValue({ rememberMe: value });
updateSwitchValue("rememberMe");
}} }}
/> />
)}
/>
</FormGroup> </FormGroup>
</FormAccess> </FormAccess>
</FormPanel> </FormPanel>
<FormPanel className="kc-email-settings" title={t("emailSettings")}> <FormPanel className="kc-email-settings" title={t("emailSettings")}>
<FormAccess isHorizontal role="manage-realm"> <FormAccess isHorizontal role="manage-realm">
<FormGroup <FormGroup
label={t("emailAsUsername")} label={t("registrationEmailAsUsername")}
fieldId="kc-email-as-username" fieldId="kc-email-as-username"
labelIcon={ labelIcon={
<HelpItem <HelpItem
helpText="realm-settings:emailAsUsernameHelpText" helpText="realm-settings:emailAsUsernameHelpText"
fieldLabelId="realm-settings:emailAsUsername" fieldLabelId="realm-settings:registrationEmailAsUsername"
/> />
} }
hasNoPaddingTop hasNoPaddingTop
> >
<Controller
name="registrationEmailAsUsername"
defaultValue={realm.registrationEmailAsUsername}
control={form.control}
render={({ onChange, value }) => (
<Switch <Switch
id="kc-email-as-username-switch" id="kc-email-as-username-switch"
data-testid="email-as-username-switch" data-testid="email-as-username-switch"
value={value ? "on" : "off"} value={realm.registrationEmailAsUsername ? "on" : "off"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value} isChecked={realm.registrationEmailAsUsername}
onChange={(value) => { onChange={(value) => {
onChange(value); updateSwitchValue([
updateSwitchValue("emailAsUsername"); {
registrationEmailAsUsername: value,
},
{
duplicateEmailsAllowed: false,
},
]);
}} }}
/> />
)}
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("loginWithEmail")} label={t("loginWithEmailAllowed")}
fieldId="kc-login-with-email" fieldId="kc-login-with-email"
labelIcon={ labelIcon={
<HelpItem <HelpItem
helpText="realm-settings:loginWithEmailHelpText" helpText="realm-settings:loginWithEmailHelpText"
fieldLabelId="realm-settings:loginWithEmail" fieldLabelId="realm-settings:loginWithEmailAllowed"
/> />
} }
hasNoPaddingTop hasNoPaddingTop
> >
<Controller
name="loginWithEmailAllowed"
defaultValue={realm.loginWithEmailAllowed}
control={form.control}
render={({ onChange, value }) => (
<Switch <Switch
id="kc-login-with-email-switch" id="kc-login-with-email-switch"
data-testid="login-with-email-switch" data-testid="login-with-email-switch"
value={value ? "on" : "off"} value={realm.loginWithEmailAllowed ? "on" : "off"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value} isChecked={realm.loginWithEmailAllowed}
onChange={(value) => { onChange={(value) => {
onChange(value); updateSwitchValue([
updateSwitchValue("loginWithEmail"); {
loginWithEmailAllowed: value,
},
{ duplicateEmailsAllowed: false },
]);
}} }}
/> />
)}
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("duplicateEmails")} label={t("duplicateEmailsAllowed")}
fieldId="kc-duplicate-emails" fieldId="kc-duplicate-emails"
labelIcon={ labelIcon={
<HelpItem <HelpItem
helpText="realm-settings:duplicateEmailsHelpText" helpText="realm-settings:duplicateEmailsHelpText"
fieldLabelId="realm-settings:duplicateEmails" fieldLabelId="realm-settings:duplicateEmailsAllowed"
/> />
} }
hasNoPaddingTop hasNoPaddingTop
> >
<Controller
name="duplicateEmailsAllowed"
defaultValue={realm.duplicateEmailsAllowed}
control={form.control}
render={({ onChange }) => (
<Switch <Switch
id="kc-duplicate-emails-switch" id="kc-duplicate-emails-switch"
data-testid="duplicate-emails-switch" data-testid="duplicate-emails-switch"
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={ isChecked={
form.getValues().duplicateEmailsAllowed && realm.duplicateEmailsAllowed ||
!form.getValues().loginWithEmailAllowed && realm.loginWithEmailAllowed ||
!form.getValues().registrationEmailAsUsername realm.registrationEmailAsUsername
} }
onChange={(value) => { onChange={(value) => {
onChange(value); updateSwitchValue({
updateSwitchValue("duplicateEmails"); duplicateEmailsAllowed: value,
});
}} }}
isDisabled={ isDisabled={
form.getValues().loginWithEmailAllowed || realm.loginWithEmailAllowed || realm.registrationEmailAsUsername
form.getValues().registrationEmailAsUsername
} }
/> />
)}
/>
</FormGroup> </FormGroup>
<FormGroup <FormGroup
label={t("verifyEmail")} label={t("verifyEmail")}
@ -259,26 +228,18 @@ export const RealmSettingsLoginTab = ({
} }
hasNoPaddingTop hasNoPaddingTop
> >
<Controller
name="verifyEmail"
defaultValue={realm.verifyEmail}
control={form.control}
render={({ onChange, value }) => (
<Switch <Switch
id="kc-verify-email-switch" id="kc-verify-email-switch"
data-testid="verify-email-switch" data-testid="verify-email-switch"
name="verifyEmail" name="verifyEmail"
value={value ? "on" : "off"} value={realm.verifyEmail ? "on" : "off"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value} isChecked={realm.verifyEmail}
onChange={(value) => { onChange={(value) => {
onChange(value); updateSwitchValue({ verifyEmail: value });
updateSwitchValue("verifyEmail");
}} }}
/> />
)}
/>
</FormGroup> </FormGroup>
</FormAccess> </FormAccess>
</FormPanel> </FormPanel>
@ -288,35 +249,27 @@ export const RealmSettingsLoginTab = ({
> >
<FormAccess isHorizontal role="manage-realm"> <FormAccess isHorizontal role="manage-realm">
<FormGroup <FormGroup
label={t("editUsername")} label={t("editUsernameAllowed")}
fieldId="kc-edit-username" fieldId="kc-edit-username"
labelIcon={ labelIcon={
<HelpItem <HelpItem
helpText="realm-settings-help:editUsername" helpText="realm-settings-help:editUsername"
fieldLabelId="realm-settings:editUsername" fieldLabelId="realm-settings:editUsernameAllowed"
/> />
} }
hasNoPaddingTop hasNoPaddingTop
> >
<Controller
name="editUsernameAllowed"
defaultValue={realm.editUsernameAllowed}
control={form.control}
render={({ onChange, value }) => (
<Switch <Switch
id="kc-edit-username-switch" id="kc-edit-username-switch"
data-testid="edit-username-switch" data-testid="edit-username-switch"
value={value ? "on" : "off"} value={realm.editUsernameAllowed ? "on" : "off"}
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value} isChecked={realm.editUsernameAllowed}
onChange={(value) => { onChange={(value) => {
onChange(value); updateSwitchValue({ editUsernameAllowed: value });
updateSwitchValue("userInfoSettings");
}} }}
/> />
)}
/>
</FormGroup> </FormGroup>
</FormAccess> </FormAccess>
</FormPanel> </FormPanel>

View file

@ -1,19 +1,21 @@
import React, { useState } from "react"; import React, { useState } from "react";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import type { RealmSettingsParams } from "./routes/RealmSettings";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { RealmSettingsTabs } from "./RealmSettingsTabs"; import { RealmSettingsTabs } from "./RealmSettingsTabs";
import { useParams } from "react-router-dom";
export default function RealmSettingsSection() { export default function RealmSettingsSection() {
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { realm: realmName } = useRealm(); const { realm: realmName } = useParams<RealmSettingsParams>();
const [realm, setRealm] = useState<RealmRepresentation>(); const [realm, setRealm] = useState<RealmRepresentation>();
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => { const refresh = () => {
setKey(key + 1); setKey(key + 1);
setRealm(undefined);
}; };
useFetch(() => adminClient.realms.findOne({ realm: realmName }), setRealm, [ useFetch(() => adminClient.realms.findOne({ realm: realmName }), setRealm, [

View file

@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
AlertVariant, AlertVariant,
@ -34,7 +34,7 @@ import { RealmSettingsEmailTab } from "./EmailTab";
import { EventsTab } from "./event-config/EventsTab"; import { EventsTab } from "./event-config/EventsTab";
import { RealmSettingsGeneralTab } from "./GeneralTab"; import { RealmSettingsGeneralTab } from "./GeneralTab";
import { RealmSettingsLoginTab } from "./LoginTab"; import { RealmSettingsLoginTab } from "./LoginTab";
import { SecurityDefences } from "./security-defences/SecurityDefences"; import { SecurityDefenses } from "./security-defences/SecurityDefenses";
import { RealmSettingsSessionsTab } from "./SessionsTab"; import { RealmSettingsSessionsTab } from "./SessionsTab";
import { RealmSettingsThemesTab } from "./ThemesTab"; import { RealmSettingsThemesTab } from "./ThemesTab";
import { RealmSettingsTokensTab } from "./TokensTab"; import { RealmSettingsTokensTab } from "./TokensTab";
@ -53,7 +53,7 @@ import { UserProfileTab } from "./user-profile/UserProfileTab";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled"; import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import { ClientPoliciesTab, toClientPolicies } from "./routes/ClientPolicies"; import { ClientPoliciesTab, toClientPolicies } from "./routes/ClientPolicies";
import { KeysTab } from "./keys/KeysTab"; import { KeysTab } from "./keys/KeysTab";
import { DEFAULT_LOCALE } from "../i18n"; import type { KeyValueType } from "../components/key-value-form/key-value-convert";
type RealmSettingsHeaderProps = { type RealmSettingsHeaderProps = {
onChange: (value: boolean) => void; onChange: (value: boolean) => void;
@ -176,55 +176,59 @@ export const RealmSettingsTabs = ({
const history = useHistory(); const history = useHistory();
const isFeatureEnabled = useIsFeatureEnabled(); const isFeatureEnabled = useIsFeatureEnabled();
const form = useForm({ mode: "onChange", shouldUnregister: false }); const { control, setValue, getValues } = useForm({
const { control, getValues, setValue, reset: resetForm } = form; mode: "onChange",
shouldUnregister: false,
});
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refreshHeader = () => { const refreshHeader = () => {
setKey(new Date().getTime()); setKey(key + 1);
}; };
const setupForm = (r: RealmRepresentation = realm) => { const setupForm = (r: RealmRepresentation = realm) => {
convertToFormValues(r, setValue); convertToFormValues(r, setValue);
if (r.supportedLocales?.length === 0) {
setValue("supportedLocales", [DEFAULT_LOCALE]);
}
resetForm(getValues());
}; };
useEffect(() => { useEffect(setupForm, []);
setupForm();
}, []); const save = async (r: RealmRepresentation) => {
r = convertFormValuesToObject(r);
if (
r.attributes?.["acr.loa.map"] &&
typeof r.attributes["acr.loa.map"] !== "string"
) {
const map = JSON.stringify(
Object.fromEntries(
(r.attributes["acr.loa.map"] as KeyValueType[])
.filter(({ key }) => key !== "")
.map(({ key, value }) => [key, value])
)
);
r.attributes["acr.loa.map"] = map !== "{}" ? map : "[]";
}
const save = async (realm: RealmRepresentation) => {
try { try {
realm = convertFormValuesToObject(realm);
await adminClient.realms.update( await adminClient.realms.update(
{ realm: realmName }, { realm: realmName },
{ {
...realm, ...realm,
id: realmName, ...r,
id: r.realm,
} }
); );
setupForm(realm);
const isRealmRenamed = realmName !== realm.realm;
if (isRealmRenamed) {
await refreshRealms();
history.push(toRealmSettings({ realm: realm.realm!, tab: "general" }));
}
addAlert(t("saveSuccess"), AlertVariant.success); addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) { } catch (error) {
addError("realm-settings:saveError", error); addError("realm-settings:saveError", error);
} }
};
const userProfileEnabled = useWatch({ const isRealmRenamed = realmName !== (r.realm || realm.realm);
control, if (isRealmRenamed) {
name: "attributes.userProfileEnabled", await refreshRealms();
defaultValue: "false", history.push(toRealmSettings({ realm: r.realm!, tab: "general" }));
}); }
refresh();
};
const route = (tab: RealmSettingsTab | undefined = "general") => const route = (tab: RealmSettingsTab | undefined = "general") =>
routableTab({ routableTab({
@ -258,7 +262,6 @@ export const RealmSettingsTabs = ({
)} )}
/> />
<PageSection variant="light" className="pf-u-p-0"> <PageSection variant="light" className="pf-u-p-0">
<FormProvider {...form}>
<RoutableTabs <RoutableTabs
isBox isBox
mountOnEnter mountOnEnter
@ -272,10 +275,7 @@ export const RealmSettingsTabs = ({
data-testid="rs-general-tab" data-testid="rs-general-tab"
{...route()} {...route()}
> >
<RealmSettingsGeneralTab <RealmSettingsGeneralTab realm={realm} save={save} />
save={save}
reset={() => resetForm(realm)}
/>
</Tab> </Tab>
<Tab <Tab
title={<TabTitleText>{t("login")}</TabTitleText>} title={<TabTitleText>{t("login")}</TabTitleText>}
@ -296,10 +296,7 @@ export const RealmSettingsTabs = ({
data-testid="rs-themes-tab" data-testid="rs-themes-tab"
{...route("themes")} {...route("themes")}
> >
<RealmSettingsThemesTab <RealmSettingsThemesTab realm={realm} save={save} />
save={save}
reset={() => resetForm(realm)}
/>
</Tab> </Tab>
<Tab <Tab
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>} title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
@ -324,7 +321,6 @@ export const RealmSettingsTabs = ({
key={key} key={key}
refresh={refresh} refresh={refresh}
save={save} save={save}
reset={() => resetForm(realm)}
realm={realm} realm={realm}
/> />
</Tab> </Tab>
@ -333,12 +329,10 @@ export const RealmSettingsTabs = ({
data-testid="rs-security-defenses-tab" data-testid="rs-security-defenses-tab"
{...route("securityDefences")} {...route("securityDefences")}
> >
<SecurityDefences save={save} reset={() => resetForm(realm)} /> <SecurityDefenses realm={realm} save={save} />
</Tab> </Tab>
<Tab <Tab
title={ title={<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>}
<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>
}
data-testid="rs-sessions-tab" data-testid="rs-sessions-tab"
{...route("sessions")} {...route("sessions")}
> >
@ -349,17 +343,11 @@ export const RealmSettingsTabs = ({
data-testid="rs-tokens-tab" data-testid="rs-tokens-tab"
{...route("tokens")} {...route("tokens")}
> >
<RealmSettingsTokensTab <RealmSettingsTokensTab save={save} realm={realm} />
save={save}
realm={realm}
reset={() => resetForm(realm)}
/>
</Tab> </Tab>
<Tab <Tab
title={ title={
<TabTitleText> <TabTitleText>{t("realm-settings:clientPolicies")}</TabTitleText>
{t("realm-settings:clientPolicies")}
</TabTitleText>
} }
data-testid="rs-clientPolicies-tab" data-testid="rs-clientPolicies-tab"
{...route("clientPolicies")} {...route("clientPolicies")}
@ -411,12 +399,10 @@ export const RealmSettingsTabs = ({
</RoutableTabs> </RoutableTabs>
</Tab> </Tab>
{isFeatureEnabled(Feature.DeclarativeUserProfile) && {isFeatureEnabled(Feature.DeclarativeUserProfile) &&
userProfileEnabled === "true" && ( realm.attributes?.userProfileEnabled === "true" && (
<Tab <Tab
title={ title={
<TabTitleText> <TabTitleText>{t("realm-settings:userProfile")}</TabTitleText>
{t("realm-settings:userProfile")}
</TabTitleText>
} }
data-testid="rs-user-profile-tab" data-testid="rs-user-profile-tab"
{...route("userProfile")} {...route("userProfile")}
@ -432,7 +418,6 @@ export const RealmSettingsTabs = ({
<UserRegistration /> <UserRegistration />
</Tab> </Tab>
</RoutableTabs> </RoutableTabs>
</FormProvider>
</PageSection> </PageSection>
</> </>
); );

View file

@ -1,6 +1,6 @@
import React from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, useForm, useWatch } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
Button, Button,
@ -14,6 +14,7 @@ import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem"; import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel"; import { FormPanel } from "../components/scroll-form/FormPanel";
import { TimeSelector } from "../components/time-selector/TimeSelector"; import { TimeSelector } from "../components/time-selector/TimeSelector";
import { convertToFormValues } from "../util";
import "./realm-settings-section.css"; import "./realm-settings-section.css";
@ -28,24 +29,22 @@ export const RealmSettingsSessionsTab = ({
}: RealmSettingsSessionsTabProps) => { }: RealmSettingsSessionsTabProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const { const { setValue, control, handleSubmit, formState } =
control, useForm<RealmRepresentation>({
handleSubmit, shouldUnregister: false,
reset: resetForm, });
formState,
} = useFormContext<RealmRepresentation>();
const offlineSessionMaxEnabled = useWatch({ const offlineSessionMaxEnabled = useWatch({
control, control,
name: "offlineSessionMaxLifespanEnabled", name: "offlineSessionMaxLifespanEnabled",
}); });
const reset = () => { const setupForm = () => {
if (realm) { convertToFormValues(realm, setValue);
resetForm(realm);
}
}; };
useEffect(setupForm, []);
return ( return (
<PageSection variant="light"> <PageSection variant="light">
<FormPanel <FormPanel
@ -399,7 +398,7 @@ export const RealmSettingsSessionsTab = ({
> >
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button variant="link" onClick={reset}> <Button variant="link" onClick={setupForm}>
{t("common:revert")} {t("common:revert")}
</Button> </Button>
</ActionGroup> </ActionGroup>

View file

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFormContext } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
Button, Button,
@ -15,15 +15,16 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
import { FormAccess } from "../components/form-access/FormAccess"; import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem"; import { HelpItem } from "../components/help-enabler/HelpItem";
import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { convertToFormValues } from "../util";
type RealmSettingsThemesTabProps = { type RealmSettingsThemesTabProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void; save: (realm: RealmRepresentation) => void;
reset: () => void;
}; };
export const RealmSettingsThemesTab = ({ export const RealmSettingsThemesTab = ({
realm,
save, save,
reset,
}: RealmSettingsThemesTabProps) => { }: RealmSettingsThemesTabProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
@ -32,9 +33,14 @@ export const RealmSettingsThemesTab = ({
const [adminConsoleThemeOpen, setAdminConsoleThemeOpen] = useState(false); const [adminConsoleThemeOpen, setAdminConsoleThemeOpen] = useState(false);
const [emailThemeOpen, setEmailThemeOpen] = useState(false); const [emailThemeOpen, setEmailThemeOpen] = useState(false);
const { control, handleSubmit } = useFormContext(); const { control, handleSubmit, setValue } = useForm<RealmRepresentation>();
const themeTypes = useServerInfo().themes!; const themeTypes = useServerInfo().themes!;
const setupForm = () => {
convertToFormValues(realm, setValue);
};
useEffect(setupForm, []);
return ( return (
<PageSection variant="light"> <PageSection variant="light">
<FormAccess <FormAccess
@ -217,7 +223,7 @@ export const RealmSettingsThemesTab = ({
<Button variant="primary" type="submit" data-testid="themes-tab-save"> <Button variant="primary" type="submit" data-testid="themes-tab-save">
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button variant="link" onClick={reset}> <Button variant="link" onClick={setupForm}>
{t("common:revert")} {t("common:revert")}
</Button> </Button>
</ActionGroup> </ActionGroup>

View file

@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, useForm, useWatch } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
Button, Button,
@ -27,6 +27,7 @@ import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import "./realm-settings-section.css"; import "./realm-settings-section.css";
import { useWhoAmI } from "../context/whoami/WhoAmI"; import { useWhoAmI } from "../context/whoami/WhoAmI";
import { convertToFormValues } from "../util";
type RealmSettingsSessionsTabProps = { type RealmSettingsSessionsTabProps = {
realm: RealmRepresentation; realm: RealmRepresentation;
@ -60,8 +61,8 @@ export const RealmSettingsTokensTab = ({
javaKeystoreAlgOptions! javaKeystoreAlgOptions!
); );
const form = useFormContext<RealmRepresentation>(); const form = useForm<RealmRepresentation>({ shouldUnregister: false });
const { control } = form; const { setValue, control } = form;
const offlineSessionMaxEnabled = useWatch({ const offlineSessionMaxEnabled = useWatch({
control, control,
@ -81,6 +82,10 @@ export const RealmSettingsTokensTab = ({
defaultValue: false, defaultValue: false,
}); });
useEffect(() => {
convertToFormValues(realm, setValue);
}, []);
return ( return (
<PageSection variant="light"> <PageSection variant="light">
<FormPanel <FormPanel

View file

@ -1,6 +1,6 @@
import React from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
Button, Button,
@ -13,22 +13,25 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpItem } from "../../components/help-enabler/HelpItem"; import { HelpItem } from "../../components/help-enabler/HelpItem";
import { Time } from "./Time"; import { Time } from "./Time";
import { convertToFormValues } from "../../util";
type BruteForceDetectionProps = { type BruteForceDetectionProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void; save: (realm: RealmRepresentation) => void;
reset: () => void;
}; };
export const BruteForceDetection = ({ export const BruteForceDetection = ({
realm,
save, save,
reset,
}: BruteForceDetectionProps) => { }: BruteForceDetectionProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const form = useForm({ shouldUnregister: false });
const { const {
setValue,
handleSubmit, handleSubmit,
control, control,
formState: { isDirty }, formState: { isDirty },
} = useFormContext(); } = form;
const enable = useWatch({ const enable = useWatch({
control, control,
@ -40,8 +43,16 @@ export const BruteForceDetection = ({
name: "permanentLockout", name: "permanentLockout",
}); });
const setupForm = () => convertToFormValues(realm, setValue);
useEffect(setupForm, []);
return ( return (
<FormAccess role="manage-realm" isHorizontal onSubmit={handleSubmit(save)}> <FormProvider {...form}>
<FormAccess
role="manage-realm"
isHorizontal
onSubmit={handleSubmit(save)}
>
<FormGroup <FormGroup
label={t("common:enabled")} label={t("common:enabled")}
fieldId="bruteForceProtected" fieldId="bruteForceProtected"
@ -62,6 +73,8 @@ export const BruteForceDetection = ({
)} )}
/> />
</FormGroup> </FormGroup>
{enable && (
<>
<FormGroup <FormGroup
label={t("failureFactor")} label={t("failureFactor")}
labelIcon={ labelIcon={
@ -71,7 +84,6 @@ export const BruteForceDetection = ({
/> />
} }
fieldId="failureFactor" fieldId="failureFactor"
style={enable ? {} : { display: "none" }}
> >
<Controller <Controller
name="failureFactor" name="failureFactor"
@ -96,7 +108,6 @@ export const BruteForceDetection = ({
label={t("permanentLockout")} label={t("permanentLockout")}
fieldId="permanentLockout" fieldId="permanentLockout"
hasNoPaddingTop hasNoPaddingTop
style={enable ? {} : { display: "none" }}
> >
<Controller <Controller
name="permanentLockout" name="permanentLockout"
@ -114,18 +125,13 @@ export const BruteForceDetection = ({
/> />
</FormGroup> </FormGroup>
<Time {!permanentLockout && (
name="waitIncrementSeconds" <>
style={enable && !permanentLockout ? {} : { display: "none" }} <Time name="waitIncrementSeconds" />
/> <Time name="maxFailureWaitSeconds" />
<Time <Time name="maxDeltaTimeSeconds" />
name="maxFailureWaitSeconds" </>
style={enable && !permanentLockout ? {} : { display: "none" }} )}
/>
<Time
name="maxDeltaTimeSeconds"
style={enable && !permanentLockout ? {} : { display: "none" }}
/>
<FormGroup <FormGroup
label={t("quickLoginCheckMilliSeconds")} label={t("quickLoginCheckMilliSeconds")}
@ -136,7 +142,6 @@ export const BruteForceDetection = ({
/> />
} }
fieldId="quickLoginCheckMilliSeconds" fieldId="quickLoginCheckMilliSeconds"
style={enable ? {} : { display: "none" }}
> >
<Controller <Controller
name="quickLoginCheckMilliSeconds" name="quickLoginCheckMilliSeconds"
@ -157,10 +162,9 @@ export const BruteForceDetection = ({
/> />
</FormGroup> </FormGroup>
<Time <Time name="minimumQuickLoginWaitSeconds" />
name="minimumQuickLoginWaitSeconds" </>
style={enable ? {} : { display: "none" }} )}
/>
<ActionGroup> <ActionGroup>
<Button <Button
@ -171,10 +175,11 @@ export const BruteForceDetection = ({
> >
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button variant="link" onClick={reset}> <Button variant="link" onClick={setupForm}>
{t("common:revert")} {t("common:revert")}
</Button> </Button>
</ActionGroup> </ActionGroup>
</FormAccess> </FormAccess>
</FormProvider>
); );
}; };

View file

@ -1,27 +1,34 @@
import React from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useFormContext } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
import { ActionGroup, Button } from "@patternfly/react-core"; import { ActionGroup, Button } from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
import { HelpLinkTextInput } from "./HelpLinkTextInput"; import { HelpLinkTextInput } from "./HelpLinkTextInput";
import { convertToFormValues } from "../../util";
import "./security-defences.css"; import "./security-defences.css";
type HeadersFormProps = { type HeadersFormProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void; save: (realm: RealmRepresentation) => void;
reset: () => void;
}; };
export const HeadersForm = ({ save, reset }: HeadersFormProps) => { export const HeadersForm = ({ realm, save }: HeadersFormProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const form = useForm();
const { const {
setValue,
formState: { isDirty }, formState: { isDirty },
handleSubmit, handleSubmit,
} = useFormContext(); } = form;
const setupForm = () => convertToFormValues(realm, setValue);
useEffect(setupForm, []);
return ( return (
<FormProvider {...form}>
<FormAccess <FormAccess
isHorizontal isHorizontal
role="manage-realm" role="manage-realm"
@ -66,10 +73,11 @@ export const HeadersForm = ({ save, reset }: HeadersFormProps) => {
> >
{t("common:save")} {t("common:save")}
</Button> </Button>
<Button variant="link" onClick={reset}> <Button variant="link" onClick={setupForm}>
{t("common:revert")} {t("common:revert")}
</Button> </Button>
</ActionGroup> </ActionGroup>
</FormAccess> </FormAccess>
</FormProvider>
); );
}; };

View file

@ -6,12 +6,12 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
import { HeadersForm } from "./HeadersForm"; import { HeadersForm } from "./HeadersForm";
import { BruteForceDetection } from "./BruteForceDetection"; import { BruteForceDetection } from "./BruteForceDetection";
type SecurityDefencesProps = { type SecurityDefensesProps = {
realm: RealmRepresentation;
save: (realm: RealmRepresentation) => void; save: (realm: RealmRepresentation) => void;
reset: () => void;
}; };
export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => { export const SecurityDefenses = ({ realm, save }: SecurityDefensesProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const [activeTab, setActiveTab] = useState(10); const [activeTab, setActiveTab] = useState(10);
return ( return (
@ -25,7 +25,7 @@ export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => {
title={<TabTitleText>{t("headers")}</TabTitleText>} title={<TabTitleText>{t("headers")}</TabTitleText>}
> >
<PageSection variant="light"> <PageSection variant="light">
<HeadersForm save={save} reset={reset} /> <HeadersForm realm={realm} save={save} />
</PageSection> </PageSection>
</Tab> </Tab>
<Tab <Tab
@ -34,7 +34,7 @@ export const SecurityDefences = ({ save, reset }: SecurityDefencesProps) => {
title={<TabTitleText>{t("bruteForceDetection")}</TabTitleText>} title={<TabTitleText>{t("bruteForceDetection")}</TabTitleText>}
> >
<PageSection variant="light"> <PageSection variant="light">
<BruteForceDetection save={save} reset={reset} /> <BruteForceDetection realm={realm} save={save} />
</PageSection> </PageSection>
</Tab> </Tab>
</Tabs> </Tabs>