Realm settings(login): adds login tab and tests (#531)

* realm settings tabs wip

* realm settings wip

* switches

* updates to login tab watch

* remove controller and call save in onChange

* add cypress tests

* remove log stmt

* fix lint

Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* Update src/realm-settings/LoginTab.tsx

Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* Remove form

* format

* lint

Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
Eugenia 2021-04-19 07:41:07 -04:00 committed by GitHub
parent 50f4ca3a46
commit ba4e576a2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 619 additions and 184 deletions

View file

@ -0,0 +1,40 @@
import SidebarPage from "../support/pages/admin_console/SidebarPage";
import LoginPage from "../support/pages/LoginPage";
import RealmSettingsPage from "../support/pages/admin_console/manage/realm_settings/RealmSettingsPage";
import { keycloakBefore } from "../support/util/keycloak_before";
describe("Realm settings test", () => {
const loginPage = new LoginPage();
const sidebarPage = new SidebarPage();
const realmSettingsPage = new RealmSettingsPage();
const managedAccessSwitch = "user-managed-access-switch";
const userRegSwitch = "user-reg-switch";
const forgotPwdSwitch = "forgot-pw-switch";
const rememberMeSwitch = "remember-me-switch";
const verifyEmailSwitch = "verify-email-switch";
describe("Realm settings", function () {
beforeEach(function () {
keycloakBefore();
loginPage.logIn();
});
it("Go to general tab", function () {
sidebarPage.goToRealmSettings();
realmSettingsPage.toggleSwitch(managedAccessSwitch);
realmSettingsPage.save();
realmSettingsPage.toggleSwitch(managedAccessSwitch);
realmSettingsPage.save();
});
it("Go to login tab", function () {
sidebarPage.goToRealmSettings();
cy.getId("rs-login-tab").click();
realmSettingsPage.toggleSwitch(userRegSwitch);
realmSettingsPage.toggleSwitch(forgotPwdSwitch);
realmSettingsPage.toggleSwitch(rememberMeSwitch);
realmSettingsPage.toggleSwitch(verifyEmailSwitch);
});
});
});

View file

@ -0,0 +1,40 @@
export default class RealmSettingsPage {
saveBtn: string;
loginTab: string;
managedAccessSwitch: string;
userRegSwitch: string;
forgotPwdSwitch: string;
rememberMeSwitch: string;
emailAsUsernameSwitch: string;
loginWithEmailSwitch: string;
duplicateEmailsSwitch: string;
verifyEmailSwitch: string;
constructor() {
this.saveBtn = "general-tab-save";
this.loginTab = "rs-login-tab";
this.managedAccessSwitch = "user-managed-access-switch";
this.userRegSwitch = "user-reg-switch"
this.forgotPwdSwitch = "forgot-password-switch"
this.rememberMeSwitch = "remember-me-switch"
this.emailAsUsernameSwitch = "email-as-username-switch"
this.loginWithEmailSwitch = "login-with-email-switch"
this.duplicateEmailsSwitch = "duplicate-emails-switch"
this.verifyEmailSwitch = "verify-email-switch"
}
toggleSwitch(switchName: string) {
cy.getId(switchName).next().click();
return this;
}
save() {
cy.getId(this.saveBtn).click();
return this;
}
}

View file

@ -11,7 +11,7 @@ import "./form-panel.css";
type FormPanelProps = { type FormPanelProps = {
title: string; title: string;
scrollId: string; scrollId?: string;
children: ReactNode; children: ReactNode;
}; };

View file

@ -0,0 +1,230 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Controller, useForm } from "react-hook-form";
import { useErrorHandler } from "react-error-boundary";
import {
ActionGroup,
AlertVariant,
Button,
ClipboardCopy,
FormGroup,
PageSection,
Select,
SelectOption,
SelectVariant,
Stack,
StackItem,
Switch,
TextInput,
} from "@patternfly/react-core";
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import { getBaseUrl } from "../util";
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
import { useAlerts } from "../components/alert/Alerts";
import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormattedLink } from "../components/external-link/FormattedLink";
export const RealmSettingsGeneralTab = () => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
const handleError = useErrorHandler();
const { realm: realmName } = useRealm();
const { addAlert } = useAlerts();
const { register, control, setValue, handleSubmit } = useForm();
const [realm, setRealm] = useState<RealmRepresentation>();
const [open, setOpen] = useState(false);
const baseUrl = getBaseUrl(adminClient);
const requireSslTypes = ["all", "external", "none"];
useEffect(() => {
return asyncStateFetch(
() => adminClient.realms.findOne({ realm: realmName }),
(realm) => {
setRealm(realm);
setupForm(realm);
},
handleError
);
}, []);
const setupForm = (realm: RealmRepresentation) => {
Object.entries(realm).map((entry) => setValue(entry[0], entry[1]));
};
const save = async (realm: RealmRepresentation) => {
try {
await adminClient.realms.update({ realm: realmName }, realm);
setRealm(realm);
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addAlert(t("saveError", { error }), AlertVariant.danger);
}
};
return (
<>
<PageSection variant="light">
<FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
onSubmit={handleSubmit(save)}
>
<FormGroup label={t("realmId")} fieldId="kc-realm-id" isRequired>
<ClipboardCopy isReadOnly>{realmName}</ClipboardCopy>
</FormGroup>
<FormGroup label={t("displayName")} fieldId="kc-display-name">
<TextInput
type="text"
id="kc-display-name"
name="displayName"
ref={register}
/>
</FormGroup>
<FormGroup
label={t("htmlDisplayName")}
fieldId="kc-html-display-name"
>
<TextInput
type="text"
id="kc-html-display-name"
name="displayNameHtml"
ref={register}
/>
</FormGroup>
<FormGroup
label={t("frontendUrl")}
fieldId="kc-frontend-url"
labelIcon={
<HelpItem
helpText="realm-settings-help:frontendUrl"
forLabel={t("frontendUrl")}
forID="kc-frontend-url"
/>
}
>
<TextInput
type="text"
id="kc-frontend-url"
name="attributes.frontendUrl"
ref={register}
/>
</FormGroup>
<FormGroup
label={t("requireSsl")}
fieldId="kc-require-ssl"
labelIcon={
<HelpItem
helpText="realm-settings-help:requireSsl"
forLabel={t("requireSsl")}
forID="kc-require-ssl"
/>
}
>
<Controller
name="sslRequired"
defaultValue="none"
control={control}
render={({ onChange, value }) => (
<Select
toggleId="kc-require-ssl"
onToggle={() => setOpen(!open)}
onSelect={(_, value) => {
onChange(value as string);
setOpen(false);
}}
selections={value}
variant={SelectVariant.single}
aria-label={t("requireSsl")}
isOpen={open}
>
{requireSslTypes.map((sslType) => (
<SelectOption
selected={sslType === value}
key={sslType}
value={sslType}
>
{t(`sslType.${sslType}`)}
</SelectOption>
))}
</Select>
)}
/>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("userManagedAccess")}
labelIcon={
<HelpItem
helpText="realm-settings-help:userManagedAccess"
forLabel={t("userManagedAccess")}
forID="kc-user-manged-access"
/>
}
fieldId="kc-user-manged-access"
>
<Controller
name="userManagedAccessAllowed"
control={control}
defaultValue={false}
render={({ onChange, value }) => (
<Switch
id="kc-user-managed-access"
data-testid="user-managed-access-switch"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("endpoints")}
labelIcon={
<HelpItem
helpText="realm-settings-help:endpoints"
forLabel={t("endpoints")}
forID="kc-endpoints"
/>
}
fieldId="kc-endpoints"
>
<Stack>
<StackItem>
<FormattedLink
href={`${baseUrl}realms/${realmName}/.well-known/openid-configuration`}
title={t("openEndpointConfiguration")}
/>
</StackItem>
<StackItem>
<FormattedLink
href={`${baseUrl}realms/${realmName}/protocol/saml/descriptor`}
title={t("samlIdentityProviderMetadata")}
/>
</StackItem>
</Stack>
</FormGroup>
<ActionGroup>
<Button
variant="primary"
type="submit"
data-testid="general-tab-save"
>
{t("common:save")}
</Button>
<Button variant="link" onClick={() => setupForm(realm!)}>
{t("common:revert")}
</Button>
</ActionGroup>
</FormAccess>
</PageSection>
</>
);
};

