From fa3ebf3ec010785deee596772f2399c940764ff4 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 24 Oct 2025 20:29:20 +0500 Subject: [PATCH 01/10] fix(admin-ui): incorrect cancel button behaviour in the Application Settings page in Home module --- .../admin/components/Settings/SettingsPage.js | 172 +++++++++--------- 1 file changed, 89 insertions(+), 83 deletions(-) diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index 1e9197898b..bbb64c8821 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -1,23 +1,11 @@ -import React, { useState, useContext, useEffect } from 'react' +import React, { useState, useEffect, useMemo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import GluuLabel from 'Routes/Apps/Gluu/GluuLabel' import GluuToogleRow from 'Routes/Apps/Gluu/GluuToogleRow' import { SETTINGS } from 'Utils/ApiResources' -import { - Card, - CardBody, - FormGroup, - Col, - Label, - Badge, - InputGroup, - CustomInput, - Form, - Button, -} from 'Components' +import { Card, CardBody, FormGroup, Col, InputGroup, CustomInput, Form } from 'Components' import SetTitle from 'Utils/SetTitle' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' -import { ThemeContext } from 'Context/theme/themeContext' import { putConfigWorker } from 'Redux/features/authSlice' import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' import { useFormik } from 'formik' @@ -27,6 +15,7 @@ import { SIMPLE_PASSWORD_AUTH } from 'Plugins/auth-server/common/Constants' import { getScripts } from 'Redux/features/initSlice' import GluuLoader from 'Routes/Apps/Gluu/GluuLoader' import GluuProperties from 'Routes/Apps/Gluu/GluuProperties' +import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' import packageJson from '../../../../package.json' import { CedarlingLogType } from '@/cedarling' import { updateToast } from '@/redux/features/toastSlice' @@ -38,15 +27,25 @@ function SettingsPage() { const dispatch = useDispatch() const loadingScripts = useSelector((state) => state.initReducer.loadingScripts) const loadingConfig = useSelector((state) => state.authReducer?.loadingConfig) - const theme = useContext(ThemeContext) - const selectedTheme = theme.state.theme - const [paggingSize, setPaggingSize] = useState(localStorage.getItem('paggingSize') || 10) + const initialPaggingSize = localStorage.getItem('paggingSize') || 10 + const [paggingSize, setPaggingSize] = useState(initialPaggingSize) + const [originalPaggingSize, setOriginalPaggingSize] = useState(initialPaggingSize) SetTitle(t('titles.application_settings')) useEffect(() => { dispatch(getScripts({ action: {} })) }, []) + const resetPaggingSize = () => { + setPaggingSize(originalPaggingSize) + } + + const savePaggingSize = () => { + localStorage.setItem('paggingSize', paggingSize) + // Update the original value to the newly saved value + setOriginalPaggingSize(paggingSize) + } + return ( @@ -64,6 +63,20 @@ function SettingsPage() { doc_entry="gluuCurrentVersion" /> + {window.configApiBaseUrl && ( + + )} + { - return element == paggingSize - }) - ] - } + value={paggingSize} onChange={(value) => { const size = levels[value.target.options.selectedIndex] setPaggingSize(size) - localStorage.setItem('paggingSize', size) }} > {levels.map((item, key) => ( @@ -100,30 +106,9 @@ function SettingsPage() { - - - - - - - - {!loadingScripts && } + {!loadingScripts && ( + + )} @@ -131,38 +116,50 @@ function SettingsPage() { ) } -function SettingsForm() { +function SettingsForm({ resetPaggingSize, savePaggingSize }) { const { t } = useTranslation() - const theme = useContext(ThemeContext) - const selectedTheme = theme.state.theme - const loadingConfig = useSelector((state) => state.authReducer?.loadingConfig) - const additionalParameters = - useSelector((state) => state.authReducer?.config?.additionalParameters) || [] - const acrValues = useSelector((state) => state.authReducer?.config?.acrValues) - const sessionTimeout = - useSelector((state) => state.authReducer?.config?.sessionTimeoutInMins) || 5 - const cedarlingLogType = - useSelector((state) => state.authReducer?.config?.cedarlingLogType) || CedarlingLogType.OFF - const scripts = useSelector((state) => state.initReducer.scripts) const dispatch = useDispatch() - const authScripts = scripts - .filter((item) => item.scriptType == 'person_authentication') - .filter((item) => item.enabled) - .map((item) => item.name) + const config = useSelector((state) => state.authReducer?.config) || {} + const scripts = useSelector((state) => state.initReducer.scripts) + + // Transform config to form values (Fido pattern) + const transformToFormValues = useCallback((configData) => { + return { + sessionTimeoutInMins: configData?.sessionTimeoutInMins || 30, + acrValues: configData?.acrValues || '', + cedarlingLogType: configData?.cedarlingLogType || CedarlingLogType.OFF, + additionalParameters: (configData?.additionalParameters || []).map((param) => ({ ...param })), + } + }, []) + + const initialValues = useMemo( + () => transformToFormValues(config), + [config, transformToFormValues], + ) + + const authScripts = useMemo(() => { + const filtered = scripts + .filter((item) => item.scriptType === 'person_authentication') + .filter((item) => item.enabled) + .map((item) => item.name) - authScripts.push(SIMPLE_PASSWORD_AUTH) + return [...filtered, SIMPLE_PASSWORD_AUTH] + }, [scripts]) const formik = useFormik({ - initialValues: { - sessionTimeoutInMins: sessionTimeout, - acrValues: acrValues || '', - cedarlingLogType, - }, + initialValues, + enableReinitialize: true, onSubmit: (values) => { + // Save paging size to localStorage + savePaggingSize() + + // Dispatch the action to save dispatch(putConfigWorker(values)) - !(values?.cedarlingLogType === cedarlingLogType) && + + if (values?.cedarlingLogType !== config?.cedarlingLogType) { dispatch(updateToast(true, 'success', t('fields.reloginToViewCedarlingChanges'))) + } }, validationSchema: Yup.object().shape({ sessionTimeoutInMins: Yup.number() @@ -171,6 +168,20 @@ function SettingsForm() { }), }) + const handleCancel = useCallback(() => { + const resetValues = transformToFormValues(config) + formik.resetForm({ values: resetValues }) + resetPaggingSize() + }, [formik, config, transformToFormValues, resetPaggingSize]) + + // Transform additionalParameters for GluuProperties (Fido pattern) + const additionalParametersOptions = useMemo(() => { + return (formik.values.additionalParameters || []).map((param) => ({ + key: param.key || '', + value: param.value || '', + })) + }, [formik.values.additionalParameters]) + return (
- - -
- + disableBackButton={true} + cancelHandler={handleCancel} + /> ) } From f6f22cd1e6e3c877f03f7dbaec8c10e52f133601 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 24 Oct 2025 21:14:41 +0500 Subject: [PATCH 02/10] Improved code for the Setting page with better approach --- admin-ui/app/redux/features/authSlice.ts | 5 ++ .../app/redux/features/types/authTypes.ts | 1 + .../admin/components/Settings/SettingsPage.js | 51 ++++++++++++------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/admin-ui/app/redux/features/authSlice.ts b/admin-ui/app/redux/features/authSlice.ts index 08fc750013..a9566144a4 100644 --- a/admin-ui/app/redux/features/authSlice.ts +++ b/admin-ui/app/redux/features/authSlice.ts @@ -11,6 +11,7 @@ const initialState: AuthState = { permissions: [], location: {}, config: {}, + paggingSize: 10, defaultToken: null, codeChallenge: null, codeChallengeMethod: 'S256', @@ -105,6 +106,9 @@ const authSlice = createSlice({ putConfigWorkerResponse: (state) => { state.loadingConfig = false }, + setPaggingSize: (state, action: PayloadAction) => { + state.paggingSize = action.payload + }, }, }) @@ -123,6 +127,7 @@ export const { setBackendStatus, putConfigWorker, putConfigWorkerResponse, + setPaggingSize, } = authSlice.actions export default authSlice.reducer reducerRegistry.register('authReducer', authSlice.reducer) diff --git a/admin-ui/app/redux/features/types/authTypes.ts b/admin-ui/app/redux/features/types/authTypes.ts index b9799153cc..aafef16523 100644 --- a/admin-ui/app/redux/features/types/authTypes.ts +++ b/admin-ui/app/redux/features/types/authTypes.ts @@ -33,6 +33,7 @@ export interface AuthState { permissions: string[] location: Location config: Config + paggingSize: number defaultToken: any codeChallenge: string | null codeChallengeMethod: string diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index bbb64c8821..b92a26b1bb 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo, useCallback } from 'react' +import React, { useEffect, useMemo, useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import GluuLabel from 'Routes/Apps/Gluu/GluuLabel' import GluuToogleRow from 'Routes/Apps/Gluu/GluuToogleRow' @@ -6,7 +6,7 @@ import { SETTINGS } from 'Utils/ApiResources' import { Card, CardBody, FormGroup, Col, InputGroup, CustomInput, Form } from 'Components' import SetTitle from 'Utils/SetTitle' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' -import { putConfigWorker } from 'Redux/features/authSlice' +import { putConfigWorker, setPaggingSize } from 'Redux/features/authSlice' import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' import { useFormik } from 'formik' import { useDispatch, useSelector } from 'react-redux' @@ -27,24 +27,38 @@ function SettingsPage() { const dispatch = useDispatch() const loadingScripts = useSelector((state) => state.initReducer.loadingScripts) const loadingConfig = useSelector((state) => state.authReducer?.loadingConfig) - const initialPaggingSize = localStorage.getItem('paggingSize') || 10 - const [paggingSize, setPaggingSize] = useState(initialPaggingSize) - const [originalPaggingSize, setOriginalPaggingSize] = useState(initialPaggingSize) + + // Get saved paging size from Redux (source of truth - persisted value) + const savedPaggingSize = useSelector((state) => state.authReducer?.paggingSize) || 10 + + // Local state for editing (current value being displayed in the dropdown) + const [currentPaggingSize, setCurrentPaggingSize] = useState(savedPaggingSize) + SetTitle(t('titles.application_settings')) useEffect(() => { dispatch(getScripts({ action: {} })) }, []) - const resetPaggingSize = () => { - setPaggingSize(originalPaggingSize) - } + // Sync local state when Redux value changes (on mount or after save) + useEffect(() => { + setCurrentPaggingSize(savedPaggingSize) + }, [savedPaggingSize]) + + // Only update local state when user changes dropdown + const handlePaggingSizeChange = useCallback((size) => { + setCurrentPaggingSize(size) + }, []) + + // Reset local state to the last saved Redux value + const resetPaggingSize = useCallback(() => { + setCurrentPaggingSize(savedPaggingSize) + }, [savedPaggingSize]) - const savePaggingSize = () => { - localStorage.setItem('paggingSize', paggingSize) - // Update the original value to the newly saved value - setOriginalPaggingSize(paggingSize) - } + // Save current paging size to Redux (called on form submit) + const savePaggingSize = useCallback(() => { + dispatch(setPaggingSize(currentPaggingSize)) + }, [dispatch, currentPaggingSize]) return ( @@ -90,10 +104,10 @@ function SettingsPage() { type="select" id="pagingSize" name="pagingSize" - value={paggingSize} + value={currentPaggingSize} onChange={(value) => { const size = levels[value.target.options.selectedIndex] - setPaggingSize(size) + handlePaggingSizeChange(size) }} > {levels.map((item, key) => ( @@ -151,10 +165,10 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { initialValues, enableReinitialize: true, onSubmit: (values) => { - // Save paging size to localStorage + // Save paging size to Redux (persist it) savePaggingSize() - // Dispatch the action to save + // Save form config dispatch(putConfigWorker(values)) if (values?.cedarlingLogType !== config?.cedarlingLogType) { @@ -169,8 +183,11 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { }) const handleCancel = useCallback(() => { + // Reset form fields to last saved values const resetValues = transformToFormValues(config) formik.resetForm({ values: resetValues }) + + // Reset paging size to last saved value resetPaggingSize() }, [formik, config, transformToFormValues, resetPaggingSize]) From 4b260650cac9948fffa5f1c6a9ef954ea735a263 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 24 Oct 2025 21:14:57 +0500 Subject: [PATCH 03/10] Improved code for the Setting page with better approach --- .../admin/components/Settings/SettingsPage.js | 70 ++++++++----------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index b92a26b1bb..fbc936e876 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -1,24 +1,25 @@ import React, { useEffect, useMemo, useCallback, useState } from 'react' +import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' +import { useFormik } from 'formik' +import { useDispatch, useSelector } from 'react-redux' +import * as Yup from 'yup' +import { Card, CardBody, FormGroup, Col, InputGroup, CustomInput, Form } from 'Components' import GluuLabel from 'Routes/Apps/Gluu/GluuLabel' import GluuToogleRow from 'Routes/Apps/Gluu/GluuToogleRow' +import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' +import GluuLoader from 'Routes/Apps/Gluu/GluuLoader' +import GluuProperties from 'Routes/Apps/Gluu/GluuProperties' +import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' import { SETTINGS } from 'Utils/ApiResources' -import { Card, CardBody, FormGroup, Col, InputGroup, CustomInput, Form } from 'Components' import SetTitle from 'Utils/SetTitle' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' import { putConfigWorker, setPaggingSize } from 'Redux/features/authSlice' -import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' -import { useFormik } from 'formik' -import { useDispatch, useSelector } from 'react-redux' -import * as Yup from 'yup' -import { SIMPLE_PASSWORD_AUTH } from 'Plugins/auth-server/common/Constants' import { getScripts } from 'Redux/features/initSlice' -import GluuLoader from 'Routes/Apps/Gluu/GluuLoader' -import GluuProperties from 'Routes/Apps/Gluu/GluuProperties' -import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' -import packageJson from '../../../../package.json' +import { updateToast } from 'Redux/features/toastSlice' +import { SIMPLE_PASSWORD_AUTH } from 'Plugins/auth-server/common/Constants' import { CedarlingLogType } from '@/cedarling' -import { updateToast } from '@/redux/features/toastSlice' +import packageJson from '../../../../package.json' const levels = [1, 5, 10, 20] @@ -27,35 +28,26 @@ function SettingsPage() { const dispatch = useDispatch() const loadingScripts = useSelector((state) => state.initReducer.loadingScripts) const loadingConfig = useSelector((state) => state.authReducer?.loadingConfig) - - // Get saved paging size from Redux (source of truth - persisted value) const savedPaggingSize = useSelector((state) => state.authReducer?.paggingSize) || 10 - - // Local state for editing (current value being displayed in the dropdown) const [currentPaggingSize, setCurrentPaggingSize] = useState(savedPaggingSize) - SetTitle(t('titles.application_settings')) useEffect(() => { dispatch(getScripts({ action: {} })) - }, []) + }, [dispatch]) - // Sync local state when Redux value changes (on mount or after save) useEffect(() => { setCurrentPaggingSize(savedPaggingSize) }, [savedPaggingSize]) - // Only update local state when user changes dropdown const handlePaggingSizeChange = useCallback((size) => { setCurrentPaggingSize(size) }, []) - // Reset local state to the last saved Redux value const resetPaggingSize = useCallback(() => { setCurrentPaggingSize(savedPaggingSize) }, [savedPaggingSize]) - // Save current paging size to Redux (called on form submit) const savePaggingSize = useCallback(() => { dispatch(setPaggingSize(currentPaggingSize)) }, [dispatch, currentPaggingSize]) @@ -137,7 +129,6 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { const config = useSelector((state) => state.authReducer?.config) || {} const scripts = useSelector((state) => state.initReducer.scripts) - // Transform config to form values (Fido pattern) const transformToFormValues = useCallback((configData) => { return { sessionTimeoutInMins: configData?.sessionTimeoutInMins || 30, @@ -165,10 +156,7 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { initialValues, enableReinitialize: true, onSubmit: (values) => { - // Save paging size to Redux (persist it) savePaggingSize() - - // Save form config dispatch(putConfigWorker(values)) if (values?.cedarlingLogType !== config?.cedarlingLogType) { @@ -183,15 +171,11 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { }) const handleCancel = useCallback(() => { - // Reset form fields to last saved values const resetValues = transformToFormValues(config) formik.resetForm({ values: resetValues }) - - // Reset paging size to last saved value resetPaggingSize() - }, [formik, config, transformToFormValues, resetPaggingSize]) + }, [config, transformToFormValues, resetPaggingSize, formik]) - // Transform additionalParameters for GluuProperties (Fido pattern) const additionalParametersOptions = useMemo(() => { return (formik.values.additionalParameters || []).map((param) => ({ key: param.key || '', @@ -217,23 +201,25 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { {authScripts.map((item) => ( - + ))} @@ -243,10 +229,9 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { - { @@ -287,4 +272,9 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { ) } +SettingsForm.propTypes = { + resetPaggingSize: PropTypes.func.isRequired, + savePaggingSize: PropTypes.func.isRequired, +} + export default SettingsPage From 9ffe88d4bebf9cb6455f5eab7d11557b06a92e72 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Fri, 24 Oct 2025 21:24:32 +0500 Subject: [PATCH 04/10] Update the code of the config api url badge as before --- .../admin/components/Settings/SettingsPage.js | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index fbc936e876..e39c9c355e 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -1,10 +1,20 @@ -import React, { useEffect, useMemo, useCallback, useState } from 'react' +import React, { useEffect, useMemo, useCallback, useState, useContext } from 'react' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { useFormik } from 'formik' import { useDispatch, useSelector } from 'react-redux' import * as Yup from 'yup' -import { Card, CardBody, FormGroup, Col, InputGroup, CustomInput, Form } from 'Components' +import { + Card, + CardBody, + FormGroup, + Col, + Label, + Badge, + InputGroup, + CustomInput, + Form, +} from 'Components' import GluuLabel from 'Routes/Apps/Gluu/GluuLabel' import GluuToogleRow from 'Routes/Apps/Gluu/GluuToogleRow' import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' @@ -14,6 +24,7 @@ import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' import { SETTINGS } from 'Utils/ApiResources' import SetTitle from 'Utils/SetTitle' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' +import { ThemeContext } from 'Context/theme/themeContext' import { putConfigWorker, setPaggingSize } from 'Redux/features/authSlice' import { getScripts } from 'Redux/features/initSlice' import { updateToast } from 'Redux/features/toastSlice' @@ -26,6 +37,8 @@ const levels = [1, 5, 10, 20] function SettingsPage() { const { t } = useTranslation() const dispatch = useDispatch() + const theme = useContext(ThemeContext) + const selectedTheme = theme.state.theme const loadingScripts = useSelector((state) => state.initReducer.loadingScripts) const loadingConfig = useSelector((state) => state.authReducer?.loadingConfig) const savedPaggingSize = useSelector((state) => state.authReducer?.paggingSize) || 10 @@ -69,19 +82,28 @@ function SettingsPage() { doc_entry="gluuCurrentVersion" /> - {window.configApiBaseUrl && ( - + - )} + + + + Date: Wed, 29 Oct 2025 15:12:01 +0500 Subject: [PATCH 05/10] code rabbit suggestions --- admin-ui/app/redux/features/authSlice.ts | 14 ++--- .../app/redux/features/types/authTypes.ts | 2 +- .../admin/components/Settings/SettingsPage.js | 62 +++++++++---------- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/admin-ui/app/redux/features/authSlice.ts b/admin-ui/app/redux/features/authSlice.ts index a9566144a4..6b085213aa 100644 --- a/admin-ui/app/redux/features/authSlice.ts +++ b/admin-ui/app/redux/features/authSlice.ts @@ -11,7 +11,7 @@ const initialState: AuthState = { permissions: [], location: {}, config: {}, - paggingSize: 10, + pagingSize: 10, defaultToken: null, codeChallenge: null, codeChallengeMethod: 'S256', @@ -52,7 +52,7 @@ const authSlice = createSlice({ setAuthState: (state, action: PayloadAction<{ state: boolean }>) => { state.isAuthenticated = action.payload?.state }, - getUserInfo: (state, _action: PayloadAction) => {}, + getUserInfo: (_state, _action: PayloadAction) => {}, getUserInfoResponse: ( state, action: PayloadAction<{ @@ -75,7 +75,7 @@ const authSlice = createSlice({ state.isAuthenticated = true } }, - getAPIAccessToken: (state, _action: PayloadAction) => {}, + getAPIAccessToken: (_state, _action: PayloadAction) => {}, getAPIAccessTokenResponse: ( state, action: PayloadAction<{ access_token?: string; scopes?: string[]; issuer?: string }>, @@ -90,7 +90,7 @@ const authSlice = createSlice({ state.isAuthenticated = true } }, - getUserLocation: (state, _action: PayloadAction) => {}, + getUserLocation: (_state, _action: PayloadAction) => {}, getUserLocationResponse: (state, action: PayloadAction<{ location?: Location }>) => { if (action.payload?.location) { state.location = action.payload.location @@ -106,8 +106,8 @@ const authSlice = createSlice({ putConfigWorkerResponse: (state) => { state.loadingConfig = false }, - setPaggingSize: (state, action: PayloadAction) => { - state.paggingSize = action.payload + setPagingSize: (state, action: PayloadAction) => { + state.pagingSize = action.payload }, }, }) @@ -127,7 +127,7 @@ export const { setBackendStatus, putConfigWorker, putConfigWorkerResponse, - setPaggingSize, + setPagingSize, } = authSlice.actions export default authSlice.reducer reducerRegistry.register('authReducer', authSlice.reducer) diff --git a/admin-ui/app/redux/features/types/authTypes.ts b/admin-ui/app/redux/features/types/authTypes.ts index aafef16523..59208d6c33 100644 --- a/admin-ui/app/redux/features/types/authTypes.ts +++ b/admin-ui/app/redux/features/types/authTypes.ts @@ -33,7 +33,7 @@ export interface AuthState { permissions: string[] location: Location config: Config - paggingSize: number + pagingSize: number defaultToken: any codeChallenge: string | null codeChallengeMethod: string diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index e39c9c355e..1bdbb784a2 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -25,7 +25,7 @@ import { SETTINGS } from 'Utils/ApiResources' import SetTitle from 'Utils/SetTitle' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' import { ThemeContext } from 'Context/theme/themeContext' -import { putConfigWorker, setPaggingSize } from 'Redux/features/authSlice' +import { putConfigWorker, setPagingSize } from 'Redux/features/authSlice' import { getScripts } from 'Redux/features/initSlice' import { updateToast } from 'Redux/features/toastSlice' import { SIMPLE_PASSWORD_AUTH } from 'Plugins/auth-server/common/Constants' @@ -41,29 +41,32 @@ function SettingsPage() { const selectedTheme = theme.state.theme const loadingScripts = useSelector((state) => state.initReducer.loadingScripts) const loadingConfig = useSelector((state) => state.authReducer?.loadingConfig) - const savedPaggingSize = useSelector((state) => state.authReducer?.paggingSize) || 10 - const [currentPaggingSize, setCurrentPaggingSize] = useState(savedPaggingSize) + const savedPagingSize = useSelector((state) => state.authReducer?.pagingSize) || 10 + const [currentPagingSize, setCurrentPagingSize] = useState(savedPagingSize) SetTitle(t('titles.application_settings')) + const configApiUrl = + typeof window !== 'undefined' && window.configApiBaseUrl ? window.configApiBaseUrl : 'N/A' + useEffect(() => { dispatch(getScripts({ action: {} })) }, [dispatch]) useEffect(() => { - setCurrentPaggingSize(savedPaggingSize) - }, [savedPaggingSize]) + setCurrentPagingSize(savedPagingSize) + }, [savedPagingSize]) - const handlePaggingSizeChange = useCallback((size) => { - setCurrentPaggingSize(size) + const handlePagingSizeChange = useCallback((size) => { + setCurrentPagingSize(size) }, []) - const resetPaggingSize = useCallback(() => { - setCurrentPaggingSize(savedPaggingSize) - }, [savedPaggingSize]) + const resetPagingSize = useCallback(() => { + setCurrentPagingSize(savedPagingSize) + }, [savedPagingSize]) - const savePaggingSize = useCallback(() => { - dispatch(setPaggingSize(currentPaggingSize)) - }, [dispatch, currentPaggingSize]) + const savePagingSize = useCallback(() => { + dispatch(setPagingSize(currentPagingSize)) + }, [dispatch, currentPagingSize]) return ( @@ -99,7 +102,7 @@ function SettingsPage() { }} >

- {window.configApiBaseUrl} + {configApiUrl}

@@ -118,11 +121,8 @@ function SettingsPage() { type="select" id="pagingSize" name="pagingSize" - value={currentPaggingSize} - onChange={(value) => { - const size = levels[value.target.options.selectedIndex] - handlePaggingSizeChange(size) - }} + value={currentPagingSize} + onChange={(e) => handlePagingSizeChange(parseInt(e.target.value, 10))} > {levels.map((item, key) => (
{!loadingScripts && ( - + )} @@ -144,7 +144,7 @@ function SettingsPage() { ) } -function SettingsForm({ resetPaggingSize, savePaggingSize }) { +function SettingsForm({ resetPagingSize, savePagingSize }) { const { t } = useTranslation() const dispatch = useDispatch() @@ -166,19 +166,17 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { ) const authScripts = useMemo(() => { - const filtered = scripts - .filter((item) => item.scriptType === 'person_authentication') - .filter((item) => item.enabled) - .map((item) => item.name) - - return [...filtered, SIMPLE_PASSWORD_AUTH] + const names = (scripts || []) + .filter((s) => s.scriptType === 'person_authentication' && s.enabled) + .map((s) => s.name) + return Array.from(new Set([...names, SIMPLE_PASSWORD_AUTH])) }, [scripts]) const formik = useFormik({ initialValues, enableReinitialize: true, onSubmit: (values) => { - savePaggingSize() + savePagingSize() dispatch(putConfigWorker(values)) if (values?.cedarlingLogType !== config?.cedarlingLogType) { @@ -195,8 +193,8 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { const handleCancel = useCallback(() => { const resetValues = transformToFormValues(config) formik.resetForm({ values: resetValues }) - resetPaggingSize() - }, [config, transformToFormValues, resetPaggingSize, formik]) + resetPagingSize() + }, [config, transformToFormValues, resetPagingSize, formik]) const additionalParametersOptions = useMemo(() => { return (formik.values.additionalParameters || []).map((param) => ({ @@ -295,8 +293,8 @@ function SettingsForm({ resetPaggingSize, savePaggingSize }) { } SettingsForm.propTypes = { - resetPaggingSize: PropTypes.func.isRequired, - savePaggingSize: PropTypes.func.isRequired, + resetPagingSize: PropTypes.func.isRequired, + savePagingSize: PropTypes.func.isRequired, } export default SettingsPage From e8498f3102f94d5e02e3d82a597884b2b8fc5a55 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Wed, 29 Oct 2025 17:43:34 +0500 Subject: [PATCH 06/10] code rabbit suggestions --- admin-ui/app/redux/features/authSlice.ts | 5 - .../app/redux/features/types/authTypes.ts | 1 - .../app/routes/Apps/Gluu/GluuCommitFooter.tsx | 8 + admin-ui/app/utils/pagingUtils.ts | 10 + .../CustomScripts/ScriptListPage.tsx | 9 +- .../components/Permissions/UiPermListPage.js | 3 +- .../admin/components/Roles/UiRoleListPage.js | 4 +- .../admin/components/Settings/SettingsPage.js | 394 +++++++++--------- .../components/Sessions/SessionListPage.tsx | 3 +- .../components/Person/AttributeListPage.tsx | 5 +- .../Components/Configuration/LdapListPage.js | 3 +- .../Components/Configuration/SqlListPage.js | 3 +- 12 files changed, 233 insertions(+), 215 deletions(-) create mode 100644 admin-ui/app/utils/pagingUtils.ts diff --git a/admin-ui/app/redux/features/authSlice.ts b/admin-ui/app/redux/features/authSlice.ts index 6b085213aa..94df83d006 100644 --- a/admin-ui/app/redux/features/authSlice.ts +++ b/admin-ui/app/redux/features/authSlice.ts @@ -11,7 +11,6 @@ const initialState: AuthState = { permissions: [], location: {}, config: {}, - pagingSize: 10, defaultToken: null, codeChallenge: null, codeChallengeMethod: 'S256', @@ -106,9 +105,6 @@ const authSlice = createSlice({ putConfigWorkerResponse: (state) => { state.loadingConfig = false }, - setPagingSize: (state, action: PayloadAction) => { - state.pagingSize = action.payload - }, }, }) @@ -127,7 +123,6 @@ export const { setBackendStatus, putConfigWorker, putConfigWorkerResponse, - setPagingSize, } = authSlice.actions export default authSlice.reducer reducerRegistry.register('authReducer', authSlice.reducer) diff --git a/admin-ui/app/redux/features/types/authTypes.ts b/admin-ui/app/redux/features/types/authTypes.ts index 59208d6c33..b9799153cc 100644 --- a/admin-ui/app/redux/features/types/authTypes.ts +++ b/admin-ui/app/redux/features/types/authTypes.ts @@ -33,7 +33,6 @@ export interface AuthState { permissions: string[] location: Location config: Config - pagingSize: number defaultToken: any codeChallenge: string | null codeChallengeMethod: string diff --git a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx index 15b38831f2..617738f890 100644 --- a/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx +++ b/admin-ui/app/routes/Apps/Gluu/GluuCommitFooter.tsx @@ -13,6 +13,10 @@ interface GluuCommitFooterProps { save?: boolean back?: boolean } + disableButtons?: { + save?: boolean + back?: boolean + } type?: 'button' | 'submit' disableBackButton?: boolean cancelHandler?: () => void @@ -25,6 +29,7 @@ function GluuCommitFooter({ saveHandler, extraLabel, hideButtons, + disableButtons, type = 'button', backButtonLabel, backButtonHandler, @@ -54,6 +59,7 @@ function GluuCommitFooter({ type="button" onClick={disableBackButton ? cancelHandler : goBack} className="d-flex m-1 mx-5" + disabled={disableButtons?.back} > {!disableBackButton && } {backButtonLabel || t('actions.cancel')} @@ -84,6 +90,7 @@ function GluuCommitFooter({ color={`primary-${selectedTheme}`} style={{ ...applicationStyle.buttonStyle, ...applicationStyle.buttonFlexIconStyles }} className="ms-auto px-4" + disabled={disableButtons?.save} > {t('actions.apply')} @@ -97,6 +104,7 @@ function GluuCommitFooter({ style={{ ...applicationStyle.buttonStyle, ...applicationStyle.buttonFlexIconStyles }} className="ms-auto px-4" onClick={saveHandler} + disabled={disableButtons?.save} > {t('actions.apply')} diff --git a/admin-ui/app/utils/pagingUtils.ts b/admin-ui/app/utils/pagingUtils.ts new file mode 100644 index 0000000000..7749dbaacf --- /dev/null +++ b/admin-ui/app/utils/pagingUtils.ts @@ -0,0 +1,10 @@ +export const getPagingSize = (defaultSize: number = 10): number => { + const stored = localStorage.getItem('pagingSize') + if (!stored) return defaultSize + const parsed = parseInt(stored, 10) + return isNaN(parsed) ? defaultSize : parsed +} + +export const savePagingSize = (size: number): void => { + localStorage.setItem('pagingSize', String(size)) +} diff --git a/admin-ui/plugins/admin/components/CustomScripts/ScriptListPage.tsx b/admin-ui/plugins/admin/components/CustomScripts/ScriptListPage.tsx index 138557bd0f..ccc26507af 100644 --- a/admin-ui/plugins/admin/components/CustomScripts/ScriptListPage.tsx +++ b/admin-ui/plugins/admin/components/CustomScripts/ScriptListPage.tsx @@ -27,6 +27,7 @@ import { ThemeContext } from 'Context/theme/themeContext' import getThemeColor from 'Context/theme/config' import { adminUiFeatures } from 'Plugins/admin/helper/utils' import customColors from '@/customColors' +import { getPagingSize } from '@/utils/pagingUtils' import { RootState, UserAction } from './types' function ScriptListTable(): JSX.Element { @@ -39,7 +40,7 @@ function ScriptListTable(): JSX.Element { const [myActions, setMyActions] = useState([]) const [item, setItem] = useState({}) const [modal, setModal] = useState(false) - const pageSize = localStorage.getItem('paggingSize') || 10 + const pageSize = getPagingSize() const [limit, setLimit] = useState(pageSize) const [pattern, setPattern] = useState('') const [type, setType] = useState('person_authentication') @@ -192,7 +193,7 @@ function ScriptListTable(): JSX.Element { isFreeAction: true, onClick: handleGoToCustomScriptAddPage, }) - actions.push((rowData: any) => ({ + actions.push((_rowData: any) => ({ icon: 'edit', iconProps: { color: 'primary', @@ -209,7 +210,7 @@ function ScriptListTable(): JSX.Element { } if (canRead) { - actions.push((rowData: any) => ({ + actions.push((_rowData: any) => ({ icon: 'visibility', iconProps: { color: 'primary', @@ -271,7 +272,7 @@ function ScriptListTable(): JSX.Element { } if (canDelete) { - actions.push((rowData: any) => ({ + actions.push((_rowData: any) => ({ icon: () => , iconProps: { color: 'primary', diff --git a/admin-ui/plugins/admin/components/Permissions/UiPermListPage.js b/admin-ui/plugins/admin/components/Permissions/UiPermListPage.js index 8238cc653b..682dd04d16 100644 --- a/admin-ui/plugins/admin/components/Permissions/UiPermListPage.js +++ b/admin-ui/plugins/admin/components/Permissions/UiPermListPage.js @@ -27,6 +27,7 @@ import { ThemeContext } from 'Context/theme/themeContext' import getThemeColor from 'Context/theme/config' import { isEmpty } from 'lodash' import customColors from '@/customColors' +import { getPagingSize } from '@/utils/pagingUtils' function UiPermListPage() { const { hasCedarPermission, authorize } = useCedarling() @@ -49,7 +50,7 @@ function UiPermListPage() { // Constants const userAction = {} - const pageSize = localStorage.getItem('paggingSize') || 10 + const pageSize = getPagingSize() SetTitle(t('menus.securityDropdown.capabilities')) diff --git a/admin-ui/plugins/admin/components/Roles/UiRoleListPage.js b/admin-ui/plugins/admin/components/Roles/UiRoleListPage.js index 374a7621d5..01980eeaae 100644 --- a/admin-ui/plugins/admin/components/Roles/UiRoleListPage.js +++ b/admin-ui/plugins/admin/components/Roles/UiRoleListPage.js @@ -17,18 +17,18 @@ import { ThemeContext } from 'Context/theme/themeContext' import getThemeColor from 'Context/theme/config' import { toast } from 'react-toastify' import customColors from '@/customColors' +import { getPagingSize } from '@/utils/pagingUtils' function UiRoleListPage() { const { hasCedarPermission, authorize } = useCedarling() const apiRoles = useSelector((state) => state.apiRoleReducer.items) const loading = useSelector((state) => state.apiRoleReducer.loading) - const { permissions: cedarPermissions } = useSelector((state) => state.cedarPermissions) const [modal, setModal] = useState(false) const myActions = [], options = [], userAction = {}, - pageSize = localStorage.getItem('paggingSize') || 10, + pageSize = getPagingSize(), theme = useContext(ThemeContext), selectedTheme = theme.state.theme, themeColors = getThemeColor(selectedTheme), diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index 1bdbb784a2..953a386c1f 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -1,5 +1,4 @@ import React, { useEffect, useMemo, useCallback, useState, useContext } from 'react' -import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { useFormik } from 'formik' import { useDispatch, useSelector } from 'react-redux' @@ -25,15 +24,15 @@ import { SETTINGS } from 'Utils/ApiResources' import SetTitle from 'Utils/SetTitle' import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' import { ThemeContext } from 'Context/theme/themeContext' -import { putConfigWorker, setPagingSize } from 'Redux/features/authSlice' +import { putConfigWorker } from 'Redux/features/authSlice' import { getScripts } from 'Redux/features/initSlice' import { updateToast } from 'Redux/features/toastSlice' import { SIMPLE_PASSWORD_AUTH } from 'Plugins/auth-server/common/Constants' import { CedarlingLogType } from '@/cedarling' +import { getPagingSize, savePagingSize as savePagingSizeToStorage } from 'Utils/pagingUtils' +import { buildPayload } from 'Utils/PermChecker' import packageJson from '../../../../package.json' -const levels = [1, 5, 10, 20] - function SettingsPage() { const { t } = useTranslation() const dispatch = useDispatch() @@ -41,116 +40,33 @@ function SettingsPage() { const selectedTheme = theme.state.theme const loadingScripts = useSelector((state) => state.initReducer.loadingScripts) const loadingConfig = useSelector((state) => state.authReducer?.loadingConfig) - const savedPagingSize = useSelector((state) => state.authReducer?.pagingSize) || 10 + const config = useSelector((state) => state.authReducer?.config) || {} + const scripts = useSelector((state) => state.initReducer.scripts) + + const savedPagingSize = useMemo(() => getPagingSize(10), []) const [currentPagingSize, setCurrentPagingSize] = useState(savedPagingSize) + + const pagingSizeOptions = useMemo(() => [1, 5, 10, 20], []) + SetTitle(t('titles.application_settings')) - const configApiUrl = - typeof window !== 'undefined' && window.configApiBaseUrl ? window.configApiBaseUrl : 'N/A' + const configApiUrl = useMemo( + () => + typeof window !== 'undefined' && window.configApiBaseUrl ? window.configApiBaseUrl : 'N/A', + [], + ) useEffect(() => { - dispatch(getScripts({ action: {} })) + const userAction = {} + const options = {} + buildPayload(userAction, 'Fetch custom scripts', options) + dispatch(getScripts({ action: userAction })) }, [dispatch]) - useEffect(() => { - setCurrentPagingSize(savedPagingSize) - }, [savedPagingSize]) - const handlePagingSizeChange = useCallback((size) => { setCurrentPagingSize(size) }, []) - const resetPagingSize = useCallback(() => { - setCurrentPagingSize(savedPagingSize) - }, [savedPagingSize]) - - const savePagingSize = useCallback(() => { - dispatch(setPagingSize(currentPagingSize)) - }, [dispatch, currentPagingSize]) - - return ( - - - - - - - - - - - - - - - - - - handlePagingSizeChange(parseInt(e.target.value, 10))} - > - {levels.map((item, key) => ( - - ))} - - - - - - {!loadingScripts && ( - - )} - - - - - ) -} - -function SettingsForm({ resetPagingSize, savePagingSize }) { - const { t } = useTranslation() - const dispatch = useDispatch() - - const config = useSelector((state) => state.authReducer?.config) || {} - const scripts = useSelector((state) => state.initReducer.scripts) - const transformToFormValues = useCallback((configData) => { return { sessionTimeoutInMins: configData?.sessionTimeoutInMins || 30, @@ -176,7 +92,10 @@ function SettingsForm({ resetPagingSize, savePagingSize }) { initialValues, enableReinitialize: true, onSubmit: (values) => { - savePagingSize() + // Save paging size to localStorage before dispatching saga + savePagingSizeToStorage(currentPagingSize) + + // Dispatch saga after saving paging size dispatch(putConfigWorker(values)) if (values?.cedarlingLogType !== config?.cedarlingLogType) { @@ -190,11 +109,15 @@ function SettingsForm({ resetPagingSize, savePagingSize }) { }), }) + const isPagingSizeChanged = currentPagingSize !== savedPagingSize + const isFormChanged = formik.dirty || isPagingSizeChanged + const hasErrors = !formik.isValid + const handleCancel = useCallback(() => { const resetValues = transformToFormValues(config) formik.resetForm({ values: resetValues }) - resetPagingSize() - }, [config, transformToFormValues, resetPagingSize, formik]) + setCurrentPagingSize(savedPagingSize) + }, [config, transformToFormValues, savedPagingSize, formik]) const additionalParametersOptions = useMemo(() => { return (formik.values.additionalParameters || []).map((param) => ({ @@ -203,98 +126,177 @@ function SettingsForm({ resetPagingSize, savePagingSize }) { })) }, [formik.values.additionalParameters]) - return ( -
- - - - - - - - {authScripts.map((item) => ( - - ))} - - - - - - - - { - formik.setFieldValue( - 'cedarlingLogType', - event.target.checked ? CedarlingLogType.STD_OUT : CedarlingLogType.OFF, - ) - }} - /> - - -
- -
- - + const formGroupRowStyle = useMemo(() => ({ justifyContent: 'space-between' }), []) + + const labelContainerStyle = useMemo( + () => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + paddingRight: '15px', + }), + [], ) -} -SettingsForm.propTypes = { - resetPagingSize: PropTypes.func.isRequired, - savePagingSize: PropTypes.func.isRequired, + return ( + + + + +
+ + + + + + + + + + + + + + handlePagingSizeChange(parseInt(e.target.value, 10))} + > + {pagingSizeOptions.map((option, index) => ( + + ))} + + + + + + {!loadingScripts && ( + <> + + + + + + + + + {authScripts.map((item) => ( + + ))} + + + + + + + + + { + formik.setFieldValue( + 'cedarlingLogType', + event.target.checked ? CedarlingLogType.STD_OUT : CedarlingLogType.OFF, + ) + }} + /> + + + +
+ +
+ + + + )} + +
+
+
+
+ ) } export default SettingsPage diff --git a/admin-ui/plugins/auth-server/components/Sessions/SessionListPage.tsx b/admin-ui/plugins/auth-server/components/Sessions/SessionListPage.tsx index 02973a4f79..375e02fb4d 100644 --- a/admin-ui/plugins/auth-server/components/Sessions/SessionListPage.tsx +++ b/admin-ui/plugins/auth-server/components/Sessions/SessionListPage.tsx @@ -37,6 +37,7 @@ import GetAppIcon from '@mui/icons-material/GetApp' import ViewColumnIcon from '@mui/icons-material/ViewColumn' import { DeleteOutlined } from '@mui/icons-material' import customColors from '@/customColors' +import { getPagingSize } from '@/utils/pagingUtils' import { useGetSessions, useDeleteSession, @@ -170,7 +171,7 @@ const SessionListPage: React.FC = () => { updatedColumns: [], }) - const pageSize = Number.parseInt(localStorage.getItem('paggingSize') || '10', 10) + const pageSize = getPagingSize() const toggle = () => setModal(!modal) const theme = useContext(ThemeContext) const selectedTheme = theme?.state?.theme || 'default' diff --git a/admin-ui/plugins/schema/components/Person/AttributeListPage.tsx b/admin-ui/plugins/schema/components/Person/AttributeListPage.tsx index 54fdef734a..92825f7494 100644 --- a/admin-ui/plugins/schema/components/Person/AttributeListPage.tsx +++ b/admin-ui/plugins/schema/components/Person/AttributeListPage.tsx @@ -27,6 +27,7 @@ import { adminUiFeatures } from 'Plugins/admin/helper/utils' import customColors from '@/customColors' import styled from 'styled-components' import { LIMIT_ID, PATTERN_ID } from 'Plugins/admin/common/Constants' +import { getPagingSize } from '@/utils/pagingUtils' import type { JansAttribute, GetAttributesOptions } from 'Plugins/schema/types' import type { Dispatch } from '@reduxjs/toolkit' import type { @@ -66,9 +67,7 @@ function AttributeListPage(): JSX.Element { useEffect(() => {}, [cedarPermissions]) const options: GetAttributesOptions = {} - const pageSize = localStorage.getItem('paggingSize') - ? parseInt(localStorage.getItem('paggingSize')!) - : 10 + const pageSize = getPagingSize() const [limit, setLimit] = useState(pageSize) const [pageNumber, setPageNumber] = useState(0) const [pattern, setPattern] = useState(null) diff --git a/admin-ui/plugins/services/Components/Configuration/LdapListPage.js b/admin-ui/plugins/services/Components/Configuration/LdapListPage.js index 1a47cca31e..ca0c589d69 100644 --- a/admin-ui/plugins/services/Components/Configuration/LdapListPage.js +++ b/admin-ui/plugins/services/Components/Configuration/LdapListPage.js @@ -27,6 +27,7 @@ import SetTitle from 'Utils/SetTitle' import { ThemeContext } from 'Context/theme/themeContext' import getThemeColor from 'Context/theme/config' import customColors from '@/customColors' +import { getPagingSize } from '@/utils/pagingUtils' function LdapListPage() { const { hasCedarPermission, authorize } = useCedarling() @@ -60,7 +61,7 @@ function LdapListPage() { // Constants const userAction = {} - const pageSize = localStorage.getItem('paggingSize') || 10 + const pageSize = getPagingSize() SetTitle(t('titles.ldap_authentication')) diff --git a/admin-ui/plugins/services/Components/Configuration/SqlListPage.js b/admin-ui/plugins/services/Components/Configuration/SqlListPage.js index 447f514d23..9a4f479d86 100644 --- a/admin-ui/plugins/services/Components/Configuration/SqlListPage.js +++ b/admin-ui/plugins/services/Components/Configuration/SqlListPage.js @@ -25,6 +25,7 @@ import SetTitle from 'Utils/SetTitle' import { ThemeContext } from 'Context/theme/themeContext' import getThemeColor from 'Context/theme/config' import customColors from '@/customColors' +import { getPagingSize } from '@/utils/pagingUtils' function SqlListPage() { const { hasCedarPermission, authorize } = useCedarling() @@ -56,7 +57,7 @@ function SqlListPage() { const navigate = useNavigate() const [item, setItem] = useState({}) const [modal, setModal] = useState(false) - const pageSize = localStorage.getItem('paggingSize') || 10 + const pageSize = getPagingSize() const [alertObj, setAlertObj] = useState({ severity: '', message: '', From 0e660493d06aa191c6ad80b6aa68f1bb4f7a0f59 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Wed, 29 Oct 2025 17:56:45 +0500 Subject: [PATCH 07/10] code rabbit suggestions --- admin-ui/app/utils/pagingUtils.ts | 45 ++++++++++++++++--- .../admin/components/Settings/SettingsPage.js | 17 ++++--- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/admin-ui/app/utils/pagingUtils.ts b/admin-ui/app/utils/pagingUtils.ts index 7749dbaacf..6387ab8e3f 100644 --- a/admin-ui/app/utils/pagingUtils.ts +++ b/admin-ui/app/utils/pagingUtils.ts @@ -1,10 +1,45 @@ export const getPagingSize = (defaultSize: number = 10): number => { - const stored = localStorage.getItem('pagingSize') - if (!stored) return defaultSize - const parsed = parseInt(stored, 10) - return isNaN(parsed) ? defaultSize : parsed + // Guard against SSR/test environments where localStorage is unavailable + if (typeof window === 'undefined' || !window.localStorage) { + return defaultSize + } + + try { + const stored = localStorage.getItem('gluu.pagingSize') + + if (!stored) return defaultSize + + const parsed = parseInt(stored, 10) + // Only return if it's a valid positive integer (>0) + if (!isNaN(parsed) && parsed > 0) { + return parsed + } + + return defaultSize + } catch (error) { + // Silently handle localStorage errors (quota exceeded, privacy mode, etc.) + console.warn('Failed to read paging size from localStorage:', error) + return defaultSize + } } export const savePagingSize = (size: number): void => { - localStorage.setItem('pagingSize', String(size)) + // Validate and coerce input to a positive integer + const validSize = Math.floor(size) + if (validSize <= 0) { + console.warn('Invalid paging size:', size, '- must be a positive integer') + return + } + + // Guard against SSR/test environments where localStorage is unavailable + if (typeof window === 'undefined' || !window.localStorage) { + return + } + + try { + localStorage.setItem('gluu.pagingSize', String(validSize)) + } catch (error) { + // Silently handle localStorage errors (quota exceeded, privacy mode, etc.) + console.warn('Failed to save paging size to localStorage:', error) + } } diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index 953a386c1f..01405600db 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -37,17 +37,22 @@ function SettingsPage() { const { t } = useTranslation() const dispatch = useDispatch() const theme = useContext(ThemeContext) - const selectedTheme = theme.state.theme + const selectedTheme = theme?.state?.theme || 'darkBlack' const loadingScripts = useSelector((state) => state.initReducer.loadingScripts) const loadingConfig = useSelector((state) => state.authReducer?.loadingConfig) const config = useSelector((state) => state.authReducer?.config) || {} const scripts = useSelector((state) => state.initReducer.scripts) - const savedPagingSize = useMemo(() => getPagingSize(10), []) - const [currentPagingSize, setCurrentPagingSize] = useState(savedPagingSize) - const pagingSizeOptions = useMemo(() => [1, 5, 10, 20], []) + const defaultPagingSize = useMemo(() => { + // Use the third option (10) as default, or fallback to first option if array is too short + return pagingSizeOptions[2] || pagingSizeOptions[0] || 10 + }, [pagingSizeOptions]) + + const savedPagingSize = useMemo(() => getPagingSize(defaultPagingSize), [defaultPagingSize]) + const [currentPagingSize, setCurrentPagingSize] = useState(savedPagingSize) + SetTitle(t('titles.application_settings')) const configApiUrl = useMemo( @@ -188,8 +193,8 @@ function SettingsPage() { value={currentPagingSize} onChange={(e) => handlePagingSizeChange(parseInt(e.target.value, 10))} > - {pagingSizeOptions.map((option, index) => ( - ))} From 114285be7b270f6fd621430fa2c04990684dc0a9 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Wed, 29 Oct 2025 18:04:00 +0500 Subject: [PATCH 08/10] code rabbit suggestions --- admin-ui/plugins/admin/components/Settings/SettingsPage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index 01405600db..06565e33b7 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -114,15 +114,17 @@ function SettingsPage() { }), }) + const { resetForm } = formik + const isPagingSizeChanged = currentPagingSize !== savedPagingSize const isFormChanged = formik.dirty || isPagingSizeChanged const hasErrors = !formik.isValid const handleCancel = useCallback(() => { const resetValues = transformToFormValues(config) - formik.resetForm({ values: resetValues }) + resetForm({ values: resetValues }) setCurrentPagingSize(savedPagingSize) - }, [config, transformToFormValues, savedPagingSize, formik]) + }, [config, transformToFormValues, savedPagingSize, resetForm]) const additionalParametersOptions = useMemo(() => { return (formik.values.additionalParameters || []).map((param) => ({ From 269602112e5f49e38c8c19cfcb0c620791b4e2f9 Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Wed, 29 Oct 2025 18:45:43 +0500 Subject: [PATCH 09/10] code rabbit suggestions --- admin-ui/app/redux/sagas/AuthSaga.ts | 12 +++++-- .../admin/components/Settings/SettingsPage.js | 32 ++++++++++++------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/admin-ui/app/redux/sagas/AuthSaga.ts b/admin-ui/app/redux/sagas/AuthSaga.ts index 39967525fb..30b68dacb7 100644 --- a/admin-ui/app/redux/sagas/AuthSaga.ts +++ b/admin-ui/app/redux/sagas/AuthSaga.ts @@ -58,11 +58,19 @@ function* getOAuth2ConfigWorker({ payload }) { function* putConfigWorker({ payload }) { try { + // Extract metadata (if any) from payload + const { _meta, ...configData } = payload const token = yield select((state) => state.authReducer.token.access_token) - const response = yield call(putServerConfiguration, { token, props: payload }) + const response = yield call(putServerConfiguration, { token, props: configData }) if (response) { yield put(getOAuth2ConfigResponse({ config: response })) - yield put(updateToast(true, 'success')) + + // If cedarlingLogType changed, show specific toast; otherwise show generic success + if (_meta?.cedarlingLogTypeChanged && _meta?.toastMessage) { + yield put(updateToast(true, 'success', _meta.toastMessage)) + } else { + yield put(updateToast(true, 'success')) + } return } } catch (error) { diff --git a/admin-ui/plugins/admin/components/Settings/SettingsPage.js b/admin-ui/plugins/admin/components/Settings/SettingsPage.js index 06565e33b7..7096fa4c4e 100644 --- a/admin-ui/plugins/admin/components/Settings/SettingsPage.js +++ b/admin-ui/plugins/admin/components/Settings/SettingsPage.js @@ -26,7 +26,6 @@ import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle' import { ThemeContext } from 'Context/theme/themeContext' import { putConfigWorker } from 'Redux/features/authSlice' import { getScripts } from 'Redux/features/initSlice' -import { updateToast } from 'Redux/features/toastSlice' import { SIMPLE_PASSWORD_AUTH } from 'Plugins/auth-server/common/Constants' import { CedarlingLogType } from '@/cedarling' import { getPagingSize, savePagingSize as savePagingSizeToStorage } from 'Utils/pagingUtils' @@ -51,6 +50,7 @@ function SettingsPage() { }, [pagingSizeOptions]) const savedPagingSize = useMemo(() => getPagingSize(defaultPagingSize), [defaultPagingSize]) + const [baselinePagingSize, setBaselinePagingSize] = useState(savedPagingSize) const [currentPagingSize, setCurrentPagingSize] = useState(savedPagingSize) SetTitle(t('titles.application_settings')) @@ -97,15 +97,23 @@ function SettingsPage() { initialValues, enableReinitialize: true, onSubmit: (values) => { - // Save paging size to localStorage before dispatching saga savePagingSizeToStorage(currentPagingSize) - // Dispatch saga after saving paging size - dispatch(putConfigWorker(values)) + const cedarlingLogTypeChanged = values?.cedarlingLogType !== config?.cedarlingLogType - if (values?.cedarlingLogType !== config?.cedarlingLogType) { - dispatch(updateToast(true, 'success', t('fields.reloginToViewCedarlingChanges'))) - } + dispatch( + putConfigWorker({ + ...values, + _meta: { + cedarlingLogTypeChanged, + toastMessage: cedarlingLogTypeChanged + ? t('fields.reloginToViewCedarlingChanges') + : undefined, + }, + }), + ) + + setBaselinePagingSize(currentPagingSize) }, validationSchema: Yup.object().shape({ sessionTimeoutInMins: Yup.number() @@ -116,15 +124,15 @@ function SettingsPage() { const { resetForm } = formik - const isPagingSizeChanged = currentPagingSize !== savedPagingSize + const isPagingSizeChanged = currentPagingSize !== baselinePagingSize const isFormChanged = formik.dirty || isPagingSizeChanged const hasErrors = !formik.isValid const handleCancel = useCallback(() => { const resetValues = transformToFormValues(config) resetForm({ values: resetValues }) - setCurrentPagingSize(savedPagingSize) - }, [config, transformToFormValues, savedPagingSize, resetForm]) + setCurrentPagingSize(baselinePagingSize) + }, [config, transformToFormValues, baselinePagingSize, resetForm]) const additionalParametersOptions = useMemo(() => { return (formik.values.additionalParameters || []).map((param) => ({ @@ -192,11 +200,11 @@ function SettingsPage() { type="select" id="pagingSize" name="pagingSize" - value={currentPagingSize} + value={String(currentPagingSize)} onChange={(e) => handlePagingSizeChange(parseInt(e.target.value, 10))} > {pagingSizeOptions.map((option) => ( - ))} From 9f942083ccd2caef1b79b9314dfd0b40c416fefe Mon Sep 17 00:00:00 2001 From: faisalsiddique4400 Date: Wed, 29 Oct 2025 18:59:30 +0500 Subject: [PATCH 10/10] tests added --- admin-ui/app/redux/sagas/AuthSaga.ts | 6 +- .../redux/sagas/__tests__/AuthSaga.test.ts | 249 ++++++++++++++++++ 2 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 admin-ui/app/redux/sagas/__tests__/AuthSaga.test.ts diff --git a/admin-ui/app/redux/sagas/AuthSaga.ts b/admin-ui/app/redux/sagas/AuthSaga.ts index 30b68dacb7..fa6d8ffd68 100644 --- a/admin-ui/app/redux/sagas/AuthSaga.ts +++ b/admin-ui/app/redux/sagas/AuthSaga.ts @@ -1,7 +1,5 @@ // @ts-nocheck -/** - * Auth Sagas - */ + import { all, call, fork, put, select, takeEvery } from 'redux-saga/effects' import { getOAuth2ConfigResponse, @@ -56,7 +54,7 @@ function* getOAuth2ConfigWorker({ payload }) { yield put(getOAuth2ConfigResponse()) } -function* putConfigWorker({ payload }) { +export function* putConfigWorker({ payload }) { try { // Extract metadata (if any) from payload const { _meta, ...configData } = payload diff --git a/admin-ui/app/redux/sagas/__tests__/AuthSaga.test.ts b/admin-ui/app/redux/sagas/__tests__/AuthSaga.test.ts new file mode 100644 index 0000000000..fd6a330891 --- /dev/null +++ b/admin-ui/app/redux/sagas/__tests__/AuthSaga.test.ts @@ -0,0 +1,249 @@ +import { expectSaga } from 'redux-saga-test-plan' +import * as matchers from 'redux-saga-test-plan/matchers' +import { throwError } from 'redux-saga-test-plan/providers' +import { getOAuth2ConfigResponse, putConfigWorkerResponse } from '../../features/authSlice' +import { updateToast } from '../../features/toastSlice' +import { putServerConfiguration } from '../../api/backend-api' +import * as AuthSaga from '../AuthSaga' + +const { putConfigWorker } = AuthSaga + +describe('AuthSaga - putConfigWorker', () => { + const mockToken = 'mock-access-token' + const mockConfig = { + sessionTimeoutInMins: 30, + acrValues: 'simple_password_auth', + cedarlingLogType: 'OFF', + additionalParameters: [], + } + const mockResponse = { + ...mockConfig, + postLogoutRedirectUri: 'https://example.com/logout', + } + + const mockState = { + authReducer: { + token: { + access_token: mockToken, + }, + }, + } + + describe('when cedarlingLogType changes', () => { + // eslint-disable-next-line jest/expect-expect + it('should dispatch updateToast with custom message on success', () => { + const customToastMessage = 'Please relogin to view cedarling changes' + const action = { + type: 'auth/putConfigWorker', + payload: { + ...mockConfig, + _meta: { + cedarlingLogTypeChanged: true, + toastMessage: customToastMessage, + }, + }, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), mockResponse]]) + .put(getOAuth2ConfigResponse({ config: mockResponse })) + .put(updateToast(true, 'success', customToastMessage)) + .put(putConfigWorkerResponse()) + .run() + }) + + // eslint-disable-next-line jest/expect-expect + it('should not dispatch custom toast message on error', () => { + const customToastMessage = 'Please relogin to view cedarling changes' + const mockError = new Error('Network error') + const action = { + type: 'auth/putConfigWorker', + payload: { + ...mockConfig, + _meta: { + cedarlingLogTypeChanged: true, + toastMessage: customToastMessage, + }, + }, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), throwError(mockError)]]) + .not.put(updateToast(true, 'success', customToastMessage)) + .put(updateToast(true, 'error')) + .put(putConfigWorkerResponse()) + .run() + }) + }) + + describe('when cedarlingLogType does not change', () => { + // eslint-disable-next-line jest/expect-expect + it('should dispatch generic success toast when no metadata provided', () => { + const action = { + type: 'auth/putConfigWorker', + payload: mockConfig, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), mockResponse]]) + .put(getOAuth2ConfigResponse({ config: mockResponse })) + .put(updateToast(true, 'success')) + .put(putConfigWorkerResponse()) + .run() + }) + + // eslint-disable-next-line jest/expect-expect + it('should dispatch generic success toast when cedarlingLogTypeChanged is false', () => { + const action = { + type: 'auth/putConfigWorker', + payload: { + ...mockConfig, + _meta: { + cedarlingLogTypeChanged: false, + }, + }, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), mockResponse]]) + .put(getOAuth2ConfigResponse({ config: mockResponse })) + .put(updateToast(true, 'success')) + .put(putConfigWorkerResponse()) + .run() + }) + + // eslint-disable-next-line jest/expect-expect + it('should dispatch generic success toast when toastMessage is undefined', () => { + const action = { + type: 'auth/putConfigWorker', + payload: { + ...mockConfig, + _meta: { + cedarlingLogTypeChanged: true, + toastMessage: undefined, + }, + }, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), mockResponse]]) + .put(getOAuth2ConfigResponse({ config: mockResponse })) + .put(updateToast(true, 'success')) + .put(putConfigWorkerResponse()) + .run() + }) + }) + + describe('error handling', () => { + // eslint-disable-next-line jest/expect-expect + it('should dispatch error toast on failure', () => { + const mockError = new Error('API Error') + const action = { + type: 'auth/putConfigWorker', + payload: mockConfig, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), throwError(mockError)]]) + .not.put(getOAuth2ConfigResponse({ config: mockResponse })) + .put(updateToast(true, 'error')) + .put(putConfigWorkerResponse()) + .run() + }) + }) + + describe('metadata extraction', () => { + // eslint-disable-next-line jest/expect-expect + it('should not send _meta to the API', () => { + const action = { + type: 'auth/putConfigWorker', + payload: { + ...mockConfig, + _meta: { + cedarlingLogTypeChanged: true, + toastMessage: 'Test message', + }, + }, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), mockResponse]]) + .call(putServerConfiguration, { + token: mockToken, + props: mockConfig, // Should NOT include _meta + }) + .run() + }) + + // eslint-disable-next-line jest/expect-expect + it('should handle payload without _meta correctly', () => { + const action = { + type: 'auth/putConfigWorker', + payload: mockConfig, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), mockResponse]]) + .call(putServerConfiguration, { + token: mockToken, + props: mockConfig, + }) + .put(getOAuth2ConfigResponse({ config: mockResponse })) + .put(updateToast(true, 'success')) + .run() + }) + }) + + describe('response handling', () => { + // eslint-disable-next-line jest/expect-expect + it('should handle successful response with config update', () => { + const action = { + type: 'auth/putConfigWorker', + payload: mockConfig, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), mockResponse]]) + .put(getOAuth2ConfigResponse({ config: mockResponse })) + .run() + }) + + // eslint-disable-next-line jest/expect-expect + it('should always call putConfigWorkerResponse in finally block', () => { + const action = { + type: 'auth/putConfigWorker', + payload: mockConfig, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), mockResponse]]) + .put(putConfigWorkerResponse()) + .run() + }) + + // eslint-disable-next-line jest/expect-expect + it('should call putConfigWorkerResponse even on error', () => { + const mockError = new Error('API Error') + const action = { + type: 'auth/putConfigWorker', + payload: mockConfig, + } + + return expectSaga(putConfigWorker, action) + .withState(mockState) + .provide([[matchers.call.fn(putServerConfiguration), throwError(mockError)]]) + .put(putConfigWorkerResponse()) + .run() + }) + }) +})