diff --git a/src/realm-settings/EmailTab.tsx b/src/realm-settings/EmailTab.tsx index 3c72f8fbdd..f2ee872ed6 100644 --- a/src/realm-settings/EmailTab.tsx +++ b/src/realm-settings/EmailTab.tsx @@ -10,7 +10,7 @@ import { } from "@patternfly/react-core"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; 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 { useTranslation } from "react-i18next"; import { useAlerts } from "../components/alert/Alerts"; @@ -41,7 +41,7 @@ export const RealmSettingsEmailTab = ({ const [realm, setRealm] = useState(initialRealm); const [userEmailModalOpen, setUserEmailModalOpen] = useState(false); - const [currentUser, setCurrentUser] = useState(); + const [currentUser, setCurrentUser] = useState(user); const { register, control, @@ -51,7 +51,7 @@ export const RealmSettingsEmailTab = ({ setValue, reset: resetForm, getValues, - } = useForm(); + } = useForm({ defaultValues: realm }); const userForm = useForm({ mode: "onChange" }); const watchFromValue = watch("smtpServer.from", ""); @@ -63,14 +63,6 @@ export const RealmSettingsEmailTab = ({ defaultValue: {}, }); - useEffect(() => { - reset(); - }, [realm]); - - useEffect(() => { - setCurrentUser(user); - }, []); - const handleModalToggle = () => { setUserEmailModalOpen(!userEmailModalOpen); }; @@ -89,10 +81,7 @@ export const RealmSettingsEmailTab = ({ const saveAndTestEmail = async (email?: UserRepresentation) => { if (email) { await adminClient.users.update({ id: whoAmI.getUserId() }, email); - const updated = await adminClient.users.findOne({ - id: whoAmI.getUserId(), - }); - setCurrentUser(updated); + setCurrentUser(email); await save(getValues()); testConnection(); @@ -322,7 +311,7 @@ export const RealmSettingsEmailTab = ({ ( { const activeKeysCopy = keys!.filter((i) => i.status === "ACTIVE"); return activeKeysCopy?.map((key) => { - const provider = realmComponents!.find( + const provider = realmComponents.find( (component: ComponentRepresentation) => component.id === key.providerId ); return { ...key, provider: provider?.name } as KeyData; @@ -89,7 +89,7 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => { const passiveKeys = keys!.filter((i) => i.status === "PASSIVE"); return passiveKeys?.map((key) => { - const provider = realmComponents!.find( + const provider = realmComponents.find( (component: ComponentRepresentation) => component.id === key.providerId ); return { ...key, provider: provider?.name } as KeyData; diff --git a/src/realm-settings/LocalizationTab.tsx b/src/realm-settings/LocalizationTab.tsx index 108fa644c0..617a52e2f2 100644 --- a/src/realm-settings/LocalizationTab.tsx +++ b/src/realm-settings/LocalizationTab.tsx @@ -66,16 +66,15 @@ export const LocalizationTab = ({ const { addAlert, addError } = useAlerts(); const { realm: currentRealm } = useRealm(); - const watchSupportedLocales = useWatch({ + const watchSupportedLocales = useWatch({ control, name: "supportedLocales", - defaultValue: themeTypes?.account![0].locales, }); const internationalizationEnabled = useWatch({ control, name: "internationalizationEnabled", - defaultValue: realm?.internationalizationEnabled, + defaultValue: false, }); const loader = async () => { @@ -112,7 +111,7 @@ export const LocalizationTab = ({ const options = [ {watchSupportedLocales - .filter((item) => item === realm?.defaultLocale) + ?.filter((item) => item === realm?.defaultLocale) .map((locale) => ( {t(`allSupportedLocales.${locale}`)} @@ -122,7 +121,7 @@ export const LocalizationTab = ({ , {watchSupportedLocales - .filter((item) => item !== realm?.defaultLocale) + ?.filter((item) => item !== realm?.defaultLocale) .map((locale) => ( {t(`allSupportedLocales.${locale}`)} @@ -218,7 +217,6 @@ export const LocalizationTab = ({ ( - {watchSupportedLocales.map( + {watchSupportedLocales?.map( (locale: string, idx: number) => ( void; - value: boolean; - save: () => void; - realmName: string; -}; +import { RealmSettingsTabs } from "./RealmSettingsTabs"; export const EditProviderCrumb = () => { 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 ( - <> - - - setPartialImportOpen(!partialImportOpen)} - /> - { - setPartialImportOpen(true); - }} - > - {t("partialImport")} - , - {t("partialExport")}, - , - - {t("common:delete")} - , - ]} - isEnabled={value} - onToggle={(value) => { - if (!value) { - toggleDisableDialog(); - } else { - onChange(value); - save(); - } - }} - /> - - ); -}; - const sortByPriority = (components: ComponentRepresentation[]) => { const sortedComponents = [...components].sort((a, b) => { const priorityA = Number(a.config?.priority); @@ -162,27 +45,13 @@ const sortByPriority = (components: ComponentRepresentation[]) => { }; export const RealmSettingsSection = () => { - const { t } = useTranslation("realm-settings"); const adminClient = useAdminClient(); - const { - 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: realmName } = useRealm(); const [realm, setRealm] = useState(); - const [activeTab, setActiveTab] = useState(0); const [realmComponents, setRealmComponents] = useState(); const [currentUser, setCurrentUser] = useState(); const { whoAmI } = useWhoAmI(); - const history = useHistory(); - - const kpComponentTypes = - useServerInfo().componentTypes?.[KEY_PROVIDER_TYPE] ?? []; useFetch( async () => { @@ -200,45 +69,9 @@ export const RealmSettingsSection = () => { setCurrentUser(user); 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) { return (
@@ -247,151 +80,10 @@ export const RealmSettingsSection = () => { ); } return ( - <> - ( - save(getValues())} - /> - )} - /> - - - - {t("general")}} - data-testid="rs-general-tab" - aria-label="general-tab" - > - resetForm(realm)} - /> - - {t("login")}} - data-testid="rs-login-tab" - aria-label="login-tab" - > - - - {t("email")}} - data-testid="rs-email-tab" - aria-label="email-tab" - > - - - {t("themes")}} - data-testid="rs-themes-tab" - aria-label="themes-tab" - > - resetForm(realm)} - realm={realm} - /> - - {t("realm-settings:keys")}} - data-testid="rs-keys-tab" - aria-label="keys-tab" - > - setActiveTab(Number(key))} - > - {t("keysList")}} - > - - - {t("providers")}} - > - - - - - {t("events")}} - data-testid="rs-realm-events-tab" - aria-label="realm-events-tab" - > - - - {t("localization")}} - > - resetForm(realm)} - realm={realm} - /> - - {t("securityDefences")}} - > - resetForm(realm)} /> - - {t("realm-settings:sessions")} - } - > - - - {t("realm-settings:tokens")}} - > - resetForm(realm)} - /> - - - - - + ); }; diff --git a/src/realm-settings/RealmSettingsTabs.tsx b/src/realm-settings/RealmSettingsTabs.tsx new file mode 100644 index 0000000000..1a17add98e --- /dev/null +++ b/src/realm-settings/RealmSettingsTabs.tsx @@ -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 ( + <> + + + setPartialImportOpen(!partialImportOpen)} + /> + { + setPartialImportOpen(true); + }} + > + {t("partialImport")} + , + {t("partialExport")}, + , + + {t("common:delete")} + , + ]} + 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 ( + <> + ( + save(getValues())} + /> + )} + /> + + + + {t("general")}} + data-testid="rs-general-tab" + aria-label="general-tab" + > + resetForm(realm)} + /> + + {t("login")}} + data-testid="rs-login-tab" + aria-label="login-tab" + > + + + {t("email")}} + data-testid="rs-email-tab" + aria-label="email-tab" + > + + + {t("themes")}} + data-testid="rs-themes-tab" + aria-label="themes-tab" + > + resetForm(realm)} + realm={realm} + /> + + {t("realm-settings:keys")}} + data-testid="rs-keys-tab" + aria-label="keys-tab" + > + setActiveTab(Number(key))} + > + {t("keysList")}} + > + + + {t("providers")}} + > + + + + + {t("events")}} + data-testid="rs-realm-events-tab" + aria-label="realm-events-tab" + > + + + {t("localization")}} + > + resetForm(realm)} + realm={realm} + /> + + {t("securityDefences")}} + > + resetForm(realm)} /> + + {t("realm-settings:sessions")} + } + > + + + {t("realm-settings:tokens")}} + > + resetForm(realm)} + /> + + + + + + ); +}; diff --git a/src/realm-settings/SessionsTab.tsx b/src/realm-settings/SessionsTab.tsx index fc8ece9592..cc652ebe38 100644 --- a/src/realm-settings/SessionsTab.tsx +++ b/src/realm-settings/SessionsTab.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; -import { Controller, useForm, useWatch } from "react-hook-form"; +import { Controller, useFormContext, useWatch } from "react-hook-form"; import { ActionGroup, - AlertVariant, Button, FormGroup, PageSection, @@ -14,55 +13,33 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r import { FormAccess } from "../components/form-access/FormAccess"; import { HelpItem } from "../components/help-enabler/HelpItem"; 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 "./RealmSettingsSection.css"; + type RealmSettingsSessionsTabProps = { - realm?: RealmRepresentation; - user?: UserRepresentation; + realm: RealmRepresentation; + save: (realm: RealmRepresentation) => void; }; export const RealmSettingsSessionsTab = ({ - realm: initialRealm, + realm, + save, }: RealmSettingsSessionsTabProps) => { const { t } = useTranslation("realm-settings"); - const adminClient = useAdminClient(); - const { realm: realmName } = useRealm(); - const { addAlert, addError } = useAlerts(); - - const [realm, setRealm] = useState(initialRealm); const { control, handleSubmit, reset: resetForm, formState, - } = useForm(); + } = useFormContext(); const offlineSessionMaxEnabled = useWatch({ control, 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 = () => { if (realm) { resetForm(realm); @@ -94,7 +71,7 @@ export const RealmSettingsSessionsTab = ({ > ( ( void; reset?: () => void; }; export const RealmSettingsTokensTab = ({ - realm: initialRealm, + realm, reset, + save, }: RealmSettingsSessionsTabProps) => { const { t } = useTranslation("realm-settings"); - const adminClient = useAdminClient(); - const { realm: realmName } = useRealm(); - const { addAlert, addError } = useAlerts(); const serverInfo = useServerInfo(); - const [realm, setRealm] = useState(initialRealm); const [defaultSigAlgDrpdwnIsOpen, setDefaultSigAlgDrpdwnOpen] = useState(false); @@ -78,28 +65,6 @@ export const RealmSettingsTokensTab = ({ 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 (