refactor realm settings (#1083)

also fixes: #1038
This commit is contained in:
Erik Jan de Wit 2021-09-02 22:14:43 +02:00 committed by GitHub
parent 3f061efead
commit 2416d25c38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 398 additions and 421 deletions

View file

@ -10,7 +10,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 type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form"; import { Controller, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
@ -41,7 +41,7 @@ export const RealmSettingsEmailTab = ({
const [realm, setRealm] = useState(initialRealm); const [realm, setRealm] = useState(initialRealm);
const [userEmailModalOpen, setUserEmailModalOpen] = useState(false); const [userEmailModalOpen, setUserEmailModalOpen] = useState(false);
const [currentUser, setCurrentUser] = useState<UserRepresentation>(); const [currentUser, setCurrentUser] = useState<UserRepresentation>(user);
const { const {
register, register,
control, control,
@ -51,7 +51,7 @@ export const RealmSettingsEmailTab = ({
setValue, setValue,
reset: resetForm, reset: resetForm,
getValues, getValues,
} = useForm<RealmRepresentation>(); } = useForm<RealmRepresentation>({ defaultValues: realm });
const userForm = useForm<UserRepresentation>({ mode: "onChange" }); const userForm = useForm<UserRepresentation>({ mode: "onChange" });
const watchFromValue = watch("smtpServer.from", ""); const watchFromValue = watch("smtpServer.from", "");
@ -63,14 +63,6 @@ export const RealmSettingsEmailTab = ({
defaultValue: {}, defaultValue: {},
}); });
useEffect(() => {
reset();
}, [realm]);
useEffect(() => {
setCurrentUser(user);
}, []);
const handleModalToggle = () => { const handleModalToggle = () => {
setUserEmailModalOpen(!userEmailModalOpen); setUserEmailModalOpen(!userEmailModalOpen);
}; };
@ -89,10 +81,7 @@ export const RealmSettingsEmailTab = ({
const saveAndTestEmail = async (email?: UserRepresentation) => { const saveAndTestEmail = async (email?: UserRepresentation) => {
if (email) { if (email) {
await adminClient.users.update({ id: whoAmI.getUserId() }, email); await adminClient.users.update({ id: whoAmI.getUserId() }, email);
const updated = await adminClient.users.findOne({ setCurrentUser(email);
id: whoAmI.getUserId(),
});
setCurrentUser(updated);
await save(getValues()); await save(getValues());
testConnection(); testConnection();
@ -322,7 +311,7 @@ export const RealmSettingsEmailTab = ({
<Controller <Controller
name="smtpServer.authentication" name="smtpServer.authentication"
control={control} control={control}
defaultValue={authenticationEnabled} defaultValue={{}}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
id="kc-authentication-switch" id="kc-authentication-switch"

View file

@ -73,7 +73,7 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
const activeKeysCopy = keys!.filter((i) => i.status === "ACTIVE"); const activeKeysCopy = keys!.filter((i) => i.status === "ACTIVE");
return activeKeysCopy?.map((key) => { return activeKeysCopy?.map((key) => {
const provider = realmComponents!.find( const provider = realmComponents.find(
(component: ComponentRepresentation) => component.id === key.providerId (component: ComponentRepresentation) => component.id === key.providerId
); );
return { ...key, provider: provider?.name } as KeyData; return { ...key, provider: provider?.name } as KeyData;
@ -89,7 +89,7 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
const passiveKeys = keys!.filter((i) => i.status === "PASSIVE"); const passiveKeys = keys!.filter((i) => i.status === "PASSIVE");
return passiveKeys?.map((key) => { return passiveKeys?.map((key) => {
const provider = realmComponents!.find( const provider = realmComponents.find(
(component: ComponentRepresentation) => component.id === key.providerId (component: ComponentRepresentation) => component.id === key.providerId
); );
return { ...key, provider: provider?.name } as KeyData; return { ...key, provider: provider?.name } as KeyData;

View file

@ -66,16 +66,15 @@ export const LocalizationTab = ({
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const { realm: currentRealm } = useRealm(); const { realm: currentRealm } = useRealm();
const watchSupportedLocales = useWatch({ const watchSupportedLocales = useWatch<string[]>({
control, control,
name: "supportedLocales", name: "supportedLocales",
defaultValue: themeTypes?.account![0].locales,
}); });
const internationalizationEnabled = useWatch({ const internationalizationEnabled = useWatch({
control, control,
name: "internationalizationEnabled", name: "internationalizationEnabled",
defaultValue: realm?.internationalizationEnabled, defaultValue: false,
}); });
const loader = async () => { const loader = async () => {
@ -112,7 +111,7 @@ export const LocalizationTab = ({
const options = [ const options = [
<SelectGroup label={t("defaultLocale")} key="group1"> <SelectGroup label={t("defaultLocale")} key="group1">
{watchSupportedLocales {watchSupportedLocales
.filter((item) => item === realm?.defaultLocale) ?.filter((item) => item === realm?.defaultLocale)
.map((locale) => ( .map((locale) => (
<SelectOption key={locale} value={locale}> <SelectOption key={locale} value={locale}>
{t(`allSupportedLocales.${locale}`)} {t(`allSupportedLocales.${locale}`)}
@ -122,7 +121,7 @@ export const LocalizationTab = ({
<Divider key="divider" />, <Divider key="divider" />,
<SelectGroup label={t("supportedLocales")} key="group2"> <SelectGroup label={t("supportedLocales")} key="group2">
{watchSupportedLocales {watchSupportedLocales
.filter((item) => item !== realm?.defaultLocale) ?.filter((item) => item !== realm?.defaultLocale)
.map((locale) => ( .map((locale) => (
<SelectOption key={locale} value={locale}> <SelectOption key={locale} value={locale}>
{t(`allSupportedLocales.${locale}`)} {t(`allSupportedLocales.${locale}`)}
@ -218,7 +217,6 @@ export const LocalizationTab = ({
<Controller <Controller
name="supportedLocales" name="supportedLocales"
control={control} control={control}
defaultValue={themeTypes?.account![0].locales}
render={({ onChange }) => ( render={({ onChange }) => (
<Select <Select
toggleId="kc-l-supported-locales" toggleId="kc-l-supported-locales"
@ -270,7 +268,6 @@ export const LocalizationTab = ({
<Controller <Controller
name="defaultLocale" name="defaultLocale"
control={control} control={control}
defaultValue={realm?.defaultLocale}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="kc-default-locale" toggleId="kc-default-locale"
@ -299,7 +296,7 @@ export const LocalizationTab = ({
placeholderText={t("placeholderText")} placeholderText={t("placeholderText")}
data-testid="select-default-locale" data-testid="select-default-locale"
> >
{watchSupportedLocales.map( {watchSupportedLocales?.map(
(locale: string, idx: number) => ( (locale: string, idx: number) => (
<SelectOption <SelectOption
key={`default-locale-${idx}`} key={`default-locale-${idx}`}

View file

@ -1,52 +1,16 @@
import { import { Breadcrumb, BreadcrumbItem, Spinner } from "@patternfly/react-core";
AlertVariant,
Breadcrumb,
BreadcrumbItem,
ButtonVariant,
DropdownItem,
DropdownSeparator,
PageSection,
Spinner,
Tab,
Tabs,
TabTitleText,
} from "@patternfly/react-core";
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation"; import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import React, { useEffect, useState } from "react"; import React, { useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom"; import { Link } from "react-router-dom";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient, useFetch } from "../context/auth/AdminClient"; import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { LocalizationTab } from "./LocalizationTab";
import { useWhoAmI } from "../context/whoami/WhoAmI"; import { useWhoAmI } from "../context/whoami/WhoAmI";
import { convertToFormValues, KEY_PROVIDER_TYPE, toUpperCase } from "../util"; import { KEY_PROVIDER_TYPE } from "../util";
import { RealmSettingsEmailTab } from "./EmailTab";
import { EventsTab } from "./event-config/EventsTab";
import { RealmSettingsGeneralTab } from "./GeneralTab";
import { KeysListTab } from "./KeysListTab";
import { KeysProvidersTab } from "./KeysProvidersTab";
import { RealmSettingsLoginTab } from "./LoginTab";
import { PartialImportDialog } from "./PartialImport";
import { toRealmSettings } from "./routes/RealmSettings"; import { toRealmSettings } from "./routes/RealmSettings";
import { SecurityDefences } from "./security-defences/SecurityDefences"; import { RealmSettingsTabs } from "./RealmSettingsTabs";
import { RealmSettingsSessionsTab } from "./SessionsTab";
import { RealmSettingsThemesTab } from "./ThemesTab";
import { RealmSettingsTokensTab } from "./TokensTab";
type RealmSettingsHeaderProps = {
onChange: (value: boolean) => void;
value: boolean;
save: () => void;
realmName: string;
};
export const EditProviderCrumb = () => { export const EditProviderCrumb = () => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
@ -67,87 +31,6 @@ export const EditProviderCrumb = () => {
); );
}; };
const RealmSettingsHeader = ({
save,
onChange,
value,
realmName,
}: RealmSettingsHeaderProps) => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts();
const history = useHistory();
const { refresh } = useRealm();
const [partialImportOpen, setPartialImportOpen] = useState(false);
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
titleKey: "realm-settings:disableConfirmTitle",
messageKey: "realm-settings:disableConfirm",
continueButtonLabel: "common:disable",
onConfirm: () => {
onChange(!value);
save();
},
});
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "realm-settings:deleteConfirmTitle",
messageKey: "realm-settings:deleteConfirm",
continueButtonLabel: "common:delete",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.realms.del({ realm: realmName });
addAlert(t("deletedSuccess"), AlertVariant.success);
history.push("/master/");
refresh();
} catch (error) {
addError("realm-settings:deleteError", error);
}
},
});
return (
<>
<DisableConfirm />
<DeleteConfirm />
<PartialImportDialog
open={partialImportOpen}
toggleDialog={() => setPartialImportOpen(!partialImportOpen)}
/>
<ViewHeader
titleKey={toUpperCase(realmName)}
divider={false}
dropdownItems={[
<DropdownItem
key="import"
data-testid="openPartialImportModal"
onClick={() => {
setPartialImportOpen(true);
}}
>
{t("partialImport")}
</DropdownItem>,
<DropdownItem key="export">{t("partialExport")}</DropdownItem>,
<DropdownSeparator key="separator" />,
<DropdownItem key="delete" onClick={toggleDeleteDialog}>
{t("common:delete")}
</DropdownItem>,
]}
isEnabled={value}
onToggle={(value) => {
if (!value) {
toggleDisableDialog();
} else {
onChange(value);
save();
}
}}
/>
</>
);
};
const sortByPriority = (components: ComponentRepresentation[]) => { const sortByPriority = (components: ComponentRepresentation[]) => {
const sortedComponents = [...components].sort((a, b) => { const sortedComponents = [...components].sort((a, b) => {
const priorityA = Number(a.config?.priority); const priorityA = Number(a.config?.priority);
@ -162,27 +45,13 @@ const sortByPriority = (components: ComponentRepresentation[]) => {
}; };
export const RealmSettingsSection = () => { export const RealmSettingsSection = () => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { const { realm: realmName } = useRealm();
realm: realmName,
refresh: refreshRealm,
setRealm: setCurrentRealm,
} = useRealm();
const { addAlert, addError } = useAlerts();
const form = useForm({ mode: "onChange" });
const { control, getValues, setValue, reset: resetForm } = form;
const [key, setKey] = useState(0);
const [realm, setRealm] = useState<RealmRepresentation>(); const [realm, setRealm] = useState<RealmRepresentation>();
const [activeTab, setActiveTab] = useState(0);
const [realmComponents, setRealmComponents] = const [realmComponents, setRealmComponents] =
useState<ComponentRepresentation[]>(); useState<ComponentRepresentation[]>();
const [currentUser, setCurrentUser] = useState<UserRepresentation>(); const [currentUser, setCurrentUser] = useState<UserRepresentation>();
const { whoAmI } = useWhoAmI(); const { whoAmI } = useWhoAmI();
const history = useHistory();
const kpComponentTypes =
useServerInfo().componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
useFetch( useFetch(
async () => { async () => {
@ -200,45 +69,9 @@ export const RealmSettingsSection = () => {
setCurrentUser(user); setCurrentUser(user);
setRealm(realm); setRealm(realm);
}, },
[key] []
); );
const refresh = () => {
setKey(new Date().getTime());
};
useEffect(() => {
if (realm) {
Object.entries(realm).map(([key, value]) => {
if (key === "attributes") {
convertToFormValues(value, "attributes", form.setValue);
} else {
setValue(key, value);
}
});
resetForm(getValues());
}
}, [realm]);
const save = async (realm: RealmRepresentation) => {
try {
await adminClient.realms.update(
{ realm: realmName },
{ ...realm, id: realmName }
);
setRealm(realm);
const isRealmRenamed = realmName !== realm.realm;
if (isRealmRenamed) {
await refreshRealm();
setCurrentRealm(realm.realm!);
history.push(toRealmSettings({ realm: realm.realm! }));
}
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addError("realm-settings:saveError", error);
}
};
if (!realm || !realmComponents || !currentUser) { if (!realm || !realmComponents || !currentUser) {
return ( return (
<div className="pf-u-text-align-center"> <div className="pf-u-text-align-center">
@ -247,151 +80,10 @@ export const RealmSettingsSection = () => {
); );
} }
return ( return (
<> <RealmSettingsTabs
<Controller
name="enabled"
control={control}
defaultValue={true}
render={({ onChange, value }) => (
<RealmSettingsHeader
value={value}
onChange={onChange}
realmName={realmName}
save={() => save(getValues())}
/>
)}
/>
<PageSection variant="light" className="pf-u-p-0">
<FormProvider {...form}>
<KeycloakTabs isBox>
<Tab
eventKey="general"
title={<TabTitleText>{t("general")}</TabTitleText>}
data-testid="rs-general-tab"
aria-label="general-tab"
>
<RealmSettingsGeneralTab
save={save}
reset={() => resetForm(realm)}
/>
</Tab>
<Tab
eventKey="login"
title={<TabTitleText>{t("login")}</TabTitleText>}
data-testid="rs-login-tab"
aria-label="login-tab"
>
<RealmSettingsLoginTab save={save} realm={realm} />
</Tab>
<Tab
eventKey="email"
title={<TabTitleText>{t("email")}</TabTitleText>}
data-testid="rs-email-tab"
aria-label="email-tab"
>
<RealmSettingsEmailTab user={currentUser} realm={realm} />
</Tab>
<Tab
eventKey="themes"
title={<TabTitleText>{t("themes")}</TabTitleText>}
data-testid="rs-themes-tab"
aria-label="themes-tab"
>
<RealmSettingsThemesTab
save={save}
reset={() => resetForm(realm)}
realm={realm} realm={realm}
/>
</Tab>
<Tab
eventKey="keys"
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
data-testid="rs-keys-tab"
aria-label="keys-tab"
>
<Tabs
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(Number(key))}
>
<Tab
id="keysList"
eventKey={0}
data-testid="rs-keys-list-tab"
aria-label="keys-list-subtab"
title={<TabTitleText>{t("keysList")}</TabTitleText>}
>
<KeysListTab realmComponents={realmComponents} />
</Tab>
<Tab
id="providers"
data-testid="rs-providers-tab"
aria-label="rs-providers-tab"
eventKey={1}
title={<TabTitleText>{t("providers")}</TabTitleText>}
>
<KeysProvidersTab
realmComponents={realmComponents} realmComponents={realmComponents}
keyProviderComponentTypes={kpComponentTypes} currentUser={currentUser}
refresh={refresh}
/> />
</Tab>
</Tabs>
</Tab>
<Tab
eventKey="events"
title={<TabTitleText>{t("events")}</TabTitleText>}
data-testid="rs-realm-events-tab"
aria-label="realm-events-tab"
>
<EventsTab />
</Tab>
<Tab
id="localization"
eventKey="localization"
data-testid="rs-localization-tab"
title={<TabTitleText>{t("localization")}</TabTitleText>}
>
<LocalizationTab
key={key}
refresh={refresh}
save={save}
reset={() => resetForm(realm)}
realm={realm}
/>
</Tab>
<Tab
id="securityDefences"
eventKey="securityDefences"
title={<TabTitleText>{t("securityDefences")}</TabTitleText>}
>
<SecurityDefences save={save} reset={() => resetForm(realm)} />
</Tab>
<Tab
id="sessions"
eventKey="sessions"
data-testid="rs-sessions-tab"
aria-label="sessions-tab"
title={
<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>
}
>
<RealmSettingsSessionsTab key={key} realm={realm} />
</Tab>
<Tab
id="tokens"
eventKey="tokens"
data-testid="rs-tokens-tab"
aria-label="tokens-tab"
title={<TabTitleText>{t("realm-settings:tokens")}</TabTitleText>}
>
<RealmSettingsTokensTab
realm={realm}
reset={() => resetForm(realm)}
/>
</Tab>
</KeycloakTabs>
</FormProvider>
</PageSection>
</>
); );
}; };

View file

@ -0,0 +1,358 @@
import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
AlertVariant,
ButtonVariant,
DropdownItem,
DropdownSeparator,
PageSection,
Tab,
Tabs,
TabTitleText,
} from "@patternfly/react-core";
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
import { useRealm } from "../context/realm-context/RealmContext";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient } from "../context/auth/AdminClient";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { useAlerts } from "../components/alert/Alerts";
import {
convertFormValuesToObject,
convertToFormValues,
KEY_PROVIDER_TYPE,
toUpperCase,
} from "../util";
import { RealmSettingsEmailTab } from "./EmailTab";
import { EventsTab } from "./event-config/EventsTab";
import { RealmSettingsGeneralTab } from "./GeneralTab";
import { KeysListTab } from "./KeysListTab";
import { KeysProvidersTab } from "./KeysProvidersTab";
import { RealmSettingsLoginTab } from "./LoginTab";
import { SecurityDefences } from "./security-defences/SecurityDefences";
import { RealmSettingsSessionsTab } from "./SessionsTab";
import { RealmSettingsThemesTab } from "./ThemesTab";
import { RealmSettingsTokensTab } from "./TokensTab";
import { PartialImportDialog } from "./PartialImport";
import { toRealmSettings } from "./routes/RealmSettings";
import { LocalizationTab } from "./LocalizationTab";
type RealmSettingsHeaderProps = {
onChange: (value: boolean) => void;
value: boolean;
save: () => void;
realmName: string;
};
const RealmSettingsHeader = ({
save,
onChange,
value,
realmName,
}: RealmSettingsHeaderProps) => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts();
const history = useHistory();
const { refresh } = useRealm();
const [partialImportOpen, setPartialImportOpen] = useState(false);
const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({
titleKey: "realm-settings:disableConfirmTitle",
messageKey: "realm-settings:disableConfirm",
continueButtonLabel: "common:disable",
onConfirm: () => {
onChange(!value);
save();
},
});
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
titleKey: "realm-settings:deleteConfirmTitle",
messageKey: "realm-settings:deleteConfirm",
continueButtonLabel: "common:delete",
continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => {
try {
await adminClient.realms.del({ realm: realmName });
addAlert(t("deletedSuccess"), AlertVariant.success);
history.push("/master/");
refresh();
} catch (error) {
addError("realm-settings:deleteError", error);
}
},
});
return (
<>
<DisableConfirm />
<DeleteConfirm />
<PartialImportDialog
open={partialImportOpen}
toggleDialog={() => setPartialImportOpen(!partialImportOpen)}
/>
<ViewHeader
titleKey={toUpperCase(realmName)}
divider={false}
dropdownItems={[
<DropdownItem
key="import"
data-testid="openPartialImportModal"
onClick={() => {
setPartialImportOpen(true);
}}
>
{t("partialImport")}
</DropdownItem>,
<DropdownItem key="export">{t("partialExport")}</DropdownItem>,
<DropdownSeparator key="separator" />,
<DropdownItem key="delete" onClick={toggleDeleteDialog}>
{t("common:delete")}
</DropdownItem>,
]}
isEnabled={value}
onToggle={(value) => {
if (!value) {
toggleDisableDialog();
} else {
onChange(value);
save();
}
}}
/>
</>
);
};
type RealmSettingsTabsProps = {
realm: RealmRepresentation;
realmComponents: ComponentRepresentation[];
currentUser: UserRepresentation;
};
export const RealmSettingsTabs = ({
realm,
realmComponents,
currentUser,
}: RealmSettingsTabsProps) => {
const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
const { addAlert, addError } = useAlerts();
const {
realm: realmName,
refresh: refreshRealm,
setRealm: setCurrentRealm,
} = useRealm();
const history = useHistory();
const kpComponentTypes =
useServerInfo().componentTypes?.[KEY_PROVIDER_TYPE] ?? [];
const form = useForm({ mode: "onChange" });
const { control, getValues, setValue, reset: resetForm } = form;
const [activeTab, setActiveTab] = useState(0);
const [key, setKey] = useState(0);
const refresh = () => {
setKey(new Date().getTime());
};
const setupForm = (r: RealmRepresentation = realm) => {
Object.entries(r).map(([key, value]) => {
if (key === "attributes") {
convertToFormValues(value, "attributes", setValue);
} else {
setValue(key, value);
}
});
resetForm(getValues());
};
useEffect(() => {
setupForm();
}, []);
const save = async (realm: RealmRepresentation) => {
try {
const attributes = Object.fromEntries(
Object.entries(
convertFormValuesToObject(realm.attributes, true)
).filter(([, v]) => v !== "")
);
await adminClient.realms.update(
{ realm: realmName },
{ ...realm, id: realmName, attributes }
);
setupForm(realm);
const isRealmRenamed = realmName !== realm.realm;
if (isRealmRenamed) {
await refreshRealm();
setCurrentRealm(realm.realm!);
history.push(toRealmSettings({ realm: realm.realm! }));
}
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addError("realm-settings:saveError", error);
}
};
return (
<>
<Controller
name="enabled"
defaultValue={true}
control={control}
render={({ onChange, value }) => (
<RealmSettingsHeader
value={value}
onChange={onChange}
realmName={realmName}
save={() => save(getValues())}
/>
)}
/>
<PageSection variant="light" className="pf-u-p-0">
<FormProvider {...form}>
<KeycloakTabs isBox>
<Tab
eventKey="general"
title={<TabTitleText>{t("general")}</TabTitleText>}
data-testid="rs-general-tab"
aria-label="general-tab"
>
<RealmSettingsGeneralTab
save={save}
reset={() => resetForm(realm)}
/>
</Tab>
<Tab
eventKey="login"
title={<TabTitleText>{t("login")}</TabTitleText>}
data-testid="rs-login-tab"
aria-label="login-tab"
>
<RealmSettingsLoginTab save={save} realm={realm} />
</Tab>
<Tab
eventKey="email"
title={<TabTitleText>{t("email")}</TabTitleText>}
data-testid="rs-email-tab"
aria-label="email-tab"
>
<RealmSettingsEmailTab user={currentUser} realm={realm} />
</Tab>
<Tab
eventKey="themes"
title={<TabTitleText>{t("themes")}</TabTitleText>}
data-testid="rs-themes-tab"
aria-label="themes-tab"
>
<RealmSettingsThemesTab
save={save}
reset={() => resetForm(realm)}
realm={realm}
/>
</Tab>
<Tab
eventKey="keys"
title={<TabTitleText>{t("realm-settings:keys")}</TabTitleText>}
data-testid="rs-keys-tab"
aria-label="keys-tab"
>
<Tabs
activeKey={activeTab}
onSelect={(_, key) => setActiveTab(Number(key))}
>
<Tab
id="keysList"
eventKey={0}
data-testid="rs-keys-list-tab"
aria-label="keys-list-subtab"
title={<TabTitleText>{t("keysList")}</TabTitleText>}
>
<KeysListTab realmComponents={realmComponents} />
</Tab>
<Tab
id="providers"
data-testid="rs-providers-tab"
aria-label="rs-providers-tab"
eventKey={1}
title={<TabTitleText>{t("providers")}</TabTitleText>}
>
<KeysProvidersTab
realmComponents={realmComponents}
keyProviderComponentTypes={kpComponentTypes}
refresh={refresh}
/>
</Tab>
</Tabs>
</Tab>
<Tab
eventKey="events"
title={<TabTitleText>{t("events")}</TabTitleText>}
data-testid="rs-realm-events-tab"
aria-label="realm-events-tab"
>
<EventsTab />
</Tab>
<Tab
id="localization"
eventKey="localization"
data-testid="rs-localization-tab"
title={<TabTitleText>{t("localization")}</TabTitleText>}
>
<LocalizationTab
key={key}
refresh={refresh}
save={save}
reset={() => resetForm(realm)}
realm={realm}
/>
</Tab>
<Tab
id="securityDefences"
eventKey="securityDefences"
title={<TabTitleText>{t("securityDefences")}</TabTitleText>}
>
<SecurityDefences save={save} reset={() => resetForm(realm)} />
</Tab>
<Tab
id="sessions"
eventKey="sessions"
data-testid="rs-sessions-tab"
aria-label="sessions-tab"
title={
<TabTitleText>{t("realm-settings:sessions")}</TabTitleText>
}
>
<RealmSettingsSessionsTab key={key} realm={realm} save={save} />
</Tab>
<Tab
id="tokens"
eventKey="tokens"
data-testid="rs-tokens-tab"
aria-label="tokens-tab"
title={<TabTitleText>{t("realm-settings:tokens")}</TabTitleText>}
>
<RealmSettingsTokensTab
save={save}
realm={realm}
reset={() => resetForm(realm)}
/>
</Tab>
</KeycloakTabs>
</FormProvider>
</PageSection>
</>
);
};

View file

@ -1,9 +1,8 @@
import React, { useEffect, useState } from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Controller, useForm, useWatch } from "react-hook-form"; import { Controller, useFormContext, useWatch } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
AlertVariant,
Button, Button,
FormGroup, FormGroup,
PageSection, PageSection,
@ -14,55 +13,33 @@ 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 { FormPanel } from "../components/scroll-form/FormPanel"; import { FormPanel } from "../components/scroll-form/FormPanel";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
import { useRealm } from "../context/realm-context/RealmContext";
import "./RealmSettingsSection.css";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { TimeSelector } from "../components/time-selector/TimeSelector"; import { TimeSelector } from "../components/time-selector/TimeSelector";
import "./RealmSettingsSection.css";
type RealmSettingsSessionsTabProps = { type RealmSettingsSessionsTabProps = {
realm?: RealmRepresentation; realm: RealmRepresentation;
user?: UserRepresentation; save: (realm: RealmRepresentation) => void;
}; };
export const RealmSettingsSessionsTab = ({ export const RealmSettingsSessionsTab = ({
realm: initialRealm, realm,
save,
}: RealmSettingsSessionsTabProps) => { }: RealmSettingsSessionsTabProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
const { addAlert, addError } = useAlerts();
const [realm, setRealm] = useState(initialRealm);
const { const {
control, control,
handleSubmit, handleSubmit,
reset: resetForm, reset: resetForm,
formState, formState,
} = useForm<RealmRepresentation>(); } = useFormContext<RealmRepresentation>();
const offlineSessionMaxEnabled = useWatch({ const offlineSessionMaxEnabled = useWatch({
control, control,
name: "offlineSessionMaxLifespanEnabled", name: "offlineSessionMaxLifespanEnabled",
defaultValue: realm?.offlineSessionMaxLifespanEnabled,
}); });
useEffect(() => resetForm(realm), [realm]);
const save = async (form: RealmRepresentation) => {
try {
const savedRealm = { ...realm, ...form };
await adminClient.realms.update({ realm: realmName }, savedRealm);
setRealm(savedRealm);
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addError("realm-settings:saveError", error);
}
};
const reset = () => { const reset = () => {
if (realm) { if (realm) {
resetForm(realm); resetForm(realm);
@ -94,7 +71,7 @@ export const RealmSettingsSessionsTab = ({
> >
<Controller <Controller
name="ssoSessionIdleTimeout" name="ssoSessionIdleTimeout"
defaultValue={realm?.ssoSessionIdleTimeout} defaultValue={realm.ssoSessionIdleTimeout}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<TimeSelector <TimeSelector
@ -351,7 +328,6 @@ export const RealmSettingsSessionsTab = ({
> >
<Controller <Controller
name="offlineSessionMaxLifespan" name="offlineSessionMaxLifespan"
defaultValue=""
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<TimeSelector <TimeSelector

View file

@ -3,7 +3,6 @@ import { useTranslation } from "react-i18next";
import { Controller, useFormContext, useWatch } from "react-hook-form"; import { Controller, useFormContext, useWatch } from "react-hook-form";
import { import {
ActionGroup, ActionGroup,
AlertVariant,
Button, Button,
FormGroup, FormGroup,
NumberInput, NumberInput,
@ -20,38 +19,26 @@ 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 { FormPanel } from "../components/scroll-form/FormPanel"; import { FormPanel } from "../components/scroll-form/FormPanel";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAlerts } from "../components/alert/Alerts";
import { useRealm } from "../context/realm-context/RealmContext";
import "./RealmSettingsSection.css";
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { TimeSelector } from "../components/time-selector/TimeSelector"; import { TimeSelector } from "../components/time-selector/TimeSelector";
import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { import { forHumans, interpolateTimespan } from "../util";
forHumans,
flatten, import "./RealmSettingsSection.css";
convertFormValuesToObject,
interpolateTimespan,
} from "../util";
type RealmSettingsSessionsTabProps = { type RealmSettingsSessionsTabProps = {
realm: RealmRepresentation; realm: RealmRepresentation;
user?: UserRepresentation; save: (realm: RealmRepresentation) => void;
reset?: () => void; reset?: () => void;
}; };
export const RealmSettingsTokensTab = ({ export const RealmSettingsTokensTab = ({
realm: initialRealm, realm,
reset, reset,
save,
}: RealmSettingsSessionsTabProps) => { }: RealmSettingsSessionsTabProps) => {
const { t } = useTranslation("realm-settings"); const { t } = useTranslation("realm-settings");
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
const { addAlert, addError } = useAlerts();
const serverInfo = useServerInfo(); const serverInfo = useServerInfo();
const [realm, setRealm] = useState(initialRealm);
const [defaultSigAlgDrpdwnIsOpen, setDefaultSigAlgDrpdwnOpen] = const [defaultSigAlgDrpdwnIsOpen, setDefaultSigAlgDrpdwnOpen] =
useState(false); useState(false);
@ -78,28 +65,6 @@ export const RealmSettingsTokensTab = ({
defaultValue: realm?.offlineSessionMaxLifespanEnabled, defaultValue: realm?.offlineSessionMaxLifespanEnabled,
}); });
const save = async () => {
const firstInstanceOnly = true;
const flattenedAttributes = convertFormValuesToObject(
flatten(form.getValues()["attributes"]),
firstInstanceOnly
);
try {
const newRealm: RealmRepresentation = {
...realm,
...form.getValues(),
attributes: flattenedAttributes,
};
await adminClient.realms.update({ realm: realmName }, newRealm);
setRealm(newRealm);
addAlert(t("saveSuccess"), AlertVariant.success);
} catch (error) {
addError("realm-settings:saveError", error);
}
};
return ( return (
<PageSection variant="light"> <PageSection variant="light">
<FormPanel <FormPanel