mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
[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 0e6719b32f.
* 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>
This commit is contained in:
co-authored by
mr-anton-t
parent
1c57e670c0
commit
e466462269
@@ -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<TPartState, TFormState = any> implements IFormPart<TPartState> {
|
||||
export abstract class FormPart<TPartState extends object, TFormState = any> implements IFormPart<TPartState> {
|
||||
state: TPartState;
|
||||
initialState: TPartState;
|
||||
isSaving: boolean;
|
||||
@@ -24,10 +25,12 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
|
||||
|
||||
protected loaded: boolean;
|
||||
protected loading: boolean;
|
||||
readonly schema: schema.ZodType<TPartState> | null;
|
||||
|
||||
constructor(
|
||||
protected readonly formState: IFormState<TFormState>,
|
||||
initialState: TPartState,
|
||||
schema: schema.ZodType<TPartState> | null = null,
|
||||
) {
|
||||
this.initialState = initialState;
|
||||
this.state = toJS(this.initialState);
|
||||
@@ -38,14 +41,16 @@ export abstract class FormPart<TPartState, TFormState = any> 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, 'loaded' | 'loading' | 'setInitialState' | 'setState'>(this, {
|
||||
makeObservable<this, 'loaded' | 'loading' | 'setInitialState' | 'setState' | 'schema'>(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<TPartState, TFormState = any> implements IFormPar
|
||||
}
|
||||
|
||||
private async handleValidation(data: IFormState<TFormState>, contexts: IExecutionContextProvider<IFormState<TFormState>>): Promise<void> {
|
||||
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;
|
||||
|
||||
+11
-10
@@ -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<typeof ServerConfigurationFormPartStateConfigSchema>;
|
||||
export type INavigatorConfig = schema.infer<typeof ServerConfigurationFormPartStateNavigatorSchema>;
|
||||
|
||||
export type IServerConfigurationFormPartState = {
|
||||
serverConfig: IServerConfig;
|
||||
navigatorConfig: INavigatorConfig;
|
||||
};
|
||||
export type IServerConfigurationFormPartState = schema.infer<typeof ServerConfigStateSchema>;
|
||||
|
||||
+2
-20
@@ -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<IServerConfigurationFo
|
||||
private readonly passwordPolicyService: PasswordPolicyService,
|
||||
private readonly localizationService: LocalizationService,
|
||||
) {
|
||||
super(formState, DEFAULT_STATE_GETTER());
|
||||
super(formState, DEFAULT_STATE_GETTER(), ServerConfigStateSchema);
|
||||
}
|
||||
|
||||
override isOutdated(): boolean {
|
||||
@@ -99,24 +99,6 @@ export class ServerConfigurationFormPart extends FormPart<IServerConfigurationFo
|
||||
}
|
||||
}
|
||||
|
||||
protected override format() {
|
||||
if (this.state.serverConfig.adminName) {
|
||||
this.state.serverConfig.adminName = this.state.serverConfig.adminName.trim();
|
||||
}
|
||||
|
||||
if (this.state.serverConfig.adminPassword) {
|
||||
this.state.serverConfig.adminPassword = this.state.serverConfig.adminPassword.trim();
|
||||
}
|
||||
|
||||
if (this.state.serverConfig.serverName) {
|
||||
this.state.serverConfig.serverName = this.state.serverConfig.serverName.trim();
|
||||
}
|
||||
|
||||
if (this.state.serverConfig.serverURL) {
|
||||
this.state.serverConfig.serverURL = this.state.serverConfig.serverURL.trim();
|
||||
}
|
||||
}
|
||||
|
||||
override get isChanged(): boolean {
|
||||
if (this.loaded && this.administrationScreenService.isConfigurationMode) {
|
||||
return true;
|
||||
|
||||
+12
-9
@@ -1,17 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface IUserFormInfoState {
|
||||
userId: string;
|
||||
enabled: boolean;
|
||||
password: string;
|
||||
metaParameters: Record<string, any>;
|
||||
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<typeof USER_FORM_INFO_PART_SCHEMA>;
|
||||
|
||||
+13
-26
@@ -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<IUserFormInfoState, IUserFormStat
|
||||
private readonly usersResource: UsersResource,
|
||||
private readonly usersMetaParametersResource: UsersMetaParametersResource,
|
||||
) {
|
||||
super(formState, {
|
||||
userId: formState.state.userId || '',
|
||||
enabled: DEFAULT_ENABLED,
|
||||
password: '',
|
||||
metaParameters: {},
|
||||
teams: [],
|
||||
authRole: '',
|
||||
});
|
||||
super(
|
||||
formState,
|
||||
{
|
||||
userId: formState.state.userId || '',
|
||||
enabled: DEFAULT_ENABLED,
|
||||
password: '',
|
||||
metaParameters: {},
|
||||
teams: [],
|
||||
authRole: '',
|
||||
},
|
||||
USER_FORM_INFO_PART_SCHEMA,
|
||||
);
|
||||
|
||||
this.disableUser = this.disableUser.bind(this);
|
||||
}
|
||||
|
||||
protected override format(data: IFormState<IUserFormState>, contexts: IExecutionContextProvider<IFormState<IUserFormState>>): void | Promise<void> {
|
||||
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);
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user