View file

@ -0,0 +1,240 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
AlertVariant,
FormGroup,
PageSection,
Switch,
} from "@patternfly/react-core";
import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel";
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient";
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import { useErrorHandler } from "react-error-boundary";
import { useRealm } from "../context/realm-context/RealmContext";
import { useAlerts } from "../components/alert/Alerts";
export const RealmSettingsLoginTab = () => {
const { t } = useTranslation("realm-settings");
const [realm, setRealm] = useState<RealmRepresentation>();
const handleError = useErrorHandler();
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
const { addAlert } = useAlerts();
useEffect(() => {
return asyncStateFetch(
() => adminClient.realms.findOne({ realm: realmName }),
(realm) => {
setRealm(realm);
},
handleError
);
}, []);
const save = async (realm: RealmRepresentation) => {
try {
await adminClient.realms.update({ realm: realmName }, realm);
setRealm(realm);
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addAlert(t("saveError", { error }), AlertVariant.danger);
}
};
return (
<>
<PageSection variant="light">
<FormPanel title="Login screen customization">
{
<FormAccess isHorizontal role="manage-realm">
<FormGroup
label={t("userRegistration")}
fieldId="kc-user-reg"
labelIcon={
<HelpItem
helpText={t("userRegistrationHelpText")}
forLabel={t("userRegistration")}
forID="kc-user-reg"
/>
}
hasNoPaddingTop
>
<Switch
id="kc-user-reg"
data-testid="user-reg-switch"
name="registrationAllowed"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm?.registrationAllowed}
onChange={(value) => {
save({ ...realm, registrationAllowed: value });
}}
/>
</FormGroup>
<FormGroup
label={t("forgotPassword")}
fieldId="kc-forgot-pw"
labelIcon={
<HelpItem
helpText={t("forgotPasswordHelpText")}
forLabel={t("forgotPassword")}
forID="kc-forgot-pw"
/>
}
hasNoPaddingTop
>
<Switch
id="kc-forgot-pw"
data-testid="forgot-pw-switch"
name="resetPasswordAllowed"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm?.resetPasswordAllowed}
onChange={(value) => {
save({ ...realm, resetPasswordAllowed: value });
}}
/>
</FormGroup>
<FormGroup
label={t("rememberMe")}
fieldId="kc-remember-me"
labelIcon={
<HelpItem
helpText={t("rememberMeHelpText")}
forLabel={t("rememberMe")}
forID="kc-remember-me"
/>
}
hasNoPaddingTop
>
<Switch
id="kc-remember-me"
data-testid="remember-me-switch"
name="rememberMe"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm?.rememberMe}
onChange={(value) => {
save({ ...realm, rememberMe: value });
}}
/>
</FormGroup>
</FormAccess>
}
</FormPanel>
<FormPanel title="Email settings">
{
<FormAccess isHorizontal role="manage-realm">
<FormGroup
label={t("emailAsUsername")}
fieldId="kc-email-as-username"
labelIcon={
<HelpItem
helpText={t("emailAsUsernameHelpText")}
forLabel={t("emailAsUsername")}
forID="kc-email-as-username"
/>
}
hasNoPaddingTop
>
<Switch
id="kc-email-as-username"
data-testid="email-as-username-switch"
name="registrationEmailAsUsername"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm?.registrationEmailAsUsername}
onChange={(value) => {
save({ ...realm, registrationEmailAsUsername: value });
}}
/>
</FormGroup>
<FormGroup
label={t("loginWithEmail")}
fieldId="kc-login-with-email"
labelIcon={
<HelpItem
helpText={t("loginWithEmailHelpText")}
forLabel={t("loginWithEmail")}
forID="kc-login-with-email"
/>
}
hasNoPaddingTop
>
<Switch
id="kc-login-with-email"
data-testid="login-with-email-switch"
name="loginWithEmailAllowed"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm?.loginWithEmailAllowed}
onChange={(value) => {
save({ ...realm, loginWithEmailAllowed: value });
}}
/>
</FormGroup>
<FormGroup
label={t("duplicateEmails")}
fieldId="kc-duplicate-emails"
labelIcon={
<HelpItem
helpText={t("duplicateEmailsHelpText")}
forLabel={t("duplicateEmails")}
forID="kc-duplicate-emails"
/>
}
hasNoPaddingTop
>
<Switch
id="kc-duplicate-emails"
data-testid="duplicate-emails-switch"
label={t("common:on")}
labelOff={t("common:off")}
name="duplicateEmailsAllowed"
isChecked={
realm?.duplicateEmailsAllowed &&
!realm?.loginWithEmailAllowed &&
!realm?.registrationEmailAsUsername
}
onChange={(value) => {
save({ ...realm, duplicateEmailsAllowed: value });
}}
isDisabled={
realm?.loginWithEmailAllowed ||
realm?.registrationEmailAsUsername
}
/>
</FormGroup>
<FormGroup
label={t("verifyEmail")}
fieldId="kc-verify-email"
labelIcon={
<HelpItem
helpText={t("verifyEmailHelpText")}
forLabel={t("verifyEmail")}
forID="kc-verify-email"
/>
}
hasNoPaddingTop
>
<Switch
id="kc-verify-email"
data-testid="verify-email-switch"
name="verifyEmail"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={realm?.verifyEmail}
onChange={(value) => {
save({ ...realm, verifyEmail: value });
}}
/>
</FormGroup>
</FormAccess>
}
</FormPanel>
</PageSection>
</>
);
};

