From e466462269954ab54be81b1f4bba2a9901e47883 Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Mon, 15 Sep 2025 15:47:55 +0200 Subject: [PATCH] [CB-4362] Add trim to all inputs in forms (#3687) * dbeaver/pro#4696 adds zod schema parsing for form parts * adds validation error to context * adds schema parsing for UserFormInfoPart & ConnectionFormOptionsPart * spreads RecordKeySchema across zod validations * fixes form editing for connection info part * Revert "spreads RecordKeySchema across zod validations" This reverts commit 0e6719b32fcaefcbee85c3e443e648f3638a3590. * removes connection form errors during saving when empty fields * reverts connection form options part parsing * removes trimming from teams & authRole * build fix --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> --- webapp/packages/core-ui/src/Form/FormPart.ts | 24 ++++++++++-- .../IServerConfigurationFormPartState.ts | 21 +++++----- .../ServerConfigurationFormPart.ts | 22 +---------- .../Users/UserForm/Info/IUserFormInfoState.ts | 21 +++++----- .../Users/UserForm/Info/UserFormInfoPart.ts | 39 +++++++------------ .../Options/AdvancedPropertiesForm.tsx | 2 +- .../ConnectionForm/Options/ParametersForm.tsx | 2 +- 7 files changed, 61 insertions(+), 70 deletions(-) diff --git a/webapp/packages/core-ui/src/Form/FormPart.ts b/webapp/packages/core-ui/src/Form/FormPart.ts index d6971b6bc0..9ca8c8b036 100644 --- a/webapp/packages/core-ui/src/Form/FormPart.ts +++ b/webapp/packages/core-ui/src/Form/FormPart.ts @@ -8,13 +8,14 @@ import { action, computed, makeObservable, observable, toJS } from 'mobx'; import { executorHandlerFilter, ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeaver/core-executor'; -import { isObjectsEqual } from '@cloudbeaver/core-utils'; +import { isObjectsEqual, schema } from '@cloudbeaver/core-utils'; import type { IFormPart } from './IFormPart.js'; import type { IFormState } from './IFormState.js'; import { formSubmitContext } from './formSubmitContext.js'; +import { formValidationContext } from './formValidationContext.js'; -export abstract class FormPart implements IFormPart { +export abstract class FormPart implements IFormPart { state: TPartState; initialState: TPartState; isSaving: boolean; @@ -24,10 +25,12 @@ export abstract class FormPart implements IFormPar protected loaded: boolean; protected loading: boolean; + readonly schema: schema.ZodType | null; constructor( protected readonly formState: IFormState, initialState: TPartState, + schema: schema.ZodType | null = null, ) { this.initialState = initialState; this.state = toJS(this.initialState); @@ -38,14 +41,16 @@ export abstract class FormPart implements IFormPar this.loaded = false; this.loading = false; + this.schema = schema; this.formState.submitTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.save.bind(this))); this.formState.formatTask.addHandler(executorHandlerFilter(() => this.isLoaded() && this.isChanged, this.format.bind(this))); this.formState.validationTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.handleValidation.bind(this))); - makeObservable(this, { + makeObservable(this, { initialState: observable, state: observable, + schema: observable.ref, exception: observable.ref, promise: observable.ref, isSaving: observable.ref, @@ -150,8 +155,21 @@ export abstract class FormPart implements IFormPar } private async handleValidation(data: IFormState, contexts: IExecutionContextProvider>): Promise { + const validation = contexts.getContext(formValidationContext); + + try { + if (this.schema) { + const parsedState = this.schema.parse(toJS(this.state)); + this.setState(observable(parsedState)); + } + } catch (e: any) { + validation.error(schema.prettifyError(e)); + return; + } + try { this.exception = null; + await this.validate(data, contexts); } catch (exception: any) { this.exception = exception; diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts index daa08906b9..bcfaa6d7db 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts @@ -9,9 +9,9 @@ import { schema } from '@cloudbeaver/core-utils'; const ServerConfigurationFormPartStateConfigSchema = schema.object({ adminCredentialsSaveEnabled: schema.boolean().optional(), - adminName: schema.string().optional(), - adminPassword: schema.string().optional(), - adminPasswordRepeat: schema.string().optional(), + adminName: schema.string().trim().optional(), + adminPassword: schema.string().trim().optional(), + adminPasswordRepeat: schema.string().trim().optional(), anonymousAccessEnabled: schema.boolean().optional(), authenticationEnabled: schema.boolean().optional(), customConnectionsEnabled: schema.boolean().optional(), @@ -21,8 +21,8 @@ const ServerConfigurationFormPartStateConfigSchema = schema.object({ publicCredentialsSaveEnabled: schema.boolean().optional(), resourceManagerEnabled: schema.boolean().optional(), secretManagerEnabled: schema.boolean().optional(), - serverName: schema.string().optional(), - serverURL: schema.string().optional(), + serverName: schema.string().trim().optional(), + serverURL: schema.string().trim().optional(), sessionExpireTime: schema.number().optional(), forceHttps: schema.boolean().optional(), supportedHosts: schema.string(), @@ -39,10 +39,11 @@ const ServerConfigurationFormPartStateNavigatorSchema = schema.object({ showUtilityObjects: schema.boolean(), }); +export const ServerConfigStateSchema = schema.object({ + serverConfig: ServerConfigurationFormPartStateConfigSchema, + navigatorConfig: ServerConfigurationFormPartStateNavigatorSchema, +}); + export type IServerConfig = schema.infer; export type INavigatorConfig = schema.infer; - -export type IServerConfigurationFormPartState = { - serverConfig: IServerConfig; - navigatorConfig: INavigatorConfig; -}; +export type IServerConfigurationFormPartState = schema.infer; diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts index 8de41ae551..f16b23de7c 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts @@ -16,7 +16,7 @@ import { isIp, isObjectsEqual, isValuesEqual } from '@cloudbeaver/core-utils'; import { LocalizationService } from '@cloudbeaver/core-localization'; import { MIN_SESSION_EXPIRE_TIME } from './Form/MIN_SESSION_EXPIRE_TIME.js'; -import type { IServerConfigurationFormPartState } from './IServerConfigurationFormPartState.js'; +import { ServerConfigStateSchema, type IServerConfigurationFormPartState } from './IServerConfigurationFormPartState.js'; function DEFAULT_STATE_GETTER(): IServerConfigurationFormPartState { return { @@ -54,7 +54,7 @@ export class ServerConfigurationFormPart extends FormPart; - teams: string[]; +import { schema } from '@cloudbeaver/core-utils'; - authRole: string; // used in TE product -} +export const USER_FORM_INFO_PART_SCHEMA = schema.object({ + userId: schema.string().trim(), + enabled: schema.boolean(), + password: schema.string().trim(), + metaParameters: schema.record(schema.string(), schema.string().trim().or(schema.any())), + teams: schema.array(schema.string()), + authRole: schema.string(), +}); + +export type IUserFormInfoState = schema.infer; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPart.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPart.ts index 8bde7f297b..271bc9074f 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPart.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/Info/UserFormInfoPart.ts @@ -15,7 +15,7 @@ import { isArraysEqual, isObjectsEqual, isValuesEqual } from '@cloudbeaver/core- import { isDefined } from '@dbeaver/js-helpers'; import type { IUserFormState } from '../AdministrationUserFormService.js'; -import type { IUserFormInfoState } from './IUserFormInfoState.js'; +import { USER_FORM_INFO_PART_SCHEMA, type IUserFormInfoState } from './IUserFormInfoState.js'; const DEFAULT_ENABLED = true; @@ -27,35 +27,22 @@ export class UserFormInfoPart extends FormPart, contexts: IExecutionContextProvider>): void | Promise { - this.state.password = this.state.password.trim(); - const metaParameters = this.state.metaParameters; - - if (this.formState.mode === FormMode.Create) { - this.state.userId = this.state.userId.trim(); - } - - for (const key in metaParameters) { - const value = metaParameters[key]; - - if (typeof value === 'string') { - metaParameters[key] = value.trim(); - } - } - } - override isOutdated(): boolean { if (this.formState.mode === FormMode.Edit && this.initialState.userId) { return this.usersResource.isOutdated(this.initialState.userId) || this.usersMetaParametersResource.isOutdated(this.initialState.userId); diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/AdvancedPropertiesForm.tsx b/webapp/packages/plugin-connections/src/ConnectionForm/Options/AdvancedPropertiesForm.tsx index ceb6b3a6d5..df18e6c9dd 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/AdvancedPropertiesForm.tsx +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/AdvancedPropertiesForm.tsx @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ParametersForm.tsx b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ParametersForm.tsx index 1a557dc05b..9fe2aac6e1 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ParametersForm.tsx +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ParametersForm.tsx @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others + * Copyright (C) 2020-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License.