From 69ae0e0922692a9ba99f40eb6882b30ea9d8f1fb Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 13:55:05 +0500 Subject: [PATCH 01/41] fix(admin-ui): unable to add Custom and Module properties in Scripts page (#2401) --- admin-ui/app/routes/Apps/Gluu/GluuDialog.tsx | 9 +- admin-ui/package.json | 3 + .../CustomScripts/CustomScriptAddPage.tsx | 9 +- .../CustomScripts/CustomScriptEditPage.tsx | 11 +- .../CustomScripts/CustomScriptForm.tsx | 266 +++++++----------- .../components/CustomScripts/types/forms.ts | 9 +- 6 files changed, 122 insertions(+), 185 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuDialog.tsx b/admin-ui/app/routes/Apps/Gluu/GluuDialog.tsx index 94af1fb807..9a1126771e 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuDialog.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuDialog.tsx @@ -41,6 +41,13 @@ const GluuDialog = ({ row, handler, modal, onAccept, subject, name, feature }: a } }, [userMessage]) + // Reset user message when modal opens with a new item + useEffect(() => { + if (modal) { + setUserMessage('') + } + }, [modal, row]) + function handleAccept() { onAccept(userMessage) } @@ -65,7 +72,7 @@ const GluuDialog = ({ row, handler, modal, onAccept, subject, name, feature }: a style={{ color: customColors.accentRed }} className="fa fa-2x fa-warning fa-fw modal-icon mb-3" > - {`${t('messages.action_deletion_for')} ${subject} (${name}${row.inum || row.id ? `-${row.inum || row.id}` : ''})`} + {`${t('messages.action_deletion_for')} ${subject} (${row.name || name || ''}${row.inum || row.id ? `-${row.inum || row.id}` : ''})`} {t('messages.action_deletion_question')} diff --git a/admin-ui/package.json b/admin-ui/package.json index 2529ccae7e..a942e6c8a9 100644 --- a/admin-ui/package.json +++ b/admin-ui/package.json @@ -33,6 +33,9 @@ "start:dev": "node \"./build/cli-tools.js\" --clear dist --create dist && webpack serve --config \"./config/webpack.config.client.dev.ts\"", "start:prod": "node \"./build/cli-tools.js\" --clear dist --create dist && webpack-dev-server --config \"./config/webpack.config.client.prod.ts\"", "lint": "eslint app/ plugins/ --ext .js,.jsx,.ts,.tsx --fix", + "lint:check": "eslint app/ plugins/ --ext .js,.jsx,.ts,.tsx", + "type-check": "tsc --noEmit", + "check:all": "npm run lint:check && npm run type-check", "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json}\"", "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json}\"", "api": "rimraf jans_config_api && openapi-merge-cli && openapi-generator-cli generate -i ./configApiSpecs.yaml --global-property skipFormModel=false -g javascript -o jans_config_api && cd jans_config_api && npm i mocha@10.2.0 && npm install && npm run build", diff --git a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptAddPage.tsx b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptAddPage.tsx index 37a3a573d7..0b58468c0d 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptAddPage.tsx +++ b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptAddPage.tsx @@ -5,15 +5,8 @@ import { CardBody, Card } from 'Components' import CustomScriptForm from './CustomScriptForm' import { addCustomScript } from 'Plugins/admin/redux/features/customScriptSlice' import { buildPayload } from 'Utils/PermChecker' -import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' import GluuLoader from 'Routes/Apps/Gluu/GluuLoader' -import { - CustomScriptItem, - CustomScriptReducerState, - RootState, - UserAction, - SubmitData, -} from './types' +import { CustomScriptItem, RootState, UserAction, SubmitData } from './types' function CustomScriptAddPage() { const userAction: UserAction = {} diff --git a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptEditPage.tsx b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptEditPage.tsx index 42bdccb048..1029d005ff 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptEditPage.tsx +++ b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptEditPage.tsx @@ -8,20 +8,11 @@ import { editCustomScript } from 'Plugins/admin/redux/features/customScriptSlice import { buildPayload } from 'Utils/PermChecker' import GluuAlert from 'Routes/Apps/Gluu/GluuAlert' import { useTranslation } from 'react-i18next' -import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' -import { - CustomScriptItem, - CustomScriptReducerState, - RootState, - UserAction, - SubmitData, - ModuleProperty, -} from './types' +import { RootState, UserAction, SubmitData, ModuleProperty } from './types' function CustomScriptEditPage() { const dispatch = useDispatch() const item = useSelector((state: RootState) => state.customScriptReducer.item) - const scripts = useSelector((state: RootState) => state.customScriptReducer.items) const loading = useSelector((state: RootState) => state.customScriptReducer.loading) const saveOperationFlag = useSelector( (state: RootState) => state.customScriptReducer.saveOperationFlag, diff --git a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx index a3560603a9..77fb10ae0f 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx +++ b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx @@ -1,4 +1,4 @@ -import React, { Suspense, lazy, useState, ChangeEvent } from 'react' +import React, { Suspense, lazy, useState, ChangeEvent, useMemo } from 'react' import { useFormik } from 'formik' import * as Yup from 'yup' import Toggle from 'react-toggle' @@ -19,14 +19,13 @@ import { adminUiFeatures } from 'Plugins/admin/helper/utils' import customColors from '@/customColors' import { CustomScriptFormProps, - CustomScriptItem, - CustomScriptReducerState, RootState, FormValues, ModuleProperty, ConfigurationProperty, ScriptType, } from './types' +import { CustomScriptItem } from './types/customScript' const GluuScriptErrorModal = lazy(() => import('Routes/Apps/Gluu/GluuScriptErrorModal')) const Counter = lazy(() => import('@/components/Widgets/GroupedButtons/Counter')) const GluuInputEditor = lazy(() => import('Routes/Apps/Gluu/GluuInputEditor')) @@ -58,47 +57,40 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript item.programmingLanguage, ) - function activate(): void { - if (!init) { - setInit(true) - } - } - - function toggle(): void { - setModal(!modal) - } + const activate = () => !init && setInit(true) + const toggle = () => setModal((prev) => !prev) - function submitForm(): void { + const submitForm = () => { toggle() const submitButton = document.getElementsByClassName( 'UserActionSubmitButton', )[0] as HTMLButtonElement - if (submitButton) { - submitButton.click() - } + submitButton?.click() } - function getPropertiesConfig( - entry: CustomScriptItem, - key: keyof CustomScriptItem, - ): Array<{ key: string; value: string }> { - const entryValue = entry[key] - if (entryValue && Array.isArray(entryValue)) { - return entryValue.map((e: any) => ({ - key: e.value1, - value: e.value2, - })) + // Helper functions to reduce repetition + const getModuleProperty = (key: string): string | undefined => { + return item?.moduleProperties?.find((p) => p.value1 === key)?.value2 + } + + const updateModuleProperty = (key: string, value: string): void => { + if (!item.moduleProperties) item.moduleProperties = [] + const index = item.moduleProperties.findIndex((p) => p.value1 === key) + + if (index >= 0) { + item.moduleProperties[index] = { value1: key, value2: value, description: '' } } else { - return [] + item.moduleProperties.push({ value1: key, value2: value, description: '' }) } } - const defaultScriptPathValue: string | undefined = - !!item?.moduleProperties && - item?.moduleProperties?.filter((i: ModuleProperty) => i.value1 === 'location_path').length > 0 - ? item?.moduleProperties?.filter((it: ModuleProperty) => it.value1 === 'location_path')[0] - .value2 - : undefined + const removeModuleProperty = (key: string): void => { + if (!item.moduleProperties) return + const index = item.moduleProperties.findIndex((p) => p.value1 === key) + if (index >= 0) item.moduleProperties.splice(index, 1) + } + + const defaultScriptPathValue: string | undefined = getModuleProperty('location_path') const formik = useFormik({ initialValues: { @@ -107,12 +99,12 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript scriptType: item.scriptType || '', programmingLanguage: item.programmingLanguage || '', level: item.level || 0, - script: item.script || '', + script: item.script, aliases: item.aliases || [], moduleProperties: item.moduleProperties || [], configurationProperties: item.configurationProperties || [], script_path: defaultScriptPathValue || '', - locationPath: item.locationPath || '', + locationPath: item.locationPath, location_type: item.locationType || '', enabled: item.enabled, }, @@ -139,52 +131,53 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript }), onSubmit: (values: FormValues) => { + const submitValues: FormValues = { ...values } + if (item.locationType === 'db') { const moduleProperties = item?.moduleProperties?.filter( (moduleItem: ModuleProperty) => moduleItem?.value1 !== 'location_path', ) item.moduleProperties = moduleProperties delete item?.locationPath - delete (values as any).locationPath + submitValues.locationPath = undefined } else if (item.locationType === 'file') { delete item?.script - delete (values as any).script + submitValues.script = undefined } - ;(values as any).level = item.level - - if (!!values.configurationProperties) { - ;(values as any).configurationProperties = values.configurationProperties - .filter((e: ConfigurationProperty) => e != null) - .filter((e: ConfigurationProperty) => Object.keys(e).length !== 0) - .map((e: ConfigurationProperty) => ({ - value1: e.key || e.value1, - value2: e.value || e.value2, - hide: false, - })) + submitValues.level = item.level || 0 + + const transformProperty = (prop: ModuleProperty | ConfigurationProperty) => ({ + value1: prop.key || prop.value1 || '', + value2: prop.value || prop.value2 || '', + hide: false, + }) + + if (values.configurationProperties) { + submitValues.configurationProperties = values.configurationProperties + .filter((e) => e != null && Object.keys(e).length !== 0) + .map(transformProperty) } - if (!!values.moduleProperties && item.locationType !== 'db') { - ;(values as any).moduleProperties = values.moduleProperties - .filter((e: ModuleProperty) => e != null) - .filter((e: ModuleProperty) => Object.keys(e).length !== 0) - .map((e: ModuleProperty) => ({ - value1: e.key || e.value1, - value2: e.value || e.value2, - hide: false, - })) + if (values.moduleProperties && item.locationType !== 'db') { + submitValues.moduleProperties = values.moduleProperties + .filter((e) => e != null && Object.keys(e).length !== 0) + .map(transformProperty) } if (typeof values.enabled == 'object') { if (Array.isArray(values.enabled) && values.enabled.length > 0) { - ;(values as any).enabled = true + submitValues.enabled = true } else { - ;(values as any).enabled = false + submitValues.enabled = false } } - const result = Object.assign(item, values) + const result = Object.assign(item, submitValues) const reqBody = { customScript: result } // Remove internal form fields that shouldn't be sent to the API - const customScript = reqBody.customScript as any + const customScript = result as CustomScriptItem & { + script_path?: string + location_type?: string + } delete customScript.script_path delete customScript.location_type @@ -195,100 +188,62 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript }, }) - function locationTypeChange(value: string): void { - if (value !== '') { - if (!item.moduleProperties) { - item.moduleProperties = [] - } - if (value === 'db') { - const moduleProperties = item?.moduleProperties?.filter( - (moduleItem: ModuleProperty) => moduleItem?.value1 !== 'location_path', - ) - item.moduleProperties = moduleProperties - formik.setFieldValue('script_path', undefined) - } else if (value === 'file') { - delete item.script - formik.setFieldValue('script', undefined) - } - if ( - item.moduleProperties.filter( - (candidate: ModuleProperty) => candidate.value1 === 'location_type', - ).length > 0 - ) { - item.moduleProperties.splice( - item.moduleProperties.findIndex((el: ModuleProperty) => el.value1 === 'location_type'), - 1, - ) - } - item.moduleProperties.push({ - value1: 'location_type', - value2: value, - description: '', - }) + const locationTypeChange = (value: string): void => { + if (!value) return + + if (value === 'db') { + item.moduleProperties = item?.moduleProperties?.filter((p) => p.value1 !== 'location_path') + formik.setFieldValue('script_path', undefined) + } else if (value === 'file') { + delete item.script + formik.setFieldValue('script', undefined) } + + removeModuleProperty('location_type') + updateModuleProperty('location_type', value) + item.locationType = value formik.setFieldValue('location_type', value) - if (value === 'file') { - setScriptPath(true) - } else { - setScriptPath(false) - } + setScriptPath(value === 'file') } - function scriptPathChange(value: string): void { - if (value !== '') { - formik.setFieldValue('locationPath', value) - if (!item.moduleProperties) { - item.moduleProperties = [] - } - - if ( - item.moduleProperties.filter( - (candidate: ModuleProperty) => candidate.value1 === 'location_path', - ).length > 0 - ) { - item.moduleProperties.splice( - item.moduleProperties.findIndex((el: ModuleProperty) => el.value1 === 'location_path'), - 1, - ) - } - item.moduleProperties.push({ - value1: 'location_path', - value2: value, - description: '', - }) - } + const scriptPathChange = (value: string): void => { + if (!value) return + formik.setFieldValue('locationPath', value) + removeModuleProperty('location_path') + updateModuleProperty('location_path', value) } - function usageTypeChange(value: string): void { - if (value !== '') { - if (!item.moduleProperties) { - item.moduleProperties = [] - } - if ( - item.moduleProperties.filter((ligne: ModuleProperty) => ligne.value1 === 'usage_type') - .length > 0 - ) { - item.moduleProperties.splice( - item.moduleProperties.findIndex((row: ModuleProperty) => row.value1 === 'usage_type'), - 1, - ) - } - item.moduleProperties.push({ - value1: 'usage_type', - value2: value, - description: '', - }) - } + const usageTypeChange = (value: string): void => { + if (!value) return + removeModuleProperty('usage_type') + updateModuleProperty('usage_type', value) } - function onLevelChange(level: number): void { + const onLevelChange = (level: number): void => { item.level = level } - const showErrorModal = (): void => { - setIsModalOpen(true) - } + const showErrorModal = (): void => setIsModalOpen(true) + + // Memoize the options to prevent unnecessary re-renders in GluuProperties + const configurationPropertiesOptions = useMemo(() => { + return ( + formik.values.configurationProperties?.map((e) => ({ + key: e.key || e.value1 || '', + value: e.value || e.value2 || '', + })) || [] + ) + }, [formik.values.configurationProperties]) + + const modulePropertiesOptions = useMemo(() => { + return ( + formik.values.moduleProperties?.map((e) => ({ + key: e.key || e.value1 || '', + value: e.value || e.value2 || '', + })) || [] + ) + }, [formik.values.moduleProperties]) return ( <> @@ -473,15 +428,7 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript id="location_type" name="location_type" disabled={viewOnly} - defaultValue={ - !!item.moduleProperties && - item.moduleProperties.filter((i: ModuleProperty) => i.value1 === 'location_type') - .length > 0 - ? item.moduleProperties.filter( - (it: ModuleProperty) => it.value1 === 'location_type', - )[0].value2 - : undefined - } + defaultValue={getModuleProperty('location_type')} onChange={(e: ChangeEvent) => { locationTypeChange(e.target.value) }} @@ -533,16 +480,7 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript id="usage_type" name="usage_type" disabled={viewOnly} - defaultValue={ - !!item.moduleProperties && - item.moduleProperties.filter( - (vItem: ModuleProperty) => vItem.value1 === 'usage_type', - ).length > 0 - ? item.moduleProperties.filter( - (kItem: ModuleProperty) => kItem.value1 === 'usage_type', - )[0].value2 - : undefined - } + defaultValue={getModuleProperty('usage_type')} onChange={(e: ChangeEvent) => { usageTypeChange(e.target.value) }} @@ -576,7 +514,7 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript formik={formik} keyPlaceholder={t('placeholders.enter_property_key')} valuePlaceholder={t('placeholders.enter_property_value')} - options={getPropertiesConfig(item, 'configurationProperties')} + options={configurationPropertiesOptions} disabled={viewOnly} > {!scriptPath && ( diff --git a/admin-ui/plugins/admin/components/CustomScripts/types/forms.ts b/admin-ui/plugins/admin/components/CustomScripts/types/forms.ts index 9d40953312..4a0ef560f4 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/types/forms.ts +++ b/admin-ui/plugins/admin/components/CustomScripts/types/forms.ts @@ -18,12 +18,17 @@ export interface FormValues { scriptType: string programmingLanguage: string level: number - script: string + script?: string aliases: string[] moduleProperties: ModuleProperty[] configurationProperties: ConfigurationProperty[] script_path: string - locationPath: string + locationPath?: string location_type: string enabled?: boolean | string[] } + +// Utility type for property option mapping +export type PropertyOptionMap = ( + properties: Array | undefined, +) => Array<{ key: string; value: string }> From 38a4851faf815585496523f2b0f9c485afcccad2 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 14:59:07 +0500 Subject: [PATCH 02/41] code rabbit suggestions --- .../app/routes/Apps/Gluu/GluuProperties.tsx | 47 +++++--- admin-ui/app/utils/Util.ts | 16 +++ .../CustomScripts/CustomScriptForm.tsx | 106 ++++++++---------- 3 files changed, 95 insertions(+), 74 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx b/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx index 7e511d10e7..813c7680a4 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx @@ -51,7 +51,18 @@ function GluuProperties({ } else { item = { key: '', value: '' } } - setProperties((prev: any) => [...prev, item]) + const newProperties = [...properties, item] + setProperties(newProperties) + + // Sync with formik + if (formik && compName) { + if (!isKeys && !multiProperties) { + const valuesOnly = newProperties.map((property: any) => property.value) + formik.setFieldValue(compName, valuesOnly) + } else { + formik.setFieldValue(compName, newProperties) + } + } } const changeProperty = (position: any, e: any) => { const { name, value } = e.target @@ -59,12 +70,14 @@ function GluuProperties({ newDataArr[position] = { ...newDataArr[position], [name]: value } setProperties(newDataArr) - // When isKeys is false and not using multiProperties, extract only the values as strings for formik - if (!isKeys && !multiProperties) { - const valuesOnly = newDataArr.map((item: any) => item.value) - formik.setFieldValue(compName, valuesOnly) - } else { - formik.setFieldValue(compName, newDataArr) + // Sync with formik + if (formik && compName) { + if (!isKeys && !multiProperties) { + const valuesOnly = newDataArr.map((item: any) => item.value) + formik.setFieldValue(compName, valuesOnly) + } else { + formik.setFieldValue(compName, newDataArr) + } } } const removeProperty = (position: any) => { @@ -73,15 +86,17 @@ function GluuProperties({ data = data.filter((element: any) => element != null) setProperties(data) - // When isKeys is false and not using multiProperties, extract only the values as strings for formik - if (!isKeys && !multiProperties) { - const valuesOnly = data.filter((element) => element != null).map((item: any) => item.value) - formik.setFieldValue(compName, valuesOnly) - } else { - formik.setFieldValue( - compName, - data.filter((element) => element != null), - ) + // Sync with formik + if (formik && compName) { + if (!isKeys && !multiProperties) { + const valuesOnly = data.filter((element) => element != null).map((item: any) => item.value) + formik.setFieldValue(compName, valuesOnly) + } else { + formik.setFieldValue( + compName, + data.filter((element) => element != null), + ) + } } } diff --git a/admin-ui/app/utils/Util.ts b/admin-ui/app/utils/Util.ts index af13452e48..e7ebc3192a 100644 --- a/admin-ui/app/utils/Util.ts +++ b/admin-ui/app/utils/Util.ts @@ -76,3 +76,19 @@ export const trimObjectStrings = >(obj: T): T } return trimmed as T } + +export const filterEmptyObjects = (items?: T[]): T[] => { + return (items || []).filter((item) => item && Object.keys(item).length !== 0) +} + +export const mapPropertyToKeyValue = (prop: { + key?: string + value?: string + value1?: string + value2?: string +}): { key: string; value: string } => { + return { + key: prop.key || prop.value1 || '', + value: prop.value || prop.value2 || '', + } +} diff --git a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx index 77fb10ae0f..ed09d26f10 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx +++ b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx @@ -26,6 +26,7 @@ import { ScriptType, } from './types' import { CustomScriptItem } from './types/customScript' +import { filterEmptyObjects, mapPropertyToKeyValue } from 'Utils/Util' const GluuScriptErrorModal = lazy(() => import('Routes/Apps/Gluu/GluuScriptErrorModal')) const Counter = lazy(() => import('@/components/Widgets/GroupedButtons/Counter')) const GluuInputEditor = lazy(() => import('Routes/Apps/Gluu/GluuInputEditor')) @@ -43,15 +44,10 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript if (!item.moduleProperties) { return false } - if ( - item.moduleProperties.filter((i: ModuleProperty) => i.value1 === 'location_type').length > 0 - ) { - return ( - item.moduleProperties.filter((it: ModuleProperty) => it.value1 === 'location_type')[0] - .value2 === 'file' - ) - } - return false + const locationTypeProp = item.moduleProperties.find( + (prop: ModuleProperty) => prop.value1 === 'location_type', + ) + return locationTypeProp?.value2 === 'file' }) const [selectedLanguage, setSelectedLanguage] = useState( item.programmingLanguage, @@ -68,30 +64,20 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript submitButton?.click() } - // Helper functions to reduce repetition - const getModuleProperty = (key: string): string | undefined => { - return item?.moduleProperties?.find((p) => p.value1 === key)?.value2 - } - - const updateModuleProperty = (key: string, value: string): void => { - if (!item.moduleProperties) item.moduleProperties = [] - const index = item.moduleProperties.findIndex((p) => p.value1 === key) - - if (index >= 0) { - item.moduleProperties[index] = { value1: key, value2: value, description: '' } - } else { - item.moduleProperties.push({ value1: key, value2: value, description: '' }) - } - } - - const removeModuleProperty = (key: string): void => { - if (!item.moduleProperties) return - const index = item.moduleProperties.findIndex((p) => p.value1 === key) - if (index >= 0) item.moduleProperties.splice(index, 1) + const getModuleProperty = (key: string, properties?: ModuleProperty[]): string | undefined => { + const moduleProps = properties || item?.moduleProperties || [] + return moduleProps.find((p) => p.value1 === key)?.value2 } const defaultScriptPathValue: string | undefined = getModuleProperty('location_path') + // Helper to transform property format for API (key/value → value1/value2) + const transformPropertyForApi = (prop: ModuleProperty | ConfigurationProperty) => ({ + value1: prop.key || prop.value1 || '', + value2: prop.value || prop.value2 || '', + hide: false, + }) + const formik = useFormik({ initialValues: { name: item.name || '', @@ -101,8 +87,8 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript level: item.level || 0, script: item.script, aliases: item.aliases || [], - moduleProperties: item.moduleProperties || [], - configurationProperties: item.configurationProperties || [], + moduleProperties: filterEmptyObjects(item.moduleProperties), + configurationProperties: filterEmptyObjects(item.configurationProperties), script_path: defaultScriptPathValue || '', locationPath: item.locationPath, location_type: item.locationType || '', @@ -147,21 +133,15 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript submitValues.level = item.level || 0 - const transformProperty = (prop: ModuleProperty | ConfigurationProperty) => ({ - value1: prop.key || prop.value1 || '', - value2: prop.value || prop.value2 || '', - hide: false, - }) - if (values.configurationProperties) { - submitValues.configurationProperties = values.configurationProperties - .filter((e) => e != null && Object.keys(e).length !== 0) - .map(transformProperty) + submitValues.configurationProperties = filterEmptyObjects( + values.configurationProperties, + ).map(transformPropertyForApi) } if (values.moduleProperties && item.locationType !== 'db') { - submitValues.moduleProperties = values.moduleProperties - .filter((e) => e != null && Object.keys(e).length !== 0) - .map(transformProperty) + submitValues.moduleProperties = filterEmptyObjects(values.moduleProperties).map( + transformPropertyForApi, + ) } if (typeof values.enabled == 'object') { if (Array.isArray(values.enabled) && values.enabled.length > 0) { @@ -188,18 +168,39 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript }, }) + const updateModuleProperty = (key: string, value: string): void => { + const currentProperties = formik.values.moduleProperties || [] + const index = currentProperties.findIndex((p) => p.value1 === key) + + let newProperties: ModuleProperty[] + if (index >= 0) { + newProperties = currentProperties.map((p, idx) => + idx === index ? { ...p, value1: key, value2: value, description: p.description || '' } : p, + ) + } else { + newProperties = [...currentProperties, { value1: key, value2: value, description: '' }] + } + formik.setFieldValue('moduleProperties', newProperties) + item.moduleProperties = newProperties + } + + const removeModuleProperty = (key: string): void => { + const currentProperties = formik.values.moduleProperties || [] + const newProperties = currentProperties.filter((p) => p.value1 !== key) + formik.setFieldValue('moduleProperties', newProperties) + item.moduleProperties = newProperties + } + const locationTypeChange = (value: string): void => { if (!value) return if (value === 'db') { - item.moduleProperties = item?.moduleProperties?.filter((p) => p.value1 !== 'location_path') formik.setFieldValue('script_path', undefined) } else if (value === 'file') { delete item.script formik.setFieldValue('script', undefined) } - removeModuleProperty('location_type') updateModuleProperty('location_type', value) item.locationType = value @@ -226,23 +227,12 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript const showErrorModal = (): void => setIsModalOpen(true) - // Memoize the options to prevent unnecessary re-renders in GluuProperties const configurationPropertiesOptions = useMemo(() => { - return ( - formik.values.configurationProperties?.map((e) => ({ - key: e.key || e.value1 || '', - value: e.value || e.value2 || '', - })) || [] - ) + return filterEmptyObjects(formik.values.configurationProperties)?.map(mapPropertyToKeyValue) }, [formik.values.configurationProperties]) const modulePropertiesOptions = useMemo(() => { - return ( - formik.values.moduleProperties?.map((e) => ({ - key: e.key || e.value1 || '', - value: e.value || e.value2 || '', - })) || [] - ) + return filterEmptyObjects(formik.values.moduleProperties)?.map(mapPropertyToKeyValue) }, [formik.values.moduleProperties]) return ( From a7c8332613a6e6f66d4f85f5737367c1abfb85cd Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 16:34:05 +0500 Subject: [PATCH 03/41] code rabbit suggestions --- admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx b/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx index 813c7680a4..db53f48464 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx @@ -89,13 +89,10 @@ function GluuProperties({ // Sync with formik if (formik && compName) { if (!isKeys && !multiProperties) { - const valuesOnly = data.filter((element) => element != null).map((item: any) => item.value) + const valuesOnly = data.map((item: { key: string; value: string }) => item.value) formik.setFieldValue(compName, valuesOnly) } else { - formik.setFieldValue( - compName, - data.filter((element) => element != null), - ) + formik.setFieldValue(compName, data) } } } From 5a84bf4a4d3368e9fcd14346e9b828d2a818d1af Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 16:40:49 +0500 Subject: [PATCH 04/41] code rabbit suggestions --- admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx b/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx index db53f48464..1e717d66f6 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx @@ -7,6 +7,11 @@ import { Tooltip as ReactTooltip } from 'react-tooltip' import { HelpOutline } from '@mui/icons-material' import customColors from '@/customColors' +// Property type definitions +type KeyValueProperty = { key: string; value: string } +type SourceDestinationProperty = { source: string; destination: string } +type Property = KeyValueProperty | SourceDestinationProperty + function GluuProperties({ compName, label, @@ -42,7 +47,7 @@ function GluuProperties({ }, [options]) const addProperty = () => { - let item + let item: Property if (multiProperties) { item = { source: '', destination: '' } } else if (!isKeys) { @@ -57,7 +62,7 @@ function GluuProperties({ // Sync with formik if (formik && compName) { if (!isKeys && !multiProperties) { - const valuesOnly = newProperties.map((property: any) => property.value) + const valuesOnly = newProperties.map((property: KeyValueProperty) => property.value) formik.setFieldValue(compName, valuesOnly) } else { formik.setFieldValue(compName, newProperties) @@ -73,7 +78,7 @@ function GluuProperties({ // Sync with formik if (formik && compName) { if (!isKeys && !multiProperties) { - const valuesOnly = newDataArr.map((item: any) => item.value) + const valuesOnly = newDataArr.map((item: KeyValueProperty) => item.value) formik.setFieldValue(compName, valuesOnly) } else { formik.setFieldValue(compName, newDataArr) @@ -83,13 +88,13 @@ function GluuProperties({ const removeProperty = (position: any) => { let data = [...properties] delete data[position] - data = data.filter((element: any) => element != null) + data = data.filter((element: Property | undefined) => element != null) setProperties(data) // Sync with formik if (formik && compName) { if (!isKeys && !multiProperties) { - const valuesOnly = data.map((item: { key: string; value: string }) => item.value) + const valuesOnly = data.map((item: KeyValueProperty) => item.value) formik.setFieldValue(compName, valuesOnly) } else { formik.setFieldValue(compName, data) From 4809e9529f56accb650c9d5e41299c699d5914a6 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 17:02:35 +0500 Subject: [PATCH 05/41] pagination count issues fixed --- admin-ui/plugins/admin/redux/features/customScriptSlice.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/admin-ui/plugins/admin/redux/features/customScriptSlice.ts b/admin-ui/plugins/admin/redux/features/customScriptSlice.ts index 19bf82b955..111b8fd6c7 100644 --- a/admin-ui/plugins/admin/redux/features/customScriptSlice.ts +++ b/admin-ui/plugins/admin/redux/features/customScriptSlice.ts @@ -54,6 +54,7 @@ const customScriptSlice = createSlice({ state.errorInSaveOperationFlag = false if (action.payload?.data) { state.saveOperationFlag = true + state.totalItems = state.totalItems + 1 } else { state.saveOperationFlag = false } @@ -93,6 +94,7 @@ const customScriptSlice = createSlice({ if (action.payload?.inum) { const items = state.items.filter((item) => item.inum !== action.payload.inum) state.items = items + state.totalItems = Math.max(0, state.totalItems - 1) } else { state.saveOperationFlag = false state.errorInSaveOperationFlag = false From eae25f96e7b6ac3d8234be01c48a198999e006c4 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 18:15:32 +0500 Subject: [PATCH 06/41] fix(admin-ui): unable to map permission to a role using GUI (#2400) --- .../app/routes/Apps/Gluu/GluuTypeAhead.tsx | 74 +++++++++++++++---- .../admin/components/Mapping/MappingItem.js | 3 +- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx index 779daeff58..7a80e938b9 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx @@ -1,10 +1,15 @@ +import React, { useMemo, useCallback, MutableRefObject, memo } from 'react' import { FormGroup, Col } from 'Components' import { Typeahead } from 'react-bootstrap-typeahead' +import type { TypeaheadRef } from 'react-bootstrap-typeahead' import GluuLabel from '../Gluu/GluuLabel' import Typography from '@mui/material/Typography' import { createTheme, ThemeProvider } from '@mui/material/styles' import { useTranslation } from 'react-i18next' import customColors from '@/customColors' +import { FormikContextType } from 'formik' + +type Option = string | Record const theme = createTheme({ typography: { @@ -14,7 +19,32 @@ const theme = createTheme({ }, }) -function GluuTypeAhead({ +interface GluuTypeAheadProps { + label: string + labelKey?: string + name: string + value?: Option[] + options: Option[] + formik?: FormikContextType> | null + required?: boolean + doc_category?: string + doc_entry?: string + forwardRef?: MutableRefObject | null + onChange?: ((selected: Option[]) => void) | null + lsize?: number + rsize?: number + disabled?: boolean + showError?: boolean + errorMessage?: string + allowNew?: boolean + isLoading?: boolean + multiple?: boolean + hideHelperMessage?: boolean + minLength?: number + emptyLabel?: string +} + +const GluuTypeAhead = memo(function GluuTypeAhead({ label, labelKey, name, @@ -37,8 +67,30 @@ function GluuTypeAhead({ hideHelperMessage = false, minLength = 0, emptyLabel = 'fields.nothingToShowInTheList', -}: any) { +}: GluuTypeAheadProps) { const { t } = useTranslation() + + const selectedValue = useMemo( + () => (value !== undefined ? value : (formik?.values?.[name] as Option[]) || []), + [value, formik, name], + ) + + const handleChange = useCallback( + (selected: Option[]) => { + if (formik) { + formik.setFieldValue(name, selected) + if (onChange) { + onChange(selected) + } + } else if (onChange) { + onChange(selected) + } + }, + [formik, name, onChange], + ) + + const resolvedLabelKey = useMemo(() => labelKey || name, [labelKey, name]) + return ( {required ? ( @@ -63,23 +115,14 @@ function GluuTypeAhead({ disabled={disabled} ref={forwardRef} emptyLabel={t(emptyLabel)} - labelKey={labelKey || name} + labelKey={resolvedLabelKey} isLoading={isLoading} minLength={minLength} - onChange={(selected) => { - if (formik) { - formik.setFieldValue(name, selected) - if (onChange) { - onChange(selected) - } - } else if (onChange) { - onChange(selected) - } - }} + onChange={handleChange} id={name} data-testid={name} multiple={multiple} - selected={value || []} + selected={selectedValue} options={options} /> {!hideHelperMessage && ( @@ -93,5 +136,6 @@ function GluuTypeAhead({ ) -} +}) + export default GluuTypeAhead diff --git a/admin-ui/plugins/admin/components/Mapping/MappingItem.js b/admin-ui/plugins/admin/components/Mapping/MappingItem.js index b636682268..a947c6c9ec 100644 --- a/admin-ui/plugins/admin/components/Mapping/MappingItem.js +++ b/admin-ui/plugins/admin/components/Mapping/MappingItem.js @@ -100,7 +100,7 @@ function MappingItem({ candidate, roles }) { getPermissionsForSearch() }, [permissions, candidate?.permissions?.length, cedarPermissions]) - const initialValues = {} + const initialValues = { mappingAddPermissions: [] } const handleAddPermission = (values, { resetForm }) => { if (values?.mappingAddPermissions?.length) { @@ -200,7 +200,6 @@ function MappingItem({ candidate, roles }) { formik={formik} options={searchablePermissions} required={false} - value={[]} forwardRef={autocompleteRef} doc_category={'Mapping'} allowNew={false} From 94b105be99b06d173d7f2e22b0d305c9d5a88318 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 20:03:26 +0500 Subject: [PATCH 07/41] fix(admin-ui): adding uniformity in cancel and back buttons present in SCIM forms (#2361) --- admin-ui/app/locales/en/translation.json | 1 + admin-ui/app/locales/es/translation.json | 1 + admin-ui/app/locales/fr/translation.json | 1 + admin-ui/app/locales/pt/translation.json | 1 + .../app/routes/Apps/Gluu/GluuCommitFooter.tsx | 331 +++++++++++++++--- admin-ui/app/utils/formUtils.ts | 57 +++ admin-ui/app/utils/storageUtils.ts | 78 +++++ .../fido/components/DynamicConfiguration.tsx | 2 +- .../fido/components/StaticConfiguration.tsx | 2 +- .../scim/components/ScimConfiguration.tsx | 50 ++- admin-ui/plugins/scim/helper/index.ts | 2 + admin-ui/plugins/scim/helper/schema.ts | 66 ++++ admin-ui/plugins/scim/helper/validations.ts | 77 +--- 13 files changed, 531 insertions(+), 138 deletions(-) create mode 100644 admin-ui/app/utils/formUtils.ts create mode 100644 admin-ui/app/utils/storageUtils.ts create mode 100644 admin-ui/plugins/scim/helper/schema.ts diff --git a/admin-ui/app/locales/en/translation.json b/admin-ui/app/locales/en/translation.json index a8925257ea..b38663b6f4 100644 --- a/admin-ui/app/locales/en/translation.json +++ b/admin-ui/app/locales/en/translation.json @@ -20,6 +20,7 @@ "configuration_copied": "Configuration copied", "copy_configuration": "Copy configuration", "apply": "Apply", + "back": "Back", "back_home": "Back Home", "cancel": "Cancel", "currentMonth": "Current Month", diff --git a/admin-ui/app/locales/es/translation.json b/admin-ui/app/locales/es/translation.json index 756d139c65..0746e79e36 100644 --- a/admin-ui/app/locales/es/translation.json +++ b/admin-ui/app/locales/es/translation.json @@ -20,6 +20,7 @@ "configuration_copied": "Configuración copiada", "copy_configuration": "Copiar configuración", "apply": "Aplicar", + "back": "Atrás", "back_home": "Volver al inicio", "cancel": "Cancelar", "currentMonth": "Mes actual", diff --git a/admin-ui/app/locales/fr/translation.json b/admin-ui/app/locales/fr/translation.json index 991e0a612c..6c927e8f97 100644 --- a/admin-ui/app/locales/fr/translation.json +++ b/admin-ui/app/locales/fr/translation.json @@ -131,6 +131,7 @@ "reject": "Rejeter", "add_header": "Ajouter un en-tête", "apply": "Appliquer", + "back": "Retour", "back_home": "Retour à la maison", "cancel": "Annuler", "clear": "Effacer", diff --git a/admin-ui/app/locales/pt/translation.json b/admin-ui/app/locales/pt/translation.json index 3f9a8ce640..411570d108 100644 --- a/admin-ui/app/locales/pt/translation.json +++ b/admin-ui/app/locales/pt/translation.json @@ -129,6 +129,7 @@ "add_mapping": "Add Mapping", "add_property": "Adicionar propriedade", "apply": "Aplicar", + "back": "Voltar", "back_home": "Voltar para casa", "add_server": "Adicionar servidor", "add_base_dn": "Adicionar DN básico", diff --git a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx index 617738f890..5d92d078c6 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx @@ -1,4 +1,5 @@ -import React, { useContext } from 'react' +import { useContext, useMemo, useCallback, memo } from 'react' +import { useNavigate } from 'react-router-dom' import { Button, Divider } from 'Components' import { useTranslation } from 'react-i18next' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' @@ -6,9 +7,24 @@ import { ThemeContext } from 'Context/theme/themeContext' import { Box } from '@mui/material' interface GluuCommitFooterProps { + showBack?: boolean + backButtonLabel?: string + backButtonHandler?: () => void + onBack?: () => void + disableBack?: boolean + showCancel?: boolean + cancelButtonLabel?: string + cancelHandler?: () => void + onCancel?: () => void + disableCancel?: boolean + showApply?: boolean + applyHandler?: () => void + onApply?: () => void + disableApply?: boolean + applyButtonType?: 'button' | 'submit' extraOnClick?: () => void - saveHandler?: () => void extraLabel?: string + saveHandler?: () => void hideButtons?: { save?: boolean back?: boolean @@ -19,100 +35,321 @@ interface GluuCommitFooterProps { } type?: 'button' | 'submit' disableBackButton?: boolean - cancelHandler?: () => void - backButtonLabel?: string - backButtonHandler?: () => void + isLoading?: boolean + className?: string } -function GluuCommitFooter({ +const GluuCommitFooter = memo(function GluuCommitFooter({ + showBack, + backButtonLabel, + backButtonHandler, + onBack, + disableBack, + showCancel, + cancelButtonLabel, + cancelHandler: newCancelHandler, + onCancel, + disableCancel, + showApply, + applyHandler, + onApply, + disableApply, + applyButtonType = 'submit', extraOnClick, saveHandler, extraLabel, hideButtons, disableButtons, type = 'button', - backButtonLabel, - backButtonHandler, disableBackButton = false, - cancelHandler = () => {}, + cancelHandler: legacyCancelHandler, + isLoading = false, + className = '', }: GluuCommitFooterProps) { const { t } = useTranslation() const theme = useContext(ThemeContext) - const selectedTheme = theme?.state.theme || 'darkBlack' + const selectedTheme = useMemo(() => theme?.state.theme || 'darkBlack', [theme?.state.theme]) + const navigate = useNavigate() + + const isNewApiUsed = useMemo( + () => showBack !== undefined || showCancel !== undefined || showApply !== undefined, + [showBack, showCancel, showApply], + ) + + const finalShowBack = useMemo(() => { + return isNewApiUsed ? showBack : !hideButtons || !hideButtons.back + }, [isNewApiUsed, showBack, hideButtons]) + + const finalShowCancel = useMemo(() => { + return isNewApiUsed ? showCancel : false + }, [isNewApiUsed, showCancel]) + + const finalShowApply = useMemo(() => { + return isNewApiUsed ? showApply : !hideButtons || !hideButtons.save + }, [isNewApiUsed, showApply, hideButtons]) - function goBack() { - if (backButtonHandler) { - backButtonHandler() + const finalBackHandler = useMemo(() => backButtonHandler || onBack, [backButtonHandler, onBack]) + const finalCancelHandler = useMemo( + () => newCancelHandler || legacyCancelHandler || onCancel, + [newCancelHandler, legacyCancelHandler, onCancel], + ) + const finalApplyHandler = useMemo( + () => applyHandler || onApply || saveHandler, + [applyHandler, onApply, saveHandler], + ) + const finalButtonType = useMemo(() => applyButtonType || type, [applyButtonType, type]) + const finalDisableBack = useMemo( + () => disableBack || disableButtons?.back, + [disableBack, disableButtons?.back], + ) + const finalDisableCancel = useMemo(() => disableCancel, [disableCancel]) + const finalDisableApply = useMemo( + () => disableApply || disableButtons?.save, + [disableApply, disableButtons?.save], + ) + + const handleBackClick = useCallback(() => { + if (disableBackButton && finalCancelHandler) { + finalCancelHandler() + } else if (finalBackHandler) { + finalBackHandler() } else { - window.history.back() + navigate('/home/dashboard') + } + }, [disableBackButton, finalCancelHandler, finalBackHandler, navigate]) + + const handleCancelClick = useCallback(() => { + if (finalCancelHandler) { + finalCancelHandler() } + }, [finalCancelHandler]) + + const buttonStates = useMemo(() => { + const showExtra = Boolean(extraLabel && extraOnClick) + const hasAnyButton = finalShowBack || finalShowCancel || finalShowApply || showExtra + const hasAllThreeButtons = finalShowBack && finalShowCancel && finalShowApply + const hasBackAndCancel = finalShowBack && finalShowCancel && !finalShowApply + + return { + showBack: finalShowBack, + showCancel: finalShowCancel, + showApply: finalShowApply, + showExtra, + hasAnyButton, + hasAllThreeButtons, + hasBackAndCancel, + } + }, [finalShowBack, finalShowCancel, finalShowApply, extraLabel, extraOnClick]) + + const buttonStyle = useMemo( + () => ({ ...applicationStyle.buttonStyle, ...applicationStyle.buttonFlexIconStyles }), + [], + ) + + const extraButtonStyle = useMemo(() => applicationStyle.buttonStyle, []) + const hiddenButtonStyle = useMemo(() => ({ visibility: 'hidden' as const }), []) + const buttonColor = useMemo(() => `primary-${selectedTheme}`, [selectedTheme]) + + const backLabel = useMemo(() => backButtonLabel || t('actions.back'), [backButtonLabel, t]) + const cancelLabel = useMemo( + () => cancelButtonLabel || t('actions.cancel'), + [cancelButtonLabel, t], + ) + const applyLabel = useMemo(() => t('actions.apply'), [t]) + const submitLabel = useMemo(() => t('actions.submit'), [t]) + + const buttonLayout = useMemo(() => { + if (buttonStates.hasAllThreeButtons) { + return { + back: 'd-flex', + cancel: 'd-flex', + apply: 'd-flex ms-auto', + } + } + if (buttonStates.hasBackAndCancel) { + return { + back: 'd-flex', + cancel: 'd-flex ms-auto', + apply: '', + } + } + if (finalShowBack && !finalShowCancel && !finalShowApply) { + return { + back: 'd-flex', + cancel: '', + apply: '', + } + } + if (finalShowCancel && !finalShowBack && !finalShowApply) { + return { + back: '', + cancel: 'd-flex', + apply: '', + } + } + if (finalShowBack && finalShowApply && !finalShowCancel) { + return { + back: 'd-flex', + cancel: '', + apply: 'd-flex ms-auto', + } + } + if (finalShowCancel && finalShowApply && !finalShowBack) { + return { + back: '', + cancel: 'd-flex', + apply: 'd-flex ms-auto', + } + } + if (finalShowApply && !finalShowBack && !finalShowCancel) { + return { + back: '', + cancel: '', + apply: 'd-flex ms-auto', + } + } + return { + back: 'd-flex', + cancel: 'd-flex', + apply: 'd-flex ms-auto', + } + }, [buttonStates, finalShowBack, finalShowCancel, finalShowApply]) + + const actualDisableBack = useMemo(() => { + if (finalShowBack) { + return false + } + return finalDisableBack + }, [finalShowBack, finalDisableBack]) + + if (!buttonStates.hasAnyButton) { + return null } return ( <> - - - {(!hideButtons || !hideButtons['back']) && ( + + + {buttonStates.showBack && ( )} - {extraLabel && extraOnClick && ( + + {buttonStates.showCancel && ( + + + + )} + + {buttonStates.showExtra && ( )} + - {type === 'submit' && ( + {buttonStates.showApply && finalButtonType === 'submit' && ( )} - {!hideButtons || !hideButtons['save'] ? ( + {buttonStates.showApply && finalButtonType === 'button' && ( - ) : null} + )} ) -} +}) export default GluuCommitFooter diff --git a/admin-ui/app/utils/formUtils.ts b/admin-ui/app/utils/formUtils.ts new file mode 100644 index 0000000000..33a68c6c07 --- /dev/null +++ b/admin-ui/app/utils/formUtils.ts @@ -0,0 +1,57 @@ +type Primitive = string | number | boolean | null | undefined + +export const isObjectEqual = >( + obj1: T, + obj2: T, +): boolean => { + if (obj1 === obj2) return true + if (obj1 == null || obj2 == null) return false + if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2 + + const keys1 = Object.keys(obj1) as Array + const keys2 = Object.keys(obj2) as Array + + if (keys1.length !== keys2.length) return false + + for (const key of keys1) { + if (!keys2.includes(key)) return false + + const val1 = obj1[key] + const val2 = obj2[key] + + if (typeof val1 === 'object' && typeof val2 === 'object') { + if (JSON.stringify(val1) !== JSON.stringify(val2)) return false + } else if (val1 !== val2) { + return false + } + } + + return true +} + +export const hasFormChanges = < + T extends Record>, +>( + currentValues: T, + initialValues: T, + excludeKeys: Array = [], +): boolean => { + const keys = (Object.keys(currentValues) as Array).filter( + (key) => !excludeKeys.includes(key), + ) + + for (const key of keys) { + const current = currentValues[key] + const initial = initialValues[key] + + if (typeof current === 'object' && typeof initial === 'object') { + if (JSON.stringify(current) !== JSON.stringify(initial)) { + return true + } + } else if (current !== initial) { + return true + } + } + + return false +} diff --git a/admin-ui/app/utils/storageUtils.ts b/admin-ui/app/utils/storageUtils.ts new file mode 100644 index 0000000000..4fd2b64822 --- /dev/null +++ b/admin-ui/app/utils/storageUtils.ts @@ -0,0 +1,78 @@ +type StorageValue = string | number | boolean | object | null + +export const getStorageItem = ( + key: string, + defaultValue: T, + parser?: (value: string) => T, +): T => { + try { + const stored = localStorage.getItem(key) + if (!stored) return defaultValue + + if (parser) { + return parser(stored) + } + + return stored as T + } catch (error) { + console.error(`Error reading from localStorage key "${key}":`, error) + return defaultValue + } +} + +export const setStorageItem = ( + key: string, + value: T, + serializer?: (value: T) => string, +): boolean => { + try { + const valueToStore = serializer ? serializer(value) : String(value) + localStorage.setItem(key, valueToStore) + return true + } catch (error) { + console.error(`Error writing to localStorage key "${key}":`, error) + return false + } +} + +export const removeStorageItem = (key: string): boolean => { + try { + localStorage.removeItem(key) + return true + } catch (error) { + console.error(`Error removing localStorage key "${key}":`, error) + return false + } +} + +export const getStorageNumber = (key: string, defaultValue: number): number => { + return getStorageItem(key, defaultValue, (value) => { + const parsed = parseInt(value, 10) + return isNaN(parsed) ? defaultValue : parsed + }) +} + +export const getStorageBoolean = (key: string, defaultValue: boolean): boolean => { + return getStorageItem(key, defaultValue, (value) => value === 'true') +} + +export const getStorageJSON = (key: string, defaultValue: T): T => { + try { + const stored = localStorage.getItem(key) + if (!stored) return defaultValue + return JSON.parse(stored) as T + } catch (error) { + console.error(`Error reading JSON from localStorage key "${key}":`, error) + return defaultValue + } +} + +export const setStorageJSON = (key: string, value: T): boolean => { + try { + localStorage.setItem(key, JSON.stringify(value)) + return true + } catch (error) { + console.error(`Error writing JSON to localStorage key "${key}":`, error) + return false + } +} diff --git a/admin-ui/plugins/fido/components/DynamicConfiguration.tsx b/admin-ui/plugins/fido/components/DynamicConfiguration.tsx index 28c2edf8c9..01bce7ceeb 100644 --- a/admin-ui/plugins/fido/components/DynamicConfiguration.tsx +++ b/admin-ui/plugins/fido/components/DynamicConfiguration.tsx @@ -308,7 +308,7 @@ const DynamicConfiguration: React.FC = ({ extraLabel="Cancel" extraOnClick={handleCancel} type="submit" - disabled={isSubmitting} + isLoading={isSubmitting} /> diff --git a/admin-ui/plugins/fido/components/StaticConfiguration.tsx b/admin-ui/plugins/fido/components/StaticConfiguration.tsx index a7c45f5a35..d28b811242 100644 --- a/admin-ui/plugins/fido/components/StaticConfiguration.tsx +++ b/admin-ui/plugins/fido/components/StaticConfiguration.tsx @@ -348,7 +348,7 @@ const StaticConfiguration: React.FC = ({ extraLabel="Cancel" extraOnClick={handleCancel} type="submit" - disabled={isSubmitting} + isLoading={isSubmitting} /> diff --git a/admin-ui/plugins/scim/components/ScimConfiguration.tsx b/admin-ui/plugins/scim/components/ScimConfiguration.tsx index 34f8cc07fe..ce27331895 100644 --- a/admin-ui/plugins/scim/components/ScimConfiguration.tsx +++ b/admin-ui/plugins/scim/components/ScimConfiguration.tsx @@ -1,10 +1,10 @@ import { useFormik, FormikProps } from 'formik' -import React, { useState, useCallback } from 'react' +import React, { useState, useCallback, useMemo } from 'react' import { Row, Col, Form, FormGroup } from 'Components' import GluuCommitDialog from 'Routes/Apps/Gluu/GluuCommitDialog' import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' -import { scimConfigurationValidationSchema } from '../helper/validations' -import { transformToFormValues } from '../helper' +import { scimConfigurationSchema } from '../helper/schema' +import { transformToFormValues, createJsonPatchFromDifferences } from '../helper' import ScimFieldRenderer from './ScimFieldRenderer' import { SCIM_FIELD_CONFIGS } from './fieldConfigurations' import { adminUiFeatures } from 'Plugins/admin/helper/utils' @@ -23,14 +23,30 @@ const ScimConfiguration: React.FC = ({ const formik: FormikProps = useFormik({ initialValues: transformToFormValues(scimConfiguration), - validationSchema: scimConfigurationValidationSchema, + validationSchema: scimConfigurationSchema, onSubmit: toggle, enableReinitialize: true, }) - const handleCancel = () => { - formik.resetForm() - } + const isFormDirty = useMemo(() => { + if (!scimConfiguration || !formik.values) { + return false + } + const { action_message, ...valuesWithoutAction } = formik.values + void action_message + const patches = createJsonPatchFromDifferences( + scimConfiguration, + valuesWithoutAction as ScimFormValues, + ) + return patches.length > 0 + }, [scimConfiguration, formik.values]) + + const isFormValid = useMemo(() => { + if (!formik.values) { + return false + } + return scimConfigurationSchema.isValidSync(formik.values) + }, [formik.values]) const submitForm = useCallback( (userMessage: string): void => { @@ -40,6 +56,10 @@ const ScimConfiguration: React.FC = ({ [handleSubmit, toggle, formik.values], ) + const handleCancel = useCallback(() => { + formik.resetForm() + }, [formik]) + const handleFormSubmit = useCallback( (e: React.FormEvent): void => { e.preventDefault() @@ -59,12 +79,16 @@ const ScimConfiguration: React.FC = ({ diff --git a/admin-ui/plugins/scim/helper/index.ts b/admin-ui/plugins/scim/helper/index.ts index 2072c42193..9c59d9f635 100644 --- a/admin-ui/plugins/scim/helper/index.ts +++ b/admin-ui/plugins/scim/helper/index.ts @@ -1,2 +1,4 @@ export * from './utils' export * from './constants' +export * from './schema' +export * from './validations' diff --git a/admin-ui/plugins/scim/helper/schema.ts b/admin-ui/plugins/scim/helper/schema.ts new file mode 100644 index 0000000000..26542f413a --- /dev/null +++ b/admin-ui/plugins/scim/helper/schema.ts @@ -0,0 +1,66 @@ +import * as Yup from 'yup' +import { PROTECTION_MODES } from './constants' + +const isSecureUrl = (value: string | undefined): boolean => { + if (!value) return true + try { + const url = new URL(value) + return url.protocol === 'http:' || url.protocol === 'https:' + } catch { + return false + } +} + +const isValidSecureUrl = (value: string | undefined): boolean => { + if (!value) return true + return isSecureUrl(value) +} + +export const scimConfigurationSchema = Yup.object({ + baseDN: Yup.string(), + applicationUrl: Yup.string().test( + 'valid-secure-url', + 'Application URL must be a valid URL with http:// or https:// protocol', + isValidSecureUrl, + ), + baseEndpoint: Yup.string(), + personCustomObjectClass: Yup.string(), + oxAuthIssuer: Yup.string(), + protectionMode: Yup.string().oneOf( + [...PROTECTION_MODES, ''], + 'Protection Mode must be OAUTH or BYPASS', + ), + maxCount: Yup.number() + .typeError('Max Count must be a number') + .positive('Max Count must be positive') + .integer('Max Count must be an integer') + .max(2147483647, 'Max Count cannot exceed 2,147,483,647'), + bulkMaxOperations: Yup.number() + .typeError('Bulk Max Operations must be a number') + .positive('Bulk Max Operations must be positive') + .integer('Bulk Max Operations must be an integer') + .max(100000, 'Bulk Max Operations cannot exceed 100,000'), + bulkMaxPayloadSize: Yup.number() + .typeError('Bulk Max Payload Size must be a number') + .positive('Bulk Max Payload Size must be positive') + .integer('Bulk Max Payload Size must be an integer') + .max(104857600, 'Bulk Max Payload Size cannot exceed 100 MB (104,857,600 bytes)'), + userExtensionSchemaURI: Yup.string(), + loggingLevel: Yup.string(), + loggingLayout: Yup.string(), + metricReporterInterval: Yup.number() + .typeError('Metric Reporter Interval must be a number') + .positive('Metric Reporter Interval must be positive') + .integer('Metric Reporter Interval must be an integer') + .max(86400, 'Metric Reporter Interval cannot exceed 86,400 seconds (24 hours)'), + metricReporterKeepDataDays: Yup.number() + .typeError('Metric Reporter Keep Data Days must be a number') + .positive('Metric Reporter Keep Data Days must be positive') + .integer('Metric Reporter Keep Data Days must be an integer') + .max(3650, 'Metric Reporter Keep Data Days cannot exceed 3,650 days (10 years)'), + metricReporterEnabled: Yup.boolean(), + disableJdkLogger: Yup.boolean(), + disableLoggerTimer: Yup.boolean(), + useLocalCache: Yup.boolean(), + skipDefinedPasswordValidation: Yup.boolean(), +}) diff --git a/admin-ui/plugins/scim/helper/validations.ts b/admin-ui/plugins/scim/helper/validations.ts index f3f4f60157..ba125b0aaa 100644 --- a/admin-ui/plugins/scim/helper/validations.ts +++ b/admin-ui/plugins/scim/helper/validations.ts @@ -1,76 +1 @@ -import * as Yup from 'yup' -import { PROTECTION_MODES } from './constants' - -/** - * Validates that a URL uses only allowed protocols (http/https) - * Prevents SSRF attacks via javascript:, file:, data:, etc. - * More permissive than Yup's .url() - allows URLs without TLDs (e.g., internal hostnames) - */ -const isSecureUrl = (value: string | undefined): boolean => { - if (!value) return true // Empty values are handled by .required() if needed - try { - const url = new URL(value) - return url.protocol === 'http:' || url.protocol === 'https:' - } catch { - return false // Invalid URL format - } -} - -/** - * Validates URL format and protocol together - * Allows hostnames without TLDs (internal services, k8s, docker, etc.) - */ -const isValidSecureUrl = (value: string | undefined): boolean => { - if (!value) return true - // Must be valid URL format AND use http/https protocol - return isSecureUrl(value) -} - -export const scimConfigurationValidationSchema = Yup.object({ - baseDN: Yup.string(), - applicationUrl: Yup.string().test( - 'valid-secure-url', - 'Application URL must be a valid URL with http:// or https:// protocol', - isValidSecureUrl, - ), - baseEndpoint: Yup.string(), - personCustomObjectClass: Yup.string(), - oxAuthIssuer: Yup.string(), - protectionMode: Yup.string().oneOf( - [...PROTECTION_MODES, ''], - 'Protection Mode must be OAUTH or BYPASS', - ), - maxCount: Yup.number() - .typeError('Max Count must be a number') - .positive('Max Count must be positive') - .integer('Max Count must be an integer') - .max(2147483647, 'Max Count cannot exceed 2,147,483,647'), - bulkMaxOperations: Yup.number() - .typeError('Bulk Max Operations must be a number') - .positive('Bulk Max Operations must be positive') - .integer('Bulk Max Operations must be an integer') - .max(100000, 'Bulk Max Operations cannot exceed 100,000'), - bulkMaxPayloadSize: Yup.number() - .typeError('Bulk Max Payload Size must be a number') - .positive('Bulk Max Payload Size must be positive') - .integer('Bulk Max Payload Size must be an integer') - .max(104857600, 'Bulk Max Payload Size cannot exceed 100 MB (104,857,600 bytes)'), - userExtensionSchemaURI: Yup.string(), - loggingLevel: Yup.string(), - loggingLayout: Yup.string(), - metricReporterInterval: Yup.number() - .typeError('Metric Reporter Interval must be a number') - .positive('Metric Reporter Interval must be positive') - .integer('Metric Reporter Interval must be an integer') - .max(86400, 'Metric Reporter Interval cannot exceed 86,400 seconds (24 hours)'), - metricReporterKeepDataDays: Yup.number() - .typeError('Metric Reporter Keep Data Days must be a number') - .positive('Metric Reporter Keep Data Days must be positive') - .integer('Metric Reporter Keep Data Days must be an integer') - .max(3650, 'Metric Reporter Keep Data Days cannot exceed 3,650 days (10 years)'), - metricReporterEnabled: Yup.boolean(), - disableJdkLogger: Yup.boolean(), - disableLoggerTimer: Yup.boolean(), - useLocalCache: Yup.boolean(), - skipDefinedPasswordValidation: Yup.boolean(), -}) +export { scimConfigurationSchema as scimConfigurationValidationSchema } from './schema' From b9fd682442ff88f469801902e66847b6d2c38ec8 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 21:00:39 +0500 Subject: [PATCH 08/41] Code rabbit suggestions --- .../app/routes/Apps/Gluu/GluuCommitFooter.tsx | 5 +-- .../app/routes/Apps/Gluu/GluuProperties.tsx | 5 ++- .../app/routes/Apps/Gluu/GluuTypeAhead.tsx | 9 +++-- admin-ui/app/utils/Util.ts | 7 ++-- admin-ui/app/utils/formUtils.ts | 24 +++----------- admin-ui/app/utils/storageUtils.ts | 8 +++-- .../CustomScripts/CustomScriptForm.tsx | 33 ++++++++++++++----- .../scim/components/ScimConfiguration.tsx | 2 +- admin-ui/plugins/scim/helper/schema.ts | 7 +--- 9 files changed, 49 insertions(+), 51 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx index 5d92d078c6..0013b5061b 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx @@ -216,10 +216,7 @@ const GluuCommitFooter = memo(function GluuCommitFooter({ }, [buttonStates, finalShowBack, finalShowCancel, finalShowApply]) const actualDisableBack = useMemo(() => { - if (finalShowBack) { - return false - } - return finalDisableBack + return finalShowBack ? finalDisableBack : true }, [finalShowBack, finalDisableBack]) if (!buttonStates.hasAnyButton) { diff --git a/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx b/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx index 1e717d66f6..2951758d84 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuProperties.tsx @@ -86,9 +86,8 @@ function GluuProperties({ } } const removeProperty = (position: any) => { - let data = [...properties] - delete data[position] - data = data.filter((element: Property | undefined) => element != null) + const data = [...properties] + data.splice(position, 1) setProperties(data) // Sync with formik diff --git a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx index 7a80e938b9..48c0cf6f2c 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx @@ -21,7 +21,7 @@ const theme = createTheme({ interface GluuTypeAheadProps { label: string - labelKey?: string + labelKey?: string | ((option: Option) => string) name: string value?: Option[] options: Option[] @@ -89,7 +89,12 @@ const GluuTypeAhead = memo(function GluuTypeAhead({ [formik, name, onChange], ) - const resolvedLabelKey = useMemo(() => labelKey || name, [labelKey, name]) + const resolvedLabelKey = useMemo(() => { + if (typeof labelKey === 'function' || typeof labelKey === 'string') { + return labelKey + } + return name + }, [labelKey, name]) return ( diff --git a/admin-ui/app/utils/Util.ts b/admin-ui/app/utils/Util.ts index e7ebc3192a..c7e3906900 100644 --- a/admin-ui/app/utils/Util.ts +++ b/admin-ui/app/utils/Util.ts @@ -87,8 +87,7 @@ export const mapPropertyToKeyValue = (prop: { value1?: string value2?: string }): { key: string; value: string } => { - return { - key: prop.key || prop.value1 || '', - value: prop.value || prop.value2 || '', - } + const key = String(prop.key || prop.value1 || '').trim() + const value = String(prop.value || prop.value2 || '').trim() + return { key, value } } diff --git a/admin-ui/app/utils/formUtils.ts b/admin-ui/app/utils/formUtils.ts index 33a68c6c07..b4076d5367 100644 --- a/admin-ui/app/utils/formUtils.ts +++ b/admin-ui/app/utils/formUtils.ts @@ -1,3 +1,5 @@ +import { isEqual } from 'lodash' + type Primitive = string | number | boolean | null | undefined export const isObjectEqual = >( @@ -8,25 +10,7 @@ export const isObjectEqual = >( if (obj1 == null || obj2 == null) return false if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2 - const keys1 = Object.keys(obj1) as Array - const keys2 = Object.keys(obj2) as Array - - if (keys1.length !== keys2.length) return false - - for (const key of keys1) { - if (!keys2.includes(key)) return false - - const val1 = obj1[key] - const val2 = obj2[key] - - if (typeof val1 === 'object' && typeof val2 === 'object') { - if (JSON.stringify(val1) !== JSON.stringify(val2)) return false - } else if (val1 !== val2) { - return false - } - } - - return true + return isEqual(obj1, obj2) } export const hasFormChanges = < @@ -45,7 +29,7 @@ export const hasFormChanges = < const initial = initialValues[key] if (typeof current === 'object' && typeof initial === 'object') { - if (JSON.stringify(current) !== JSON.stringify(initial)) { + if (!isEqual(current, initial)) { return true } } else if (current !== initial) { diff --git a/admin-ui/app/utils/storageUtils.ts b/admin-ui/app/utils/storageUtils.ts index 4fd2b64822..b33a7cce2c 100644 --- a/admin-ui/app/utils/storageUtils.ts +++ b/admin-ui/app/utils/storageUtils.ts @@ -7,7 +7,7 @@ export const getStorageItem = ( ): T => { try { const stored = localStorage.getItem(key) - if (!stored) return defaultValue + if (stored === null) return defaultValue if (parser) { return parser(stored) @@ -26,6 +26,10 @@ export const setStorageItem = ( serializer?: (value: T) => string, ): boolean => { try { + if (value === null) { + localStorage.removeItem(key) + return true + } const valueToStore = serializer ? serializer(value) : String(value) localStorage.setItem(key, valueToStore) return true @@ -59,7 +63,7 @@ export const getStorageBoolean = (key: string, defaultValue: boolean): boolean = export const getStorageJSON = (key: string, defaultValue: T): T => { try { const stored = localStorage.getItem(key) - if (!stored) return defaultValue + if (stored === null) return defaultValue return JSON.parse(stored) as T } catch (error) { console.error(`Error reading JSON from localStorage key "${key}":`, error) diff --git a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx index ed09d26f10..6eb22dbf92 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx +++ b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx @@ -72,11 +72,17 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript const defaultScriptPathValue: string | undefined = getModuleProperty('location_path') // Helper to transform property format for API (key/value → value1/value2) - const transformPropertyForApi = (prop: ModuleProperty | ConfigurationProperty) => ({ - value1: prop.key || prop.value1 || '', - value2: prop.value || prop.value2 || '', - hide: false, - }) + const transformPropertyForApi = (prop: ModuleProperty | ConfigurationProperty) => { + const baseResult = { + value1: prop.key || prop.value1 || '', + value2: prop.value || prop.value2 || '', + hide: prop.hide ?? false, + } + if ('description' in prop && prop.description) { + return { ...baseResult, description: prop.description } + } + return baseResult + } const formik = useFormik({ initialValues: { @@ -138,10 +144,19 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript values.configurationProperties, ).map(transformPropertyForApi) } - if (values.moduleProperties && item.locationType !== 'db') { - submitValues.moduleProperties = filterEmptyObjects(values.moduleProperties).map( - transformPropertyForApi, - ) + if (values.moduleProperties) { + if (item.locationType === 'db') { + const filteredProps = item?.moduleProperties?.filter( + (moduleItem: ModuleProperty) => moduleItem?.value1 !== 'location_path', + ) + submitValues.moduleProperties = filteredProps + ? filterEmptyObjects(filteredProps).map(transformPropertyForApi) + : [] + } else { + submitValues.moduleProperties = filterEmptyObjects(values.moduleProperties).map( + transformPropertyForApi, + ) + } } if (typeof values.enabled == 'object') { if (Array.isArray(values.enabled) && values.enabled.length > 0) { diff --git a/admin-ui/plugins/scim/components/ScimConfiguration.tsx b/admin-ui/plugins/scim/components/ScimConfiguration.tsx index ce27331895..a7abde15e7 100644 --- a/admin-ui/plugins/scim/components/ScimConfiguration.tsx +++ b/admin-ui/plugins/scim/components/ScimConfiguration.tsx @@ -84,7 +84,7 @@ const ScimConfiguration: React.FC = ({ showApply={true} onApply={toggle} onCancel={handleCancel} - disableBack={!isFormDirty} + disableBack={false} disableCancel={!isFormDirty} disableApply={!isFormValid || !isFormDirty} applyButtonType="submit" diff --git a/admin-ui/plugins/scim/helper/schema.ts b/admin-ui/plugins/scim/helper/schema.ts index 26542f413a..2a60fb3171 100644 --- a/admin-ui/plugins/scim/helper/schema.ts +++ b/admin-ui/plugins/scim/helper/schema.ts @@ -11,17 +11,12 @@ const isSecureUrl = (value: string | undefined): boolean => { } } -const isValidSecureUrl = (value: string | undefined): boolean => { - if (!value) return true - return isSecureUrl(value) -} - export const scimConfigurationSchema = Yup.object({ baseDN: Yup.string(), applicationUrl: Yup.string().test( 'valid-secure-url', 'Application URL must be a valid URL with http:// or https:// protocol', - isValidSecureUrl, + isSecureUrl, ), baseEndpoint: Yup.string(), personCustomObjectClass: Yup.string(), From 9bc53fff0f4f49d21f2d2e878fd3844cef9d014c Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 21:39:58 +0500 Subject: [PATCH 09/41] Code rabbit suggestions --- .../app/routes/Apps/Gluu/GluuTypeAhead.tsx | 4 ++-- admin-ui/app/utils/Util.ts | 4 ++-- admin-ui/app/utils/storageUtils.ts | 20 +++++++++++++++++++ .../CustomScripts/CustomScriptForm.tsx | 15 +++----------- .../scim/components/ScimConfiguration.tsx | 2 +- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx index 48c0cf6f2c..fe6396b6b3 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx @@ -93,8 +93,8 @@ const GluuTypeAhead = memo(function GluuTypeAhead({ if (typeof labelKey === 'function' || typeof labelKey === 'string') { return labelKey } - return name - }, [labelKey, name]) + return 'name' + }, [labelKey]) return ( diff --git a/admin-ui/app/utils/Util.ts b/admin-ui/app/utils/Util.ts index c7e3906900..9a97fa9285 100644 --- a/admin-ui/app/utils/Util.ts +++ b/admin-ui/app/utils/Util.ts @@ -87,7 +87,7 @@ export const mapPropertyToKeyValue = (prop: { value1?: string value2?: string }): { key: string; value: string } => { - const key = String(prop.key || prop.value1 || '').trim() - const value = String(prop.value || prop.value2 || '').trim() + const key = (prop.key ?? prop.value1 ?? '').trim() + const value = (prop.value ?? prop.value2 ?? '').trim() return { key, value } } diff --git a/admin-ui/app/utils/storageUtils.ts b/admin-ui/app/utils/storageUtils.ts index b33a7cce2c..5fbac8bb6a 100644 --- a/admin-ui/app/utils/storageUtils.ts +++ b/admin-ui/app/utils/storageUtils.ts @@ -13,6 +13,26 @@ export const getStorageItem = ( return parser(stored) } + // Auto-coerce based on defaultValue type + if (stored === 'null') return null as T + + if (typeof defaultValue === 'boolean') { + return (stored === 'true') as T + } + + if (typeof defaultValue === 'number') { + const parsed = Number(stored) + return (isNaN(parsed) ? defaultValue : parsed) as T + } + + if (typeof defaultValue === 'object' && defaultValue !== null) { + try { + return JSON.parse(stored) as T + } catch { + return defaultValue + } + } + return stored as T } catch (error) { console.error(`Error reading from localStorage key "${key}":`, error) diff --git a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx index 6eb22dbf92..811fa6055a 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx +++ b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx @@ -145,18 +145,9 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript ).map(transformPropertyForApi) } if (values.moduleProperties) { - if (item.locationType === 'db') { - const filteredProps = item?.moduleProperties?.filter( - (moduleItem: ModuleProperty) => moduleItem?.value1 !== 'location_path', - ) - submitValues.moduleProperties = filteredProps - ? filterEmptyObjects(filteredProps).map(transformPropertyForApi) - : [] - } else { - submitValues.moduleProperties = filterEmptyObjects(values.moduleProperties).map( - transformPropertyForApi, - ) - } + submitValues.moduleProperties = filterEmptyObjects(values.moduleProperties).map( + transformPropertyForApi, + ) } if (typeof values.enabled == 'object') { if (Array.isArray(values.enabled) && values.enabled.length > 0) { diff --git a/admin-ui/plugins/scim/components/ScimConfiguration.tsx b/admin-ui/plugins/scim/components/ScimConfiguration.tsx index a7abde15e7..69c78659c4 100644 --- a/admin-ui/plugins/scim/components/ScimConfiguration.tsx +++ b/admin-ui/plugins/scim/components/ScimConfiguration.tsx @@ -32,8 +32,8 @@ const ScimConfiguration: React.FC = ({ if (!scimConfiguration || !formik.values) { return false } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { action_message, ...valuesWithoutAction } = formik.values - void action_message const patches = createJsonPatchFromDifferences( scimConfiguration, valuesWithoutAction as ScimFormValues, From 847a60764a8e80a35e6a9b09c8da4a50dd459c59 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 21:54:40 +0500 Subject: [PATCH 10/41] Code rabbit suggestions --- .../app/routes/Apps/Gluu/GluuTypeAhead.tsx | 13 ++++++++----- admin-ui/app/utils/storageUtils.ts | 11 +++++++++-- .../CustomScripts/CustomScriptForm.tsx | 18 +++++++++++------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx index fe6396b6b3..43624dd611 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuTypeAhead.tsx @@ -70,10 +70,13 @@ const GluuTypeAhead = memo(function GluuTypeAhead({ }: GluuTypeAheadProps) { const { t } = useTranslation() - const selectedValue = useMemo( - () => (value !== undefined ? value : (formik?.values?.[name] as Option[]) || []), - [value, formik, name], - ) + const selectedValue = useMemo(() => { + if (value !== undefined) { + return value + } + const fieldValue = formik?.values?.[name] + return Array.isArray(fieldValue) ? (fieldValue as Option[]) : [] + }, [value, formik?.values?.[name], name]) const handleChange = useCallback( (selected: Option[]) => { @@ -86,7 +89,7 @@ const GluuTypeAhead = memo(function GluuTypeAhead({ onChange(selected) } }, - [formik, name, onChange], + [formik?.setFieldValue, name, onChange], ) const resolvedLabelKey = useMemo(() => { diff --git a/admin-ui/app/utils/storageUtils.ts b/admin-ui/app/utils/storageUtils.ts index 5fbac8bb6a..97a530ec48 100644 --- a/admin-ui/app/utils/storageUtils.ts +++ b/admin-ui/app/utils/storageUtils.ts @@ -50,7 +50,14 @@ export const setStorageItem = ( localStorage.removeItem(key) return true } - const valueToStore = serializer ? serializer(value) : String(value) + let valueToStore: string + if (serializer) { + valueToStore = serializer(value) + } else if (typeof value === 'object' && value !== null) { + valueToStore = JSON.stringify(value) + } else { + valueToStore = String(value) + } localStorage.setItem(key, valueToStore) return true } catch (error) { @@ -71,7 +78,7 @@ export const removeStorageItem = (key: string): boolean => { export const getStorageNumber = (key: string, defaultValue: number): number => { return getStorageItem(key, defaultValue, (value) => { - const parsed = parseInt(value, 10) + const parsed = Number(value) return isNaN(parsed) ? defaultValue : parsed }) } diff --git a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx index 811fa6055a..897e678d35 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx +++ b/admin-ui/plugins/admin/components/CustomScripts/CustomScriptForm.tsx @@ -72,16 +72,20 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript const defaultScriptPathValue: string | undefined = getModuleProperty('location_path') // Helper to transform property format for API (key/value → value1/value2) - const transformPropertyForApi = (prop: ModuleProperty | ConfigurationProperty) => { - const baseResult = { + const transformPropertyForApi = ( + prop: ModuleProperty | ConfigurationProperty, + ): ModuleProperty | ConfigurationProperty => { + const baseResult: Record = { value1: prop.key || prop.value1 || '', value2: prop.value || prop.value2 || '', - hide: prop.hide ?? false, + } + if (prop.hide !== undefined) { + baseResult.hide = prop.hide } if ('description' in prop && prop.description) { - return { ...baseResult, description: prop.description } + baseResult.description = prop.description } - return baseResult + return baseResult as ModuleProperty | ConfigurationProperty } const formik = useFormik({ @@ -142,12 +146,12 @@ function CustomScriptForm({ item, handleSubmit, viewOnly = false }: CustomScript if (values.configurationProperties) { submitValues.configurationProperties = filterEmptyObjects( values.configurationProperties, - ).map(transformPropertyForApi) + ).map(transformPropertyForApi) as ConfigurationProperty[] } if (values.moduleProperties) { submitValues.moduleProperties = filterEmptyObjects(values.moduleProperties).map( transformPropertyForApi, - ) + ) as ModuleProperty[] } if (typeof values.enabled == 'object') { if (Array.isArray(values.enabled) && values.enabled.length > 0) { From 793a56ae0ac6da4b23acd23eea36801ba41d60b0 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 22:04:43 +0500 Subject: [PATCH 11/41] Code rabbit suggestions --- admin-ui/app/utils/storageUtils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/admin-ui/app/utils/storageUtils.ts b/admin-ui/app/utils/storageUtils.ts index 97a530ec48..80c08df6d0 100644 --- a/admin-ui/app/utils/storageUtils.ts +++ b/admin-ui/app/utils/storageUtils.ts @@ -13,9 +13,6 @@ export const getStorageItem = ( return parser(stored) } - // Auto-coerce based on defaultValue type - if (stored === 'null') return null as T - if (typeof defaultValue === 'boolean') { return (stored === 'true') as T } From 71819981c9bd1899ea5476216ea88cbd7a53fa0c Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Thu, 30 Oct 2025 22:10:42 +0500 Subject: [PATCH 12/41] Code rabbit suggestions --- admin-ui/app/utils/storageUtils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/admin-ui/app/utils/storageUtils.ts b/admin-ui/app/utils/storageUtils.ts index 80c08df6d0..1b492fea46 100644 --- a/admin-ui/app/utils/storageUtils.ts +++ b/admin-ui/app/utils/storageUtils.ts @@ -18,6 +18,9 @@ export const getStorageItem = ( } if (typeof defaultValue === 'number') { + if (!stored || (typeof stored === 'string' && stored.trim() === '')) { + return defaultValue + } const parsed = Number(stored) return (isNaN(parsed) ? defaultValue : parsed) as T } From 241e9c6e5662822f4ac6f71a7bc5f0581a59be8a Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 31 Oct 2025 00:32:46 +0500 Subject: [PATCH 13/41] Code rabbit suggestions --- admin-ui/app/utils/storageUtils.ts | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/admin-ui/app/utils/storageUtils.ts b/admin-ui/app/utils/storageUtils.ts index 1b492fea46..4bd00cfa73 100644 --- a/admin-ui/app/utils/storageUtils.ts +++ b/admin-ui/app/utils/storageUtils.ts @@ -18,7 +18,7 @@ export const getStorageItem = ( } if (typeof defaultValue === 'number') { - if (!stored || (typeof stored === 'string' && stored.trim() === '')) { + if (stored.trim() === '') { return defaultValue } const parsed = Number(stored) @@ -78,6 +78,9 @@ export const removeStorageItem = (key: string): boolean => { export const getStorageNumber = (key: string, defaultValue: number): number => { return getStorageItem(key, defaultValue, (value) => { + if (value.trim() === '') { + return defaultValue + } const parsed = Number(value) return isNaN(parsed) ? defaultValue : parsed }) @@ -88,22 +91,9 @@ export const getStorageBoolean = (key: string, defaultValue: boolean): boolean = } export const getStorageJSON = (key: string, defaultValue: T): T => { - try { - const stored = localStorage.getItem(key) - if (stored === null) return defaultValue - return JSON.parse(stored) as T - } catch (error) { - console.error(`Error reading JSON from localStorage key "${key}":`, error) - return defaultValue - } + return getStorageItem(key, defaultValue) } export const setStorageJSON = (key: string, value: T): boolean => { - try { - localStorage.setItem(key, JSON.stringify(value)) - return true - } catch (error) { - console.error(`Error writing JSON to localStorage key "${key}":`, error) - return false - } + return setStorageItem(key, value) } From 68f742d76c53a425e70fda6f8894dc276680f72b Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 31 Oct 2025 14:17:30 +0500 Subject: [PATCH 14/41] buttons swapping --- .../app/routes/Apps/Gluu/GluuCommitFooter.tsx | 247 +++++++----------- 1 file changed, 91 insertions(+), 156 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx index 0013b5061b..c082146699 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx @@ -5,16 +5,21 @@ import { useTranslation } from 'react-i18next' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' import { ThemeContext } from 'Context/theme/themeContext' import { Box } from '@mui/material' +// Local props for the label renderer used by buttons +interface ButtonLabelProps { + isLoading: boolean + iconClass: string + label: string + loadingIconClass?: string +} interface GluuCommitFooterProps { showBack?: boolean backButtonLabel?: string - backButtonHandler?: () => void onBack?: () => void disableBack?: boolean showCancel?: boolean cancelButtonLabel?: string - cancelHandler?: () => void onCancel?: () => void disableCancel?: boolean showApply?: boolean @@ -22,32 +27,28 @@ interface GluuCommitFooterProps { onApply?: () => void disableApply?: boolean applyButtonType?: 'button' | 'submit' - extraOnClick?: () => void - extraLabel?: string - saveHandler?: () => void - hideButtons?: { - save?: boolean - back?: boolean - } - disableButtons?: { - save?: boolean - back?: boolean - } - type?: 'button' | 'submit' disableBackButton?: boolean isLoading?: boolean className?: string } -const GluuCommitFooter = memo(function GluuCommitFooter({ +const ButtonLabel = memo((props: ButtonLabelProps) => { + const { isLoading, iconClass, label, loadingIconClass = 'fa fa-spinner fa-spin' } = props + return ( + <> + + {label} + + ) +}) + +const GluuCommitFooter = ({ showBack, backButtonLabel, - backButtonHandler, onBack, disableBack, showCancel, cancelButtonLabel, - cancelHandler: newCancelHandler, onCancel, disableCancel, showApply, @@ -55,58 +56,26 @@ const GluuCommitFooter = memo(function GluuCommitFooter({ onApply, disableApply, applyButtonType = 'submit', - extraOnClick, - saveHandler, - extraLabel, - hideButtons, - disableButtons, - type = 'button', disableBackButton = false, - cancelHandler: legacyCancelHandler, isLoading = false, className = '', -}: GluuCommitFooterProps) { +}: GluuCommitFooterProps) => { const { t } = useTranslation() const theme = useContext(ThemeContext) const selectedTheme = useMemo(() => theme?.state.theme || 'darkBlack', [theme?.state.theme]) const navigate = useNavigate() - const isNewApiUsed = useMemo( - () => showBack !== undefined || showCancel !== undefined || showApply !== undefined, - [showBack, showCancel, showApply], - ) - - const finalShowBack = useMemo(() => { - return isNewApiUsed ? showBack : !hideButtons || !hideButtons.back - }, [isNewApiUsed, showBack, hideButtons]) - - const finalShowCancel = useMemo(() => { - return isNewApiUsed ? showCancel : false - }, [isNewApiUsed, showCancel]) + const finalShowBack = useMemo(() => Boolean(showBack), [showBack]) + const finalShowCancel = useMemo(() => Boolean(showCancel), [showCancel]) + const finalShowApply = useMemo(() => Boolean(showApply), [showApply]) - const finalShowApply = useMemo(() => { - return isNewApiUsed ? showApply : !hideButtons || !hideButtons.save - }, [isNewApiUsed, showApply, hideButtons]) - - const finalBackHandler = useMemo(() => backButtonHandler || onBack, [backButtonHandler, onBack]) - const finalCancelHandler = useMemo( - () => newCancelHandler || legacyCancelHandler || onCancel, - [newCancelHandler, legacyCancelHandler, onCancel], - ) - const finalApplyHandler = useMemo( - () => applyHandler || onApply || saveHandler, - [applyHandler, onApply, saveHandler], - ) - const finalButtonType = useMemo(() => applyButtonType || type, [applyButtonType, type]) - const finalDisableBack = useMemo( - () => disableBack || disableButtons?.back, - [disableBack, disableButtons?.back], - ) + const finalBackHandler = useMemo(() => onBack, [onBack]) + const finalCancelHandler = useMemo(() => onCancel, [onCancel]) + const finalApplyHandler = useMemo(() => applyHandler || onApply, [applyHandler, onApply]) + const finalButtonType = useMemo(() => applyButtonType, [applyButtonType]) + const finalDisableBack = useMemo(() => disableBack, [disableBack]) const finalDisableCancel = useMemo(() => disableCancel, [disableCancel]) - const finalDisableApply = useMemo( - () => disableApply || disableButtons?.save, - [disableApply, disableButtons?.save], - ) + const finalDisableApply = useMemo(() => disableApply, [disableApply]) const handleBackClick = useCallback(() => { if (disableBackButton && finalCancelHandler) { @@ -125,8 +94,7 @@ const GluuCommitFooter = memo(function GluuCommitFooter({ }, [finalCancelHandler]) const buttonStates = useMemo(() => { - const showExtra = Boolean(extraLabel && extraOnClick) - const hasAnyButton = finalShowBack || finalShowCancel || finalShowApply || showExtra + const hasAnyButton = finalShowBack || finalShowCancel || finalShowApply const hasAllThreeButtons = finalShowBack && finalShowCancel && finalShowApply const hasBackAndCancel = finalShowBack && finalShowCancel && !finalShowApply @@ -134,20 +102,17 @@ const GluuCommitFooter = memo(function GluuCommitFooter({ showBack: finalShowBack, showCancel: finalShowCancel, showApply: finalShowApply, - showExtra, hasAnyButton, hasAllThreeButtons, hasBackAndCancel, } - }, [finalShowBack, finalShowCancel, finalShowApply, extraLabel, extraOnClick]) + }, [finalShowBack, finalShowCancel, finalShowApply]) const buttonStyle = useMemo( () => ({ ...applicationStyle.buttonStyle, ...applicationStyle.buttonFlexIconStyles }), [], ) - const extraButtonStyle = useMemo(() => applicationStyle.buttonStyle, []) - const hiddenButtonStyle = useMemo(() => ({ visibility: 'hidden' as const }), []) const buttonColor = useMemo(() => `primary-${selectedTheme}`, [selectedTheme]) const backLabel = useMemo(() => backButtonLabel || t('actions.back'), [backButtonLabel, t]) @@ -156,14 +121,13 @@ const GluuCommitFooter = memo(function GluuCommitFooter({ [cancelButtonLabel, t], ) const applyLabel = useMemo(() => t('actions.apply'), [t]) - const submitLabel = useMemo(() => t('actions.submit'), [t]) const buttonLayout = useMemo(() => { if (buttonStates.hasAllThreeButtons) { return { back: 'd-flex', cancel: 'd-flex', - apply: 'd-flex ms-auto', + apply: 'd-flex', } } if (buttonStates.hasBackAndCancel) { @@ -243,110 +207,81 @@ const GluuCommitFooter = memo(function GluuCommitFooter({ className={buttonLayout.back} disabled={actualDisableBack || isLoading} > - {isLoading ? ( - <> - - {backLabel} - - ) : ( - <> - - {backLabel} - - )} - - )} - - {buttonStates.showCancel && ( - - - - )} - - {buttonStates.showExtra && ( - )} - - - {buttonStates.showApply && finalButtonType === 'submit' && ( - ) : ( - <> - - {applyLabel} - + )} - + {buttonStates.hasAllThreeButtons && buttonStates.showCancel && ( + + )} + )} - {buttonStates.showApply && finalButtonType === 'button' && ( + {!buttonStates.hasAllThreeButtons && buttonStates.showCancel && ( )} ) -}) +} -export default GluuCommitFooter +export default memo(GluuCommitFooter) From ea5aa6be403b63102caf9a4d807d361413320a08 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 31 Oct 2025 14:39:14 +0500 Subject: [PATCH 15/41] Code Rabbit fixes --- .../app/routes/Apps/Gluu/GluuCommitFooter.tsx | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx index c082146699..b2e5bbb441 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx @@ -23,11 +23,9 @@ interface GluuCommitFooterProps { onCancel?: () => void disableCancel?: boolean showApply?: boolean - applyHandler?: () => void onApply?: () => void disableApply?: boolean applyButtonType?: 'button' | 'submit' - disableBackButton?: boolean isLoading?: boolean className?: string } @@ -52,11 +50,9 @@ const GluuCommitFooter = ({ onCancel, disableCancel, showApply, - applyHandler, onApply, disableApply, applyButtonType = 'submit', - disableBackButton = false, isLoading = false, className = '', }: GluuCommitFooterProps) => { @@ -65,27 +61,29 @@ const GluuCommitFooter = ({ const selectedTheme = useMemo(() => theme?.state.theme || 'darkBlack', [theme?.state.theme]) const navigate = useNavigate() - const finalShowBack = useMemo(() => Boolean(showBack), [showBack]) - const finalShowCancel = useMemo(() => Boolean(showCancel), [showCancel]) - const finalShowApply = useMemo(() => Boolean(showApply), [showApply]) + const finalShowBack = Boolean(showBack) + const finalShowCancel = Boolean(showCancel) + const finalShowApply = Boolean(showApply) - const finalBackHandler = useMemo(() => onBack, [onBack]) - const finalCancelHandler = useMemo(() => onCancel, [onCancel]) - const finalApplyHandler = useMemo(() => applyHandler || onApply, [applyHandler, onApply]) - const finalButtonType = useMemo(() => applyButtonType, [applyButtonType]) - const finalDisableBack = useMemo(() => disableBack, [disableBack]) - const finalDisableCancel = useMemo(() => disableCancel, [disableCancel]) - const finalDisableApply = useMemo(() => disableApply, [disableApply]) + const finalBackHandler = onBack + const finalCancelHandler = onCancel + const finalApplyHandler = onApply + const finalButtonType = applyButtonType + const finalDisableBack = disableBack + const finalDisableCancel = disableCancel + const finalDisableApply = disableApply const handleBackClick = useCallback(() => { - if (disableBackButton && finalCancelHandler) { - finalCancelHandler() - } else if (finalBackHandler) { + if (finalBackHandler) { finalBackHandler() + return + } + if (window.history.length > 1) { + navigate(-1) } else { navigate('/home/dashboard') } - }, [disableBackButton, finalCancelHandler, finalBackHandler, navigate]) + }, [finalBackHandler, navigate]) const handleCancelClick = useCallback(() => { if (finalCancelHandler) { @@ -127,7 +125,7 @@ const GluuCommitFooter = ({ return { back: 'd-flex', cancel: 'd-flex', - apply: 'd-flex', + apply: 'd-flex ms-auto me-0', } } if (buttonStates.hasBackAndCancel) { @@ -216,7 +214,7 @@ const GluuCommitFooter = ({ )} {buttonStates.showApply && ( - + {finalButtonType === 'submit' ? ( )} @@ -273,7 +266,7 @@ const GluuCommitFooter = ({ style={buttonStyle} type="button" onClick={handleCancelClick} - className={buttonLayout.apply} + className={buttonLayout.cancel} disabled={finalDisableCancel || isLoading} > From 0cd0df34af04d8d9e056eeae6242b7618629abdc Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 31 Oct 2025 15:35:49 +0500 Subject: [PATCH 16/41] Code Rabbit fixes --- admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx index b2e5bbb441..95c36a63c2 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx @@ -170,17 +170,9 @@ const GluuCommitFooter = ({ apply: 'd-flex ms-auto', } } - return { - back: 'd-flex', - cancel: 'd-flex', - apply: 'd-flex ms-auto', - } + throw new Error('Unhandled button layout state') }, [buttonStates, finalShowBack, finalShowCancel, finalShowApply]) - const actualDisableBack = useMemo(() => { - return finalShowBack ? finalDisableBack : true - }, [finalShowBack, finalDisableBack]) - if (!buttonStates.hasAnyButton) { return null } @@ -203,7 +195,7 @@ const GluuCommitFooter = ({ type="button" onClick={handleBackClick} className={buttonLayout.back} - disabled={actualDisableBack || isLoading} + disabled={finalDisableBack || isLoading} > + {finalButtonType === 'submit' ? ( @@ -259,7 +247,7 @@ const GluuCommitFooter = ({ type="button" onClick={handleCancelClick} className={buttonLayout.cancel} - disabled={finalDisableCancel || isLoading} + disabled={disableCancel || isLoading} > From 4e86e97ca0126ed8008f8f82a9ec540b68cc7a34 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 31 Oct 2025 16:53:51 +0500 Subject: [PATCH 18/41] Code Rabbit fixes --- admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx index 5a631e79be..b98dbf42c3 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx @@ -159,7 +159,7 @@ const GluuCommitFooter = ({ } } throw new Error('Unhandled button layout state') - }, [buttonStates, showBack, showCancel, showApply]) + }, [buttonStates]) if (!buttonStates.hasAnyButton) { return null @@ -200,7 +200,6 @@ const GluuCommitFooter = ({ type="submit" color={buttonColor} style={buttonStyle} - className="d-flex" disabled={disableApply || isLoading} > @@ -225,13 +223,13 @@ const GluuCommitFooter = ({ /> )} - {buttonStates.hasAllThreeButtons && buttonStates.showCancel && ( + {buttonStates.hasAllThreeButtons && ( )} - - {buttonStates.showApply && ( - - {applyButtonType === 'submit' ? ( - - ) : ( - - )} - {buttonStates.hasAllThreeButtons && ( - - )} - + {extraLabel && extraOnClick && ( + + )} + + + {type === 'submit' && ( + )} - {!buttonStates.hasAllThreeButtons && buttonStates.showCancel && ( + {!hideButtons || !hideButtons['save'] ? ( - )} + ) : null} ) } -const GluuCommitFooterMemoized = memo(GluuCommitFooter) -GluuCommitFooterMemoized.displayName = 'GluuCommitFooter' - -export default GluuCommitFooterMemoized +export default GluuCommitFooter diff --git a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx new file mode 100644 index 0000000000..0f4c9eafe4 --- /dev/null +++ b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx @@ -0,0 +1,260 @@ +import { useContext, useMemo, useCallback, memo } from 'react' +import { useNavigate } from 'react-router-dom' +import { Button, Divider } from 'Components' +import { useTranslation } from 'react-i18next' +import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' +import { ThemeContext } from 'Context/theme/themeContext' +import { Box } from '@mui/material' + +interface ButtonLabelProps { + isLoading: boolean + iconClass: string + label: string + loadingIconClass?: string +} + +interface GluuformfooterProps { + showBack?: boolean + backButtonLabel?: string + onBack?: () => void + disableBack?: boolean + showCancel?: boolean + cancelButtonLabel?: string + onCancel?: () => void + disableCancel?: boolean + showApply?: boolean + onApply?: () => void + disableApply?: boolean + applyButtonType?: 'button' | 'submit' + isLoading?: boolean + className?: string +} + +const ButtonLabel = memo((props: ButtonLabelProps) => { + const { isLoading, iconClass, label, loadingIconClass = 'fa fa-spinner fa-spin' } = props + return ( + <> + + {label} + + ) +}) + +ButtonLabel.displayName = 'ButtonLabel' + +const BUTTON_STYLE = { ...applicationStyle.buttonStyle, ...applicationStyle.buttonFlexIconStyles } + +const Gluuformfooter = ({ + showBack, + backButtonLabel, + onBack, + disableBack, + showCancel, + cancelButtonLabel, + onCancel, + disableCancel, + showApply, + onApply, + disableApply, + applyButtonType = 'submit', + isLoading = false, + className = '', +}: GluuformfooterProps) => { + const { t } = useTranslation() + const theme = useContext(ThemeContext) + const selectedTheme = useMemo(() => theme?.state.theme || 'darkBlack', [theme?.state.theme]) + const navigate = useNavigate() + + const handleBackClick = useCallback(() => { + if (onBack) { + onBack() + return + } + if (window.history.length > 1) { + navigate(-1) + } else { + navigate('/home/dashboard') + } + }, [onBack, navigate]) + + const handleCancelClick = useCallback(() => { + if (onCancel) { + onCancel() + } + }, [onCancel]) + + const buttonStates = useMemo(() => { + const hasAnyButton = Boolean(showBack) || Boolean(showCancel) || Boolean(showApply) + const hasAllThreeButtons = Boolean(showBack) && Boolean(showCancel) && Boolean(showApply) + const hasBackAndCancel = Boolean(showBack) && Boolean(showCancel) && !showApply + + return { + showBack: Boolean(showBack), + showCancel: Boolean(showCancel), + showApply: Boolean(showApply), + hasAnyButton, + hasAllThreeButtons, + hasBackAndCancel, + } + }, [showBack, showCancel, showApply]) + + const buttonColor = useMemo(() => `primary-${selectedTheme}`, [selectedTheme]) + + const backLabel = useMemo(() => backButtonLabel || t('actions.back'), [backButtonLabel, t]) + const cancelLabel = useMemo( + () => cancelButtonLabel || t('actions.cancel'), + [cancelButtonLabel, t], + ) + const applyLabel = useMemo(() => t('actions.apply'), [t]) + + const buttonLayout = useMemo(() => { + if (buttonStates.hasAllThreeButtons) { + return { + back: 'd-flex', + cancel: 'd-flex', + apply: 'd-flex ms-auto me-0', + } + } + if (buttonStates.hasBackAndCancel) { + return { + back: 'd-flex', + cancel: 'd-flex ms-auto', + apply: '', + } + } + if (buttonStates.showBack && !buttonStates.showCancel && !buttonStates.showApply) { + return { + back: 'd-flex', + cancel: '', + apply: '', + } + } + if (buttonStates.showCancel && !buttonStates.showBack && !buttonStates.showApply) { + return { + back: '', + cancel: 'd-flex', + apply: '', + } + } + if (buttonStates.showBack && buttonStates.showApply && !buttonStates.showCancel) { + return { + back: 'd-flex', + cancel: '', + apply: 'd-flex ms-auto', + } + } + if (buttonStates.showCancel && buttonStates.showApply && !buttonStates.showBack) { + return { + back: '', + cancel: 'd-flex', + apply: 'd-flex ms-auto', + } + } + if (buttonStates.showApply && !buttonStates.showBack && !buttonStates.showCancel) { + return { + back: '', + cancel: '', + apply: 'd-flex ms-auto', + } + } + throw new Error('Unhandled button layout state') + }, [buttonStates]) + + if (!buttonStates.hasAnyButton) { + return null + } + + return ( + <> + + + {buttonStates.showBack && ( + + )} + + {buttonStates.showApply && ( + + {applyButtonType === 'submit' ? ( + + ) : ( + + )} + {buttonStates.hasAllThreeButtons && ( + + )} + + )} + + {!buttonStates.hasAllThreeButtons && buttonStates.showCancel && ( + + )} + + + ) +} + +const GluuformfooterMemoized = memo(Gluuformfooter) +GluuformfooterMemoized.displayName = 'Gluuformfooter' + +export default GluuformfooterMemoized diff --git a/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx b/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx index 3017bb5adc..11e571f808 100644 --- a/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx +++ b/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx @@ -5,7 +5,7 @@ import { useCedarling } from '@/cedarling' import { Row, Col, Form, FormGroup } from 'Components' import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' import GluuCommitDialog from 'Routes/Apps/Gluu/GluuCommitDialog' -import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' +import Gluuformfooter from 'Routes/Apps/Gluu/Gluuformfooter' import GluuSelectRow from 'Routes/Apps/Gluu/GluuSelectRow' import GluuTypeAhead from 'Routes/Apps/Gluu/GluuTypeAhead' import { @@ -429,7 +429,7 @@ const JansLockConfiguration: React.FC = ({ <> - = ({ - Date: Mon, 3 Nov 2025 13:11:23 +0500 Subject: [PATCH 23/41] Code rabbit changes --- admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx index 0f4c9eafe4..e9f62a057b 100644 --- a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx @@ -108,6 +108,9 @@ const Gluuformfooter = ({ const applyLabel = useMemo(() => t('actions.apply'), [t]) const buttonLayout = useMemo(() => { + if (!buttonStates.hasAnyButton) { + return { back: '', cancel: '', apply: '' } + } if (buttonStates.hasAllThreeButtons) { return { back: 'd-flex', @@ -157,7 +160,7 @@ const Gluuformfooter = ({ apply: 'd-flex ms-auto', } } - throw new Error('Unhandled button layout state') + return { back: '', cancel: '', apply: '' } }, [buttonStates]) if (!buttonStates.hasAnyButton) { From c90eec1a2b9d80c3cc00873cd2ac5fab29d56cde Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Mon, 3 Nov 2025 13:30:53 +0500 Subject: [PATCH 24/41] Code rabbit changes --- .../app/routes/Apps/Gluu/Gluuformfooter.tsx | 71 +++++-------------- 1 file changed, 18 insertions(+), 53 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx index e9f62a057b..26478ea66c 100644 --- a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx @@ -26,6 +26,7 @@ interface GluuformfooterProps { onApply?: () => void disableApply?: boolean applyButtonType?: 'button' | 'submit' + applyButtonLabel?: string isLoading?: boolean className?: string } @@ -57,6 +58,7 @@ const Gluuformfooter = ({ onApply, disableApply, applyButtonType = 'submit', + applyButtonLabel, isLoading = false, className = '', }: GluuformfooterProps) => { @@ -105,62 +107,29 @@ const Gluuformfooter = ({ () => cancelButtonLabel || t('actions.cancel'), [cancelButtonLabel, t], ) - const applyLabel = useMemo(() => t('actions.apply'), [t]) + const applyLabel = useMemo(() => applyButtonLabel || t('actions.apply'), [applyButtonLabel, t]) const buttonLayout = useMemo(() => { if (!buttonStates.hasAnyButton) { return { back: '', cancel: '', apply: '' } } - if (buttonStates.hasAllThreeButtons) { - return { - back: 'd-flex', - cancel: 'd-flex', - apply: 'd-flex ms-auto me-0', - } - } - if (buttonStates.hasBackAndCancel) { - return { - back: 'd-flex', - cancel: 'd-flex ms-auto', - apply: '', - } - } - if (buttonStates.showBack && !buttonStates.showCancel && !buttonStates.showApply) { - return { - back: 'd-flex', - cancel: '', - apply: '', - } - } - if (buttonStates.showCancel && !buttonStates.showBack && !buttonStates.showApply) { - return { - back: '', - cancel: 'd-flex', - apply: '', - } - } - if (buttonStates.showBack && buttonStates.showApply && !buttonStates.showCancel) { - return { - back: 'd-flex', - cancel: '', - apply: 'd-flex ms-auto', - } - } - if (buttonStates.showCancel && buttonStates.showApply && !buttonStates.showBack) { - return { - back: '', - cancel: 'd-flex', - apply: 'd-flex ms-auto', - } + + const layout = { + back: buttonStates.showBack ? 'd-flex' : '', + cancel: buttonStates.showCancel ? 'd-flex' : '', + apply: buttonStates.showApply ? 'd-flex' : '', } - if (buttonStates.showApply && !buttonStates.showBack && !buttonStates.showCancel) { - return { - back: '', - cancel: '', - apply: 'd-flex ms-auto', + + if (buttonStates.showApply) { + layout.apply += ' ms-auto' + if (buttonStates.hasAllThreeButtons) { + layout.apply += ' me-0' } + } else if (buttonStates.showCancel) { + layout.cancel += ' ms-auto' } - return { back: '', cancel: '', apply: '' } + + return layout }, [buttonStates]) if (!buttonStates.hasAnyButton) { @@ -187,11 +156,7 @@ const Gluuformfooter = ({ className={buttonLayout.back} disabled={disableBack || isLoading} > - + )} From b2b52b01ee9039b44234f2be983f434ea2afa77e Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Mon, 3 Nov 2025 13:39:48 +0500 Subject: [PATCH 25/41] Code rabbit changes --- .../app/routes/Apps/Gluu/Gluuformfooter.tsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx index 26478ea66c..3c9c085080 100644 --- a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx @@ -154,7 +154,7 @@ const Gluuformfooter = ({ type="button" onClick={handleBackClick} className={buttonLayout.back} - disabled={disableBack || isLoading} + disabled={disableBack} > @@ -190,21 +190,22 @@ const Gluuformfooter = ({ /> )} - {buttonStates.hasAllThreeButtons && ( - - )} )} + {buttonStates.hasAllThreeButtons && ( + + )} + {!buttonStates.hasAllThreeButtons && buttonStates.showCancel && ( )} From 8aba2043774c7c1f41394635df80d3062d1c178d Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Mon, 3 Nov 2025 13:41:22 +0500 Subject: [PATCH 26/41] Code rabbit changes --- admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx index 3c9c085080..18edbb04e5 100644 --- a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx @@ -71,9 +71,6 @@ const Gluuformfooter = ({ if (onBack) { onBack() return - } - if (window.history.length > 1) { - navigate(-1) } else { navigate('/home/dashboard') } From f5fc2beff0bf6dfe468ad6c29fdfb8acad157cac Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Mon, 3 Nov 2025 16:36:36 +0500 Subject: [PATCH 27/41] file notations fixes --- admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx | 6 +++--- .../plugins/jans-lock/components/JansLockConfiguration.tsx | 2 +- admin-ui/plugins/scim/components/ScimConfiguration.tsx | 2 +- admin-ui/plugins/scim/helper/index.ts | 1 - admin-ui/plugins/scim/helper/validations.ts | 1 - 5 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 admin-ui/plugins/scim/helper/validations.ts diff --git a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx index 18edbb04e5..66199efe71 100644 --- a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx @@ -45,7 +45,7 @@ ButtonLabel.displayName = 'ButtonLabel' const BUTTON_STYLE = { ...applicationStyle.buttonStyle, ...applicationStyle.buttonFlexIconStyles } -const Gluuformfooter = ({ +const GluuFormFooter = ({ showBack, backButtonLabel, onBack, @@ -220,7 +220,7 @@ const Gluuformfooter = ({ ) } -const GluuformfooterMemoized = memo(Gluuformfooter) -GluuformfooterMemoized.displayName = 'Gluuformfooter' +const GluuformfooterMemoized = memo(GluuFormFooter) +GluuformfooterMemoized.displayName = 'GluuFormFooter' export default GluuformfooterMemoized diff --git a/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx b/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx index 11e571f808..58997675aa 100644 --- a/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx +++ b/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx @@ -5,7 +5,7 @@ import { useCedarling } from '@/cedarling' import { Row, Col, Form, FormGroup } from 'Components' import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' import GluuCommitDialog from 'Routes/Apps/Gluu/GluuCommitDialog' -import Gluuformfooter from 'Routes/Apps/Gluu/Gluuformfooter' +import Gluuformfooter from '@/routes/Apps/Gluu/GluuFormFooter' import GluuSelectRow from 'Routes/Apps/Gluu/GluuSelectRow' import GluuTypeAhead from 'Routes/Apps/Gluu/GluuTypeAhead' import { diff --git a/admin-ui/plugins/scim/components/ScimConfiguration.tsx b/admin-ui/plugins/scim/components/ScimConfiguration.tsx index e9eef784ee..a1b6a3df68 100644 --- a/admin-ui/plugins/scim/components/ScimConfiguration.tsx +++ b/admin-ui/plugins/scim/components/ScimConfiguration.tsx @@ -2,7 +2,7 @@ import { useFormik, FormikProps } from 'formik' import React, { useState, useCallback, useMemo } from 'react' import { Row, Col, Form, FormGroup } from 'Components' import GluuCommitDialog from 'Routes/Apps/Gluu/GluuCommitDialog' -import Gluuformfooter from 'Routes/Apps/Gluu/Gluuformfooter' +import Gluuformfooter from '@/routes/Apps/Gluu/GluuFormFooter' import { scimConfigurationSchema } from '../helper/schema' import { transformToFormValues, createJsonPatchFromDifferences } from '../helper' import ScimFieldRenderer from './ScimFieldRenderer' diff --git a/admin-ui/plugins/scim/helper/index.ts b/admin-ui/plugins/scim/helper/index.ts index 9c59d9f635..44f69d2fbb 100644 --- a/admin-ui/plugins/scim/helper/index.ts +++ b/admin-ui/plugins/scim/helper/index.ts @@ -1,4 +1,3 @@ export * from './utils' export * from './constants' export * from './schema' -export * from './validations' diff --git a/admin-ui/plugins/scim/helper/validations.ts b/admin-ui/plugins/scim/helper/validations.ts deleted file mode 100644 index ba125b0aaa..0000000000 --- a/admin-ui/plugins/scim/helper/validations.ts +++ /dev/null @@ -1 +0,0 @@ -export { scimConfigurationSchema as scimConfigurationValidationSchema } from './schema' From 00b2d45ba54f2de5f57cb072b7e82eacdb386830 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Mon, 3 Nov 2025 16:51:30 +0500 Subject: [PATCH 28/41] file notations fixes --- admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx | 6 +++--- admin-ui/plugins/fido/components/DynamicConfiguration.tsx | 2 +- admin-ui/plugins/fido/components/StaticConfiguration.tsx | 2 +- .../plugins/jans-lock/components/JansLockConfiguration.tsx | 4 ++-- admin-ui/plugins/scim/components/ScimConfiguration.tsx | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx index 66199efe71..ddc9116e6e 100644 --- a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx @@ -220,7 +220,7 @@ const GluuFormFooter = ({ ) } -const GluuformfooterMemoized = memo(GluuFormFooter) -GluuformfooterMemoized.displayName = 'GluuFormFooter' +const GluuFormFooterMemoized = memo(GluuFormFooter) +GluuFormFooterMemoized.displayName = 'GluuFormFooter' -export default GluuformfooterMemoized +export default GluuFormFooterMemoized diff --git a/admin-ui/plugins/fido/components/DynamicConfiguration.tsx b/admin-ui/plugins/fido/components/DynamicConfiguration.tsx index 01bce7ceeb..28c2edf8c9 100644 --- a/admin-ui/plugins/fido/components/DynamicConfiguration.tsx +++ b/admin-ui/plugins/fido/components/DynamicConfiguration.tsx @@ -308,7 +308,7 @@ const DynamicConfiguration: React.FC = ({ extraLabel="Cancel" extraOnClick={handleCancel} type="submit" - isLoading={isSubmitting} + disabled={isSubmitting} /> diff --git a/admin-ui/plugins/fido/components/StaticConfiguration.tsx b/admin-ui/plugins/fido/components/StaticConfiguration.tsx index d28b811242..a7c45f5a35 100644 --- a/admin-ui/plugins/fido/components/StaticConfiguration.tsx +++ b/admin-ui/plugins/fido/components/StaticConfiguration.tsx @@ -348,7 +348,7 @@ const StaticConfiguration: React.FC = ({ extraLabel="Cancel" extraOnClick={handleCancel} type="submit" - isLoading={isSubmitting} + disabled={isSubmitting} /> diff --git a/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx b/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx index 58997675aa..d3ea1b1269 100644 --- a/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx +++ b/admin-ui/plugins/jans-lock/components/JansLockConfiguration.tsx @@ -5,7 +5,7 @@ import { useCedarling } from '@/cedarling' import { Row, Col, Form, FormGroup } from 'Components' import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' import GluuCommitDialog from 'Routes/Apps/Gluu/GluuCommitDialog' -import Gluuformfooter from '@/routes/Apps/Gluu/GluuFormFooter' +import GluuFormFooter from '@/routes/Apps/Gluu/GluuFormFooter' import GluuSelectRow from 'Routes/Apps/Gluu/GluuSelectRow' import GluuTypeAhead from 'Routes/Apps/Gluu/GluuTypeAhead' import { @@ -429,7 +429,7 @@ const JansLockConfiguration: React.FC = ({ <> - = ({ - Date: Mon, 3 Nov 2025 17:04:57 +0500 Subject: [PATCH 29/41] Rename Gluuformfooter.tsx to GluuFormFooter.tsx Signed-off-by: Faisal Siddique <71010439+faisalsiddique4400@users.noreply.github.com> --- .../routes/Apps/Gluu/{Gluuformfooter.tsx => GluuFormFooter.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename admin-ui/app/routes/Apps/Gluu/{Gluuformfooter.tsx => GluuFormFooter.tsx} (100%) diff --git a/admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx similarity index 100% rename from admin-ui/app/routes/Apps/Gluu/Gluuformfooter.tsx rename to admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx From d14e1b61489e23071691c5591f278e7a3558f243 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Mon, 3 Nov 2025 17:17:51 +0500 Subject: [PATCH 30/41] code rabbit fixes --- admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx index ddc9116e6e..3c6ccedc51 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx @@ -13,7 +13,7 @@ interface ButtonLabelProps { loadingIconClass?: string } -interface GluuformfooterProps { +interface GluuFormFooterProps { showBack?: boolean backButtonLabel?: string onBack?: () => void @@ -61,7 +61,7 @@ const GluuFormFooter = ({ applyButtonLabel, isLoading = false, className = '', -}: GluuformfooterProps) => { +}: GluuFormFooterProps) => { const { t } = useTranslation() const theme = useContext(ThemeContext) const selectedTheme = useMemo(() => theme?.state.theme || 'darkBlack', [theme?.state.theme]) @@ -71,9 +71,9 @@ const GluuFormFooter = ({ if (onBack) { onBack() return - } else { - navigate('/home/dashboard') } + navigate('/home/dashboard') + return }, [onBack, navigate]) const handleCancelClick = useCallback(() => { From 1376956e4c34611f39c0c8dfcfaa7e942971bc15 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Mon, 3 Nov 2025 17:23:53 +0500 Subject: [PATCH 31/41] code rabbit fixes --- admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx index 3c6ccedc51..530afa64a5 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx @@ -73,7 +73,6 @@ const GluuFormFooter = ({ return } navigate('/home/dashboard') - return }, [onBack, navigate]) const handleCancelClick = useCallback(() => { From a838993ac4a47ba56891f6f99ceebea579982d96 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Mon, 3 Nov 2025 17:24:34 +0500 Subject: [PATCH 32/41] code rabbit fixes --- .../app/routes/Apps/Gluu/GluuFormFooter.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx index 530afa64a5..3a7d2e2d70 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx @@ -189,26 +189,13 @@ const GluuFormFooter = ({ )} - {buttonStates.hasAllThreeButtons && ( + {buttonStates.showCancel && ( - )} - - {!buttonStates.hasAllThreeButtons && buttonStates.showCancel && ( - + + + )} + - From 3a48972fbfc31c23d7712662ed0795fdd6473c4d Mon Sep 17 00:00:00 2001 From: Faisal Siddique <71010439+faisalsiddique4400@users.noreply.github.com> Date: Mon, 3 Nov 2025 20:08:53 +0500 Subject: [PATCH 38/41] feat(admin): adding uniformity in cancel and back buttons present in SERVICES (#2411) * feat(admin): adding uniformity in cancel and back buttons present in SMTP * feat(admin): adding uniformity in cancel and back buttons present in Services * commit text not getting rollover issue --------- Signed-off-by: Faisal Siddique <71010439+faisalsiddique4400@users.noreply.github.com> --- .../app/routes/Apps/Gluu/GluuCommitDialog.tsx | 16 ++++++----- .../fido/components/DynamicConfiguration.tsx | 4 +-- .../fido/components/StaticConfiguration.tsx | 4 +-- .../scim/components/ScimConfiguration.tsx | 2 +- .../Components/Configuration/CachePage.js | 28 +++++++++++-------- .../Configuration/PersistenceDetail.js | 5 +++- .../components/SmtpManagement/SmtpForm.tsx | 2 +- 7 files changed, 35 insertions(+), 26 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuCommitDialog.tsx b/admin-ui/app/routes/Apps/Gluu/GluuCommitDialog.tsx index a24807cd71..5b684e70dd 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuCommitDialog.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuCommitDialog.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useContext } from 'react' +import React, { useState, useEffect, useContext, useRef } from 'react' import { FormGroup, Col, @@ -50,13 +50,15 @@ const GluuCommitDialog = ({ modal, }) + const prevModalRef = useRef(false) useEffect(() => { - if (userMessage.length >= 10 && userMessage.length <= 512) { - setActive(true) - } else { - setActive(false) + setActive(userMessage.length >= 10 && userMessage.length <= 512) + + if (modal && !prevModalRef.current) { + setUserMessage('') } - }, [userMessage]) + prevModalRef.current = modal + }, [userMessage, modal]) function handleAccept() { if (formik) { @@ -77,7 +79,7 @@ const GluuCommitDialog = ({ const renderBadges = (values: any) => { return (
- {values.map((data: any, index: any) => ( + {values.map((data: any) => ( = ({ onCancel={handleCancel} disableBack={false} disableCancel={!formik.dirty} - disableApply={!formik.dirty} + disableApply={!formik.isValid || !formik.dirty} applyButtonType="button" - isLoading={isSubmitting} + isLoading={isSubmitting ?? false} /> diff --git a/admin-ui/plugins/fido/components/StaticConfiguration.tsx b/admin-ui/plugins/fido/components/StaticConfiguration.tsx index 8664f1eeeb..cee08139ed 100644 --- a/admin-ui/plugins/fido/components/StaticConfiguration.tsx +++ b/admin-ui/plugins/fido/components/StaticConfiguration.tsx @@ -373,9 +373,9 @@ const StaticConfiguration: React.FC = ({ onCancel={handleCancel} disableBack={false} disableCancel={!formik.dirty} - disableApply={!formik.dirty} + disableApply={!formik.isValid || !formik.dirty} applyButtonType="button" - isLoading={isSubmitting} + isLoading={isSubmitting ?? false} /> diff --git a/admin-ui/plugins/scim/components/ScimConfiguration.tsx b/admin-ui/plugins/scim/components/ScimConfiguration.tsx index 1d0f00f858..f430ecec71 100644 --- a/admin-ui/plugins/scim/components/ScimConfiguration.tsx +++ b/admin-ui/plugins/scim/components/ScimConfiguration.tsx @@ -88,7 +88,7 @@ const ScimConfiguration: React.FC = ({ disableCancel={!isFormDirty} disableApply={!isFormValid || !isFormDirty} applyButtonType="submit" - isLoading={isSubmitting} + isLoading={isSubmitting ?? false} /> diff --git a/admin-ui/plugins/services/Components/Configuration/CachePage.js b/admin-ui/plugins/services/Components/Configuration/CachePage.js index 9ec07bd186..f4a71271cb 100644 --- a/admin-ui/plugins/services/Components/Configuration/CachePage.js +++ b/admin-ui/plugins/services/Components/Configuration/CachePage.js @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react' import BlockUi from '../../../../app/components/BlockUi/BlockUi' import { Formik } from 'formik' import { Form, FormGroup, Card, Col, CardBody, InputGroup, CustomInput } from 'Components' -import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' +import GluuFormFooter from 'Routes/Apps/Gluu/GluuFormFooter' import GluuLabel from 'Routes/Apps/Gluu/GluuLabel' import GluuTooltip from 'Routes/Apps/Gluu/GluuTooltip' import CacheInMemory from './CacheInMemory' @@ -87,10 +87,6 @@ function CachePage() { function toggle() { setModal(!modal) } - function submitForm() { - toggle() - document.getElementsByClassName('LdapUserActionSubmitButton')[0].click() - } function handleCancel(formik) { return () => { formik.resetForm() @@ -224,17 +220,25 @@ function CachePage() { )} - { + formik.handleSubmit() + toggle() + }} formik={formik} /> diff --git a/admin-ui/plugins/services/Components/Configuration/PersistenceDetail.js b/admin-ui/plugins/services/Components/Configuration/PersistenceDetail.js index 100fba41ac..ed65412e7a 100644 --- a/admin-ui/plugins/services/Components/Configuration/PersistenceDetail.js +++ b/admin-ui/plugins/services/Components/Configuration/PersistenceDetail.js @@ -1,6 +1,7 @@ import React, { useEffect, useContext } from 'react' import { useDispatch, useSelector } from 'react-redux' import { Container, Row, Col, Card, CardBody } from 'Components' +import GluuFormFooter from 'Routes/Apps/Gluu/GluuFormFooter' import { getDatabaseInfo } from '../../redux/features/persistenceTypeSlice' import { useTranslation } from 'react-i18next' import { ThemeContext } from 'Context/theme/themeContext' @@ -8,7 +9,6 @@ import getThemeColor from 'Context/theme/config' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' import SetTitle from 'Utils/SetTitle' import GluuTooltip from 'Routes/Apps/Gluu/GluuTooltip' -import colors from 'colors' import customColors from '@/customColors' function PersistenceDetail() { @@ -123,6 +123,9 @@ function PersistenceDetail() { return null })} +
+ +
) diff --git a/admin-ui/plugins/smtp-management/components/SmtpManagement/SmtpForm.tsx b/admin-ui/plugins/smtp-management/components/SmtpManagement/SmtpForm.tsx index 9db58e657b..3765e1022f 100644 --- a/admin-ui/plugins/smtp-management/components/SmtpManagement/SmtpForm.tsx +++ b/admin-ui/plugins/smtp-management/components/SmtpManagement/SmtpForm.tsx @@ -360,7 +360,7 @@ function SmtpForm(props: Readonly) { disableCancel={!formik.dirty} disableApply={!formik.isValid || !formik.dirty} applyButtonType="button" - isLoading={false} + isLoading={formik.isSubmitting ?? false} /> From c1314269c0f877e11add0af87bce7276dc767172 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Wed, 5 Nov 2025 10:47:35 +0500 Subject: [PATCH 39/41] Code rabbit changes --- admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx index 3a7d2e2d70..31f6549102 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuFormFooter.tsx @@ -13,7 +13,7 @@ interface ButtonLabelProps { loadingIconClass?: string } -interface GluuFormFooterProps { +interface GluuFormFooterBaseProps { showBack?: boolean backButtonLabel?: string onBack?: () => void @@ -23,14 +23,18 @@ interface GluuFormFooterProps { onCancel?: () => void disableCancel?: boolean showApply?: boolean - onApply?: () => void disableApply?: boolean - applyButtonType?: 'button' | 'submit' applyButtonLabel?: string isLoading?: boolean className?: string } +type GluuFormFooterProps = GluuFormFooterBaseProps & + ( + | { applyButtonType?: 'submit'; onApply?: () => void } + | { applyButtonType: 'button'; onApply: () => void } + ) + const ButtonLabel = memo((props: ButtonLabelProps) => { const { isLoading, iconClass, label, loadingIconClass = 'fa fa-spinner fa-spin' } = props return ( @@ -151,6 +155,7 @@ const GluuFormFooter = ({ onClick={handleBackClick} className={buttonLayout.back} disabled={disableBack} + aria-label={backLabel} > @@ -164,6 +169,7 @@ const GluuFormFooter = ({ color={buttonColor} style={BUTTON_STYLE} disabled={disableApply || isLoading} + aria-label={applyLabel} > From 2ea443e5639c73c839d708e04eb41212b2b24224 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Wed, 5 Nov 2025 12:19:15 +0500 Subject: [PATCH 40/41] Code rabbit changes --- admin-ui/plugins/services/Components/Configuration/CachePage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin-ui/plugins/services/Components/Configuration/CachePage.js b/admin-ui/plugins/services/Components/Configuration/CachePage.js index f4a71271cb..f2627f61f9 100644 --- a/admin-ui/plugins/services/Components/Configuration/CachePage.js +++ b/admin-ui/plugins/services/Components/Configuration/CachePage.js @@ -236,8 +236,8 @@ function CachePage() { handler={toggle} modal={modal} onAccept={() => { - formik.handleSubmit() toggle() + formik.handleSubmit() }} formik={formik} /> From e993a85d6c2014331e86e5dfbbcdc34e2b5f1eff Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Wed, 5 Nov 2025 12:26:12 +0500 Subject: [PATCH 41/41] Code rabbit changes --- admin-ui/plugins/services/Components/Configuration/CachePage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin-ui/plugins/services/Components/Configuration/CachePage.js b/admin-ui/plugins/services/Components/Configuration/CachePage.js index f2627f61f9..708527a8b0 100644 --- a/admin-ui/plugins/services/Components/Configuration/CachePage.js +++ b/admin-ui/plugins/services/Components/Configuration/CachePage.js @@ -230,7 +230,7 @@ function CachePage() { disableCancel={!formik.dirty} disableApply={!formik.isValid || !formik.dirty} applyButtonType="button" - isLoading={formik.isSubmitting ?? false} + isLoading={loading} />