Convert realms settings general tab to new form controls (#28464)

Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Jon Koops 2024-04-05 12:55:25 +02:00 committed by GitHub
parent a0cf09e899
commit 3fda2c0444
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 212 additions and 271 deletions

View file

@ -1,3 +1,4 @@
import Select from "../../../../forms/Select";
import CommonPage from "../../../CommonPage";
import ListingPage from "../../ListingPage";
import RealmSettingsEventsTab from "./tabs/RealmSettingsEventsTab";
@ -66,7 +67,7 @@ export default class RealmSettingsPage extends CommonPage {
"#kc-l-supported-locales-select-multi-typeahead-typeahead";
supportedLocalesToggle = "#kc-l-supported-locales";
emailSaveBtn = "email-tab-save";
managedAccessSwitch = "user-managed-access-switch";
managedAccessSwitch = "userManagedAccessAllowed";
userRegSwitch = "user-reg-switch";
forgotPwdSwitch = "forgot-pw-switch";
rememberMeSwitch = "remember-me-switch";
@ -235,10 +236,6 @@ export default class RealmSettingsPage extends CommonPage {
#selectScopeButton = "addValue";
#deleteClientRolesConditionBtn = "delete-client-roles-condition";
#deleteClientScopesConditionBtn = "delete-client-scopes-condition";
#realmDisplayName = "#kc-display-name";
#frontEndURL = "#kc-frontend-url";
#requireSSL = "#kc-require-ssl";
#unmanagedAttributes = "#kc-user-profile-unmanaged-attribute-policy";
#fromDisplayName = "smtpServer.fromDisplayName";
#replyToEmail = "smtpServer.replyTo";
#port = "smtpServer.port";
@ -262,6 +259,22 @@ export default class RealmSettingsPage extends CommonPage {
this.#realmName = realmName;
}
#getRealmDisplayName() {
return cy.findByTestId("displayName");
}
#getFrontEndURL() {
return cy.findByTestId("attributes.frontendUrl");
}
#getSSLRequired() {
return cy.get("#sslRequired");
}
#getUnmanagedAttributes() {
return cy.get("#unmanagedAttributePolicy");
}
goToEventsTab() {
this.tabUtils().clickTab(RealmSettingsTab.Events);
return this.#realmSettingsEventsTab;
@ -311,29 +324,28 @@ export default class RealmSettingsPage extends CommonPage {
}
getDisplayName(name: string) {
cy.get(this.#realmDisplayName).should("have.value", name);
this.#getRealmDisplayName().should("have.value", name);
return this;
}
getFrontendURL(url: string) {
cy.get(this.#frontEndURL).should("have.value", url);
this.#getFrontEndURL().should("have.value", url);
return this;
}
getRequireSSL(option: string) {
cy.get(this.#requireSSL).contains(option);
Select.assertSelectedItem(this.#getSSLRequired(), option);
return this;
}
getUnmanagedAttributes(option: string) {
cy.get(this.#unmanagedAttributes).contains(option);
Select.assertSelectedItem(this.#getUnmanagedAttributes(), option);
return this;
}
fillDisplayName(displayName: string) {
cy.get(this.#realmDisplayName).clear().type(displayName);
this.#getRealmDisplayName().clear();
this.#getRealmDisplayName().type(displayName);
}
clearRealmId() {
@ -355,27 +367,20 @@ export default class RealmSettingsPage extends CommonPage {
}
fillFrontendURL(url: string) {
cy.get(this.#frontEndURL).clear().type(url);
this.clearFrontendURL();
this.#getFrontEndURL().type(url);
}
clearFrontendURL() {
cy.get(this.#frontEndURL).clear();
this.#getFrontEndURL().clear();
}
fillRequireSSL(option: string) {
cy.get(this.#requireSSL)
.click()
.get(".pf-c-select__menu-item")
.contains(option)
.click();
Select.selectItem(this.#getSSLRequired(), option);
}
fillUnmanagedAttributes(option: string) {
cy.get(this.#unmanagedAttributes)
.click()
.get(".pf-c-select__menu-item")
.contains(option)
.click();
Select.selectItem(this.#getUnmanagedAttributes(), option);
}
setDefaultLocale(locale: string) {

View file

@ -1,37 +1,34 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import {
UnmanagedAttributePolicy,
UserProfileConfig,
} from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
import {
ActionGroup,
Button,
ClipboardCopy,
FormGroup,
PageSection,
Select,
SelectOption,
SelectVariant,
Stack,
StackItem,
Switch,
} from "@patternfly/react-core";
import { useEffect, useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem } from "ui-shared";
import { HelpItem, SelectControl, TextControl } from "ui-shared";
import { adminClient } from "../admin-client";
import { DefaultSwitchControl } from "../components/SwitchControl";
import { FormattedLink } from "../components/external-link/FormattedLink";
import { FormAccess } from "../components/form/FormAccess";
import { KeyValueInput } from "../components/key-value-form/KeyValueInput";
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { useRealm } from "../context/realm-context/RealmContext";
import {
addTrailingSlash,
convertAttributeNameToForm,
convertToFormValues,
} from "../util";
import {
UnmanagedAttributePolicy,
UserProfileConfig,
} from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
import { useFetch } from "../utils/useFetch";
import { UIRealmRepresentation } from "./RealmSettingsTabs";
@ -40,36 +37,66 @@ type RealmSettingsGeneralTabProps = {
save: (realm: UIRealmRepresentation) => void;
};
type FormFields = Omit<RealmRepresentation, "groups">;
export const RealmSettingsGeneralTab = ({
realm,
save,
}: RealmSettingsGeneralTabProps) => {
const { realm: realmName } = useRealm();
const [userProfileConfig, setUserProfileConfig] =
useState<UserProfileConfig>();
useFetch(
() => adminClient.users.getProfile({ realm: realmName }),
(config) => setUserProfileConfig(config),
[],
);
if (!userProfileConfig) {
return <KeycloakSpinner />;
}
return (
<RealmSettingsGeneralTabForm
realm={realm}
save={save}
userProfileConfig={userProfileConfig}
/>
);
};
type RealmSettingsGeneralTabFormProps = {
realm: UIRealmRepresentation;
save: (realm: UIRealmRepresentation) => void;
userProfileConfig: UserProfileConfig;
};
type FormFields = Omit<RealmRepresentation, "groups"> & {
unmanagedAttributePolicy: UnmanagedAttributePolicy;
};
const REQUIRE_SSL_TYPES = ["all", "external", "none"];
const UNMANAGED_ATTRIBUTE_POLICIES = [
UnmanagedAttributePolicy.Disabled,
UnmanagedAttributePolicy.Enabled,
UnmanagedAttributePolicy.AdminView,
UnmanagedAttributePolicy.AdminEdit,
];
function RealmSettingsGeneralTabForm({
realm,
save,
userProfileConfig,
}: RealmSettingsGeneralTabFormProps) {
const { t } = useTranslation();
const { realm: realmName } = useRealm();
const form = useForm<FormFields>();
const {
register,
control,
handleSubmit,
setValue,
formState: { isDirty, errors },
} = form;
const [open, setOpen] = useState(false);
const requireSslTypes = ["all", "external", "none"];
const [userProfileConfig, setUserProfileConfig] =
useState<UserProfileConfig>();
const unmanagedAttributePolicies = [
UnmanagedAttributePolicy.Disabled,
UnmanagedAttributePolicy.Enabled,
UnmanagedAttributePolicy.AdminView,
UnmanagedAttributePolicy.AdminEdit,
];
const [isUnmanagedAttributeOpen, setIsUnmanagedAttributeOpen] =
useState(false);
const setupForm = () => {
convertToFormValues(realm, setValue);
@ -85,244 +112,153 @@ export const RealmSettingsGeneralTab = ({
}
};
useFetch(
() => adminClient.users.getProfile({ realm: realmName }),
(config) => setUserProfileConfig(config),
[],
);
useEffect(setupForm, []);
const onSubmit = handleSubmit(({ unmanagedAttributePolicy, ...data }) => {
const upConfig = { ...userProfileConfig };
if (unmanagedAttributePolicy === UnmanagedAttributePolicy.Disabled) {
delete upConfig.unmanagedAttributePolicy;
} else {
upConfig.unmanagedAttributePolicy = unmanagedAttributePolicy;
}
save({ ...data, upConfig });
});
return (
<PageSection variant="light">
<FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
onSubmit={handleSubmit((data) => {
if (
UnmanagedAttributePolicy.Disabled ===
userProfileConfig?.unmanagedAttributePolicy
) {
userProfileConfig.unmanagedAttributePolicy = undefined;
}
save({ ...data, upConfig: userProfileConfig });
})}
>
<FormGroup
label={t("realmId")}
fieldId="kc-realm-id"
isRequired
validated={errors.realm ? "error" : "default"}
helperTextInvalid={errors.realm?.message}
<FormProvider {...form}>
<FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
onSubmit={onSubmit}
>
<Controller
name="realm"
control={control}
rules={{
required: { value: true, message: t("required") },
}}
defaultValue=""
render={({ field }) => (
<ClipboardCopy data-testid="realmName" onChange={field.onChange}>
{field.value}
</ClipboardCopy>
)}
/>
</FormGroup>
<FormGroup label={t("displayName")} fieldId="kc-display-name">
<KeycloakTextInput
id="kc-display-name"
{...register("displayName")}
/>
</FormGroup>
<FormGroup label={t("htmlDisplayName")} fieldId="kc-html-display-name">
<KeycloakTextInput
id="kc-html-display-name"
{...register("displayNameHtml")}
/>
</FormGroup>
<FormGroup
label={t("frontendUrl")}
fieldId="kc-frontend-url"
labelIcon={
<HelpItem
helpText={t("frontendUrlHelp")}
fieldLabelId="frontendUrl"
<FormGroup
label={t("realmId")}
fieldId="kc-realm-id"
isRequired
validated={errors.realm ? "error" : "default"}
helperTextInvalid={errors.realm?.message}
>
<Controller
name="realm"
control={control}
rules={{
required: { value: true, message: t("required") },
}}
defaultValue=""
render={({ field }) => (
<ClipboardCopy
data-testid="realmName"
onChange={field.onChange}
>
{field.value}
</ClipboardCopy>
)}
/>
}
>
<KeycloakTextInput
</FormGroup>
<TextControl name="displayName" label={t("displayName")} />
<TextControl name="displayNameHtml" label={t("htmlDisplayName")} />
<TextControl
name={convertAttributeNameToForm("attributes.frontendUrl")}
type="url"
id="kc-frontend-url"
{...register(convertAttributeNameToForm("attributes.frontendUrl"))}
label={t("htmlDisplayName")}
labelIcon={t("frontendUrlHelp")}
/>
</FormGroup>
<FormGroup
label={t("requireSsl")}
fieldId="kc-require-ssl"
labelIcon={
<HelpItem
helpText={t("requireSslHelp")}
fieldLabelId="requireSsl"
/>
}
>
<Controller
<SelectControl
name="sslRequired"
defaultValue="none"
control={control}
render={({ field }) => (
<Select
toggleId="kc-require-ssl"
onToggle={() => setOpen(!open)}
onSelect={(_, value) => {
field.onChange(value as string);
setOpen(false);
}}
selections={field.value}
variant={SelectVariant.single}
aria-label={t("requireSsl")}
isOpen={open}
>
{requireSslTypes.map((sslType) => (
<SelectOption
selected={sslType === field.value}
key={sslType}
value={sslType}
>
{t(`sslType.${sslType}`)}
</SelectOption>
))}
</Select>
)}
label={t("requireSsl")}
labelIcon={t("requireSslHelp")}
controller={{
defaultValue: "none",
}}
options={REQUIRE_SSL_TYPES.map((sslType) => ({
key: sslType,
value: t(`sslType.${sslType}`),
}))}
/>
</FormGroup>
<FormGroup
label={t("acrToLoAMapping")}
fieldId="acrToLoAMapping"
labelIcon={
<HelpItem
helpText={t("acrToLoAMappingHelp")}
fieldLabelId="acrToLoAMapping"
/>
}
>
<FormProvider {...form}>
<FormGroup
label={t("acrToLoAMapping")}
fieldId="acrToLoAMapping"
labelIcon={
<HelpItem
helpText={t("acrToLoAMappingHelp")}
fieldLabelId="acrToLoAMapping"
/>
}
>
<KeyValueInput
label={t("acrToLoAMapping")}
name={convertAttributeNameToForm("attributes.acr.loa.map")}
/>
</FormProvider>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("userManagedAccess")}
labelIcon={
<HelpItem
helpText={t("userManagedAccessHelp")}
fieldLabelId="userManagedAccess"
/>
}
fieldId="kc-user-managed-access"
>
<Controller
</FormGroup>
<DefaultSwitchControl
name="userManagedAccessAllowed"
control={control}
defaultValue={false}
render={({ field }) => (
<Switch
id="kc-user-managed-access"
data-testid="user-managed-access-switch"
label={t("on")}
labelOff={t("off")}
isChecked={field.value}
onChange={field.onChange}
aria-label={t("userManagedAccess")}
/>
)}
label={t("userManagedAccess")}
labelIcon={t("userManagedAccessHelp")}
/>
</FormGroup>
<FormGroup
label={t("unmanagedAttributes")}
fieldId="kc-user-profile-unmanaged-attribute-policy"
labelIcon={
<HelpItem
helpText={t("unmanagedAttributesHelpText")}
fieldLabelId="unmanagedAttributes"
/>
}
>
<Select
toggleId="kc-user-profile-unmanaged-attribute-policy"
onToggle={() =>
setIsUnmanagedAttributeOpen(!isUnmanagedAttributeOpen)
}
onSelect={(_, value) => {
if (userProfileConfig) {
userProfileConfig.unmanagedAttributePolicy =
value as UnmanagedAttributePolicy;
setUserProfileConfig(userProfileConfig);
}
setIsUnmanagedAttributeOpen(false);
<SelectControl
name="unmanagedAttributePolicy"
label={t("unmanagedAttributes")}
labelIcon={t("unmanagedAttributesHelpText")}
controller={{
defaultValue: UNMANAGED_ATTRIBUTE_POLICIES[0],
}}
selections={userProfileConfig?.unmanagedAttributePolicy}
variant={SelectVariant.single}
isOpen={isUnmanagedAttributeOpen}
aria-label={t("selectUnmanagedAttributePolicy")}
>
{unmanagedAttributePolicies.map((policy) => (
<SelectOption key={policy} value={policy}>
{t(`unmanagedAttributePolicy.${policy}`)}
</SelectOption>
))}
</Select>
</FormGroup>
<FormGroup
label={t("endpoints")}
labelIcon={
<HelpItem helpText={t("endpointsHelp")} fieldLabelId="endpoints" />
}
fieldId="kc-endpoints"
>
<Stack>
<StackItem>
<FormattedLink
href={`${addTrailingSlash(
adminClient.baseUrl,
)}realms/${realmName}/.well-known/openid-configuration`}
title={t("openIDEndpointConfiguration")}
options={UNMANAGED_ATTRIBUTE_POLICIES.map((policy) => ({
key: policy,
value: t(`unmanagedAttributePolicy.${policy}`),
}))}
/>
<FormGroup
label={t("endpoints")}
labelIcon={
<HelpItem
helpText={t("endpointsHelp")}
fieldLabelId="endpoints"
/>
</StackItem>
<StackItem>
<FormattedLink
href={`${addTrailingSlash(
adminClient.baseUrl,
)}realms/${realmName}/protocol/saml/descriptor`}
title={t("samlIdentityProviderMetadata")}
/>
</StackItem>
</Stack>
</FormGroup>
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="general-tab-save"
isDisabled={!isDirty}
}
fieldId="kc-endpoints"
>
{t("save")}
</Button>
<Button
data-testid="general-tab-revert"
variant="link"
onClick={setupForm}
>
{t("revert")}
</Button>
</ActionGroup>
</FormAccess>
<Stack>
<StackItem>
<FormattedLink
href={`${addTrailingSlash(
adminClient.baseUrl,
)}realms/${realmName}/.well-known/openid-configuration`}
title={t("openIDEndpointConfiguration")}
/>
</StackItem>
<StackItem>
<FormattedLink
href={`${addTrailingSlash(
adminClient.baseUrl,
)}realms/${realmName}/protocol/saml/descriptor`}
title={t("samlIdentityProviderMetadata")}
/>
</StackItem>
</Stack>
</FormGroup>
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="general-tab-save"
isDisabled={!isDirty}
>
{t("save")}
</Button>
<Button
data-testid="general-tab-revert"
variant="link"
onClick={setupForm}
>
{t("revert")}
</Button>
</ActionGroup>
</FormAccess>
</FormProvider>
</PageSection>
);
};
}