diff --git a/webapp/packages/plugin-connections-administration/src/Administration/Connections/CreateConnectionService.ts b/webapp/packages/plugin-connections-administration/src/Administration/Connections/CreateConnectionService.ts index 7e8a9efb7d..149d1a69a9 100644 --- a/webapp/packages/plugin-connections-administration/src/Administration/Connections/CreateConnectionService.ts +++ b/webapp/packages/plugin-connections-administration/src/Administration/Connections/CreateConnectionService.ts @@ -6,12 +6,12 @@ * you may not use this file except in compliance with the License. */ -import { observable, makeObservable } from 'mobx'; +import { observable, makeObservable, action } from 'mobx'; import { AdministrationScreenService } from '@cloudbeaver/core-administration'; -import { TabsContainer } from '@cloudbeaver/core-ui'; import { injectable } from '@cloudbeaver/core-di'; import type { ConnectionConfig } from '@cloudbeaver/core-sdk'; +import { TabsContainer } from '@cloudbeaver/core-ui'; import { ConnectionFormService, ConnectionFormState, IConnectionFormState } from '@cloudbeaver/plugin-connections'; import { ConnectionsResource } from '../ConnectionsResource'; @@ -40,16 +40,23 @@ export class CreateConnectionService { this.data = null; this.tabsContainer = new TabsContainer(); - makeObservable(this, { - data: observable, - disabled: observable, - }); - this.setConnectionTemplate = this.setConnectionTemplate.bind(this); this.clearConnectionTemplate = this.clearConnectionTemplate.bind(this); this.setCreateMethod = this.setCreateMethod.bind(this); this.cancelCreate = this.cancelCreate.bind(this); this.create = this.create.bind(this); + + makeObservable(this, { + data: observable, + disabled: observable, + setCreateMethod: action, + cancelCreate: action, + create: action, + setConnectionTemplate: action, + clearConnectionTemplate: action, + close: action, + activateMethod: action, + }); } getDefault(): string | null { diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormService.ts b/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormService.ts index 29ec023b81..e7b2b35191 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormService.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormService.ts @@ -6,14 +6,14 @@ * you may not use this file except in compliance with the License. */ -import { observable, toJS } from 'mobx'; +import { observable, runInAction, toJS } from 'mobx'; -import { TabsContainer } from '@cloudbeaver/core-ui'; import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; import { injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { ENotificationType, NotificationService } from '@cloudbeaver/core-events'; import { ExecutorHandlersCollection, ExecutorInterrupter, IExecutorHandler, IExecutorHandlersCollection } from '@cloudbeaver/core-executor'; +import { TabsContainer } from '@cloudbeaver/core-ui'; import { ConnectionAuthenticationDialog } from '../ConnectionAuthentication/ConnectionAuthenticationDialog'; import { ConnectionFormBaseActions } from './ConnectionFormBaseActions'; @@ -101,7 +101,7 @@ export class ConnectionFormService { }, }); - private showSubmittingStatusMessage: IExecutorHandler = (data, contexts) => { + private readonly showSubmittingStatusMessage: IExecutorHandler = (data, contexts) => { const status = contexts.getContext(this.connectionStatusContext); if (!status.saved) { @@ -124,7 +124,7 @@ export class ConnectionFormService { } }; - private askCredentials: IExecutorHandler = async (data, contexts) => { + private readonly askCredentials: IExecutorHandler = async (data, contexts) => { const credentialsState = contexts.getContext(connectionCredentialsStateContext); if (data.submitType !== 'test' || (!credentialsState.authModelId && !credentialsState.networkHandlers.length)) { @@ -133,21 +133,23 @@ export class ConnectionFormService { const config = contexts.getContext(connectionConfigContext); - if (credentialsState.authModelId) { - if (!config.credentials) { - config.credentials = { ...data.state.config.credentials }; + runInAction(() => { + if (credentialsState.authModelId) { + if (!config.credentials) { + config.credentials = { ...data.state.config.credentials }; + } + + config.credentials = observable(config.credentials); } - config.credentials = observable(config.credentials); - } + if (credentialsState.networkHandlers.length > 0) { + if (!config.networkHandlersConfig) { + config.networkHandlersConfig = toJS(data.state.config.networkHandlersConfig) || []; + } - if (credentialsState.networkHandlers.length > 0) { - if (!config.networkHandlersConfig) { - config.networkHandlersConfig = toJS(data.state.config.networkHandlersConfig) || []; + config.networkHandlersConfig = observable(config.networkHandlersConfig); } - - config.networkHandlersConfig = observable(config.networkHandlersConfig); - } + }); const result = await this.commonDialogService.open(ConnectionAuthenticationDialog, { config, @@ -161,7 +163,7 @@ export class ConnectionFormService { } }; - private ensureValidation: IExecutorHandler = (data, contexts) => { + private readonly ensureValidation: IExecutorHandler = (data, contexts) => { const validation = contexts.getContext(this.connectionValidationContext); if (!validation.valid) { diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormState.ts b/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormState.ts index ca1182b7e8..4c2f36a378 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormState.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormState.ts @@ -6,13 +6,13 @@ * you may not use this file except in compliance with the License. */ -import { computed, makeObservable, observable } from 'mobx'; +import { action, computed, makeObservable, observable } from 'mobx'; import type { IFormStateInfo } from '@cloudbeaver/core-blocks'; import { DatabaseConnection, EConnectionFeature, IConnectionsResource } from '@cloudbeaver/core-connections'; import { Executor, IExecutionContextProvider, IExecutor } from '@cloudbeaver/core-executor'; import { ConnectionConfig, ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; -import { MetadataMap } from '@cloudbeaver/core-utils'; +import { MetadataMap, uuid } from '@cloudbeaver/core-utils'; import { connectionFormConfigureContext } from './connectionFormConfigureContext'; import type { ConnectionFormService } from './ConnectionFormService'; @@ -71,10 +71,19 @@ export class ConnectionFormState implements IConnectionFormState { return false; } + get id(): string { + if (this.mode === 'create') { + return 'create'; + } + + return this.config.connectionId || this._id; + } + readonly resource: IConnectionsResource; readonly service: ConnectionFormService; readonly submittingTask: IExecutor; + private readonly _id: string; private stateInfo: IFormStateInfo | null; private readonly loadConnectionTask: IExecutor; private readonly formStateTask: IExecutor; @@ -84,22 +93,9 @@ export class ConnectionFormState implements IConnectionFormState { service: ConnectionFormService, resource: IConnectionsResource ) { + this._id = uuid(); this.initError = null; - makeObservable(this, { - mode: observable, - type: observable, - config: observable, - availableDrivers: computed, - _availableDrivers: observable, - info: computed, - statusMessage: observable, - configured: observable, - readonly: computed, - stateInfo: observable, - initError: observable.ref, - }); - this.resource = resource; this.config = {}; this._availableDrivers = []; @@ -142,6 +138,26 @@ export class ConnectionFormState implements IConnectionFormState { }; }) .next(this.formStateTask); + + makeObservable(this, { + mode: observable, + type: observable, + config: observable, + availableDrivers: computed, + _availableDrivers: observable, + info: computed, + statusMessage: observable, + configured: observable, + readonly: computed, + stateInfo: observable, + initError: observable.ref, + id: computed, + reset: action, + setOptions: action, + setConfig: action, + setAvailableDrivers: action, + updateFormState: action, + }); } async loadConnectionInfo(): Promise { diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/DriverProperties/ConnectionDriverPropertiesTabService.ts b/webapp/packages/plugin-connections/src/ConnectionForm/DriverProperties/ConnectionDriverPropertiesTabService.ts index c8a16a6330..4504b388d1 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/DriverProperties/ConnectionDriverPropertiesTabService.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/DriverProperties/ConnectionDriverPropertiesTabService.ts @@ -6,6 +6,8 @@ * you may not use this file except in compliance with the License. */ +import { action, makeObservable } from 'mobx'; + import { DBDriverResource } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import type { IExecutionContextProvider } from '@cloudbeaver/core-executor'; @@ -24,6 +26,10 @@ export class ConnectionDriverPropertiesTabService extends Bootstrap { private readonly dbDriverResource: DBDriverResource, ) { super(); + + makeObservable(this, { + fillConfig: action, + }); } register(): void { @@ -35,7 +41,7 @@ export class ConnectionDriverPropertiesTabService extends Bootstrap { panel: () => DriverProperties, isDisabled: (tabId, props) => { if (props?.state.config.driverId) { - return !props?.state.config.driverId; + return !props.state.config.driverId; } return true; }, diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/IConnectionFormProps.ts b/webapp/packages/plugin-connections/src/ConnectionForm/IConnectionFormProps.ts index a1278da73c..89719669ab 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/IConnectionFormProps.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/IConnectionFormProps.ts @@ -23,6 +23,7 @@ export interface IConnectionFormState { partsState: MetadataMap; + readonly id: string; readonly initError: Error | null; readonly statusMessage: string | null; readonly disabled: boolean; diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts index 49c9140e2f..86e61a10cf 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts @@ -6,6 +6,8 @@ * you may not use this file except in compliance with the License. */ +import { action, makeObservable, runInAction, toJS } from 'mobx'; + import { DatabaseAuthModelsResource, DatabaseConnection, DBDriverResource, isJDBCConnection } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import type { IExecutionContextProvider } from '@cloudbeaver/core-executor'; @@ -28,6 +30,10 @@ export class ConnectionOptionsTabService extends Bootstrap { private readonly databaseAuthModelsResource: DatabaseAuthModelsResource, ) { super(); + + makeObservable(this, { + fillConfig: action, + }); } register(): void { @@ -179,45 +185,46 @@ export class ConnectionOptionsTabService extends Bootstrap { } const driver = await this.dbDriverResource.load(state.config.driverId, ['includeProviderProperties']); + const tempConfig = toJS(config); if (state.mode === 'edit') { - config.connectionId = state.config.connectionId; + tempConfig.connectionId = state.config.connectionId; } - config.name = state.config.name?.trim(); + tempConfig.name = state.config.name?.trim(); - if (config.name && state.mode === 'create') { + if (tempConfig.name && state.mode === 'create') { const connections = await state.resource.load(CachedMapAllKey); const connectionNames = connections.map(connection => connection.name); - config.name = getUniqueName(config.name, connectionNames); + tempConfig.name = getUniqueName(tempConfig.name, connectionNames); } - config.description = state.config.description; - config.template = state.config.template; - config.driverId = state.config.driverId; + tempConfig.description = state.config.description; + tempConfig.template = state.config.template; + tempConfig.driverId = state.config.driverId; if (isJDBCConnection(driver, state.info)) { - config.url = state.config.url; + tempConfig.url = state.config.url; } else { if (!driver.embedded) { - config.host = state.config.host; - config.port = state.config.port; + tempConfig.host = state.config.host; + tempConfig.port = state.config.port; } - config.databaseName = state.config.databaseName; + tempConfig.databaseName = state.config.databaseName; } if (state.config.authModelId || driver.defaultAuthModel) { - config.authModelId = state.config.authModelId || driver.defaultAuthModel; - config.saveCredentials = state.config.saveCredentials; + tempConfig.authModelId = state.config.authModelId || driver.defaultAuthModel; + tempConfig.saveCredentials = state.config.saveCredentials; - const properties = await this.getConnectionAuthModelProperties(config.authModelId, state.info); + const properties = await this.getConnectionAuthModelProperties(tempConfig.authModelId, state.info); if (this.isCredentialsChanged(properties, state.config.credentials)) { - config.credentials = { ...state.config.credentials }; + tempConfig.credentials = { ...state.config.credentials }; } - if (!config.saveCredentials) { - credentialsState.requireAuthModel(config.authModelId); + if (!tempConfig.saveCredentials) { + credentialsState.requireAuthModel(tempConfig.authModelId); } } @@ -235,8 +242,12 @@ export class ConnectionOptionsTabService extends Bootstrap { providerProperties[providerProperty.id] = providerProperty.defaultValue; } - config.providerProperties = providerProperties; + tempConfig.providerProperties = providerProperties; } + + runInAction(() => { + Object.assign(config, tempConfig); + }); } private async formState( diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/Options.tsx b/webapp/packages/plugin-connections/src/ConnectionForm/Options/Options.tsx index 7f63c55ac8..9812f6674d 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/Options.tsx +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/Options.tsx @@ -46,10 +46,9 @@ const styles = css` } `; -export const Options: TabContainerPanelComponent = observer(function Options(props) { - const { - state, - } = props; +export const Options: TabContainerPanelComponent = observer(function Options({ + state, +}) { const service = useService(ConnectionFormService); const formRef = useRef(null); const translate = useTranslate(); @@ -63,11 +62,11 @@ export const Options: TabContainerPanelComponent = observe } = state; const authentication = useAuthenticationAction({ - origin: state.info?.origin ?? { type: AUTH_PROVIDER_LOCAL_ID, displayName: 'Local' }, + origin: info?.origin ?? { type: AUTH_PROVIDER_LOCAL_ID, displayName: 'Local' }, }); useFormValidator(submittingHandlers.for(service.formValidationTask), formRef.current); - const optionsHook = useOptions(props.state); + const optionsHook = useOptions(state); const { credentialsSavingEnabled } = useAdministrationSettings(); const handleDriverSelect = useCallback(async (value?: string, name?: string, prev?: string) => { @@ -87,8 +86,8 @@ export const Options: TabContainerPanelComponent = observe { key: config.driverId || null, includes: ['includeProviderProperties'] }, { onData: (data, resource, prevData) => { - if (!prevData) { - handleDriverSelect(data.id); + if (data.id !== prevData?.id) { + optionsHook.setDefaults(data); } }, } diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/useOptions.ts b/webapp/packages/plugin-connections/src/ConnectionForm/Options/useOptions.ts index b7f11f3995..58efe93e76 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/useOptions.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/useOptions.ts @@ -6,6 +6,8 @@ * you may not use this file except in compliance with the License. */ +import { runInAction } from 'mobx'; + import { useObjectRef } from '@cloudbeaver/core-blocks'; import { DBDriver, isJDBCConnection } from '@cloudbeaver/core-connections'; import type { DatabaseAuthModel } from '@cloudbeaver/core-sdk'; @@ -23,84 +25,87 @@ export function useOptions(state: IConnectionFormState) { return useObjectRef({ updateNameTemplate(driver: DBDriver | undefined) { - const { - prevName, - state: { - config, - info, - mode, - }, - } = refObject; + runInAction(() => { + const { + prevName, + state: { + config, + info, + mode, + }, + } = refObject; - const isAutoFill = config.name === prevName || prevName === null; + const isAutoFill = config.name === prevName || prevName === null; - if (mode === 'edit' || !isAutoFill) { - return; - } - - if (isJDBCConnection(driver, info)) { - refObject.prevName = config.url || ''; - config.name = config.url || ''; - return; - } - - if (!driver) { - config.name = 'New connection'; - return; - } - - let name = driver.name || ''; - if (config.host) { - name += '@' + config.host.slice(0, MAX_HOST_LENGTH); - if (config.port && config.port !== driver.defaultPort) { - name += ':' + config.port; + if (mode === 'edit' || !isAutoFill) { + return; } - } - refObject.prevName = name; - config.name = name; + + if (isJDBCConnection(driver, info)) { + refObject.prevName = config.url || ''; + config.name = config.url || ''; + return; + } + + if (!driver) { + config.name = 'New connection'; + return; + } + + let name = driver.name || ''; + if (config.host) { + name += '@' + config.host.slice(0, MAX_HOST_LENGTH); + if (config.port && config.port !== driver.defaultPort) { + name += ':' + config.port; + } + } + refObject.prevName = name; + config.name = name; + }); }, - setDefaults(driver: DBDriver | undefined, prevDriver: DBDriver | undefined) { - const { - state: { - config, - info, - }, - } = refObject; + setDefaults(driver: DBDriver | undefined, prevDriver?: DBDriver) { + runInAction(() => { + const { + state: { + config, + info, + }, + } = refObject; - if (info) { - return; - } - - if (!prevDriver || config.host === prevDriver.defaultServer) { - config.host = driver?.defaultServer || 'localhost'; - } - - if (!prevDriver || config.port === prevDriver.defaultPort) { - config.port = driver?.defaultPort; - } - - if (!prevDriver || config.databaseName === prevDriver.defaultDatabase) { - config.databaseName = driver?.defaultDatabase; - } - - if (!prevDriver || config.url === prevDriver.sampleURL) { - config.url = driver?.sampleURL; - } - - this.updateNameTemplate(driver); - - if (driver?.id !== prevDriver?.id) { - for (const property of Object.keys(config.credentials)) { - delete config.credentials[property]; + if (info || driver?.id !== config.driverId) { + return; } - for (const property of Object.keys(config.providerProperties)) { - delete config.providerProperties[property]; + if ((!prevDriver && config.host === undefined) || config.host === prevDriver?.defaultServer) { + config.host = driver?.defaultServer || 'localhost'; } - config.authModelId = driver?.defaultAuthModel; - } - + if ((!prevDriver && config.port === undefined) || config.port === prevDriver?.defaultPort) { + config.port = driver?.defaultPort; + } + + if ((!prevDriver && config.databaseName === undefined) || config.databaseName === prevDriver?.defaultDatabase) { + config.databaseName = driver?.defaultDatabase; + } + + if ((!prevDriver && config.url === undefined) || config.url === prevDriver?.sampleURL) { + config.url = driver?.sampleURL; + } + + this.updateNameTemplate(driver); + + if (driver?.id !== prevDriver?.id) { + for (const property of Object.keys(config.credentials)) { + delete config.credentials[property]; + } + + for (const property of Object.keys(config.providerProperties)) { + delete config.providerProperties[property]; + } + + config.authModelId = driver?.defaultAuthModel; + } + }); }, setAuthModel(model: DatabaseAuthModel) { // const { diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/SSH/ConnectionSSHTabService.ts b/webapp/packages/plugin-connections/src/ConnectionForm/SSH/ConnectionSSHTabService.ts index c1fcf8f2e1..bddefe9e40 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/SSH/ConnectionSSHTabService.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/SSH/ConnectionSSHTabService.ts @@ -6,6 +6,8 @@ * you may not use this file except in compliance with the License. */ +import { action, makeObservable } from 'mobx'; + import { DBDriverResource, SSH_TUNNEL_ID } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import type { IExecutionContextProvider } from '@cloudbeaver/core-executor'; @@ -27,6 +29,11 @@ export class ConnectionSSHTabService extends Bootstrap { private readonly dbDriverResource: DBDriverResource ) { super(); + + makeObservable(this, { + fillConfig: action, + prepareConfig: action, + }); } register(): void { @@ -38,7 +45,7 @@ export class ConnectionSSHTabService extends Bootstrap { panel: () => SSH, isHidden: (tabId, props) => { if (props?.state.config.driverId) { - const driver = this.dbDriverResource.get(props?.state.config.driverId); + const driver = this.dbDriverResource.get(props.state.config.driverId); return !driver?.applicableNetworkHandlers.includes(SSH_TUNNEL_ID); } @@ -149,7 +156,7 @@ export class ConnectionSSHTabService extends Bootstrap { } } - private async prepareConfig( + private prepareConfig( { state, }: IConnectionFormSubmitData, diff --git a/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionForm.tsx b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionForm.tsx index 85c6aa54c8..96a536d27b 100644 --- a/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionForm.tsx +++ b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionForm.tsx @@ -32,6 +32,7 @@ export const PublicConnectionForm: React.FC = observer(function PublicConnection {() => service.formState && ( { this.close(true); }); + + makeObservable(this, { + formState: observable.shallow, + change: action, + open: action, + close: action, + }); } change(config: ConnectionConfig, availableDrivers?: string[]): void { + // if (this.formState) { + // this.formState.dispose(); + // } + if (!this.formState) { this.formState = new ConnectionFormState( this.connectionFormService, @@ -80,6 +85,10 @@ export class PublicConnectionFormService { } async close(saved?: boolean): Promise { + if (!this.formState) { + return; + } + if (saved) { this.clearFormState(); } @@ -101,7 +110,7 @@ export class PublicConnectionFormService { } } - private closeRemoved: IExecutorHandler> = (data, contexts) => { + private readonly closeRemoved: IExecutorHandler> = (data, contexts) => { if (!this.formState || !this.formState.config.connectionId) { return; } @@ -111,7 +120,7 @@ export class PublicConnectionFormService { } }; - private closeDeleted: IExecutorHandler> = (data, contexts) => { + private readonly closeDeleted: IExecutorHandler> = (data, contexts) => { if (!this.formState || !this.formState.config.connectionId) { return; } @@ -121,7 +130,7 @@ export class PublicConnectionFormService { } }; - private closeHandler: IExecutorHandler = async (data, contexts) => { + private readonly closeHandler: IExecutorHandler = async (data, contexts) => { if ( !this.formState || !this.optionsPanelService.isOpen(formGetter)