View file

@ -4,37 +4,25 @@ import { useTranslation } from "react-i18next";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { useErrorHandler } from "react-error-boundary"; import { useErrorHandler } from "react-error-boundary";
import { import {
ActionGroup,
AlertVariant, AlertVariant,
Button,
ButtonVariant, ButtonVariant,
ClipboardCopy,
DropdownItem, DropdownItem,
DropdownSeparator, DropdownSeparator,
FormGroup,
PageSection, PageSection,
Select,
SelectOption,
SelectVariant,
Stack,
StackItem,
Switch,
Tab, Tab,
TabTitleText, TabTitleText,
TextInput,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation"; import RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
import { getBaseUrl, toUpperCase } from "../util"; import { toUpperCase } from "../util";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient"; import { useAdminClient, asyncStateFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { ViewHeader } from "../components/view-header/ViewHeader"; import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormattedLink } from "../components/external-link/FormattedLink";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs"; import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { RealmSettingsLoginTab } from "./LoginTab";
import { RealmSettingsGeneralTab } from "./GeneralTab";
import { PartialImportDialog } from "./PartialImport"; import { PartialImportDialog } from "./PartialImport";
type RealmSettingsHeaderProps = { type RealmSettingsHeaderProps = {
@ -128,25 +116,18 @@ const RealmSettingsHeader = ({
); );
}; };
const requireSslTypes = ["all", "external", "none"];
export const RealmSettingsSection = () => { export const RealmSettingsSection = () => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const { realm: realmName } = useRealm(); const { realm: realmName } = useRealm();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const { register, control, getValues, setValue, handleSubmit } = useForm(); const { control, getValues, setValue } = useForm();
const [realm, setRealm] = useState<RealmRepresentation>();
const [open, setOpen] = useState(false);
const baseUrl = getBaseUrl(adminClient);
useEffect(() => { useEffect(() => {
return asyncStateFetch( return asyncStateFetch(
() => adminClient.realms.findOne({ realm: realmName }), () => adminClient.realms.findOne({ realm: realmName }),
(realm) => { (realm) => {
setRealm(realm);
setupForm(realm); setupForm(realm);
}, },
handleError handleError
@ -160,7 +141,6 @@ export const RealmSettingsSection = () => {
const save = async (realm: RealmRepresentation) => { const save = async (realm: RealmRepresentation) => {
try { try {
await adminClient.realms.update({ realm: realmName }, realm); await adminClient.realms.update({ realm: realmName }, realm);
setRealm(realm);
addAlert(t("saveSuccess"), AlertVariant.success); addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) { } catch (error) {
addAlert(t("saveError", { error }), AlertVariant.danger); addAlert(t("saveError", { error }), AlertVariant.danger);
@ -182,169 +162,21 @@ export const RealmSettingsSection = () => {
/> />
)} )}
/> />
<PageSection variant="light" className="pf-u-p-0"> <PageSection variant="light" className="pf-u-p-0">
<KeycloakTabs isBox> <KeycloakTabs isBox>
<Tab <Tab
eventKey="general" eventKey="general"
title={<TabTitleText>{t("general")}</TabTitleText>} title={<TabTitleText>{t("realm-settings:general")}</TabTitleText>}
data-testid="rs-general-tab"
> >
<PageSection variant="light"> <RealmSettingsGeneralTab />
<FormAccess </Tab>
isHorizontal <Tab
role="manage-realm" eventKey="login"
className="pf-u-mt-lg" title={<TabTitleText>{t("realm-settings:login")}</TabTitleText>}
onSubmit={handleSubmit(save)} data-testid="rs-login-tab"
> >
<FormGroup <RealmSettingsLoginTab />
label={t("realmId")}
fieldId="kc-realm-id"
isRequired
>
<ClipboardCopy isReadOnly>{realmName}</ClipboardCopy>
</FormGroup>
<FormGroup label={t("displayName")} fieldId="kc-display-name">
<TextInput
type="text"
id="kc-display-name"
name="displayName"
ref={register}
/>
</FormGroup>
<FormGroup
label={t("htmlDisplayName")}
fieldId="kc-html-display-name"
>
<TextInput
type="text"
id="kc-html-display-name"
name="displayNameHtml"
ref={register}
/>
</FormGroup>
<FormGroup
label={t("frontendUrl")}
fieldId="kc-frontend-url"
labelIcon={
<HelpItem
helpText="realm-settings-help:frontendUrl"
forLabel={t("frontendUrl")}
forID="kc-frontend-url"
/>
}
>
<TextInput
type="text"
id="kc-frontend-url"
name="attributes.frontendUrl"
ref={register}
/>
</FormGroup>
<FormGroup
label={t("requireSsl")}
fieldId="kc-require-ssl"
labelIcon={
<HelpItem
helpText="realm-settings-help:requireSsl"
forLabel={t("requireSsl")}
forID="kc-require-ssl"
/>
}
>
<Controller
name="sslRequired"
defaultValue="none"
control={control}
render={({ onChange, value }) => (
<Select
toggleId="kc-require-ssl"
onToggle={() => setOpen(!open)}
onSelect={(_, value) => {
onChange(value as string);
setOpen(false);
}}
selections={value}
variant={SelectVariant.single}
aria-label={t("requireSsl")}
isOpen={open}
>
{requireSslTypes.map((sslType) => (
<SelectOption
selected={sslType === value}
key={sslType}
value={sslType}
>
{t(`sslType.${sslType}`)}
</SelectOption>
))}
</Select>
)}
/>
</FormGroup>
<FormGroup
hasNoPaddingTop
label={t("userManagedAccess")}
labelIcon={
<HelpItem
helpText="realm-settings-help:userManagedAccess"
forLabel={t("userManagedAccess")}
forID="kc-user-manged-access"
/>
}
fieldId="kc-user-manged-access"
>
<Controller
name="userManagedAccessAllowed"
control={control}
defaultValue={false}
render={({ onChange, value }) => (
<Switch
id="kc-user-manged-access"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("endpoints")}
labelIcon={
<HelpItem
helpText="realm-settings-help:endpoints"
forLabel={t("endpoints")}
forID="kc-endpoints"
/>
}
fieldId="kc-endpoints"
>
<Stack>
<StackItem>
<FormattedLink
href={`${baseUrl}realms/${realmName}/.well-known/openid-configuration`}
title={t("openEndpointConfiguration")}
/>
</StackItem>
<StackItem>
<FormattedLink
href={`${baseUrl}realms/${realmName}/protocol/saml/descriptor`}
title={t("samlIdentityProviderMetadata")}
/>
</StackItem>
</Stack>
</FormGroup>
<ActionGroup>
<Button variant="primary" type="submit">
{t("common:save")}
</Button>
<Button variant="link" onClick={() => setupForm(realm!)}>
{t("common:revert")}
</Button>
</ActionGroup>
</FormAccess>
</PageSection>
</Tab> </Tab>
</KeycloakTabs> </KeycloakTabs>
</PageSection> </PageSection>

View file

@ -0,0 +1,31 @@
import React from "react";
import { PageSection, Tab, TabTitleText } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { RealmSettingsLoginTab } from "./LoginTab";
import { RealmSettingsGeneralTab } from "./GeneralTab";
export const RealmSettingsTabs = () => {
const { t } = useTranslation("roles");
return (
<>
<PageSection variant="light" className="pf-u-p-0">
<KeycloakTabs isBox>
<Tab
eventKey="general"
title={<TabTitleText>{t("realm-settings:general")}</TabTitleText>}
>
<RealmSettingsGeneralTab />
</Tab>
<Tab
eventKey="login"
title={<TabTitleText>{t("realm-settings:login")}</TabTitleText>}
>
<RealmSettingsLoginTab />
</Tab>
</KeycloakTabs>
</PageSection>
</>
);
};

View file

@ -12,6 +12,21 @@
"saveSuccess": "Realm successfully updated", "saveSuccess": "Realm successfully updated",
"saveError": "Realm could not be updated: {error}", "saveError": "Realm could not be updated: {error}",
"general": "General", "general": "General",
"login": "Login",
"userRegistration": "User registration",
"userRegistrationHelpText": "Enable/disable the registration page. A link for registration will show on login page too.",
"forgotPassword": "Forgot password",
"forgotPasswordHelpText": "Show a link on login page for user to click when they have forgotten their credentials.",
"rememberMe": "Remember me",
"rememberMeHelpText": "Show checkbox on login page to allow user to remain logged in between browser restarts until session expires.",
"emailAsUsername": "Email as username",
"emailAsUsernameHelpText": "Allow users to set email as username.",
"loginWithEmail": "Login with email",
"loginWithEmailHelpText": "Allow users to log in with their email address.",
"duplicateEmails": "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.",
"verifyEmail": "Verify email",
"verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.",
"realmId": "Realm ID", "realmId": "Realm ID",
"displayName": "Display name", "displayName": "Display name",
"htmlDisplayName": "HTML Display name", "htmlDisplayName": "HTML Display name",

View file

@ -30,6 +30,7 @@ import { RealmRoleTabs } from "./realm-roles/RealmRoleTabs";
import { SearchGroups } from "./groups/SearchGroups"; import { SearchGroups } from "./groups/SearchGroups";
import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken"; import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken";
import { LdapMappingDetails } from "./user-federation/ldap/mappers/LdapMappingDetails"; import { LdapMappingDetails } from "./user-federation/ldap/mappers/LdapMappingDetails";
import { RealmSettingsTabs } from "./realm-settings/RealmSettingsTabs";
export type RouteDef = BreadcrumbsRoute & { export type RouteDef = BreadcrumbsRoute & {
access: AccessType; access: AccessType;
@ -172,11 +173,17 @@ export const routes: RoutesFn = (t: TFunction) => [
access: "view-events", access: "view-events",
}, },
{ {
path: "/:realm/realm-settings", path: "/:realm/realm-settings/:tab?",
component: RealmSettingsSection, component: RealmSettingsSection,
breadcrumb: t("realmSettings"), breadcrumb: t("realmSettings"),
access: "view-realm", access: "view-realm",
}, },
{
path: "/:realm/realm-settings/general",
component: RealmSettingsTabs,
breadcrumb: t("realmSettings"),
access: "view-realm",
},
{ {
path: "/:realm/authentication", path: "/:realm/authentication",
component: AuthenticationSection, component: AuthenticationSection,