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) => ( = ({ fidoConfiguration, @@ -30,6 +30,7 @@ const DynamicConfiguration: React.FC = ({ onSubmit: toggle, validationSchema: validationSchema[fidoConstants.VALIDATION_SCHEMAS.DYNAMIC_CONFIG], enableReinitialize: true, + validateOnMount: true, }) const submitForm = useCallback( @@ -74,6 +75,7 @@ const DynamicConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required showError={!!(formik.errors.issuer && formik.touched.issuer)} errorMessage={formik.errors.issuer} /> @@ -87,6 +89,7 @@ const DynamicConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required showError={!!(formik.errors.baseEndpoint && formik.touched.baseEndpoint)} errorMessage={formik.errors.baseEndpoint} /> @@ -302,13 +305,17 @@ const DynamicConfiguration: React.FC = ({ - diff --git a/admin-ui/plugins/fido/components/Fido.tsx b/admin-ui/plugins/fido/components/Fido.tsx index 86d2bd5703..b963625687 100644 --- a/admin-ui/plugins/fido/components/Fido.tsx +++ b/admin-ui/plugins/fido/components/Fido.tsx @@ -16,7 +16,7 @@ import { } from 'JansConfigApi' import { updateToast } from 'Redux/features/toastSlice' import { useDispatch, useSelector } from 'react-redux' -import { DynamicConfigFormValues, StaticConfigFormValues } from '../types/fido-types' +import { DynamicConfigFormValues, StaticConfigFormValues } from '../types/fido' import { logAudit } from 'Utils/AuditLogger' import { AuthRootState } from 'Utils/types' diff --git a/admin-ui/plugins/fido/components/StaticConfiguration.tsx b/admin-ui/plugins/fido/components/StaticConfiguration.tsx index a7c45f5a35..cee08139ed 100644 --- a/admin-ui/plugins/fido/components/StaticConfiguration.tsx +++ b/admin-ui/plugins/fido/components/StaticConfiguration.tsx @@ -7,7 +7,7 @@ import GluuInputRow from 'Routes/Apps/Gluu/GluuInputRow' import GluuSelectRow from 'Routes/Apps/Gluu/GluuSelectRow' import GluuToggleRow from 'Routes/Apps/Gluu/GluuToggleRow' import GluuCommitDialog from 'Routes/Apps/Gluu/GluuCommitDialog' -import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' +import GluuFormFooter from 'Routes/Apps/Gluu/GluuFormFooter' import GluuLabel from 'Routes/Apps/Gluu/GluuLabel' import GluuProperties from 'Routes/Apps/Gluu/GluuProperties' import GluuTypeAhead from 'Routes/Apps/Gluu/GluuTypeAhead' @@ -20,7 +20,12 @@ import { getAvailableHintOptions, getEmptyDropdownMessage, } from '../helper' -import { StaticConfigurationProps, StaticConfigFormValues } from '../types/fido-types' +import { + StaticConfigurationProps, + StaticConfigFormValues, + MinimalFormik, + FormikSetFieldValue, +} from '../types/fido' import { AttestationMode } from '../types' const StaticConfiguration: React.FC = ({ @@ -46,6 +51,7 @@ const StaticConfiguration: React.FC = ({ onSubmit: toggle, validationSchema: validationSchema[fidoConstants.VALIDATION_SCHEMAS.STATIC_CONFIG], enableReinitialize: true, + validateOnMount: true, }) const submitForm = useCallback( @@ -86,25 +92,30 @@ const StaticConfiguration: React.FC = ({ }, [formik.values.metadataServers]) // Create a wrapper formik that transforms metadata servers between key/value and url/rootCert - const metadataServersFormik = useMemo(() => { - const setFieldValueWrapper = (name: string, value: any) => { + const metadataServersFormik = useMemo>(() => { + const setFieldValueWrapper: FormikSetFieldValue = ( + name, + value, + shouldValidate, + ) => { if (name === fidoConstants.FORM_FIELDS.METADATA_SERVERS) { // Transform {key, value} back to {url, rootCert} - const transformedValue = value.map((item: any) => ({ + const arr = Array.isArray(value) ? (value as Array<{ key?: string; value?: string }>) : [] + const transformedValue = arr.map((item) => ({ url: item.key || '', rootCert: item.value || '', })) - formik.setFieldValue(name, transformedValue) + formik.setFieldValue(name, transformedValue, shouldValidate) } else { - formik.setFieldValue(name, value) + formik.setFieldValue(name, value, shouldValidate) } } return { - ...formik, + values: formik.values, setFieldValue: setFieldValueWrapper, } - }, [formik.values, formik.errors, formik.touched]) + }, [formik.values, formik.setFieldValue]) const availableHintOptions = useMemo(() => { return getAvailableHintOptions(formik.values.hints) @@ -133,6 +144,7 @@ const StaticConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required showError={ !!(formik.errors.authenticatorCertsFolder && formik.touched.authenticatorCertsFolder) } @@ -147,6 +159,7 @@ const StaticConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required showError={!!(formik.errors.mdsCertsFolder && formik.touched.mdsCertsFolder)} errorMessage={formik.errors.mdsCertsFolder} /> @@ -159,6 +172,7 @@ const StaticConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required showError={!!(formik.errors.mdsTocsFolder && formik.touched.mdsTocsFolder)} errorMessage={formik.errors.mdsTocsFolder} /> @@ -173,6 +187,7 @@ const StaticConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required showError={ !!( formik.errors.unfinishedRequestExpiration && @@ -192,6 +207,7 @@ const StaticConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required showError={ !!( formik.errors.authenticationHistoryExpiration && @@ -210,6 +226,7 @@ const StaticConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required showError={ !!(formik.errors.serverMetadataFolder && formik.touched.serverMetadataFolder) } @@ -224,6 +241,7 @@ const StaticConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required doc_category={fidoConstants.DOC_CATEGORY} /> @@ -297,16 +315,20 @@ const StaticConfiguration: React.FC = ({ { + const next = (selected || []).filter((v): v is string => typeof v === 'string') + formik.setFieldValue(fidoConstants.FORM_FIELDS.HINTS, next) + }} /> @@ -317,6 +339,7 @@ const StaticConfiguration: React.FC = ({ formik={formik} lsize={4} rsize={8} + required doc_category={fidoConstants.DOC_CATEGORY} /> @@ -342,13 +365,17 @@ const StaticConfiguration: React.FC = ({ - diff --git a/admin-ui/plugins/fido/helper/utils.ts b/admin-ui/plugins/fido/helper/utils.ts index 52b0cef6cc..8bb72ebb25 100644 --- a/admin-ui/plugins/fido/helper/utils.ts +++ b/admin-ui/plugins/fido/helper/utils.ts @@ -1,4 +1,4 @@ -import { PublicKeyCredentialHints } from '../types' +import { PublicKeyCredentialHints, AttestationMode } from '../types' import { fidoConstants } from './constants' import { AppConfiguration1, Fido2Configuration } from 'JansConfigApi' import { @@ -6,7 +6,7 @@ import { StaticConfigFormValues, CreateFidoConfigPayloadParams, PutPropertiesFido2Params, -} from '../types/fido-types' +} from '../types/fido' const isStaticConfigType = (type?: string): type is typeof fidoConstants.STATIC => { return type === fidoConstants.STATIC @@ -68,7 +68,11 @@ const transformStaticConfigToFormValues = ( disableMetadataService: toBooleanValue(config?.disableMetadataService), hints: arrayValidationWithSchema(config?.hints || [], PublicKeyCredentialHints), enterpriseAttestation: toBooleanValue(config?.enterpriseAttestation), - attestationMode: config?.attestationMode || '', + attestationMode: (() => { + const mode = (config?.attestationMode as string) || '' + const validModes = Object.values(AttestationMode) + return validModes.includes(mode as AttestationMode) ? mode : '' + })(), } } diff --git a/admin-ui/plugins/fido/types/fido-types.ts b/admin-ui/plugins/fido/types/fido.ts similarity index 85% rename from admin-ui/plugins/fido/types/fido-types.ts rename to admin-ui/plugins/fido/types/fido.ts index 2d80d67018..fb4f7d17d7 100644 --- a/admin-ui/plugins/fido/types/fido-types.ts +++ b/admin-ui/plugins/fido/types/fido.ts @@ -59,3 +59,15 @@ export interface CreateFidoConfigPayloadParams { export interface PutPropertiesFido2Params { data: AppConfiguration1 } + +// Minimal Formik subset used by StaticConfiguration for typeahead adapters +export type FormikSetFieldValue = ( + field: K, + value: TValues[K], + shouldValidate?: boolean, +) => void + +export interface MinimalFormik { + values: TValues + setFieldValue: FormikSetFieldValue +} diff --git a/admin-ui/plugins/services/Components/Configuration/CachePage.js b/admin-ui/plugins/services/Components/Configuration/CachePage.js index 9ec07bd186..708527a8b0 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() { )} - { + toggle() + formik.handleSubmit() + }} 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 24dbad7038..3765e1022f 100644 --- a/admin-ui/plugins/smtp-management/components/SmtpManagement/SmtpForm.tsx +++ b/admin-ui/plugins/smtp-management/components/SmtpManagement/SmtpForm.tsx @@ -5,7 +5,7 @@ import GluuSelectRow from 'Routes/Apps/Gluu/GluuSelectRow' import { SelectChangeEvent } from '@mui/material' import { useFormik } from 'formik' import GluuCommitDialog from 'Routes/Apps/Gluu/GluuCommitDialog' -import GluuCommitFooter from 'Routes/Apps/Gluu/GluuCommitFooter' +import GluuFormFooter from 'Routes/Apps/Gluu/GluuFormFooter' import { useTranslation } from 'react-i18next' import { enableTestButton, @@ -55,6 +55,7 @@ function SmtpForm(props: Readonly) { toggle() }, validationSchema, + validateOnMount: true, }) useEffect(() => { @@ -67,7 +68,17 @@ function SmtpForm(props: Readonly) { formik.resetForm() } - const submitForm = (userMessage: string) => { + const submitForm = async (userMessage: string) => { + const errors = await formik.validateForm() + if (Object.keys(errors).length > 0) { + // mark all fields as touched to reveal validation messages + const touched: Record = {} + Object.keys(errors).forEach((k) => (touched[k] = true)) + formik.setTouched(touched) + toast.error(t('messages.mandatory_fields_required')) + return + } + toggle() const trimmedValues = trimObjectStrings( formik.values as unknown as Record, @@ -328,16 +339,28 @@ function SmtpForm(props: Readonly) { /> + {testButtonEnabled && ( + + + + + + )} + -