From 85b594d89174feaf5fd102c6248aff391fd72d6d Mon Sep 17 00:00:00 2001 From: Carla Martinez Date: Wed, 23 Aug 2023 14:52:01 +0200 Subject: [PATCH] Add functionality for Selectors The Select[1] components need to be adapted to take data from the metadata. This will be applied for the fields 'Radius proxy configuration' and 'External IdP configuration'. [1]- http://v4-archive.patternfly.org/v4/components/select Signed-off-by: Carla Martinez --- src/components/Form/IpaSelect.tsx | 91 ++++++++++++++++ src/components/UserSettings.tsx | 11 +- .../UsersSections/UsersAccountSettings.tsx | 101 ++++++------------ src/hooks/useUserSettingsData.tsx | 34 +++++- src/pages/ActiveUsers/ActiveUsersTabs.tsx | 2 + src/services/rpc.ts | 38 ++++++- src/utils/constUtils.ts | 4 + src/utils/datatypes/globalDataTypes.ts | 6 ++ src/utils/ipaObjectUtils.ts | 8 +- src/utils/userUtils.tsx | 21 ++++ src/utils/utils.tsx | 8 +- 11 files changed, 244 insertions(+), 80 deletions(-) create mode 100644 src/components/Form/IpaSelect.tsx create mode 100644 src/utils/constUtils.ts diff --git a/src/components/Form/IpaSelect.tsx b/src/components/Form/IpaSelect.tsx new file mode 100644 index 000000000..e6cb228f4 --- /dev/null +++ b/src/components/Form/IpaSelect.tsx @@ -0,0 +1,91 @@ +import React from "react"; +// PatternFly +import { + Select, + SelectOption, + SelectOptionObject, + SelectVariant, +} from "@patternfly/react-core"; +// Utils +import { + IPAParamDefinition, + getParamProperties, +} from "src/utils/ipaObjectUtils"; +import { updateIpaObject } from "src/utils/userUtils"; +import { NO_SELECTION_OPTION } from "src/utils/constUtils"; + +interface IPAParamDefinitionSelect extends IPAParamDefinition { + id?: string; + value?: string; + setIpaObject?: (ipaObject: Record) => void; + variant?: "single" | "checkbox" | "typeahead" | "typeaheadmulti"; + options: string[]; + selections?: string | SelectOptionObject | (string | SelectOptionObject)[]; + ariaLabelledBy?: string; +} + +const IpaSelect = (props: IPAParamDefinitionSelect) => { + // Obtains the metadata of the parameter + const { required, readOnly, value } = getParamProperties(props); + + // Handle selected value + let valueSelected = NO_SELECTION_OPTION; + if (value !== undefined && value && value !== "") { + valueSelected = value.toString(); + } + + const ipaObject = props.ipaObject || {}; + + const [isOpen, setIsOpen] = React.useState(false); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const onSelect = (selection: any) => { + let valueToUpdate = ""; + + if (selection.target.textContent !== NO_SELECTION_OPTION) { + valueToUpdate = selection.target.textContent as string; + } + + if (ipaObject && props.setIpaObject !== undefined) { + updateIpaObject(ipaObject, props.setIpaObject, valueToUpdate, props.name); + } + + setIsOpen(false); + }; + + // Provide empty option at the beginning of the list + const optionsToSelect: string[] = props.options; + React.useEffect(() => { + const optionsToSelect: string[] = props.options; + if (optionsToSelect.length > 0) { + optionsToSelect.unshift(NO_SELECTION_OPTION); + } + }, [props.options]); + // const optionsToSelect: string[] = props.elementsOptions || []; + // console.log("optionsToSelect: ", optionsToSelect); + // optionsToSelect.unshift(NO_SELECTION_OPTION); + // console.log("---------------"); + + return ( + + ); +}; + +export default IpaSelect; diff --git a/src/components/UserSettings.tsx b/src/components/UserSettings.tsx index e78d5a2e7..6eb66c430 100644 --- a/src/components/UserSettings.tsx +++ b/src/components/UserSettings.tsx @@ -16,7 +16,12 @@ import { // Icons import OutlinedQuestionCircleIcon from "@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon"; // Data types -import { Metadata, User } from "src/utils/datatypes/globalDataTypes"; +import { + Metadata, + User, + IDPServer, + RadiusServer, +} from "src/utils/datatypes/globalDataTypes"; // Layouts import ToolbarLayout from "src/components/layouts/ToolbarLayout"; import TitleLayout from "src/components/layouts/TitleLayout"; @@ -53,6 +58,8 @@ export interface PropsToUserSettings { isDataLoading?: boolean; modifiedValues: () => Partial; onResetValues: () => void; + radiusProxyData?: RadiusServer[]; + idpData?: IDPServer[]; from: "active-users" | "stage-users" | "preserved-users"; } @@ -254,6 +261,8 @@ const UserSettings = (props: PropsToUserSettings) => { onUserChange={props.onUserChange} metadata={props.metadata} onRefresh={props.onRefresh} + radiusProxyConf={props.radiusProxyData || []} + idpConf={props.idpData || []} /> ; onUserChange: (element: Partial) => void; metadata: Metadata; onRefresh: () => void; + radiusProxyConf: RadiusServer[]; + idpConf: IDPServer[]; } // Generic data to pass to the Textbox adder @@ -54,6 +59,14 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => { props.onUserChange ); + // Dropdown 'Radius proxy configuration' + const radiusProxyList = props.radiusProxyConf.map((item) => + item.cn.toString() + ); + + // Dropdown 'External IdP configuration' + const idpConfOptions = props.idpConf.map((item) => item.cn.toString()); + const [principalAliasList, setPrincipalAliasList] = useState([ { id: 0, @@ -415,38 +428,6 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => { /> ); - // Dropdown 'Radius proxy configuration' - const [isRadiusConfOpen, setIsRadiusConfOpen] = useState(false); - const [radiusConfSelected, setRadiusConfSelected] = useState(""); - const radiusConfOptions = [ - { value: "Option 1", disabled: false }, - { value: "Option 2", disabled: false }, - { value: "Option 3", disabled: false }, - ]; - const radiusConfOnToggle = (isOpen: boolean) => { - setIsRadiusConfOpen(isOpen); - }; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const radiusConfOnSelect = (selection: any) => { - setRadiusConfSelected(selection.target.textContent); - setIsRadiusConfOpen(false); - }; - - // Dropdown 'External IdP configuration' - const [isIdpConfOpen, setIsIdpConfOpen] = useState(false); - const [idpConfSelected, setIdpConfSelected] = useState(""); - const [idpConfOptions] = useState([]); - - const idpConfOnToggle = (isOpen: boolean) => { - setIsIdpConfOpen(isOpen); - }; - - const idpConfOnSelect = (selection: any) => { - setIdpConfSelected(selection.target.textContent); - setIsIdpConfOpen(false); - }; - // Messages for the popover const certificateMappingDataMessage = () => (
@@ -703,26 +684,15 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => { label="Radius proxy configuration" fieldId="radius-proxy-configuration" > - + options={radiusProxyList} + ipaObject={ipaObject} + setIpaObject={recordOnChange} + objectName="user" + metadata={props.metadata} + /> { label="External IdP configuration" fieldId="external-idp-configuration" > - + options={idpConfOptions} + ipaObject={ipaObject} + setIpaObject={recordOnChange} + objectName="user" + metadata={props.metadata} + /> ; refetch: () => void; modifiedValues: () => Partial; + radiusServers: RadiusServer[]; + idpServers: IDPServer[]; }; const useUserSettingsData = (userId: string): UserSettingsData => { @@ -30,14 +39,26 @@ const useUserSettingsData = (userId: string): UserSettingsData => { const metadata = metadataQuery.data || {}; const metadataLoading = metadataQuery.isLoading; + // [API call] User const userFullDataQuery = useGetUsersFullDataQuery(userId); const userFullData = userFullDataQuery.data; const isFullDataLoading = userFullDataQuery.isLoading; + // [API call] RADIUS proxy server + const radiusProxyQuery = useGetRadiusProxyQuery(); + const radiusProxyData = radiusProxyQuery.data; + const isRadiusProxyLoading = radiusProxyQuery.isLoading; + + // [API call] IdP server + const idpQuery = useGetIdpServerQuery(); + const idpData = idpQuery.data; + const isIdpLoading = idpQuery.isLoading; + const [modified, setModified] = useState(false); // Data displayed and modified by the user const [user, setUser] = useState>({}); + useEffect(() => { if (userFullData && !userFullDataQuery.isFetching) { setUser({ ...userFullData.user }); @@ -45,7 +66,11 @@ const useUserSettingsData = (userId: string): UserSettingsData => { }, [userFullData, userFullDataQuery.isFetching]); const settingsData = { - isLoading: metadataLoading || isFullDataLoading, + isLoading: + metadataLoading || + isFullDataLoading || + isRadiusProxyLoading || + isIdpLoading, isFetching: userFullDataQuery.isFetching, modified, metadata, @@ -59,6 +84,8 @@ const useUserSettingsData = (userId: string): UserSettingsData => { settingsData.pwPolicyData = userFullData.pwPolicy; settingsData.krbtPolicyData = userFullData.krbtPolicy; settingsData.certData = userFullData.cert; + settingsData.radiusServers = radiusProxyData || []; + settingsData.idpServers = idpData || []; } else { settingsData.originalUser = {}; } @@ -78,6 +105,7 @@ const useUserSettingsData = (userId: string): UserSettingsData => { }; settingsData.modifiedValues = getModifiedValues; + // Detect any change in 'originalUser' and 'user' objects useEffect(() => { if (!userFullData || !userFullData.user) { return; diff --git a/src/pages/ActiveUsers/ActiveUsersTabs.tsx b/src/pages/ActiveUsers/ActiveUsersTabs.tsx index 3de02e019..6c813d31b 100644 --- a/src/pages/ActiveUsers/ActiveUsersTabs.tsx +++ b/src/pages/ActiveUsers/ActiveUsersTabs.tsx @@ -99,6 +99,8 @@ const ActiveUsersTabs = () => { isModified={userSettingsData.modified} onResetValues={userSettingsData.resetValues} modifiedValues={userSettingsData.modifiedValues} + radiusProxyData={userSettingsData.radiusServers} + idpData={userSettingsData.idpServers} from="active-users" /> diff --git a/src/services/rpc.ts b/src/services/rpc.ts index bdaad5f51..e9dc4dc3a 100644 --- a/src/services/rpc.ts +++ b/src/services/rpc.ts @@ -8,7 +8,12 @@ import { } from "@reduxjs/toolkit/query/react"; // Utils import { API_VERSION_BACKUP } from "src/utils/utils"; -import { Metadata, User } from "src/utils/datatypes/globalDataTypes"; +import { + Metadata, + User, + IDPServer, + RadiusServer, +} from "src/utils/datatypes/globalDataTypes"; import { apiToUser } from "src/utils/userUtils"; export type UserFullData = { @@ -138,7 +143,12 @@ export const getBatchCommand = (commandData: Command[], apiVersion: string) => { export const api = createApi({ reducerPath: "api", baseQuery: fetchBaseQuery({ baseUrl: "/" }), // TODO: Global settings! - tagTypes: ["ObjectMetadata", "FullUserData"], + tagTypes: [ + "ObjectMetadata", + "FullUserData", + "RadiusServerData", + "IdpServerData", + ], endpoints: (build) => ({ simpleCommand: build.query({ query: (payloadData: Command) => getCommand(payloadData), @@ -296,6 +306,28 @@ export const api = createApi({ }, invalidatesTags: ["FullUserData"], }), + getRadiusProxy: build.query({ + query: () => { + return getCommand({ + method: "radiusproxy_find", + params: [[null], { version: API_VERSION_BACKUP }], + }); + }, + transformResponse: (response: FindRPCResponse): RadiusServer[] => + response.result.result as unknown as RadiusServer[], + providesTags: ["RadiusServerData"], + }), + getIdpServer: build.query({ + query: () => { + return getCommand({ + method: "idp_find", + params: [[null], { version: API_VERSION_BACKUP }], + }); + }, + transformResponse: (response: FindRPCResponse): IDPServer[] => + response.result.result as unknown as IDPServer[], + providesTags: ["IdpServerData"], + }), }), }); @@ -308,4 +340,6 @@ export const { useGetObjectMetadataQuery, useGetUsersFullDataQuery, useSaveUserMutation, + useGetRadiusProxyQuery, + useGetIdpServerQuery, } = api; diff --git a/src/utils/constUtils.ts b/src/utils/constUtils.ts new file mode 100644 index 000000000..228e271d8 --- /dev/null +++ b/src/utils/constUtils.ts @@ -0,0 +1,4 @@ +/* This file contains constants ready to use in any component */ + +// Selectors +export const NO_SELECTION_OPTION = "No selection"; diff --git a/src/utils/datatypes/globalDataTypes.ts b/src/utils/datatypes/globalDataTypes.ts index e3a50c932..7226531ee 100644 --- a/src/utils/datatypes/globalDataTypes.ts +++ b/src/utils/datatypes/globalDataTypes.ts @@ -198,3 +198,9 @@ export interface ParamMetadata { sortorder: number; type: string; } + +export interface RadiusServer { + ipatokenradiusserver: string; + cn: string; + dn: string; +} diff --git a/src/utils/ipaObjectUtils.ts b/src/utils/ipaObjectUtils.ts index cbc1f9d0a..089c42c59 100644 --- a/src/utils/ipaObjectUtils.ts +++ b/src/utils/ipaObjectUtils.ts @@ -25,7 +25,7 @@ export interface ParamProperties { paramMetadata: ParamMetadata; } -function getParamMetadata( +export function getParamMetadata( metadata: Metadata, objectName: string, paramName: string @@ -56,7 +56,7 @@ function isFieldWritable(acis: Record, attr: string): boolean { return false; } -function isWritable( +export function isWritable( paramMetadata: ParamMetadata, ipaObject?: IPAObject, alwaysWritable?: boolean @@ -93,7 +93,7 @@ function isWritable( return true; // we don't know, assume writable } -function isRequired( +export function isRequired( parDef: IPAParamDefinition, param: ParamMetadata, writable: boolean @@ -104,7 +104,7 @@ function isRequired( return (param && param.required) || false; } -function getValue( +export function getValue( ipaObject: Record | undefined, name: string ): BasicType { diff --git a/src/utils/userUtils.tsx b/src/utils/userUtils.tsx index 9bdb429ec..c2fc546a2 100644 --- a/src/utils/userUtils.tsx +++ b/src/utils/userUtils.tsx @@ -71,3 +71,24 @@ const dateValues = new Set(["krbpasswordexpiration", "krbprincipalexpiration"]); export function apiToUser(apiRecord: Record) { return convertApiObj(apiRecord, simpleValues, dateValues) as Partial; } + +// Determines whether a given property name is a simple value or is it multivalue (Array) +// - Returns: boolean +export const isSimpleValue = (propertyName) => { + return simpleValues.has(propertyName); +}; + +// Updates 'ipaObject' +export const updateIpaObject = ( + ipaObject: Record, + setIpaObject: (ipaObject: Record) => void, + newValue: string | string[], + paramName: string +) => { + if (!isSimpleValue(paramName)) { + const paramToModify = newValue as string[]; + setIpaObject({ ...ipaObject, [paramName]: paramToModify }); + } else { + setIpaObject({ ...ipaObject, [paramName]: newValue }); + } +}; diff --git a/src/utils/utils.tsx b/src/utils/utils.tsx index 4d646d948..16ccbb963 100644 --- a/src/utils/utils.tsx +++ b/src/utils/utils.tsx @@ -2,7 +2,13 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import React from "react"; // Data type -import { Host, Service, User } from "./datatypes/globalDataTypes"; +import { + Host, + IDPServer, + RadiusServer, + Service, + User, +} from "./datatypes/globalDataTypes"; // Errors import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query"; import { SerializedError } from "@reduxjs/toolkit";